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.ts642
1 files changed, 369 insertions, 273 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index e47bfcbed..2d3dddc8b 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1,49 +1,63 @@
-import { saveAs } from 'file-saver';
+/* eslint-disable default-param-last */
+/* eslint-disable no-use-before-define */
import { action, computed, makeObservable, observable, ObservableMap, ObservableSet, runInAction } from 'mobx';
import { computedFn } from 'mobx-utils';
import { alias, map, serializable } from 'serializr';
import { DocServer } from '../client/DocServer';
import { CollectionViewType, DocumentType } from '../client/documents/DocumentTypes';
-import { LinkManager } from '../client/util/LinkManager';
import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';
-import { undoable } from '../client/util/UndoManager';
-import { DocumentView } from '../client/views/nodes/DocumentView';
-import { decycle } from '../decycler/decycler';
-import * as JSZipUtils from '../JSZipUtils';
-import { incrementTitleCopy, Utils } from '../Utils';
-import { DateField } from './DateField';
+import { undoable, UndoManager } from '../client/util/UndoManager';
+import { ClientUtils, incrementTitleCopy } from '../ClientUtils';
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';
-import { InkField, InkTool } from './InkField';
-import { List, ListFieldName } from './List';
+import { InkTool } from './InkField';
+import { List } from './List';
import { ObjectField } from './ObjectField';
import { PrefetchProxy, ProxyField } from './Proxy';
import { FieldId, RefField } from './RefField';
import { RichTextField } from './RichTextField';
import { listSpec } from './Schema';
import { ComputedField, ScriptField } from './ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types';
-import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';
+import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor, toList } from './Types';
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 {
- const onDelegate = 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));
- }
- export function toScriptString(field: Field) {
+ /**
+ * 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 isOnDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, ''));
+ const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
+ const valFunc = (field: FieldType): 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(cfield) ? (key.startsWith('_') ? '=' : '') : (isOnDelegate ? '=' : '') + valFunc(cfield);
+ }
+ export function toScriptString(field: FieldType) {
switch (typeof field) {
case 'string': if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ". In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
return !field.includes('`') ? `\`${field}\`` : `"${field}"`;
@@ -52,8 +66,8 @@ export namespace Field {
default: return field?.[ToScriptString]?.() ?? 'null';
} // prettier-ignore
}
- export function toJavascriptString(field: Field) {
- var rawjava = '';
+ export function toJavascriptString(field: FieldType) {
+ let rawjava = '';
switch (typeof field) {
case 'string':
@@ -62,11 +76,12 @@ export namespace Field {
break;
default: rawjava = field?.[ToJavascriptString]?.() ?? '';
} // prettier-ignore
- var script = rawjava;
+ let 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 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] : '');
@@ -74,23 +89,25 @@ export namespace Field {
});
return script;
}
- export function toString(field: Field) {
+ export function toString(field: FieldType) {
if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
return field?.[ToString]?.() || '';
}
- export function IsField(field: any): field is Field;
- export function IsField(field: any, includeUndefined: true): field is Field | undefined;
- export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined {
+ export function IsField(field: any): field is FieldType;
+ export function IsField(field: any, includeUndefined: true): field is FieldType | undefined;
+ export function IsField(field: any, includeUndefined: boolean = false): field is FieldType | undefined {
return ['string', 'number', 'boolean'].includes(typeof field) || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined);
}
+ // eslint-disable-next-line @typescript-eslint/no-shadow
export function Copy(field: any) {
return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field;
}
+ UndoManager.SetFieldPrinter(toString);
}
-export type Field = number | string | boolean | ObjectField | RefField;
+export type FieldType = number | string | boolean | ObjectField | RefField;
export type Opt<T> = T | undefined;
export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
-export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
+export type FieldResult<T extends FieldType = FieldType> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
/**
* Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
@@ -99,7 +116,9 @@ export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract
* If no default value is given, and the returned value is not undefined, it can be safely modified.
*/
export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
+// eslint-disable-next-line no-redeclare
export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
+// eslint-disable-next-line no-redeclare
export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
const list = Cast(field, listSpec(Doc));
return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
@@ -135,19 +154,67 @@ export const ReverseHierarchyMap: Map<string, { level: aclLevel; acl: symbol; im
// caches the document access permissions for the current user.
// this recursively updates all protos as well.
export function updateCachedAcls(doc: Doc) {
- if (!doc) return;
+ if (doc) {
+ const target = (doc as any)?.__fieldTuples ?? doc;
+ const permissions: { [key: string]: symbol } = !target.author || target.author === ClientUtils.CurrentUserEmail() ? { acl_Me: AclAdmin } : {};
+ Object.keys(target).forEach(key => {
+ key.startsWith('acl_') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl);
+ });
+ if (Object.keys(permissions).length || doc[DocAcl]?.length) {
+ runInAction(() => {
+ doc[DocAcl] = permissions;
+ });
+ }
- const target = (doc as any)?.__fieldTuples ?? doc;
- const permissions: { [key: string]: symbol } = !target.author || target.author === Doc.CurrentUserEmail ? { 'acl-Me': AclAdmin } : {};
- Object.keys(target).filter(key => key.startsWith('acl') && (permissions[key] = ReverseHierarchyMap.get(StrCast(target[key]))!.acl));
- if (Object.keys(permissions).length || doc[DocAcl]?.length) {
- runInAction(() => (doc[DocAcl] = permissions));
+ if (doc.proto instanceof Promise) {
+ doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
+ return doc.proto;
+ }
}
+ return undefined;
+}
- if (doc.proto instanceof Promise) {
- doc.proto.then(proto => updateCachedAcls(DocCast(proto)));
- return doc.proto;
- }
+export function ActiveInkPen(): Doc { return Doc.UserDoc(); } // prettier-ignore
+export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, 'black'); } // prettier-ignore
+export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ''); } // prettier-ignore
+export function ActiveIsInkMask(): boolean { return BoolCast(ActiveInkPen()?.activeIsInkMask, false); } // prettier-ignore
+export function ActiveInkHideTextLabels(): boolean { return BoolCast(ActiveInkPen().activeInkHideTextLabels, false); } // prettier-ignore
+export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ''); } // prettier-ignore
+export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ''); } // prettier-ignore
+export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); } // prettier-ignore
+export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, '0'); } // prettier-ignore
+export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } // prettier-ignore
+export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } // prettier-ignore
+
+export function SetActiveInkWidth(width: string): void {
+ !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width);
+}
+export function SetActiveBezierApprox(bezier: string): void {
+ ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier);
+}
+export function SetActiveInkColor(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeInkColor = value);
+}
+export function SetActiveIsInkMask(value: boolean) {
+ ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value);
+}
+export function SetActiveInkHideTextLabels(value: boolean) {
+ ActiveInkPen() && (ActiveInkPen().activeInkHideTextLabels = value);
+}
+export function SetActiveFillColor(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeFillColor = value);
+}
+export function SetActiveArrowStart(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowStart = value);
+}
+export function SetActiveArrowEnd(value: string) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value);
+}
+export function SetActiveArrowScale(value: number) {
+ ActiveInkPen() && (ActiveInkPen().activeArrowScale = value);
+}
+export function SetActiveDash(dash: string): void {
+ !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash);
}
@scriptingGlobal
@@ -157,8 +224,38 @@ export class Doc extends RefField {
@observable public static GuestDashboard: Doc | undefined = undefined;
@observable public static GuestTarget: Doc | undefined = undefined;
@observable public static GuestMobile: Doc | undefined = undefined;
- public static CurrentUserEmail: string = '';
-
+ @observable.shallow public static CurrentlyLoading: Doc[] = observable([]);
+ // DocServer api
+ public static FindDocByTitle(title: string) {
+ const foundDocId =
+ title &&
+ Array.from(Object.keys(DocServer.Cache()))
+ .filter(key => DocServer.Cache()[key] instanceof Doc)
+ .find(key => (DocServer.Cache()[key] as Doc).title === title);
+
+ return foundDocId ? (DocServer.Cache()[foundDocId] as Doc) : undefined;
+ }
+ // removes from currently loading doc set
+ public static removeCurrentlyLoading(doc: Doc) {
+ if (Doc.CurrentlyLoading) {
+ const index = Doc.CurrentlyLoading.indexOf(doc);
+ runInAction(() => index !== -1 && Doc.CurrentlyLoading.splice(index, 1));
+ }
+ }
+ // adds doc to currently loading display
+ public static addCurrentlyLoading(doc: Doc) {
+ if (Doc.CurrentlyLoading.indexOf(doc) === -1) {
+ runInAction(() => Doc.CurrentlyLoading.push(doc));
+ }
+ }
+ // LinkManager api
+ 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;
+ // KeyValue SetField
+ public static SetField: (doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => boolean;
+ // UserDoc "API"
public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore
public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore
public static get MyDockedBtns() { return DocCast(Doc.UserDoc().myDockedBtns); } // prettier-ignore
@@ -182,6 +279,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
@@ -194,10 +293,16 @@ export class Doc extends RefField {
public static set ActiveDashboard(val: Opt<Doc>) { Doc.UserDoc().activeDashboard = val; } // prettier-ignore
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 AddToMyOverlay(doc: Doc) { return Doc.ActiveDashboard?.myOverlayDocs ? Doc.AddDocToList(Doc.ActiveDashboard, 'myOverlayDocs', doc) : Doc.AddDocToList(DocCast(Doc.UserDoc().myOverlayDocs), undefined, doc); } // prettier-ignore
+ public static RemFromMyOverlay(doc: Doc) { return 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[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 +330,6 @@ export class Doc extends RefField {
DocAcl,
DocCss,
DocData,
- DocFields,
DocLayout,
DocViews,
FieldKeys,
@@ -246,9 +350,9 @@ export class Doc extends RefField {
return Reflect.getOwnPropertyDescriptor(target, prop);
}
return {
- configurable: true, //TODO Should configurable be true?
+ configurable: true, // TODO Should configurable be true?
enumerable: true,
- value: 0, //() => target.__fieldTuples[prop])
+ value: 0, // () => target.__fieldTuples[prop])
};
},
deleteProperty: deleteProperty,
@@ -260,7 +364,8 @@ export class Doc extends RefField {
if (!id || forceSave) {
DocServer.CreateField(docProxy);
}
- return docProxy;
+ // eslint-disable-next-line no-constructor-return
+ return docProxy; // need to return the proxy from the constructor so that all our added fields will get called
}
[key: string]: FieldResult;
@@ -272,14 +377,16 @@ export class Doc extends RefField {
private set __fieldTuples(value) {
// called by deserializer to set all fields in one shot
this[FieldTuples] = value;
- for (const key in value) {
- const field = value[key];
- field !== undefined && (this[FieldKeys][key] = true);
- if (field instanceof ObjectField) {
- field[Parent] = this[Self];
- field[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], key, field);
+ Object.keys(value).forEach(key => {
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
+ const field = value[key];
+ field !== undefined && (this[FieldKeys][key] = true);
+ if (field instanceof ObjectField) {
+ field[Parent] = this[Self];
+ field[FieldChanged] = containedFieldChangedHandler(this[SelfProxy], key, field);
+ }
}
- }
+ });
}
@observable private [FieldTuples]: any = {};
@@ -292,7 +399,7 @@ export class Doc extends RefField {
@observable public [Animation]: Opt<Doc> = undefined;
@observable public [Highlight]: boolean = false;
@observable public [Brushed]: boolean = false;
- @observable public [DocViews] = new ObservableSet<DocumentView>();
+ @observable public [DocViews] = new ObservableSet<any /* DocumentView */>();
private [Self] = this;
private [SelfProxy]: any;
@@ -301,12 +408,11 @@ export class Doc extends RefField {
private [CachedUpdates]: { [key: string]: () => void | Promise<any> } = {};
public [Initializing]: boolean = false;
- public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: Field[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
+ public [FieldChanged] = (diff: undefined | { op: '$addToSet' | '$remFromSet' | '$set'; items: FieldType[] | undefined; length: number | undefined; hint?: any }, serverOp: any) => {
if (!this[UpdatingFromServer] || this[ForceServerWrite]) {
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;
@@ -325,7 +431,7 @@ export class Doc extends RefField {
let renderFieldKey: any;
const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')];
if (typeof layoutField === 'string') {
- renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0]; //layoutField.split("'")[1];
+ [renderFieldKey] = layoutField.split("fieldKey={'")[1].split("'"); // layoutField.split("'")[1];
} else {
return Cast(layoutField, Doc, null);
}
@@ -336,13 +442,11 @@ export class Doc extends RefField {
public async [HandleUpdate](diff: any) {
const set = diff.$set;
- const sameAuthor = this.author === Doc.CurrentUserEmail;
- if (set) {
- for (const key in set) {
- const fprefix = 'fields.';
- if (!key.startsWith(fprefix)) {
- continue;
- }
+ const sameAuthor = this.author === ClientUtils.CurrentUserEmail();
+ const fprefix = 'fields.';
+ Object.keys(set ?? {})
+ .filter(key => key.startsWith(fprefix))
+ .forEach(async key => {
const fKey = key.substring(fprefix.length);
const fn = async () => {
const value = await SerializationHelper.Deserialize(set[key]);
@@ -350,7 +454,7 @@ export class Doc extends RefField {
this[UpdatingFromServer] = true;
this[fKey] = value;
this[UpdatingFromServer] = false;
- if (fKey.startsWith('acl')) {
+ if (fKey.startsWith('acl_')) {
updateCachedAcls(this);
}
if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
@@ -358,20 +462,18 @@ export class Doc extends RefField {
}
};
const writeMode = DocServer.getFieldWriteMode(fKey);
- if (fKey.startsWith('acl') || writeMode !== DocServer.WriteMode.Playground) {
+ if (fKey.startsWith('acl_') || writeMode !== DocServer.WriteMode.Playground) {
delete this[CachedUpdates][fKey];
+ // eslint-disable-next-line no-await-in-loop
await fn();
} else {
this[CachedUpdates][fKey] = fn;
}
- }
- }
+ });
const unset = diff.$unset;
- if (unset) {
- for (const key in unset) {
- if (!key.startsWith('fields.')) {
- continue;
- }
+ Object.keys(unset ?? {})
+ .filter(key => key.startsWith(fprefix))
+ .forEach(async key => {
const fKey = key.substring(7);
const fn = () => {
this[UpdatingFromServer] = true;
@@ -384,12 +486,22 @@ export class Doc extends RefField {
} else {
this[CachedUpdates][fKey] = fn;
}
- }
- }
+ });
}
}
+// eslint-disable-next-line no-redeclare
export namespace Doc {
+ // eslint-disable-next-line import/no-mutable-exports
+ export let SelectOnLoad: Doc | undefined;
+ export function SetSelectOnLoad(doc: Doc | undefined) {
+ SelectOnLoad = doc;
+ }
+ // eslint-disable-next-line import/no-mutable-exports
+ export let DocDragDataName: string = '';
+ export function SetDocDragDataName(name: string) {
+ DocDragDataName = name;
+ }
export function SetContainer(doc: Doc, container: Doc) {
if (container !== Doc.MyRecentlyClosed) {
doc.embedContainer = container;
@@ -427,9 +539,15 @@ export namespace Doc {
return doc;
}
}
- export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
+ export function GetT<T extends FieldType>(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);
}
@@ -449,8 +567,8 @@ export namespace Doc {
// 2) if the data doc has the field, then it's written there.
// 3) if neither already has the field, then 'defaultProto' determines whether to write it to the data doc (or the embedding)
//
- export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) {
- if (key.startsWith('_')) key = key.substring(1);
+ export async function SetInPlace(doc: Doc, keyIn: string, value: FieldType | undefined, defaultProto: boolean) {
+ const key = keyIn.startsWith('_') ? keyIn.substring(1) : keyIn;
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;
@@ -467,7 +585,6 @@ export namespace Doc {
}
return protos;
}
-
/**
* This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies
* the values of the properties of a source object into the target.
@@ -479,17 +596,15 @@ export namespace Doc {
* @param fields the fields to project onto the target. Its type signature defines a mapping from some string key
* to a potentially undefined field, where each entry in this mapping is optional.
*/
- export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>, skipUndefineds: boolean = false, isInitializing = false) {
+ export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<FieldType>>>, skipUndefineds: boolean = false, isInitializing = false) {
isInitializing && (doc[Initializing] = true);
- for (const key in fields) {
- if (fields.hasOwnProperty(key)) {
- const value = fields[key];
- if (!skipUndefineds || value !== undefined) {
- // Do we want to filter out undefineds?
- doc[key] = value;
- }
+ Object.keys(fields).forEach(key => {
+ const value = (fields as any)[key];
+ if (!skipUndefineds || value !== undefined) {
+ // Do we want to filter out undefineds?
+ doc[key] = value;
}
- }
+ });
isInitializing && (doc[Initializing] = false);
return doc;
}
@@ -536,7 +651,7 @@ export namespace Doc {
* @returns true if successful, false otherwise.
*/
export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) {
- const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc);
+ 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));
if (list) {
const ind = list.indexOf(doc);
@@ -553,7 +668,7 @@ 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 ? fieldKey : Doc.LayoutFieldKey(listDoc);
+ 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));
if (list) {
if (!allowDuplicates) {
@@ -570,6 +685,7 @@ export namespace Doc {
if (reversed) list.splice(0, 0, doc);
else list.push(doc);
} else {
+ // eslint-disable-next-line no-lonely-if
if (reversed) list.splice(before ? list.length - ind + 1 : list.length - ind, 0, doc);
else list.splice(before ? ind : ind + 1, 0, doc);
}
@@ -601,7 +717,7 @@ export namespace Doc {
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 = Doc.CurrentUserEmail;
+ embedding.author = ClientUtils.CurrentUserEmail();
return embedding;
}
@@ -609,7 +725,7 @@ export namespace Doc {
export function BestEmbedding(doc: Doc) {
const dataDoc = doc[DocData];
const availableEmbeddings = Doc.GetEmbeddings(dataDoc);
- const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail);
+ const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(d => !d.embedContainer && d.author === ClientUtils.CurrentUserEmail());
bestEmbedding && Doc.AddEmbedding(doc, doc);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
@@ -627,7 +743,7 @@ export namespace Doc {
cloneLinks: boolean,
cloneTemplates: boolean
): Promise<Doc> {
- if (Doc.IsBaseProto(doc) || ((Doc.Get(doc, 'isTemplateDoc', true) || Doc.Get(doc, 'isTemplateForField', true)) && !cloneTemplates)) {
+ if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) {
return doc;
}
if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
@@ -637,37 +753,39 @@ export namespace Doc {
await Promise.all(
Object.keys(doc).map(async key => {
if (filter.includes(key)) return;
- const assignKey = (val: any) => (copy[key] = val);
+ const assignKey = (val: any) => {
+ copy[key] = val;
+ };
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
const field = ProxyField.WithoutProxy(() => doc[key]);
- const copyObjectField = async (field: ObjectField) => {
+ 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(field));
- if (field instanceof RichTextField) {
- if (DocsInTextFieldIds.some(id => field.Data.includes(`"${id}":`))) {
+ 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 = field.Data.match(docidsearch);
+ 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 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, cloneTemplates));
- rtfs.push({ copy, key, field });
+ const rdocs = results && Array.from(Object.keys(results)).map(rkey => DocCast(results[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];
if (key === 'author') {
- assignKey(Doc.CurrentUserEmail);
+ assignKey(ClientUtils.CurrentUserEmail());
} else if (docAtKey instanceof Doc) {
if (pruneDocs.includes(docAtKey)) {
// prune doc and do nothing
@@ -676,7 +794,7 @@ export namespace Doc {
(key.includes('layout[') ||
key.startsWith('layout') || //
['embedContainer', 'annotationOn', 'proto'].includes(key) ||
- (['link_anchor_1', 'link_anchor_2'].includes(key) && doc.author === Doc.CurrentUserEmail))
+ (['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 {
@@ -689,7 +807,8 @@ export namespace Doc {
} else if (field instanceof ObjectField) {
await copyObjectField(field);
} else if (field instanceof Promise) {
- debugger; //This shouldn't happen...
+ // eslint-disable-next-line no-debugger
+ debugger; // This shouldn't happen...
} else {
assignKey(field);
}
@@ -716,11 +835,11 @@ export namespace Doc {
visited.add(clone);
Object.keys(clone)
.filter(key => key !== 'cloneOf')
- .map(key => {
+ .forEach(key => {
const docAtKey = DocCast(clone[key]);
if (docAtKey && !Doc.IsSystem(docAtKey)) {
if (!Array.from(cloneMap.values()).includes(docAtKey)) {
- clone[key] = !cloneTemplates && (Doc.Get(docAtKey, 'isTemplateDoc', true) || Doc.Get(docAtKey, 'isTemplateForField', true)) ? docAtKey : cloneMap.get(docAtKey[Id]);
+ clone[key] = !cloneTemplates && (Doc.isTemplateDoc(docAtKey) || Doc.isTemplateForField(docAtKey)) ? docAtKey : cloneMap.get(docAtKey[Id]);
} else {
repairClone(docAtKey, cloneMap, cloneTemplates, visited);
}
@@ -735,16 +854,16 @@ export namespace Doc {
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, cloneTemplates);
+ const clone = 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));
- rtfMap.map(({ copy, key, field }) => {
- const replacer = (match: any, attr: string, id: string, offset: any, string: any) => {
+ linkedDocs.forEach(link => Doc.AddLink?.(link, true));
+ rtfMap.forEach(({ copy, key, field }) => {
+ const replacer = (match: any, attr: string, id: string /* , offset: any, string: any */) => {
const mapped = cloneMap.get(id);
return attr + '"' + (mapped ? mapped[Id] : id) + '"';
};
- const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => {
+ const replacer2 = (match: any, href: string, id: string /* , offset: any, string: any */) => {
const mapped = cloneMap.get(id);
return href + (mapped ? mapped[Id] : id);
};
@@ -753,86 +872,8 @@ 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, cloneTemplates, repaired));
- return { clone: copy, map: cloneMap, linkMap };
- }
-
- export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') {
- const { clone, map, linkMap } = await Doc.MakeClone(doc);
- const proms = new Set<string>();
- function replacer(key: any, value: any) {
- if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined;
- if (value?.__type === 'image') {
- const extension = value.url.replace(/.*\./, '');
- proms.add(value.url.replace('.' + extension, '_o.' + extension));
- return SerializationHelper.Serialize(new ImageField(value.url));
- }
- if (value?.__type === 'pdf') {
- proms.add(value.url);
- return SerializationHelper.Serialize(new PdfField(value.url));
- }
- if (value?.__type === 'audio') {
- proms.add(value.url);
- return SerializationHelper.Serialize(new AudioField(value.url));
- }
- if (value?.__type === 'video') {
- proms.add(value.url);
- return SerializationHelper.Serialize(new VideoField(value.url));
- }
- if (
- value instanceof Doc ||
- value instanceof ScriptField ||
- value instanceof RichTextField ||
- value instanceof InkField ||
- value instanceof CsvField ||
- value instanceof WebField ||
- value instanceof DateField ||
- value instanceof ProxyField ||
- value instanceof ComputedField
- ) {
- return SerializationHelper.Serialize(value);
- }
- if (value instanceof Array && key !== ListFieldName && key !== InkField.InkDataFieldName) return { fields: value, __type: 'list' };
- return value;
- }
-
- const docs: { [id: string]: any } = {};
- const links: { [id: string]: any } = {};
- Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1]));
- Array.from(linkMap.entries()).forEach(l => (links[l[0]] = l[1]));
- const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer));
-
- const zip = new JSZip();
- var count = 0;
- const promArr = Array.from(proms)
- .filter(url => url?.startsWith('/files'))
- .map(url => url.replace('/', '')); // window.location.origin));
- console.log(promArr.length);
- if (!promArr.length) {
- zip.file('docs.json', jsonDocs);
- zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
- } else
- promArr.forEach((url, i) => {
- // loading a file and add it in a zip file
- JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => {
- if (err) throw err; // or handle the error
- // // Generate a directory within the Zip file structure
- // const assets = zip.folder("assets");
- // assets.file(filename, data, {binary: true});
- const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%');
- zip.file(assetPathOnServer, data, { binary: true });
- console.log(' => ' + url);
- if (++count === promArr.length) {
- zip.file('docs.json', jsonDocs);
- zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename));
- // const a = document.createElement("a");
- // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
- // a.href = url;
- // a.download = `DocExport-${this.props.Document[Id]}.zip`;
- // a.click();
- }
- });
- });
+ clonedDocs.forEach(cloneDoc => Doc.repairClone(cloneDoc, cloneMap, cloneTemplates, repaired));
+ return { clone, map: cloneMap, linkMap };
}
const _pendingMap = new Set<string>();
@@ -842,7 +883,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;
}
@@ -859,9 +900,10 @@ 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 {
+ // 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
if (!targetDoc[expandedLayoutFieldKey]) {
_pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey);
@@ -872,7 +914,7 @@ export namespace Doc {
newLayoutDoc.rootDocument = targetDoc;
newLayoutDoc.embedContainer = targetDoc;
newLayoutDoc.resolvedDataDoc = dataDoc;
- newLayoutDoc['acl-Guest'] = SharingPermissions.Edit;
+ newLayoutDoc.acl_Guest = SharingPermissions.Edit;
if (dataDoc[templateField] === undefined && (templateLayoutDoc[templateField] as any)?.length) {
dataDoc[templateField] = ObjectField.MakeCopy(templateLayoutDoc[templateField] as List<Doc>);
// ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"])`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc });
@@ -895,8 +937,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) {
@@ -920,7 +963,7 @@ export namespace Doc {
}
} else {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+ const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]);
if (field instanceof RefField) {
if (field instanceof Doc) {
if (key === 'myLinkDatabase') {
@@ -931,6 +974,7 @@ export namespace Doc {
}
}
} else if (cfield instanceof ComputedField) {
+ /* empty */
} else if (field instanceof ObjectField) {
if (field instanceof Doc) {
Doc.FindReferences(field, references, system);
@@ -947,7 +991,8 @@ export namespace Doc {
Doc.FindReferences(field.value, references, system);
}
} else if (field instanceof Promise) {
- debugger; //This shouldn't happend...
+ // eslint-disable-next-line no-debugger
+ debugger; // This shouldn't happend...
}
}
});
@@ -966,7 +1011,7 @@ export namespace Doc {
}
} else {
const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key]));
- const field = key === 'author' ? Doc.CurrentUserEmail : ProxyField.WithoutProxy(() => doc[key]);
+ const field = key === 'author' ? ClientUtils.CurrentUserEmail() : ProxyField.WithoutProxy(() => doc[key]);
if (field instanceof RefField) {
copy[key] = field;
} else if (cfield instanceof ComputedField) {
@@ -977,7 +1022,8 @@ export namespace Doc {
? 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...
+ // eslint-disable-next-line no-debugger
+ debugger; // This shouldn't happend...
} else {
copy[key] = field;
}
@@ -1004,10 +1050,12 @@ export namespace Doc {
delegate[Initializing] = true;
updateCachedAcls(delegate);
delegate.proto = doc;
- delegate.author = Doc.CurrentUserEmail;
+ delegate.author = ClientUtils.CurrentUserEmail();
Object.keys(doc)
- .filter(key => key.startsWith('acl'))
- .forEach(key => (delegate[key] = doc[key]));
+ .filter(key => key.startsWith('acl_'))
+ .forEach(key => {
+ delegate[key] = doc[key];
+ });
title && (delegate.title = title);
delegate[Initializing] = false;
if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate);
@@ -1020,27 +1068,20 @@ 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;
- 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;
export function ApplyTemplate(templateDoc: Doc) {
if (templateDoc) {
const proto = new Doc();
- proto.author = Doc.CurrentUserEmail;
+ 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++ + ')');
@@ -1076,7 +1117,6 @@ export namespace Doc {
!keepFieldKey && (templateField.title = metadataFieldKey);
const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
- const templateCaptionValue = templateField.caption;
// 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.
@@ -1096,7 +1136,7 @@ export namespace Doc {
// converts a document id to a url path on the server
export function globalServerPath(doc: Doc | string = ''): string {
- return Utils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
+ return ClientUtils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc));
}
// converts a document id to a url path on the server
export function localServerPath(doc?: Doc): string {
@@ -1135,7 +1175,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);
@@ -1161,7 +1202,9 @@ export namespace Doc {
return manager._searchQuery;
}
export function SetSearchQuery(query: string) {
- runInAction(() => (manager._searchQuery = query));
+ runInAction(() => {
+ manager._searchQuery = query;
+ });
}
export function UserDoc(): Doc {
return manager._user_doc;
@@ -1173,12 +1216,13 @@ export namespace Doc {
return Cast(Doc.UserDoc().myLinkDatabase, Doc, null);
}
export function SetUserDoc(doc: Doc) {
+ // eslint-disable-next-line no-return-assign
return (manager._user_doc = doc);
}
- const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) {
- return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined;
- });
+ const isSearchMatchCache = computedFn((doc: Doc) =>
+ (brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) :
+ brushManager.SearchMatchDoc.has(doc[DocData]) ? brushManager.SearchMatchDoc.get(doc[DocData]) : undefined)); // prettier-ignore
export function IsSearchMatch(doc: Doc) {
return isSearchMatchCache(doc);
}
@@ -1224,11 +1268,16 @@ export namespace Doc {
return BrushDoc(doc, true);
}
export function UnBrushAllDocs() {
- Array.from(brushManager.BrushedDoc).forEach(action(doc => (doc[Brushed] = false)));
+ Array.from(brushManager.BrushedDoc).forEach(
+ action(doc => {
+ doc[Brushed] = false;
+ })
+ );
}
- let UnhighlightWatchers: (() => void)[] = [];
- export let UnhighlightTimer: any;
+ const UnhighlightWatchers: (() => void)[] = [];
+ let UnhighlightTimer: any;
+ export function IsUnhighlightTimerSet() { return UnhighlightTimer; } // prettier-ignore
export function AddUnHighlightWatcher(watcher: () => void) {
if (UnhighlightTimer) {
UnhighlightWatchers.push(watcher);
@@ -1242,42 +1291,48 @@ export namespace Doc {
highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc));
document.removeEventListener('pointerdown', linkFollowUnhighlight);
}
- export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentation_effect?: Doc) {
+ export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presentationEffect?: Doc) {
linkFollowUnhighlight();
- (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentation_effect));
+ toList(destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presentationEffect));
document.removeEventListener('pointerdown', linkFollowUnhighlight);
document.addEventListener('pointerdown', linkFollowUnhighlight);
if (UnhighlightTimer) clearTimeout(UnhighlightTimer);
+ const presTransition = Number(presentationEffect?.presentation_transition);
+ const duration = isNaN(presTransition) ? 5000 : presTransition;
UnhighlightTimer = window.setTimeout(() => {
linkFollowUnhighlight();
UnhighlightTimer = 0;
- }, 5000);
+ }, duration);
}
- export var highlightedDocs = new ObservableSet<Doc>();
+ export const highlightedDocs = new ObservableSet<Doc>();
export function IsHighlighted(doc: Doc) {
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) {
+ export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presentationEffect?: Doc) {
runInAction(() => {
highlightedDocs.add(doc);
doc[Highlight] = true;
- doc[Animation] = presentation_effect;
- if (dataAndDisplayDocs) {
+ doc[Animation] = presentationEffect;
+ 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;
+ // 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 doc is defined, then it is unhighlighted, otherwise all highlighted docs are unhighlighted
- export function UnHighlightDoc(doc?: Doc) {
+ export function UnHighlightDoc(docs?: Doc) {
runInAction(() => {
- (doc ? [doc] : Array.from(highlightedDocs)).forEach(doc => {
+ (docs ? [docs] : Array.from(highlightedDocs)).forEach(doc => {
highlightedDocs.delete(doc);
highlightedDocs.delete(doc[DocData]);
doc[Highlight] = doc[DocData][Highlight] = false;
doc[Animation] = undefined;
+ if (doc.presentation_targetDoc) DocCast(doc.presentation_targetDoc)[Highlight] = false;
});
});
}
@@ -1351,6 +1406,7 @@ export namespace Doc {
const fields = childFilters[i].split(FilterSep); // split key:value:modifier
if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) {
+ // eslint-disable-next-line no-param-reassign
if (toggle) modifiers = 'remove';
else return;
}
@@ -1375,9 +1431,12 @@ export namespace Doc {
return [Number(childFiltersByRanges[i + 1]), Number(childFiltersByRanges[i + 2])];
}
}
+ return undefined;
}
export function assignDocToField(doc: Doc, field: string, id: string) {
- DocServer.GetRefField(id).then(layout => layout instanceof Doc && (doc[field] = layout));
+ DocServer.GetRefField(id).then(layout => {
+ layout instanceof Doc && (doc[field] = layout);
+ });
return id;
}
@@ -1397,20 +1456,6 @@ export namespace Doc {
});
}
- 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) => {
- // bcz: this executes a script to convert a property expression string: { script } into a value
- 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 as any)[prop];
- typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer));
- });
- return style;
- }
-
export function Paste(docids: string[], clone: boolean, addDocument: (doc: Doc | Doc[]) => boolean, ptx?: number, pty?: number, newPoint?: number[]) {
DocServer.GetRefFields(docids).then(async fieldlist => {
const list = Array.from(Object.values(fieldlist))
@@ -1466,6 +1511,7 @@ export namespace Doc {
case DocumentType.EQUATION: return 'calculator';
case DocumentType.SIMULATION: return 'rocket';
case DocumentType.CONFIG: return 'folder-closed';
+ default:
}
return 'question';
}
@@ -1476,7 +1522,7 @@ export namespace Doc {
// If they are not remapped, loading the file will overwrite any existing documents with those ids
//
export async function importDocument(file: File, remap = false) {
- const upload = Utils.prepend('/uploadDoc');
+ const upload = ClientUtils.prepend('/uploadDoc');
const formData = new FormData();
if (file) {
formData.append('file', file);
@@ -1484,12 +1530,13 @@ export namespace Doc {
const response = await fetch(upload, { method: 'POST', body: formData });
const json = await response.json();
if (json !== 'error') {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
const docs = await DocServer.GetRefFields(json.docids as string[]);
const doc = DocCast(await DocServer.GetRefField(json.id));
const links = await DocServer.GetRefFields(json.linkids as string[]);
Array.from(Object.keys(links))
.map(key => links[key])
- .forEach(link => link instanceof Doc && LinkManager.Instance.addLink(link));
+ .forEach(link => link instanceof Doc && Doc.AddLink?.(link));
return doc;
}
}
@@ -1549,6 +1596,7 @@ export namespace Doc {
*/
export function FromJson({ data, title, appendToExisting, excludeEmptyObjects }: JsonConversionOpts): Opt<Doc> {
if (excludeEmptyObjects === undefined) {
+ // eslint-disable-next-line no-param-reassign
excludeEmptyObjects = true;
}
if (data === undefined || data === null || ![...primitives, 'object'].includes(typeof data)) {
@@ -1588,12 +1636,12 @@ export namespace Doc {
if (hasEntries || !excludeEmptyObjects) {
const resolved = target ?? new Doc();
if (hasEntries) {
- let result: Opt<Field>;
- Object.keys(object).map(key => {
+ Object.keys(object).forEach(key => {
// if excludeEmptyObjects is true, any qualifying conversions from toField will
// be undefined, and thus the results that would have
// otherwise been empty (List or Doc)s will just not be written
- if ((result = toField(object[key], excludeEmptyObjects, key))) {
+ const result = toField(object[key], excludeEmptyObjects, key);
+ if (result) {
resolved[key] = result;
}
});
@@ -1601,6 +1649,7 @@ export namespace Doc {
title && (resolved.title = title);
return resolved;
}
+ return undefined;
};
/**
@@ -1610,19 +1659,22 @@ export namespace Doc {
* @returns the list mapped from JSON to field values, where each mapping
* might involve arbitrary recursion (since toField might itself call convertList)
*/
- const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
+ const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<FieldType>> => {
const target = new List();
- let result: Opt<Field>;
+ let result: Opt<FieldType>;
// if excludeEmptyObjects is true, any qualifying conversions from toField will
// be undefined, and thus the results that would have
// otherwise been empty (List or Doc)s will just not be written
- list.map(item => (result = toField(item, excludeEmptyObjects)) && target.push(result));
+ list.forEach(item => {
+ (result = toField(item, excludeEmptyObjects)) && target.push(result);
+ });
if (target.length || !excludeEmptyObjects) {
return target;
}
+ return undefined;
};
- const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<Field> => {
+ const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<FieldType> => {
if (data === null || data === undefined) {
return undefined;
}
@@ -1637,58 +1689,102 @@ export namespace Doc {
}
}
+export function RTFIsFragment(html: string) {
+ return html.indexOf('data-pm-slice') !== -1;
+}
+export function GetHrefFromHTML(html: string): string {
+ const parser = new DOMParser();
+ const parsedHtml = parser.parseFromString(html, 'text/html');
+ if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
+ return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
+ }
+ return '';
+}
+export function GetDocFromUrl(url: string) {
+ return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
+}
+
+let activeAudioLinker: (f: () => Doc | undefined, broadcast?: boolean) => (Doc | undefined)[];
+export function SetActiveAudioLinker(func: (f: () => Doc | undefined, broadcast?: boolean) => (Doc | undefined)[]) {
+ activeAudioLinker = func;
+}
+export function CreateLinkToActiveAudio(func: () => Doc | undefined, broadcast?: boolean) {
+ return activeAudioLinker(func, broadcast);
+}
+
export function IdToDoc(id: string) {
return DocCast(DocServer.GetCachedRefField(id));
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function idToDoc(id: string): any {
return IdToDoc(id);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function renameEmbedding(doc: any) {
return StrCast(doc[DocData].title).replace(/\([0-9]*\)/, '') + `(${doc.proto_embeddingId})`;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getProto(doc: any) {
return Doc.GetProto(doc);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getDocTemplate(doc?: any) {
return Doc.getDocTemplate(doc);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function getEmbedding(doc: any) {
return Doc.MakeEmbedding(doc);
});
+// eslint-disable-next-line prefer-arrow-callback
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);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function copyField(field: any) {
return Field.Copy(field);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function docList(field: any) {
return DocListCast(field);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added: Doc) {
return Doc.AddDocToList(doc, field, added);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) {
return Doc.SetInPlace(doc, field, value, false);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) {
return Doc.AreProtosEqual(doc1, doc2);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) {
return Doc.assignDocToField(doc, field, id);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function docCastAsync(doc: FieldResult): any {
return Cast(doc, 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)];
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') {
Doc.setDocFilter(container, key, value, modifiers);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) {
Doc.setDocRangeFilter(container, key, range);
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toJavascriptString(str: string) {
- return Field.toJavascriptString(str as Field);
+ return Field.toJavascriptString(str as FieldType);
+});
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function RtfField() {
+ return RichTextField.RTFfield();
});