diff options
Diffstat (limited to 'src/fields/Doc.ts')
-rw-r--r-- | src/fields/Doc.ts | 546 |
1 files changed, 305 insertions, 241 deletions
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 48214cf25..2d3dddc8b 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1,37 +1,31 @@ -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, 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 { /** @@ -43,9 +37,9 @@ export namespace Field { * @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])); - const valFunc = (field: Field): 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) @@ -61,9 +55,9 @@ export namespace Field { .trim() .replace(/^new List\((.*)\)$/, '$1'); }; - return !Field.IsField(field) ? (key.startsWith('_') ? '=' : '') : (onDelegate ? '=' : '') + valFunc(field); + return !Field.IsField(cfield) ? (key.startsWith('_') ? '=' : '') : (isOnDelegate ? '=' : '') + valFunc(cfield); } - export function toScriptString(field: Field) { + 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}"`; @@ -72,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': @@ -82,12 +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 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 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] : ''); @@ -95,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. @@ -120,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); @@ -156,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 @@ -178,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 @@ -217,8 +293,8 @@ 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 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'; @@ -274,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, @@ -288,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; @@ -300,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 = {}; @@ -320,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; @@ -329,7 +408,7 @@ 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); } @@ -352,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); } @@ -363,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]); @@ -377,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) { @@ -385,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; @@ -411,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; @@ -454,7 +539,7 @@ 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) { @@ -482,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; @@ -500,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. @@ -512,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; } @@ -569,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); @@ -586,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) { @@ -603,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); } @@ -634,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; } @@ -642,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); } @@ -670,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 @@ -709,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 { @@ -722,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); } @@ -749,7 +835,7 @@ 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)) { @@ -768,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); }; @@ -786,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>(); @@ -895,6 +903,7 @@ export namespace Doc { 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); @@ -905,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 }); @@ -954,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') { @@ -965,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); @@ -981,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... } } }); @@ -1000,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) { @@ -1011,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; } @@ -1038,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); @@ -1054,7 +1068,7 @@ 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) { + export function MakeDelegateWithProto(doc: Doc /* , id?: string, title?: string */) { const ndoc = Doc.ApplyTemplate(doc); if (ndoc) { Doc.GetProto(ndoc).isDataDoc = true; @@ -1067,7 +1081,7 @@ export namespace Doc { 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++ + ')'); @@ -1103,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. @@ -1123,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 { @@ -1189,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; @@ -1201,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); } @@ -1252,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); @@ -1270,43 +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; + 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; }); }); } @@ -1380,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; } @@ -1404,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; } @@ -1426,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)) @@ -1495,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'; } @@ -1505,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); @@ -1513,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; } } @@ -1578,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)) { @@ -1617,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; } }); @@ -1630,6 +1649,7 @@ export namespace Doc { title && (resolved.title = title); return resolved; } + return undefined; }; /** @@ -1639,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; } @@ -1666,61 +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.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(); }); |