diff options
Diffstat (limited to 'src/fields')
-rw-r--r-- | src/fields/Doc.ts | 172 | ||||
-rw-r--r-- | src/fields/DocSymbols.ts | 37 | ||||
-rw-r--r-- | src/fields/PresField.ts | 6 | ||||
-rw-r--r-- | src/fields/RichTextField.ts | 6 | ||||
-rw-r--r-- | src/fields/ScriptField.ts | 110 | ||||
-rw-r--r-- | src/fields/documentSchemas.ts | 6 | ||||
-rw-r--r-- | src/fields/util.ts | 20 |
7 files changed, 228 insertions, 129 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 56d50846a..48214cf25 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -15,7 +15,7 @@ import { incrementTitleCopy, Utils } from '../Utils'; import { DateField } from './DateField'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks, - DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight, + DocAcl, DocCss, DocData, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight, Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width } from './DocSymbols'; // prettier-ignore import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; @@ -34,14 +34,34 @@ import * as JSZip from 'jszip'; import { FieldViewProps } from '../client/views/nodes/FieldView'; export const LinkedTo = '-linkedTo'; export namespace Field { - export function toKeyValueString(doc: Doc, key: string): string { - const onDelegate = Object.keys(doc).includes(key.replace(/^_/, '')); + /** + * Converts a field to its equivalent input string in the key value box such that if the string + * is entered into a keyValueBox it will create an equivalent field (except if showComputedValue is set). + * @param doc doc containing key + * @param key field key to display + * @param showComputedValue whether copmuted function should display its value instead of its function + * @returns string representation of the field + */ + export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string { + const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, '')); const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - return !Field.IsField(field) - ? key.startsWith('_') - ? '=' - : '' - : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field)); + const valFunc = (field: Field): string => { + const res = + field instanceof ComputedField && showComputedValue + ? field.value(doc) + : field instanceof ComputedField + ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}` + : field instanceof ScriptField + ? `$=${field.script.originalScript}` + : Field.toScriptString(field); + const resStr = (res + '').replace(/^`(.*)`$/, '$1'); + return typeof field === 'string' && (+resStr).toString() !== resStr && !Array.from('+-*/.').some(k => Array.from(resStr).includes(k)) + ? resStr + : (res + '') // adjust the key value string to be easier to enter: represent any initial list as an array with [] + .trim() + .replace(/^new List\((.*)\)$/, '$1'); + }; + return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field); } export function toScriptString(field: Field) { switch (typeof field) { @@ -66,7 +86,8 @@ export namespace Field { // this is a bit hacky, but we treat '^@' references to a published document // as a kind of macro to include the content of those documents Doc.MyPublishedDocs.forEach(doc => { - const regex = new RegExp(`^\\^${doc.title}\\s`, 'm'); + const regexMultilineFlag = 'm'; + const regex = new RegExp(`^\\^${StrCast(doc.title).replace(/[\(\)]*/g, '')}\\s`, regexMultilineFlag); // need to remove characters that can cause the regular expression to be invalid const sections = (Cast(doc.text, RichTextField, null)?.Text ?? '').split('--DOCDATA--'); if (script.match(regex)) { script = script.replace(regex, sections[0]) + (sections.length > 1 ? sections[1] : ''); @@ -172,7 +193,7 @@ export class Doc extends RefField { public static get MyTrails() { return DocCast(Doc.ActiveDashboard?.myTrails); } // prettier-ignore public static get MyCalendars() { return DocCast(Doc.ActiveDashboard?.myCalendars); } // prettier-ignore public static get MyOverlayDocs() { return DocListCast(Doc.ActiveDashboard?.myOverlayDocs ?? DocCast(Doc.UserDoc().myOverlayDocs)?.data); } // prettier-ignore - public static get MyPublishedDocs() { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs ?? DocCast(Doc.UserDoc().myPublishedDocs)?.data); } // prettier-ignore + 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 MyImports() { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore @@ -182,6 +203,8 @@ export class Doc extends RefField { public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; } // prettier-ignore public static get IsSharingEnabled() { return BoolCast(Doc.UserDoc().isSharingEnabled); } // prettier-ignore public static set IsSharingEnabled(val) { Doc.UserDoc().isSharingEnabled = val; } // prettier-ignore + public static get IsInfoUIDisabled() { return BoolCast(Doc.UserDoc().isInfoUIDisabled); } // prettier-ignore + public static set IsInfoUIDisabled(val) { Doc.UserDoc().isInfoUIDisabled = val; } // prettier-ignore public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; } // prettier-ignore public static set defaultAclPrivate(val) { Doc.UserDoc().defaultAclPrivate = val; } // prettier-ignore public static get ActivePage() { return StrCast(Doc.UserDoc().activePage); } // prettier-ignore @@ -196,8 +219,14 @@ export class Doc extends RefField { public static IsInMyOverlay(doc: Doc) { return Doc.MyOverlayDocs.includes(doc); } // prettier-ignore public static AddToMyOverlay(doc: Doc) { Doc.ActiveDashboard?.myOverlayDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore public static RemFromMyOverlay(doc: Doc) { Doc.ActiveDashboard?.myOverlayDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myOverlayDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore - public static AddToMyPublished(doc: Doc) { Doc.ActiveDashboard?.myPublishedDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myPublishedDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore - public static RemFromMyPublished(doc: Doc){ Doc.ActiveDashboard?.myPublishedDocs ? Doc.RemoveDocFromList(Doc.ActiveDashboard,'myPublishedDocs', doc) : Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myPublishedDocs), undefined, doc); } // prettier-ignore + public static AddToMyPublished(doc: Doc) { + doc[DocData].title_custom = true; + doc[DocData].layout_showTitle = 'title'; + Doc.ActiveDashboard?.myPublishedDocs ? 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?.myPublishedDocs ? 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) { @@ -225,7 +254,6 @@ export class Doc extends RefField { DocAcl, DocCss, DocData, - DocFields, DocLayout, DocViews, FieldKeys, @@ -306,7 +334,6 @@ export class Doc extends RefField { DocServer.UpdateField(this[Id], serverOp); } }; - public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any); public [Width] = () => NumCast(this[SelfProxy]._width); public [Height] = () => NumCast(this[SelfProxy]._height); public [TransitionTimer]: any = undefined; @@ -391,7 +418,10 @@ export class Doc extends RefField { export namespace Doc { export function SetContainer(doc: Doc, container: Doc) { - container !== Doc.MyRecentlyClosed && (doc.embedContainer = container); + if (container !== Doc.MyRecentlyClosed) { + doc.embedContainer = container; + Doc.AddEmbedding(doc, doc); + } } export function RunCachedUpdate(doc: Doc, field: string) { const update = doc[CachedUpdates][field]; @@ -427,6 +457,12 @@ export namespace Doc { export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> { return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>; } + export function isTemplateDoc(doc: Doc) { + return GetT(doc, 'isTemplateDoc', 'boolean', true); + } + export function isTemplateForField(doc: Doc) { + return GetT(doc, 'isTemplateForField', 'string', true); + } export function IsDataProto(doc: Doc) { return GetT(doc, 'isDataDoc', 'boolean', true); } @@ -580,6 +616,9 @@ export namespace Doc { Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', embedding); } export function AddEmbedding(doc: Doc, embedding: Doc) { + if (embedding === null) { + console.log('WHAT?'); + } Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding, undefined, undefined, undefined, undefined, undefined, true); } export function GetEmbeddings(doc: Doc) { @@ -604,15 +643,26 @@ export namespace Doc { const dataDoc = doc[DocData]; const availableEmbeddings = Doc.GetEmbeddings(dataDoc); const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail); - bestEmbedding && Doc.AddDocToList(dataDoc, 'protoEmbeddings', doc, undefined, undefined, undefined, undefined, undefined, true); + 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): Promise<Doc> { - if (Doc.IsBaseProto(doc)) return doc; + 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> { + if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) { + return doc; + } if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; const copy = new Doc(undefined, true); cloneMap.set(doc[Id], copy); @@ -627,7 +677,7 @@ 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, pruneDocs, cloneLinks))); + 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(field)); @@ -642,7 +692,7 @@ export namespace Doc { ); const results = docids && (await DocServer.GetRefFields(docids)); const docs = results && Array.from(Object.keys(results)).map(key => DocCast(results[key])); - docs?.map(doc => doc && Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks)); + docs?.map(doc => doc && Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)); rtfs.push({ copy, key, field }); } } @@ -654,8 +704,14 @@ export namespace Doc { } else if (docAtKey instanceof Doc) { if (pruneDocs.includes(docAtKey)) { // prune doc and do nothing - } else if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || ['embedContainer', 'annotationOn', 'proto'].includes(key) || ((key === 'link_anchor_1' || key === 'link_anchor_2') && doc.author === Doc.CurrentUserEmail))) { - assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks)); + } 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 === Doc.CurrentUserEmail)) + ) { + assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)); } else { assignKey(docAtKey); } @@ -678,16 +734,17 @@ export namespace Doc { ((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)); + linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, pruneDocs, cloneLinks, cloneTemplates)); } }); - Doc.SetInPlace(copy, 'title', '>:' + doc.title, true); + if (Doc.Get(copy, 'title', true)) copy.title = '>:' + doc.title; + // Doc.SetInPlace(copy, 'title', '>:' + doc.title, true); copy.cloneOf = doc; cloneMap.set(doc[Id], copy); return copy; } - export function repairClone(clone: Doc, cloneMap: Map<string, Doc>, visited: Set<Doc>) { + export function repairClone(clone: Doc, cloneMap: Map<string, Doc>, cloneTemplates: boolean, visited: Set<Doc>) { if (visited.has(clone)) return; visited.add(clone); Object.keys(clone) @@ -696,22 +753,22 @@ export namespace Doc { const docAtKey = DocCast(clone[key]); if (docAtKey && !Doc.IsSystem(docAtKey)) { if (!Array.from(cloneMap.values()).includes(docAtKey)) { - clone[key] = cloneMap.get(docAtKey[Id]); + clone[key] = !cloneTemplates && (Doc.isTemplateDoc(docAtKey) || Doc.isTemplateForField(docAtKey)) ? docAtKey : cloneMap.get(docAtKey[Id]); } else { - repairClone(docAtKey, cloneMap, visited); + repairClone(docAtKey, cloneMap, cloneTemplates, visited); } } }); } - export function MakeClones(docs: Doc[], cloneLinks: boolean) { + export function MakeClones(docs: Doc[], cloneLinks: boolean, cloneTemplates: boolean) { const cloneMap = new Map<string, Doc>(); - return docs.map(doc => Doc.MakeClone(doc, cloneLinks, cloneMap)); + return docs.map(doc => Doc.MakeClone(doc, cloneLinks, cloneTemplates, cloneMap)); } - export async function MakeClone(doc: Doc, cloneLinks = true, cloneMap: Map<string, Doc> = new Map()) { + export async 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 copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], doc.embedContainer ? [DocCast(doc.embedContainer)] : [], cloneLinks, cloneTemplates); const repaired = new Set<Doc>(); const linkedDocs = Array.from(linkMap.values()); linkedDocs.map((link: Doc) => LinkManager.Instance.addLink(link, true)); @@ -729,7 +786,7 @@ export namespace Doc { copy[key] = new RichTextField(field.Data.replace(docidsearch, replacer).replace(re, replacer2), field.Text); }); const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs]; - clonedDocs.map(clone => Doc.repairClone(clone, cloneMap, repaired)); + clonedDocs.map(clone => Doc.repairClone(clone, cloneMap, cloneTemplates, repaired)); return { clone: copy, map: cloneMap, linkMap }; } @@ -818,7 +875,7 @@ export namespace Doc { // of the original layout while allowing for individual layout properties to be overridden in the expanded layout. export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc) { // 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 || (!templateLayoutDoc.isTemplateForField && !templateLayoutDoc.isTemplateDoc)) { + if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.isTemplateForField(templateLayoutDoc) && !Doc.isTemplateDoc(templateLayoutDoc))) { return templateLayoutDoc; } @@ -835,7 +892,7 @@ export namespace Doc { expandedTemplateLayout = undefined; _pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey); } else if (expandedTemplateLayout === undefined && !_pendingMap.has(targetDoc[Id] + expandedLayoutFieldKey)) { - if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument ?? Doc.GetProto(targetDoc))) { + if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) { expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params } else { templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype @@ -871,8 +928,9 @@ export namespace Doc { console.log('Warning: GetLayoutDataDocPair childDoc not defined'); return { layout: childDoc, data: childDoc }; } - const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc; - return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc }; + const resolvedDataDoc = 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 }; } export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) { @@ -950,7 +1008,7 @@ export namespace Doc { } else if (field instanceof ObjectField) { copy[key] = doc[key] instanceof Doc && key.includes('layout[') - ? undefined // remove expanded template field documents + ? new ProxyField(Doc.MakeCopy(doc[key] as any)) // copy the expanded render template : ObjectField.MakeCopy(field); } else if (field instanceof Promise) { debugger; //This shouldn't happend... @@ -984,10 +1042,9 @@ export namespace Doc { Object.keys(doc) .filter(key => key.startsWith('acl')) .forEach(key => (delegate[key] = doc[key])); - if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate); title && (delegate.title = title); delegate[Initializing] = false; - Doc.AddEmbedding(doc, delegate); + if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate); return delegate; } return undefined; @@ -997,21 +1054,13 @@ export namespace Doc { // (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.isDataDoc = true; - title && (delegateProto.title = title); - const delegate = new Doc(id, true); - delegate[Initializing] = true; - delegate.proto = delegateProto; - delegate.author = Doc.CurrentUserEmail; - Doc.AddEmbedding(delegateProto, delegate); - delegate[Initializing] = false; - delegateProto[Initializing] = false; - return delegate; + export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string) { + const ndoc = Doc.ApplyTemplate(doc); + if (ndoc) { + Doc.GetProto(ndoc).isDataDoc = true; + ndoc && (Doc.GetProto(ndoc).proto = doc); + } + return ndoc; } let _applyCount: number = 0; @@ -1113,7 +1162,8 @@ export namespace Doc { return doc[StrCast(doc.layout_fieldKey, 'layout')]; } export function LayoutFieldKey(doc: Doc, templateLayoutString?: string): string { - return StrCast(templateLayoutString || Doc.Layout(doc).layout).split("'")[1]; // bcz: TODO check on this . used to always reference 'layout', now it uses the layout speicfied by the current layout_fieldKey + 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 } export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) { return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1); @@ -1242,7 +1292,8 @@ export namespace Doc { highlightedDocs.add(doc); doc[Highlight] = true; doc[Animation] = presentation_effect; - if (dataAndDisplayDocs) { + if (dataAndDisplayDocs && !doc.resolvedDataDoc) { + // 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; } @@ -1394,7 +1445,7 @@ export namespace Doc { const list = Array.from(Object.values(fieldlist)) .map(d => DocCast(d)) .filter(d => d); - const docs = clone ? (await Promise.all(Doc.MakeClones(list, false))).map(res => res.clone) : list; + const docs = clone ? (await Promise.all(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; @@ -1432,7 +1483,7 @@ export namespace Doc { case DocumentType.WEBCAM: return 'video'; case DocumentType.AUDIO: return 'microphone'; case DocumentType.BUTTON: return 'bolt'; - case DocumentType.PRES: return 'tv'; + case DocumentType.PRES: return 'route'; case DocumentType.SCRIPTING: return 'terminal'; case DocumentType.IMPORT: return 'cloud-upload-alt'; case DocumentType.VID: return 'video'; @@ -1634,7 +1685,7 @@ ScriptingGlobals.add(function getEmbedding(doc: any) { return Doc.MakeEmbedding(doc); }); ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { - return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); + return doc.isTemplateDoc ? Doc.MakeDelegateWithProto(doc) : Doc.MakeCopy(doc, copyProto); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); @@ -1670,3 +1721,6 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran ScriptingGlobals.add(function toJavascriptString(str: string) { return Field.toJavascriptString(str as Field); }); +ScriptingGlobals.add(function RtfField() { + return RichTextField.RTFfield(); +}); diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index f8a57acd5..837fcc90e 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -1,8 +1,26 @@ -export const DocUpdated = Symbol('DocUpdated'); +// Symbols for fundamental Doc operations such as: permissions, field and proxy access and server interactions +export const AclPrivate = Symbol('DocAclOwnerOnly'); +export const AclReadonly = Symbol('DocAclReadOnly'); +export const AclAugment = Symbol('DocAclAugment'); +export const AclSelfEdit = Symbol('DocAclSelfEdit'); +export const AclEdit = Symbol('DocAclEdit'); +export const AclAdmin = Symbol('DocAclAdmin'); +export const DocAcl = Symbol('DocAcl'); +export const CachedUpdates = Symbol('DocCachedUpdates'); +export const UpdatingFromServer = Symbol('DocUpdatingFromServer'); +export const ForceServerWrite = Symbol('DocForceServerWrite'); export const Self = Symbol('DocSelf'); export const SelfProxy = Symbol('DocSelfProxy'); export const FieldKeys = Symbol('DocFieldKeys'); export const FieldTuples = Symbol('DocFieldTuples'); +export const Initializing = Symbol('DocInitializing'); + +// Symbols for core Dash document model including data docs, layout docs, and links +export const DocData = Symbol('DocData'); +export const DocLayout = Symbol('DocLayout'); +export const DirectLinks = Symbol('DocDirectLinks'); + +// Symbols for view related operations for Documents export const AudioPlay = Symbol('DocAudioPlay'); export const Width = Symbol('DocWidth'); export const Height = Symbol('DocHeight'); @@ -10,22 +28,7 @@ export const Animation = Symbol('DocAnimation'); export const Highlight = Symbol('DocHighlight'); export const DocViews = Symbol('DocViews'); export const Brushed = Symbol('DocBrushed'); -export const DocData = Symbol('DocData'); -export const DocLayout = Symbol('DocLayout'); -export const DocFields = Symbol('DocFields'); export const DocCss = Symbol('DocCss'); -export const DocAcl = Symbol('DocAcl'); export const TransitionTimer = Symbol('DocTransitionTimer'); -export const DirectLinks = Symbol('DocDirectLinks'); -export const AclPrivate = Symbol('DocAclOwnerOnly'); -export const AclReadonly = Symbol('DocAclReadOnly'); -export const AclAugment = Symbol('DocAclAugment'); -export const AclSelfEdit = Symbol('DocAclSelfEdit'); -export const AclEdit = Symbol('DocAclEdit'); -export const AclAdmin = Symbol('DocAclAdmin'); -export const UpdatingFromServer = Symbol('DocUpdatingFromServer'); -export const Initializing = Symbol('DocInitializing'); -export const ForceServerWrite = Symbol('DocForceServerWrite'); -export const CachedUpdates = Symbol('DocCachedUpdates'); -export const DashVersion = 'v0.7.0'; +export const DashVersion = 'v0.8.0'; diff --git a/src/fields/PresField.ts b/src/fields/PresField.ts deleted file mode 100644 index f236a04fd..000000000 --- a/src/fields/PresField.ts +++ /dev/null @@ -1,6 +0,0 @@ -//insert code here -import { ObjectField } from "./ObjectField"; - -export abstract class PresField extends ObjectField { - -}
\ No newline at end of file diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 50cfab988..3f13f7e6d 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -31,15 +31,15 @@ export class RichTextField extends ObjectField { return '`' + this.Text + '`'; } [ToScriptString]() { - return `new RichTextField("${this.Data.replace(/"/g, '\\"')}", "${this.Text}")`; + return `new RichTextField(\`${this.Data?.replace(/"/g, '\\"')}\`, \`${this.Text}\`)`; } [ToString]() { return this.Text; } - public static DashField(fieldKey: string) { + public static RTFfield() { return new RichTextField( - `{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"${fieldKey}","docId":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`, + `{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`, '' ); } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index c7fe72ca6..8b51088b2 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -1,15 +1,17 @@ +import { action, makeObservable, observable } from 'mobx'; import { computedFn } from 'mobx-utils'; import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from 'serializr'; import { DocServer } from '../client/DocServer'; -import { CompiledScript, CompileScript, ScriptOptions } from '../client/util/Scripting'; +import { CompiledScript, CompileScript, ScriptOptions, Transformer } from '../client/util/Scripting'; import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { autoObject, Deserializable } from '../client/util/SerializationHelper'; import { numberRange } from '../Utils'; -import { Doc, Field, Opt } from './Doc'; -import { Copy, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols'; +import { Doc, Field, FieldResult, Opt } from './Doc'; +import { Copy, FieldChanged, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols'; import { List } from './List'; import { ObjectField } from './ObjectField'; import { Cast, StrCast } from './Types'; +import { GPTCallType, gptAPICall } from '../client/apis/gpt/GPT'; function optional(propSchema: PropSchema) { return custom( @@ -85,6 +87,13 @@ export class ScriptField extends ObjectField { readonly script: CompiledScript; @serializable(object(scriptSchema)) readonly setterscript: CompiledScript | undefined; + @serializable + @observable + _cachedResult: FieldResult = undefined; + setCacheResult = action((value: FieldResult) => { + this._cachedResult = value; + this[FieldChanged]?.(); + }); @serializable(autoObject()) captures?: List<string>; @@ -122,21 +131,25 @@ export class ScriptField extends ObjectField { [ToString]() { return this.script.originalScript; } - public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }) { + public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }, transformer?: Transformer) { return CompileScript(script, { params: { this: Doc?.name || 'Doc', // this is the doc that executes the script self: Doc?.name || 'Doc', // self is the root doc of the doc that executes the script + documentView: 'any', _last_: 'any', // _last_ is the previous value of a computed field when it is being triggered to re-run. + _setCacheResult_: 'any', // set the cached value of the function _readOnly_: 'boolean', // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox) ...params, }, + transformer, typecheck: false, editable: true, addReturn: addReturn, capturedVariables, }); } + public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) { const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); return compiled.compiled ? new ScriptField(compiled) : undefined; @@ -146,29 +159,70 @@ export class ScriptField extends ObjectField { const compiled = ScriptField.CompileScript(script, params, false, capturedVariables); return compiled.compiled ? new ScriptField(compiled) : undefined; } + public static CallGpt(queryText: string, setVal: (val: FieldResult) => void, target: Doc) { + if (typeof queryText === 'string' && setVal) { + while (queryText.match(/\(this\.[a-zA-Z_]*\)/)?.length) { + const fieldRef = queryText.split('(this.')[1].replace(/\).*/, ''); + queryText = queryText.replace(/\(this\.[a-zA-Z_]*\)/, Field.toString(target[fieldRef] as Field)); + } + setVal(`Chat Pending: ${queryText}`); + gptAPICall(queryText, GPTCallType.COMPLETION).then(result => { + if (queryText.includes('#')) { + const matches = result.match(/-?[0-9][0-9,]+[.]?[0-9]*/); + if (matches?.length) setVal(Number(matches[0].replace(/,/g, ''))); + } else setVal(result.trim()); + }); + } + } } @scriptingGlobal @Deserializable('computed', deserializeScript) export class ComputedField extends ScriptField { - _lastComputedResult: any; - //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc - value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); - _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); - - [ToValue](doc: Doc) { - return ComputedField.toValue(doc, this); + static undefined = '__undefined'; + static useComputed = true; + static DisableComputedFields() { this.useComputed = false; } // prettier-ignore + static EnableComputedFields() { this.useComputed = true; } // prettier-ignore + static WithoutComputed<T>(fn: () => T) { + this.DisableComputedFields(); + try { + return fn(); + } finally { + this.EnableComputedFields(); + } } - [Copy](): ObjectField { - return new ComputedField(this.script, this.setterscript, this.rawscript); + + constructor(script: CompiledScript | undefined, setterscript?: CompiledScript, rawscript?: string) { + super(script, setterscript, rawscript); + makeObservable(this); } + _lastComputedResult: FieldResult; + value = (doc:Doc) => (this._lastComputedResult = this._cachedResult ?? + computedFn((doc: Doc) => + this.script.compiled && + this.script.run( { + this: doc, + //value: '', + _setCacheResult_: this.setCacheResult, + _last_: this._lastComputedResult, + _readOnly_: true, + }, + console.log + ).result + )(doc) + ); // prettier-ignore + + [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this.value(doc) }; } // prettier-ignore + [Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore + public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) { const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables }); const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined; const compiledsetscript = compiledsetter?.compiled ? compiledsetter : undefined; return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined; } + public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt<number>) { if (!doc[`${fieldKey}_indexed`]) { const flist = new List<number>(numberRange(curTimecode + 1).map(i => undefined) as any as number[]); @@ -206,33 +260,6 @@ export class ComputedField extends ScriptField { return (doc[`${fieldKey}`] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined); } } -export namespace ComputedField { - let useComputed = true; - export function DisableComputedFields() { - useComputed = false; - } - - export function EnableComputedFields() { - useComputed = true; - } - - export const undefined = '__undefined'; - - export function WithoutComputed<T>(fn: () => T) { - DisableComputedFields(); - try { - return fn(); - } finally { - EnableComputedFields(); - } - } - - export function toValue(doc: any, value: any) { - if (useComputed) { - return { value: value._valueOutsideReaction(doc) }; - } - } -} ScriptingGlobals.add( function setIndexVal(list: any[], index: number, value: any) { @@ -258,3 +285,6 @@ ScriptingGlobals.add( 'returns the value at a given index of a list', '(list: any[], index: number)' ); +ScriptingGlobals.add(function dashCallChat(setVal: (val: FieldResult) => void, target: Doc, queryText: string) { + ScriptField.CallGpt(queryText, setVal, target); +}, 'calls chat gpt for the query string and then calls setVal with the result'); diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 8eeb52709..1cacfe30c 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -96,9 +96,9 @@ export const documentSchema = createSchema({ // drag drop properties _dragOnlyWithinContainer: 'boolean', // whether document can be dropped into a different collection dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. - dropAction: 'string', // override specifying what should happen when something is dropped on this document (can be "embed", "copy", "move") - dragAction: 'string', // override specifying what should happen when this document s dragged (can be "embed", "copy", "move") - childDragAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "embed" or "copy") + dropAction: 'string', // override specifying what should happen when something is dropped on this document (dropActionType) + dragAction: 'string', // override specifying what should happen when this document s dragged (dropActionType) + childDragAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (dropActionType) dropPropertiesToRemove: listSpec('string'), // properties that should be removed from the embed/copy/etc of this document when it is dropped }); diff --git a/src/fields/util.ts b/src/fields/util.ts index b73520999..ad592391e 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,4 +1,4 @@ -import { $mobx, action, observable, runInAction, trace } from 'mobx'; +import { $mobx, action, observable, runInAction, trace, values } from 'mobx'; import { computedFn } from 'mobx-utils'; import { returnZero } from '../Utils'; import { DocServer } from '../client/DocServer'; @@ -16,6 +16,7 @@ import { RichTextField } from './RichTextField'; import { SchemaHeaderField } from './SchemaHeaderField'; import { ComputedField } from './ScriptField'; import { DocCast, ScriptCast, StrCast } from './Types'; +import { BaseException } from 'pdfjs-dist/types/src/shared/util'; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); @@ -56,6 +57,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number delete curValue[FieldChanged]; } + if (typeof prop === 'string' && _propSetterCB.has(prop)) _propSetterCB.get(prop)!(target[SelfProxy], value); + const effectiveAcl = GetEffectiveAcl(target); const writeMode = DocServer.getFieldWriteMode(prop as string); @@ -282,10 +285,25 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc dataDocChanged && updateCachedAcls(dataDoc); } +export var _propSetterCB = new Map<string, ((target: any, value: any) => void) | undefined>(); +/** + * sets a callback function to be called whenever a value is assigned to the specified field key. + * For example, this is used to "publish" documents with titles that start with '@' + * @param prop + * @param setter + */ +export function SetPropSetterCb(prop: string, setter: ((target: any, value: any) => void) | undefined) { + _propSetterCB.set(prop, setter); +} + // // 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 { + if (!in_prop) { + console.log('WARNING: trying to set an empty property. This should be fixed. '); + return false; + } let prop = in_prop; const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop); if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true; |