diff options
Diffstat (limited to 'src/fields')
| -rw-r--r-- | src/fields/Doc.ts | 70 | ||||
| -rw-r--r-- | src/fields/InkField.ts | 33 | ||||
| -rw-r--r-- | src/fields/RichTextField.ts | 32 | ||||
| -rw-r--r-- | src/fields/RichTextUtils.ts | 8 | ||||
| -rw-r--r-- | src/fields/ScriptField.ts | 15 | ||||
| -rw-r--r-- | src/fields/Types.ts | 12 | ||||
| -rw-r--r-- | src/fields/documentSchemas.ts | 7 | ||||
| -rw-r--r-- | src/fields/util.ts | 1 | 
8 files changed, 126 insertions, 52 deletions
| diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index a9bc5ee70..bdd41b0bb 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -7,14 +7,14 @@ import { CollectionViewType, DocumentType } from '../client/documents/DocumentTy  import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';  import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper';  import { undoable, UndoManager } from '../client/util/UndoManager'; -import { ClientUtils, incrementTitleCopy } from '../ClientUtils'; +import { ClientUtils, imageUrlToBase64, 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 { InkTool } from './InkField'; +import { InkEraserTool, InkInkTool, InkTool } from './InkField';  import { List } from './List';  import { ObjectField, serverOpType } from './ObjectField';  import { PrefetchProxy, ProxyField } from './Proxy'; @@ -22,8 +22,9 @@ 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, toList } from './Types'; +import { BoolCast, Cast, DocCast, FieldValue, ImageCastWithSuffix, NumCast, RTFCast, StrCast, ToConstructor, toList } from './Types';  import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util'; +import { gptImageLabel } from '../client/apis/gpt/GPT';  export let ObjGetRefField: (id: string, force?: boolean) => Promise<Doc | undefined>;  export let ObjGetRefFields: (ids: string[]) => Promise<Map<string, Doc | undefined>>; @@ -97,9 +98,8 @@ export namespace Field {          });          return script;      } -    export function toString(fieldIn: unknown) { -        const field = fieldIn as FieldType; -        if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field); +    export function toString(field: FieldResult<FieldType> | FieldType | undefined) { +        if (field instanceof Promise || typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);          return field?.[ToString]?.() || '';      }      export function IsField(field: unknown): field is FieldType; @@ -111,7 +111,7 @@ export namespace Field {      export function Copy(field: unknown) {          return field instanceof ObjectField ? ObjectField.MakeCopy(field) : (field as FieldType);      } -    UndoManager.SetFieldPrinter(toString); +    UndoManager.SetFieldPrinter((val: unknown) => (IsField(val) ? toString(val) : ''));  }  export type FieldType = number | string | boolean | ObjectField | RefField;  export type Opt<T> = T | undefined; @@ -237,7 +237,7 @@ export class Doc extends RefField {      public static get MyPublishedDocs()       { return DocListCast(Doc.ActiveDashboard?.myPublishedDocs).concat(DocListCast(DocCast(Doc.UserDoc().myPublishedDocs)?.data)); } // prettier-ignore      public static get MyDashboards()          { return DocCast(Doc.UserDoc().myDashboards); } // prettier-ignore      public static get MyTemplates()           { return DocCast(Doc.UserDoc().myTemplates); } // prettier-ignore -    public static get MyAnnos()               { return DocCast(Doc.UserDoc().myAnnos); } // prettier-ignore +    public static get MyStickers()               { return DocCast(Doc.UserDoc().myStickers); } // prettier-ignore      public static get MyLightboxDrawings()    { return DocCast(Doc.UserDoc().myLightboxDrawings); } // prettier-ignore      public static get MyImports()             { return DocCast(Doc.UserDoc().myImports); } // prettier-ignore      public static get MyFilesystem()          { return DocCast(Doc.UserDoc().myFilesystem); } // prettier-ignore @@ -254,6 +254,10 @@ export class Doc extends RefField {      public static set ActivePage(val)         { Doc.UserDoc().activePage = val; } // prettier-ignore      public static get ActiveTool(): InkTool   { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool;  } // prettier-ignore      public static set ActiveTool(tool:InkTool){ Doc.UserDoc().activeTool = tool; } // prettier-ignore +    public static get ActiveInk(): InkInkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkInkTool;  } // prettier-ignore +    public static set ActiveInk(tool:InkInkTool){ Doc.UserDoc().activeInkTool = tool; } // prettier-ignore +    public static get ActiveEraser(): InkEraserTool { return StrCast(Doc.UserDoc().activeEraserTool, InkTool.None) as InkEraserTool;  } // prettier-ignore +    public static set ActiveEraser(tool:InkEraserTool){ Doc.UserDoc().activeEraserTool = tool; } // prettier-ignore      public static get ActivePresentation()    { return DocCast(Doc.ActiveDashboard?.activePresentation) as Opt<Doc>;  } // prettier-ignore      public static set ActivePresentation(val) { Doc.ActiveDashboard && (Doc.ActiveDashboard.activePresentation = val) } // prettier-ignore      public static get ActiveDashboard()       { return DocCast(Doc.UserDoc().activeDashboard); } // prettier-ignore @@ -337,7 +341,6 @@ export class Doc extends RefField {          if (!id || forceSave) {              DocServer.CreateDocField(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      } @@ -464,8 +467,6 @@ export class Doc extends RefField {              });      }  } - -// eslint-disable-next-line no-redeclare  export namespace Doc {      export let SelectOnLoad: Doc | undefined;      export function SetSelectOnLoad(doc: Doc | undefined) { @@ -661,7 +662,6 @@ 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);                  } @@ -965,6 +965,19 @@ export namespace Doc {                          }                      } else if (field instanceof PrefetchProxy) {                          Doc.FindReferences(field.value, references, system); +                    } else if (field instanceof RichTextField) { +                        const re = /"docId"\s*:\s*"(.*?)"/g; +                        let match: string[] | null; +                        while ((match = re.exec(field.Data)) !== null) { +                            const urlString = match[1]; +                            if (urlString) { +                                const rdoc = DocServer.GetCachedRefField(urlString); +                                if (rdoc) { +                                    references.add(rdoc); +                                    Doc.FindReferences(rdoc, references, system); +                                } +                            } +                        }                      }                  } else if (field instanceof Promise) {                      // eslint-disable-next-line no-debugger @@ -995,7 +1008,7 @@ export namespace Doc {                      } else if (field instanceof ObjectField) {                          const docAtKey = doc[key];                          copy[key] = -                            docAtKey instanceof Doc && key.includes('layout[') +                            docAtKey instanceof Doc && (key.includes('layout[') || docAtKey.cloneOnCopy)                                  ? new ProxyField(Doc.MakeCopy(docAtKey)) // copy the expanded render template                                  : ObjectField.MakeCopy(field);                      } else if (field instanceof Promise) { @@ -1194,7 +1207,6 @@ 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);      } @@ -1369,6 +1381,13 @@ export namespace Doc {      export const FilterAny = '--any--';      export const FilterNone = '--undefined--'; +    export function hasDocFilter(container: Opt<Doc>, key: string, value: string | undefined, fieldPrefix?: string) { +        if (!container) return; +        const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'childFilters'; +        const childFilters = StrListCast(container[filterField]); +        return childFilters.some(filter => filter.split(FilterSep)[0] === key && (value === undefined || value === Doc.FilterAny || filter.split(FilterSep)[1] === value)); +    } +      // filters document in a container collection:      // all documents with the specified value for the specified key are included/excluded      // based on the modifiers :"check", "x", undefined @@ -1379,8 +1398,8 @@ export namespace Doc {          runInAction(() => {              for (let i = 0; i < childFilters.length; i++) {                  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()) { +                if (fields[0] === key && (fields[1] === value?.toString() || value === Doc.FilterAny || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) { +                    if (fields[2] === modifiers && modifiers && (fields[1] === value?.toString() || value === Doc.FilterAny)) {                          // eslint-disable-next-line no-param-reassign                          if (toggle) modifiers = 'remove';                          else return; @@ -1449,6 +1468,25 @@ export namespace Doc {          });      } +    /** +     * text description of a Doc. RTF documents will have just their text and pdf documents will have the first 50 words. +     * Image documents are converted to bse64 and gpt generates a description for them. all other documents use their title. +     * @param doc +     * @returns +     */ +    export function getDescription(doc: Doc) { +        const curDescription = StrCast(doc[DocData][Doc.LayoutFieldKey(doc) + '_description']); +        const docText = (async (tdoc:Doc) => { +            switch (tdoc.type) { +                case DocumentType.PDF: return curDescription || StrCast(tdoc.text).split(/\s+/).slice(0, 50).join(' '); // first 50 words of pdf text +                case DocumentType.IMG: return curDescription || imageUrlToBase64(ImageCastWithSuffix(Doc.LayoutField(tdoc), '_o') ?? '') +                                                .then(hrefBase64 => gptImageLabel(hrefBase64, 'Give three to five labels to describe this image.')); +                case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutFieldKey(tdoc)]).Text; +                default:               return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title); +        }}); // prettier-ignore +        return docText(doc).then(text => (doc[DocData][Doc.LayoutFieldKey(doc) + '_description'] = text)); +    } +      // prettier-ignore      export function toIcon(doc?: Doc, isOpen?: Opt<boolean>) {          if (isOpen) return doc?.isFolder ? 'chevron-down' : 'folder-open'; diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 17b99b033..d1dda106a 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -8,19 +8,32 @@ import { ObjectField } from './ObjectField';  // Helps keep track of the current ink tool in use.  export enum InkTool { -    None = 'none', -    Pen = 'pen', -    Highlighter = 'highlighter', -    StrokeEraser = 'strokeeraser', -    SegmentEraser = 'segmenteraser', -    RadiusEraser = 'radiuseraser', -    Eraser = 'eraser', // not a real tool, but a class of tools -    Stamp = 'stamp', -    Write = 'write', -    PresentationPin = 'presentationpin', +    None = 'None', +    Ink = 'Ink', +    Eraser = 'Eraser', // not a real tool, but a class of tools      SmartDraw = 'smartdraw',  } +export enum InkInkTool { +    Pen = 'Pen', +    Highlight = 'Highlight', +    Write = 'Write', +} + +export enum InkEraserTool { +    Stroke = 'Stroke', +    Segment = 'Segment', +    Radius = 'Radius', +} + +export enum InkProperty { +    Mask = 'inkMask', +    Labels = 'labels', +    StrokeWidth = 'strokeWidth', +    StrokeColor = 'strokeColor', +    EraserWidth = ' eraserWidth', +} +  export type Segment = Array<Bezier>;  // Defines an ink as an array of points. diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 3f13f7e6d..dc636031a 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -13,10 +13,15 @@ export class RichTextField extends ObjectField {      @serializable(true)      readonly Text: string; -    constructor(data: string, text: string = '') { +    /** +     * NOTE: if 'text' doesn't match the plain text of 'data', this can cause infinite loop problems or other artifacts when rendered. +     * @param data this is the formatted text representation of the RTF +     * @param text this is the plain text of whatever text is in the 'data' +     */ +    constructor(data: string, text: string) {          super();          this.Data = data; -        this.Text = text; +        this.Text = text; // ideally, we'd compute 'text' from 'data' by doing what Prosemirror does at run-time ... just need to figure out how to write that function accurately      }      Empty() { @@ -43,4 +48,27 @@ export class RichTextField extends ObjectField {              ''          );      } + +    public static textToRtf(text: string, imgDocId?: string) { +        return new RichTextField( +            JSON.stringify({ +                // this is a RichText json that has the question text placed above a related image +                doc: { +                    type: 'doc', +                    content: [ +                        { +                            type: 'paragraph', +                            attrs: { align: 'center', color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, +                            content: [ +                                ...(text ? [{ type: 'text', text }] : []), // +                                ...(imgDocId ? [{ type: 'dashDoc', attrs: { width: '200px', height: '200px', title: 'dashDoc', float: 'unset', hidden: false, docId: imgDocId } }] : []), +                            ], +                        }, +                    ], +                }, +                selection: { type: 'text', anchor: 2, head: 2 }, +            }), +            text +        ); +    }  } diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index b3534dde7..42dd0d432 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -1,8 +1,7 @@ -/* eslint-disable @typescript-eslint/no-namespace */  /* eslint-disable no-await-in-loop */  /* eslint-disable no-use-before-define */  import { AssertionError } from 'assert'; -import * as Color from 'color'; +import Color from 'color';  import { docs_v1 as docsV1 } from 'googleapis';  import { Fragment, Mark, Node, Schema } from 'prosemirror-model';  import { sinkListItem } from 'prosemirror-schema-list'; @@ -175,7 +174,6 @@ export namespace RichTextUtils {              const indentMap = new Map<ListGroup, BulletPosition[]>();              let globalOffset = 0;              const nodes: Node[] = []; -            // eslint-disable-next-line no-restricted-syntax              for (const element of structured) {                  if (Array.isArray(element)) {                      lists.push(element); @@ -374,11 +372,9 @@ export namespace RichTextUtils {          const marksToStyle = async (nodes: (Node | null)[]): Promise<docsV1.Schema$Request[]> => {              const requests: docsV1.Schema$Request[] = [];              let position = 1; -            // eslint-disable-next-line no-restricted-syntax              for (const node of nodes) {                  if (node === null) {                      position += 2; -                    // eslint-disable-next-line no-continue                      continue;                  }                  const { marks, attrs, nodeSize } = node; @@ -390,9 +386,7 @@ export namespace RichTextUtils {                  };                  let mark: Mark;                  const markMap = BuildMarkMap(marks); -                // eslint-disable-next-line no-restricted-syntax                  for (const markName of Object.keys(schema.marks)) { -                    // eslint-disable-next-line no-cond-assign                      if (ignored.includes(markName) || !(mark = markMap[markName])) {                          continue;                      } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 582c09f29..b294ee8c6 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -85,6 +85,7 @@ async function deserializeScript(scriptIn: ScriptField) {  }  @scriptingGlobal +// eslint-disable-next-line no-use-before-define  @Deserializable('script', (obj: unknown) => deserializeScript(obj as ScriptField))  export class ScriptField extends ObjectField {      @serializable @@ -137,7 +138,6 @@ export class ScriptField extends ObjectField {      [ToString]() {          return this.script.originalScript;      } -    // eslint-disable-next-line default-param-last      public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }, transformer?: Transformer) {          return CompileScript(script, {              params: { @@ -156,13 +156,11 @@ export class ScriptField extends ObjectField {          });      } -    // eslint-disable-next-line default-param-last      public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {          const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);          return compiled.compiled ? new ScriptField(compiled) : undefined;      } -    // eslint-disable-next-line default-param-last      public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {          const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);          return compiled.compiled ? new ScriptField(compiled) : undefined; @@ -186,6 +184,7 @@ export class ScriptField extends ObjectField {  }  @scriptingGlobal +// eslint-disable-next-line no-use-before-define  @Deserializable('computed', (obj: unknown) => deserializeScript(obj as ComputedField))  export class ComputedField extends ScriptField {      static undefined = '__undefined'; @@ -229,7 +228,6 @@ export class ComputedField extends ScriptField {      [ToValue](doc: Doc)   { return ComputedField.useComputed ? { value: this.value(doc) } : undefined; } // prettier-ignore      [Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore -    // eslint-disable-next-line default-param-last      public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {          const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables });          const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined; @@ -265,14 +263,9 @@ export class ComputedField extends ScriptField {              doc[`${fieldKey}_indexed`] = flist;          }          const getField = ScriptField.CompileScript(`getIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey})`, {}, true, {}); -        const setField = ScriptField.CompileScript( -            `{setIndexVal (this['${fieldKey}_indexed'], this.${interpolatorKey}, value); console.log(this["${fieldKey}_indexed"][this.${interpolatorKey}],this.data,this["${fieldKey}_indexed"]))}`, -            { value: 'any' }, -            false, -            {} -        ); +        const setField = ScriptField.CompileScript(`{setIndexVal(this['${fieldKey}_indexed'], this.${interpolatorKey}, value);}`, { value: 'any' }, false, {});          doc[fieldKey] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; -        return doc[fieldKey]; +        return Field.Copy(doc[fieldKey]);      }  } diff --git a/src/fields/Types.ts b/src/fields/Types.ts index ef79f72e4..474882959 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -5,7 +5,7 @@ import { ProxyField } from './Proxy';  import { RefField } from './RefField';  import { RichTextField } from './RichTextField';  import { ScriptField } from './ScriptField'; -import { CsvField, ImageField, PdfField, WebField } from './URLField'; +import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField';  // eslint-disable-next-line no-use-before-define  export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T; @@ -122,12 +122,22 @@ export function CsvCast(field: FieldResult, defaultVal: CsvField | null = null)  export function WebCast(field: FieldResult, defaultVal: WebField | null = null) {      return Cast(field, WebField, defaultVal);  } +export function VideoCast(field: FieldResult, defaultVal: VideoField | null = null) { +    return Cast(field, VideoField, defaultVal); +} +export function AudioCast(field: FieldResult, defaultVal: AudioField | null = null) { +    return Cast(field, AudioField, defaultVal); +}  export function PDFCast(field: FieldResult, defaultVal: PdfField | null = null) {      return Cast(field, PdfField, defaultVal);  }  export function ImageCast(field: FieldResult, defaultVal: ImageField | null = null) {      return Cast(field, ImageField, defaultVal);  } +export function ImageCastWithSuffix(field: FieldResult, suffix: string, defaultVal: ImageField | null = null) { +    const href = ImageCast(field, defaultVal)?.url.href; +    return href ? `${href.split('.')[0]}${suffix}.${href.split('.')[1]}` : null; +}  export function FieldValue<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;  // eslint-disable-next-line no-redeclare diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 335683270..b27816f55 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -48,8 +48,8 @@ export const documentSchema = createSchema({      _columnsHideIfEmpty: 'boolean', // whether empty stacking view column headings should be hidden      // _columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry      // _schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views -    _text_fontSize: 'string', -    _text_fontFamily: 'string', +    text_fontSize: 'string', +    text_fontFamily: 'string',      _layout_sidebarWidthPercent: 'string', // percent of text window width taken up by sidebar      // appearance properties on the data document @@ -70,7 +70,7 @@ export const documentSchema = createSchema({      stroke_startMarker: 'string',      stroke_endMarker: 'string',      stroke_dash: 'string', -    textTransform: 'string', +    text_transform: 'string',      treeView_Open: 'boolean', //  flag denoting whether the documents sub-tree (contents) is visible or hidden      treeView_ExpandedView: 'string', // name of field whose contents are being displayed as the document's subtree      treeView_ExpandedViewLock: 'boolean', // whether the expanded view can be changed @@ -112,5 +112,4 @@ export const collectionSchema = createSchema({  });  export type Document = makeInterface<[typeof documentSchema]>; -// eslint-disable-next-line no-redeclare  export const Document = makeInterface(documentSchema); diff --git a/src/fields/util.ts b/src/fields/util.ts index 60eadcdfd..33764aca5 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -227,7 +227,6 @@ function getEffectiveAcl(target: Doc | ListImpl<FieldType>, user?: string): symb   * @param allowUpgrade whether permissions can be made less restrictive   * @param layoutOnly just sets the layout doc's ACL (unless the data doc has no entry for the ACL, in which case it will be set as well)   */ -// eslint-disable-next-line default-param-last  export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited: Doc[] = [], allowUpgrade?: boolean, layoutOnly = false) {      const selfKey = `acl_${normalizeEmail(ClientUtils.CurrentUserEmail())}`;      if (!target || visited.includes(target) || key === selfKey) return; | 
