aboutsummaryrefslogtreecommitdiff
path: root/src/fields/Doc.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/fields/Doc.ts')
-rw-r--r--src/fields/Doc.ts132
1 files changed, 85 insertions, 47 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index d726f7064..b1bdd50a7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -16,9 +16,9 @@ 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,
- Initializing, Self, SelfProxy, UpdatingFromServer, Width
+ Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
} from './DocSymbols'; // prettier-ignore
-import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToScriptString, ToString } from './FieldSymbols';
+import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { InkField, InkTool } from './InkField';
import { List, ListFieldName } from './List';
import { ObjectField } from './ObjectField';
@@ -31,6 +31,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor }
import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util';
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 {
@@ -51,6 +52,28 @@ export namespace Field {
default: return field?.[ToScriptString]?.() ?? 'null';
} // prettier-ignore
}
+ export function toJavascriptString(field: Field) {
+ var rawjava = '';
+
+ switch (typeof field) {
+ case 'string':
+ case 'number':
+ case 'boolean':rawjava = String(field);
+ break;
+ default: rawjava = field?.[ToJavascriptString]?.() ?? '';
+ } // prettier-ignore
+ var script = rawjava;
+ // 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 sections = (Cast(doc.text, RichTextField, null)?.Text ?? '').split('--DOCDATA--');
+ if (script.match(regex)) {
+ script = script.replace(regex, sections[0]) + (sections.length > 1 ? sections[1] : '');
+ }
+ });
+ return script;
+ }
export function toString(field: Field) {
if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
return field?.[ToString]?.() || '';
@@ -286,6 +309,8 @@ export class Doc extends RefField {
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;
+ public [ToJavascriptString] = () => `idToDoc("${this[Self][Id]}")`; // what should go here?
public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
public get [DocLayout]() { return this[SelfProxy].__LAYOUT__; } // prettier-ignore
@@ -366,7 +391,10 @@ export class Doc extends RefField {
export namespace Doc {
export function SetContainer(doc: Doc, container: Doc) {
- 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];
@@ -423,7 +451,7 @@ export namespace Doc {
//
export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
if (key.startsWith('_')) key = key.substring(1);
- const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined;
+ const hasProto = doc[DocData] !== doc ? doc[DocData] : undefined;
const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1;
const onProto = hasProto && Object.getOwnPropertyNames(hasProto).indexOf(key) !== -1;
if (onDeleg || !hasProto || (!onProto && !defaultProto)) {
@@ -507,12 +535,9 @@ export namespace Doc {
* Removes doc from the list of Docs at listDoc[fieldKey]
* @returns true if successful, false otherwise.
*/
- export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) {
+ export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) {
const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
- if (listDoc[key] === undefined) {
- Doc.GetProto(listDoc)[key] = new List<Doc>();
- }
- const list = Cast(listDoc[key], listSpec(Doc));
+ const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List<Doc>()) : Cast(listDoc[key], listSpec(Doc));
if (list) {
const ind = list.indexOf(doc);
if (ind !== -1) {
@@ -527,12 +552,9 @@ export namespace Doc {
* Adds doc to the list of Docs stored at listDoc[fieldKey].
* @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) {
+ 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 ? fieldKey : Doc.LayoutFieldKey(listDoc);
- if (listDoc[key] === undefined) {
- Doc.GetProto(listDoc)[key] = new List<Doc>();
- }
- const list = Cast(listDoc[key], listSpec(Doc));
+ const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][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]);
@@ -557,6 +579,16 @@ export namespace Doc {
return false;
}
+ export function RemoveEmbedding(doc: Doc, embedding: Doc) {
+ Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', embedding);
+ }
+ export function AddEmbedding(doc: Doc, embedding: Doc) {
+ Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding, undefined, undefined, undefined, undefined, undefined, true);
+ }
+ export function GetEmbeddings(doc: Doc) {
+ return DocListCast(Doc.Get(doc[DocData], 'proto_embeddings', true));
+ }
+
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);
@@ -564,20 +596,18 @@ export namespace Doc {
Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
}
embedding.createdFrom = doc;
- embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length - 1;
+ 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 = Doc.CurrentUserEmail;
- Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding);
-
return embedding;
}
export function BestEmbedding(doc: Doc) {
const dataDoc = doc[DocData];
- const availableEmbeddings = DocListCast(dataDoc.proto_embeddings);
+ 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);
+ bestEmbedding && Doc.AddDocToList(dataDoc, 'proto_embeddings', doc, undefined, undefined, undefined, undefined, undefined, true);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
@@ -851,7 +881,7 @@ export namespace Doc {
export function FindReferences(infield: Doc | List<any>, 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));
+ infield?.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
return;
}
const doc = infield as Doc;
@@ -936,7 +966,7 @@ export namespace Doc {
Doc.GetProto(copy).embedContainer = undefined;
Doc.GetProto(copy).proto_embeddings = new List<Doc>([copy]);
} else {
- Doc.AddDocToList(copy[DocData], 'proto_embeddings', copy);
+ Doc.AddEmbedding(copy, copy);
}
copy.embedContainer = undefined;
if (retitle) {
@@ -957,9 +987,9 @@ export namespace Doc {
Object.keys(doc)
.filter(key => key.startsWith('acl'))
.forEach(key => (delegate[key] = doc[key]));
- if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DocData], 'proto_embeddings', delegate);
title && (delegate.title = title);
delegate[Initializing] = false;
+ if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate);
return delegate;
}
return undefined;
@@ -980,7 +1010,6 @@ export namespace Doc {
delegate[Initializing] = true;
delegate.proto = delegateProto;
delegate.author = Doc.CurrentUserEmail;
- Doc.AddDocToList(delegateProto[DocData], 'proto_embeddings', delegate);
delegate[Initializing] = false;
delegateProto[Initializing] = false;
return delegate;
@@ -1017,13 +1046,13 @@ export namespace Doc {
// This function converts a generic field layout display into a field layout that displays a specific
// metadata field indicated by the title of the template field (not the default field that it was rendering)
//
- export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
+ 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 = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '');
+ const metadataFieldKey = keepFieldKey ? Doc.LayoutFieldKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
// update the original template to mark it as a template
templateField.isTemplateForField = metadataFieldKey;
- templateField.title = metadataFieldKey;
+ !keepFieldKey && (templateField.title = metadataFieldKey);
const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
const templateCaptionValue = templateField.caption;
@@ -1084,8 +1113,8 @@ export namespace Doc {
export function LayoutField(doc: Doc) {
return doc[StrCast(doc.layout_fieldKey, 'layout')];
}
- export function LayoutFieldKey(doc: Doc): string {
- return StrCast(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
+ 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
}
export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
@@ -1100,10 +1129,10 @@ export namespace Doc {
return NumCast(doc._nativeHeight, nheight || dheight);
}
export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
- doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width;
+ doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeWidth'] = width;
}
export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) {
- doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
+ doc[(fieldKey || Doc.LayoutFieldKey(doc)) + '_nativeHeight'] = height;
}
const manager = new UserDocData();
@@ -1127,22 +1156,22 @@ export namespace Doc {
}
const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined;
});
export function IsSearchMatch(doc: Doc) {
return isSearchMatchCache(doc);
}
export function IsSearchMatchUnmemoized(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined;
+ return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined;
}
export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) {
- if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+ if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(doc[DocData]) !== AclPrivate) {
brushManager.SearchMatchDoc.set(doc, results);
}
return doc;
}
export function SearchMatchNext(doc: Doc, backward: boolean) {
- if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return 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 }));
@@ -1160,13 +1189,13 @@ export namespace Doc {
}
// returns 'how' a Doc has been brushed over - whether the document itself was brushed, it's prototype, or neither
export function GetBrushStatus(doc: Doc) {
- if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
- return doc[Brushed] ? DocBrushStatus.selfBrushed : Doc.GetProto(doc)[Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return DocBrushStatus.unbrushed;
+ return doc[Brushed] ? DocBrushStatus.selfBrushed : doc[DocData][Brushed] ? DocBrushStatus.protoBrushed : DocBrushStatus.unbrushed;
}
export function BrushDoc(doc: Doc, unbrush = false) {
- if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) {
+ if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(doc[DocData]) !== AclPrivate) {
brushManager.brushDoc(doc, unbrush);
- brushManager.brushDoc(Doc.GetProto(doc), unbrush);
+ brushManager.brushDoc(doc[DocData], unbrush);
}
return doc;
}
@@ -1186,6 +1215,7 @@ export namespace Doc {
}
export function linkFollowUnhighlight() {
clearTimeout(UnhighlightTimer);
+ UnhighlightTimer = 0;
UnhighlightWatchers.forEach(watcher => watcher());
UnhighlightWatchers.length = 0;
highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
@@ -1205,8 +1235,8 @@ export namespace Doc {
export var highlightedDocs = new ObservableSet<Doc>();
export function IsHighlighted(doc: Doc) {
- if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false;
- return doc[Highlight] || Doc.GetProto(doc)[Highlight];
+ if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(doc[DocData]) === AclPrivate || doc.opacity === 0) return false;
+ return doc[Highlight] || doc[DocData][Highlight];
}
export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentation_effect?: Doc) {
runInAction(() => {
@@ -1214,8 +1244,8 @@ export namespace Doc {
doc[Highlight] = true;
doc[Animation] = presentation_effect;
if (dataAndDisplayDocs) {
- highlightedDocs.add(Doc.GetProto(doc));
- Doc.GetProto(doc)[Highlight] = true;
+ highlightedDocs.add(doc[DocData]);
+ doc[DocData][Highlight] = true;
}
});
}
@@ -1224,8 +1254,8 @@ export namespace Doc {
runInAction(() => {
(doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => {
highlightedDocs.delete(doc);
- highlightedDocs.delete(Doc.GetProto(doc));
- doc[Highlight] = Doc.GetProto(doc)[Highlight] = false;
+ highlightedDocs.delete(doc[DocData]);
+ doc[Highlight] = doc[DocData][Highlight] = false;
doc[Animation] = undefined;
});
});
@@ -1245,6 +1275,11 @@ export namespace Doc {
: undefined;
}
+ export function toggleLockedPosition(doc: Doc) {
+ doc._lockedPosition = !doc._lockedPosition;
+ doc._pointerEvents = doc._lockedPosition ? 'none' : undefined;
+ }
+
export function deiconifyView(doc: Doc) {
StrCast(doc.layout_fieldKey).split('_')[1] === 'icon' && setNativeView(doc);
}
@@ -1341,7 +1376,7 @@ export namespace Doc {
});
}
- export function styleFromLayoutString(doc: Doc, props: any, scale: number) {
+ export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: number) {
const style: { [key: string]: any } = {};
const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position'];
const replacer = (match: any, expr: string, offset: any, string: any) => {
@@ -1349,7 +1384,7 @@ export namespace Doc {
return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? '';
};
divKeys.map((prop: string) => {
- const p = props[prop];
+ const p = (props as any)[prop];
typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
});
return style;
@@ -1588,7 +1623,7 @@ ScriptingGlobals.add(function idToDoc(id: string): any {
return IdToDoc(id);
});
ScriptingGlobals.add(function renameEmbedding(doc: any) {
- return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
+ return StrCast(doc[DocData].title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
});
ScriptingGlobals.add(function getProto(doc: any) {
return Doc.GetProto(doc);
@@ -1633,3 +1668,6 @@ ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: a
ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) {
Doc.setDocRangeFilter(container, key, range);
});
+ScriptingGlobals.add(function toJavascriptString(str: string) {
+ return Field.toJavascriptString(str as Field);
+});