{"version":3,"file":"main-a45d81a5.js","sources":["../../../src/contacts/model/ContactUtils.ts","../../../src/file/FileController.ts","../../../src/misc/ListModel.ts","../../../src/misc/Formatter.ts","../../../src/gui/nav/NavFunctions.ts","../../../src/misc/parsing/ParserCombinator.ts","../../../src/misc/parsing/MailAddressParser.ts","../../../src/mail/model/MailUtils.ts","../../../src/misc/MailboxPropertiesUtils.ts","../../../src/gui/SelectableRowContainer.ts","../../../src/gui/cards.ts","../../../src/misc/passwords/PasswordUtils.ts","../../../src/misc/OutOfOfficeNotificationUtils.ts","../../../src/gui/dialogs/SpellcheckLanguageDialog.ts","../../../src/search/model/SearchUtils.ts","../../../src/search/model/SearchModel.ts","../../../src/mail/model/InboxRuleHandler.ts","../../../src/misc/FormatValidator.ts","../../../src/misc/Website.ts","../../../src/misc/2fa/webauthn/WebauthnClient.ts","../../../src/misc/passwords/PasswordGeneratorDialog.ts","../../../src/misc/credentials/CredentialsProviderFactory.ts","../../../src/api/main/EventController.ts","../../../src/api/main/WorkerClient.ts","../../../src/api/main/EntropyCollector.ts","../../../src/api/main/UserError.ts","../../../src/mail/model/MailModel.ts","../../../src/gui/Notifications.ts","../../../src/calendar/model/CalendarModel.ts","../../../src/api/main/ProgressTracker.ts","../../../src/mail/model/MinimizedMailEditorViewModel.ts","../../../src/misc/credentials/CredentialsProvider.ts","../../../src/misc/credentials/CredentialsKeyProvider.ts","../../../src/misc/credentials/NativeCredentialsEncryption.ts","../../../src/misc/credentials/CredentialsKeyMigrator.ts","../../../src/gui/CompletenessIndicator.ts","../../../src/gui/dialogs/ProgressDialog.ts","../../../src/misc/2fa/SecondFactorAuthView.ts","../../../src/misc/2fa/SecondFactorUtils.ts","../../../src/misc/2fa/SecondFactorAuthDialog.ts","../../../src/misc/2fa/SecondFactorHandler.ts","../../../src/misc/2fa/webauthn/WebAuthn.ts","../../../src/misc/2fa/webauthn/WebauthnTypes.ts","../../../src/misc/2fa/webauthn/BrowserWebauthn.ts","../../packages/tutanota-usagetests/dist/model/Stage.js","../../packages/tutanota-usagetests/dist/model/UsageTest.js","../../packages/tutanota-usagetests/dist/model/UsageTestController.js","../../../src/misc/UsageTestModel.ts","../../../src/api/main/PageContextLoginListener.ts","../../../src/file/FileControllerBrowser.ts","../../../src/file/FileControllerNative.ts","../../../src/misc/news/NewsModel.ts","../../../src/misc/WebsocketConnectivityModel.ts","../../../src/api/main/OperationProgressTracker.ts","../../../src/gui/InfoMessageHandler.ts","../../../src/gui/ScopedRouter.ts","../../../src/api/main/MainLocator.ts","../../../src/gui/dialogs/ShortcutDialog.ts","../../../src/misc/SubscriptionDialogs.ts","../../../src/misc/ClipboardUtils.ts","../../../src/misc/ErrorReporter.ts","../../../src/misc/ErrorHandlerImpl.ts","../../../src/gui/main-styles.ts","../../../src/misc/LoginUtils.ts","../../../src/misc/NavShortcuts.ts","../../../src/gui/BaseTopLevelView.ts","../../../src/gui/LoginScreenHeader.ts","../../../src/gui/nav/ViewSlider.ts","../../../src/gui/Header.ts","../../../src/gui/ListColumnWrapper.ts","../../../src/misc/news/NewsList.ts","../../../src/gui/nav/DrawerMenu.ts","../../../src/misc/news/NewsDialog.ts","../../../src/gui/FolderColumnView.ts","../../../src/gui/SidebarSection.ts","../../../src/gui/nav/BottomNav.ts","../../../src/gui/ScrollSelectList.ts","../../../src/gui/SearchDropDown.ts","../../../src/gui/MailRecipientsTextField.ts","../../../src/gui/AttachmentBubble.ts","../../../src/gui/BackgroundColumnLayout.ts","../../../src/gui/DesktopToolbars.ts","../../../src/gui/SelectAllCheckbox.ts","../../../src/gui/MobileBottomActionBar.ts","../../../src/gui/BaseMobileHeader.ts","../../../src/gui/MobileHeader.ts","../../../src/misc/LazySearchBar.ts","../../../src/gui/MultiselectMobileHeader.ts","../../../src/gui/EnterMultiselectIconButton.ts","../../../src/gui/dialogs/SelectCredentialsEncryptionModeDialog.ts","../../../src/misc/passwords/PasswordGenerator.ts","../../../src/api/main/UpgradeRequiredError.ts","../../../src/api/main/RecipientsModel.ts","../../../src/misc/news/items/ReferralLinkViewer.ts","../../../src/misc/SanitizedTextViewModel.ts","../../../src/api/main/UserController.ts","../../../src/misc/RecipientsSearchModel.ts","../../../src/misc/news/MoreInfoLink.ts","../../../src/misc/news/items/UsageOptInNews.ts","../../../src/misc/news/items/RecoveryCodeNews.ts","../../../src/misc/news/items/PinBiometricsNews.ts","../../../src/misc/news/items/ReferralLinkNews.ts","../../../src/misc/news/items/NewPlansNews.ts","../../../src/misc/news/items/NewPlansOfferEndingNews.ts","../../../src/contacts/model/ContactModel.ts"],"sourcesContent":["import { lang } from \"../../misc/LanguageViewModel\"\nimport type { Birthday, Contact, ContactSocialId } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { formatDate } from \"../../misc/Formatter\"\nimport { isoDateToBirthday } from \"../../api/common/utils/BirthdayUtils\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport { ContactSocialType } from \"../../api/common/TutanotaConstants\"\n\nassertMainOrNode()\n\nexport type ContactNames = Pick<Contact, \"nickname\" | \"firstName\" | \"lastName\">\n\nexport function getContactDisplayName(contact: ContactNames): string {\n\tif (contact.nickname != null) {\n\t\treturn contact.nickname\n\t} else {\n\t\treturn `${contact.firstName} ${contact.lastName}`.trim()\n\t}\n}\n\nexport function getContactListName(contact: Contact): string {\n\tlet name = `${contact.firstName} ${contact.lastName}`.trim()\n\n\tif (name.length === 0) {\n\t\tname = contact.company.trim()\n\t}\n\n\treturn name\n}\n\nexport function formatBirthdayNumeric(birthday: Birthday): string {\n\tif (birthday.year) {\n\t\treturn formatDate(new Date(Number(birthday.year), Number(birthday.month) - 1, Number(birthday.day)))\n\t} else {\n\t\t//if no year is specified a leap year is used to allow 2/29 as birthday\n\t\treturn lang.formats.simpleDateWithoutYear.format(new Date(Number(2016), Number(birthday.month) - 1, Number(birthday.day)))\n\t}\n}\n\n/**\n * Returns the birthday of the contact as formatted string using default date formatter including date, month and year.\n * If birthday contains no year only month and day will be included.\n * If there is no birthday or an invalid birthday format an empty string returns.\n */\nexport function formatBirthdayOfContact(contact: Contact): string {\n\tif (contact.birthdayIso) {\n\t\tconst isoDate = contact.birthdayIso\n\n\t\ttry {\n\t\t\treturn formatBirthdayNumeric(isoDateToBirthday(isoDate))\n\t\t} catch (e) {\n\t\t\t// cant format, cant do anything\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\nexport function getSocialUrl(contactId: ContactSocialId): string {\n\tlet socialUrlType = \"\"\n\tlet http = \"https://\"\n\tlet worldwidew = \"www.\"\n\n\tconst isSchemePrefixed = contactId.socialId.indexOf(\"http\") !== -1\n\tconst isWwwDotPrefixed = contactId.socialId.indexOf(worldwidew) !== -1\n\n\tif (!isSchemePrefixed && !isWwwDotPrefixed) {\n\t\tswitch (contactId.type) {\n\t\t\tcase ContactSocialType.TWITTER:\n\t\t\t\tsocialUrlType = \"twitter.com/\"\n\t\t\t\tbreak\n\n\t\t\tcase ContactSocialType.FACEBOOK:\n\t\t\t\tsocialUrlType = \"facebook.com/\"\n\t\t\t\tbreak\n\n\t\t\tcase ContactSocialType.XING:\n\t\t\t\tsocialUrlType = \"xing.com/profile/\"\n\t\t\t\tbreak\n\n\t\t\tcase ContactSocialType.LINKED_IN:\n\t\t\t\tsocialUrlType = \"linkedin.com/in/\"\n\t\t}\n\t}\n\n\tif (isSchemePrefixed) {\n\t\thttp = \"\"\n\t}\n\n\tif (isSchemePrefixed || isWwwDotPrefixed) {\n\t\tworldwidew = \"\"\n\t}\n\n\treturn `${http}${worldwidew}${socialUrlType}${contactId.socialId.trim()}`\n}\n","import { Dialog } from \"../gui/base/Dialog\"\nimport { convertToDataFile, createDataFile, DataFile } from \"../api/common/DataFile\"\nimport { assertMainOrNode } from \"../api/common/Env\"\nimport { assertNotNull, neverNull, promiseMap } from \"@tutao/tutanota-utils\"\nimport { CryptoError } from \"../api/common/error/CryptoError\"\nimport { lang, TranslationKey } from \"../misc/LanguageViewModel\"\nimport { BrowserType } from \"../misc/ClientConstants\"\nimport { client } from \"../misc/ClientDetector\"\nimport { File as TutanotaFile } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { deduplicateFilenames, FileReference, sanitizeFilename } from \"../api/common/utils/FileUtils\"\nimport { isOfflineError } from \"../api/common/utils/ErrorCheckUtils.js\"\nimport { FileFacade } from \"../api/worker/facades/lazy/FileFacade.js\"\nimport { BlobFacade } from \"../api/worker/facades/lazy/BlobFacade.js\"\nimport { ArchiveDataType } from \"../api/common/TutanotaConstants.js\"\nimport stream from \"mithril/stream\"\nimport Stream from \"mithril/stream\"\nimport { showProgressDialog } from \"../gui/dialogs/ProgressDialog.js\"\nimport { CancelledError } from \"../api/common/error/CancelledError.js\"\nimport { ConnectionError } from \"../api/common/error/RestError.js\"\nimport { elementIdPart, listIdPart } from \"../api/common/utils/EntityUtils.js\"\nimport { BlobReferencingInstance } from \"../api/worker/facades/BlobAccessTokenFacade.js\"\n\nassertMainOrNode()\nexport const CALENDAR_MIME_TYPE = \"text/calendar\"\n\nconst enum DownloadPostProcessing {\n\tOpen,\n\tWrite,\n}\n\nexport type ProgressObserver = (somePromise: Promise<void>, progress?: Stream<number>) => Promise<void>\n\n/**\n * coordinates single and multiple downloads on different platforms\n */\nexport abstract class FileController {\n\tprotected constructor(\n\t\tprotected readonly blobFacade: BlobFacade,\n\t\tprotected readonly fileFacade: FileFacade,\n\t\tprotected readonly observeProgress: ProgressObserver,\n\t) {}\n\n\tprivate async doDownload(tutanotaFiles: TutanotaFile[], action: DownloadPostProcessing, progress?: stream<number>): Promise<void> {\n\t\tconst downloadedFiles: Array<FileReference | DataFile> = []\n\t\ttry {\n\t\t\tlet isOffline = false\n\t\t\tfor (const file of tutanotaFiles) {\n\t\t\t\ttry {\n\t\t\t\t\tconst downloadedFile = await this.downloadAndDecrypt(file)\n\t\t\t\t\tdownloadedFiles.push(downloadedFile)\n\t\t\t\t\tif (progress != null) {\n\t\t\t\t\t\tprogress(((tutanotaFiles.indexOf(file) + 1) / tutanotaFiles.length) * 100)\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {\n\t\t\t\t\tawait handleDownloadErrors(e, (msg) => {\n\t\t\t\t\t\tif (msg === \"couldNotAttachFile_msg\") {\n\t\t\t\t\t\t\tisOffline = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tDialog.message(() => lang.get(msg) + \" \" + file.name)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tif (isOffline) break // don't try to download more files, but the previous ones (if any) will still be downloaded\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (downloadedFiles.length > 0) {\n\t\t\t\tif (action === DownloadPostProcessing.Open) {\n\t\t\t\t\tawait this.openDownloadedFiles(downloadedFiles)\n\t\t\t\t} else {\n\t\t\t\t\tawait this.writeDownloadedFiles(downloadedFiles)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isOffline) {\n\t\t\t\tthrow new ConnectionError(\"currently offline\")\n\t\t\t}\n\t\t} finally {\n\t\t\t// we don't necessarily know when the user is done with the temporary file that was opened.\n\t\t\tif (action !== DownloadPostProcessing.Open) await this.cleanUp(downloadedFiles)\n\t\t}\n\t}\n\n\t/**\n\t * get the referenced TutanotaFile as a DataFile without writing anything to disk\n\t */\n\tasync getAsDataFile(file: TutanotaFile): Promise<DataFile> {\n\t\t// using the browser's built-in download since we don't want to write anything to disk here\n\t\treturn downloadAndDecryptDataFile(file, this.fileFacade, this.blobFacade)\n\t}\n\n\t/**\n\t * Save a DataFile locally\n\t */\n\tabstract saveDataFile(file: DataFile): Promise<void>\n\n\t/**\n\t * Download a file from the server to the filesystem\n\t */\n\tasync download(file: TutanotaFile) {\n\t\tawait this.observeProgress(this.doDownload([file], DownloadPostProcessing.Write))\n\t}\n\n\t/**\n\t * Download all provided files\n\t *\n\t * Temporary files are deleted afterwards in apps.\n\t */\n\tasync downloadAll(files: Array<TutanotaFile>): Promise<void> {\n\t\tconst progress = stream(0)\n\t\tawait this.observeProgress(this.doDownload(files, DownloadPostProcessing.Write, progress), progress)\n\t}\n\n\t/**\n\t * Open a file in the host system\n\t * Temporary files are deleted afterwards in apps.\n\t */\n\tasync open(file: TutanotaFile) {\n\t\tawait this.observeProgress(this.doDownload([file], DownloadPostProcessing.Open))\n\t}\n\n\tprotected abstract writeDownloadedFiles(downloadedFiles: Array<FileReference | DataFile>): Promise<void>\n\n\tprotected abstract openDownloadedFiles(downloadedFiles: Array<FileReference | DataFile>): Promise<void>\n\n\tprotected abstract cleanUp(downloadedFiles: Array<FileReference | DataFile>): Promise<void>\n\n\t/**\n\t * Get a file from the server and decrypt it\n\t */\n\tprotected abstract downloadAndDecrypt(file: TutanotaFile): Promise<FileReference | DataFile>\n}\n\n/**\n * The migration to blob attachments does not remove the FileData reference from files. This might change, therefore,\n * everytime we need to decide whether to treat a file as legacy, we should use this method, so that it is easier to\n * change this behavior in the future.\n * @param file\n */\nexport function isLegacyFile(file: TutanotaFile): boolean {\n\treturn file.blobs.length === 0\n}\n\nexport function handleDownloadErrors<R>(e: Error, errorAction: (msg: TranslationKey) => R): R {\n\tif (isOfflineError(e)) {\n\t\treturn errorAction(\"couldNotAttachFile_msg\")\n\t} else if (e instanceof CryptoError) {\n\t\treturn errorAction(\"corrupted_msg\")\n\t} else {\n\t\tthrow e\n\t}\n}\n\nexport function readLocalFiles(fileList: FileList): Promise<Array<DataFile>> {\n\t// create an array of files form the FileList because we can not iterate the FileList directly\n\tlet nativeFiles: File[] = []\n\n\tfor (let i = 0; i < fileList.length; i++) {\n\t\tnativeFiles.push(fileList[i])\n\t}\n\n\treturn promiseMap(\n\t\tnativeFiles,\n\t\t(nativeFile) => {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tlet reader = new FileReader()\n\n\t\t\t\treader.onloadend = function (evt: ProgressEvent) {\n\t\t\t\t\tconst target: any = evt.target\n\n\t\t\t\t\tif (target.readyState === reader.DONE && target.result) {\n\t\t\t\t\t\t// DONE == 2\n\t\t\t\t\t\tresolve(convertToDataFile(nativeFile, new Uint8Array(target.result)))\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject(new Error(\"could not load file\"))\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treader.readAsArrayBuffer(nativeFile)\n\t\t\t})\n\t\t},\n\t\t{\n\t\t\tconcurrency: 5,\n\t\t},\n\t)\n}\n\n/**\n * @param allowMultiple allow selecting multiple files\n * @param allowedExtensions Array of extensions strings without \".\"\n */\nexport function showFileChooser(allowMultiple: boolean, allowedExtensions?: Array<string>): Promise<Array<DataFile>> {\n\t// each time when called create a new file chooser to make sure that the same file can be selected twice directly after another\n\t// remove the last file input\n\tconst fileInput = document.getElementById(\"hiddenFileChooser\")\n\tconst body = neverNull(document.body)\n\n\tif (fileInput) {\n\t\t// remove the old one because it may contain a file already\n\t\tbody.removeChild(fileInput)\n\t}\n\n\tconst newFileInput = document.createElement(\"input\")\n\tnewFileInput.setAttribute(\"type\", \"file\")\n\n\tif (allowMultiple) {\n\t\tnewFileInput.setAttribute(\"multiple\", \"multiple\")\n\t}\n\n\tnewFileInput.setAttribute(\"id\", \"hiddenFileChooser\")\n\n\tif (allowedExtensions) {\n\t\tnewFileInput.setAttribute(\"accept\", allowedExtensions.map((e) => \".\" + e).join(\",\"))\n\t}\n\n\tnewFileInput.style.display = \"none\"\n\tconst promise: Promise<Array<DataFile>> = new Promise((resolve) => {\n\t\tnewFileInput.addEventListener(\"change\", (e: Event) => {\n\t\t\treadLocalFiles((e.target as any).files)\n\t\t\t\t.then(resolve)\n\t\t\t\t.catch(async (e) => {\n\t\t\t\t\tconsole.log(e)\n\t\t\t\t\tawait Dialog.message(\"couldNotAttachFile_msg\")\n\t\t\t\t\tresolve([])\n\t\t\t\t})\n\t\t})\n\t})\n\t// the file input must be put into the dom, otherwise it does not work in IE\n\tbody.appendChild(newFileInput)\n\tnewFileInput.click()\n\treturn promise\n}\n\n/**\n * takes a list of DataFiles and creates one DataFile from them that represents a zip\n * containing the the other files\n *\n * currently waits on all DataFiles being available before starting to add them to the zip.\n * It may be even faster to create the zip asap and adding the datafiles as they resolve.\n *\n * duplicate file names lead to the second file added overwriting the first one.\n *\n * @param dataFiles Promise resolving to an array of DataFiles\n * @param name the name of the new zip file\n */\nexport async function zipDataFiles(dataFiles: Array<DataFile>, name: string): Promise<DataFile> {\n\tconst jsZip = await import(\"jszip\")\n\tconst zip = jsZip.default()\n\tconst deduplicatedMap = deduplicateFilenames(dataFiles.map((df) => sanitizeFilename(df.name)))\n\tfor (let file of dataFiles) {\n\t\tconst filename = assertNotNull(deduplicatedMap[file.name].shift())\n\t\tzip.file(sanitizeFilename(filename), file.data, { binary: true })\n\t}\n\tconst zipData = await zip.generateAsync({ type: \"uint8array\" })\n\treturn createDataFile(name, \"application/zip\", zipData)\n}\n\nexport async function openDataFileInBrowser(dataFile: DataFile): Promise<void> {\n\ttry {\n\t\tconst URL = window.URL ?? window.webkitURL\n\n\t\t// Workaround for new behaviour in firefox 98 where PDF attachments open in the same tab by default\n\t\t// Users can always change their settings to \"always ask\" or somesuch, but it's very not nice for this to happen at all\n\t\t// because the app gets clobbered, logging users out as well as losing their non-persistent sessions\n\t\t// There is a bug report: https://bugzilla.mozilla.org/show_bug.cgi?id=1756980\n\t\t// It is unclear whether this will be fixed on the firefox side as it seems that they consider it to be expected behaviour\n\t\t// Maybe it will gain enough traction that it will be reverted\n\t\t// It's unclear to me why target=_blank is being ignored. If there is a way to ensure that it always opens a new tab,\n\t\t// Then we should do that instead of this, because it's preferable to keep the mime type.\n\t\tconst needsPdfWorkaround = dataFile.mimeType === \"application/pdf\" && client.browser === BrowserType.FIREFOX && client.browserVersion >= 98\n\n\t\tconst mimeType = needsPdfWorkaround ? \"application/octet-stream\" : dataFile.mimeType\n\n\t\tconst blob = new Blob([dataFile.data], { type: mimeType })\n\t\tconst url = URL.createObjectURL(blob)\n\t\tconst a = document.createElement(\"a\")\n\n\t\tif (typeof a.download !== \"undefined\") {\n\t\t\ta.href = url\n\t\t\ta.download = dataFile.name\n\t\t\ta.style.display = \"none\"\n\t\t\ta.target = \"_blank\"\n\t\t\tdocument.body.appendChild(a)\n\t\t\ta.click()\n\t\t\tdocument.body.removeChild(a)\n\t\t\t// Do not revoke object URL right away so that the browser has a chance to open it\n\t\t\tsetTimeout(() => {\n\t\t\t\twindow.URL.revokeObjectURL(url)\n\t\t\t}, 2000)\n\t\t} else {\n\t\t\tif (client.isIos() && client.browser === BrowserType.CHROME && typeof FileReader === \"function\") {\n\t\t\t\tconst reader = new FileReader()\n\t\t\t\tconst downloadPromise = new Promise((resolve) => {\n\t\t\t\t\treader.onloadend = async function () {\n\t\t\t\t\t\tconst url = reader.result as any\n\t\t\t\t\t\tresolve(await Dialog.legacyDownload(dataFile.name, url))\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\treader.readAsDataURL(blob)\n\t\t\t\tawait downloadPromise\n\t\t\t} else {\n\t\t\t\t// if the download attribute is not supported try to open the link in a new tab.\n\t\t\t\tawait Dialog.legacyDownload(dataFile.name, url)\n\t\t\t}\n\t\t}\n\t} catch (e) {\n\t\tconsole.log(e)\n\t\treturn Dialog.message(\"canNotOpenFileOnDevice_msg\")\n\t}\n}\n\nexport async function downloadAndDecryptDataFile(file: TutanotaFile, fileFacade: FileFacade, blobFacade: BlobFacade): Promise<DataFile> {\n\tif (isLegacyFile(file)) {\n\t\treturn await fileFacade.downloadFileContent(file)\n\t} else {\n\t\tconst bytes = await blobFacade.downloadAndDecrypt(ArchiveDataType.Attachments, createReferencingInstance(file))\n\t\treturn convertToDataFile(file, bytes)\n\t}\n}\n\nexport function createReferencingInstance(tutanotaFile: TutanotaFile): BlobReferencingInstance {\n\treturn {\n\t\tblobs: tutanotaFile.blobs,\n\t\telementId: elementIdPart(tutanotaFile._id),\n\t\tlistId: listIdPart(tutanotaFile._id),\n\t\tentity: tutanotaFile,\n\t}\n}\n\nexport async function guiDownload(downloadPromise: Promise<void>, progress?: stream<number>): Promise<void> {\n\ttry {\n\t\tawait showProgressDialog(\"pleaseWait_msg\", downloadPromise, progress)\n\t} catch (e) {\n\t\t// handle the user cancelling the dialog\n\t\tif (e instanceof CancelledError) {\n\t\t\treturn\n\t\t}\n\t\tconsole.log(\"downloadAndOpen error\", e.message)\n\t\tawait handleDownloadErrors(e, Dialog.message)\n\t}\n}\n","import { getElementId, isSameId, ListElement } from \"../api/common/utils/EntityUtils.js\"\nimport { ListLoadingState, ListState } from \"../gui/base/List.js\"\nimport { isOfflineError } from \"../api/common/utils/ErrorCheckUtils.js\"\nimport { OperationType } from \"../api/common/TutanotaConstants.js\"\nimport { settledThen } from \"@tutao/tutanota-utils/dist/PromiseUtils.js\"\nimport { assertNonNull, binarySearch, defer, findLast, first, getFirstOrThrow, last, lastThrow, remove } from \"@tutao/tutanota-utils\"\nimport { findBy, setAddAll } from \"@tutao/tutanota-utils/dist/CollectionUtils.js\"\nimport { memoizedWithHiddenArgument } from \"@tutao/tutanota-utils/dist/Utils.js\"\nimport Stream from \"mithril/stream\"\nimport stream from \"mithril/stream\"\nimport { ListFetchResult, PageSize } from \"../gui/base/ListUtils.js\"\n\nexport interface ListModelConfig<ElementType> {\n\ttopId: Id\n\n\t/**\n\t * Get the given number of entities starting after the given id. May return more elements than requested, e.g. if all elements are available on first fetch.\n\t */\n\tfetch(startId: Id, count: number): Promise<ListFetchResult<ElementType>>\n\n\t/**\n\t * Returns null if the given element could not be loaded\n\t */\n\tloadSingle(elementId: Id): Promise<ElementType | null>\n\n\tsortCompare(entity1: ElementType, entity2: ElementType): number\n}\n\nexport type ListFilter<ElementType extends ListElement> = (item: ElementType) => boolean\n\ntype PrivateListState<ElementType> = Omit<ListState<ElementType>, \"items\" | \"activeIndex\"> & {\n\tunfilteredItems: ElementType[]\n\tfilteredItems: ElementType[]\n\tactiveElement: ElementType | null\n}\n\n/** ListModel that does the state upkeep for the List, including loading state, loaded items, selection and filters*/\nexport class ListModel<ElementType extends ListElement> {\n\tconstructor(private readonly config: ListModelConfig<ElementType>) {}\n\n\tprivate loadState: \"created\" | \"initialized\" = \"created\"\n\tprivate loading: Promise<unknown> = Promise.resolve()\n\tprivate filter: ListFilter<ElementType> | null = null\n\tprivate rangeSelectionAnchorElement: ElementType | null = null\n\n\tget state(): ListState<ElementType> {\n\t\treturn this.stateStream()\n\t}\n\n\tprivate get rawState(): PrivateListState<ElementType> {\n\t\treturn this.rawStateStream()\n\t}\n\n\tprivate rawStateStream: Stream<PrivateListState<ElementType>> = stream({\n\t\tunfilteredItems: [],\n\t\tfilteredItems: [],\n\t\tinMultiselect: false,\n\t\tloadingStatus: ListLoadingState.Idle,\n\t\tloadingAll: false,\n\t\tselectedItems: new Set(),\n\t\tactiveElement: null,\n\t})\n\n\treadonly stateStream: Stream<ListState<ElementType>> = this.rawStateStream.map((state) => {\n\t\tconst activeElement = state.activeElement\n\t\tconst foundIndex = activeElement ? binarySearch(state.filteredItems, activeElement, (l, r) => this.config.sortCompare(l, r)) : -1\n\t\tconst activeIndex = foundIndex < 0 ? null : foundIndex\n\t\treturn { ...state, items: state.filteredItems, activeIndex }\n\t})\n\n\tprivate updateState(newStatePart: Partial<PrivateListState<ElementType>>) {\n\t\tthis.rawStateStream({ ...this.rawState, ...newStatePart })\n\t}\n\n\tprivate waitUtilInit(): Promise<unknown> {\n\t\tconst deferred = defer()\n\t\tconst subscription = this.rawStateStream.map(() => {\n\t\t\tif (this.loadState === \"initialized\") {\n\t\t\t\tPromise.resolve().then(() => {\n\t\t\t\t\tsubscription.end(true)\n\t\t\t\t\tdeferred.resolve(undefined)\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t\treturn deferred.promise\n\t}\n\n\tasync loadInitial() {\n\t\tif (this.loadState !== \"created\") {\n\t\t\treturn\n\t\t}\n\t\tthis.loadState = \"initialized\"\n\t\tawait this.doLoad()\n\t}\n\n\tasync loadMore() {\n\t\tif (this.rawState.loadingStatus === ListLoadingState.Loading) {\n\t\t\treturn this.loading\n\t\t}\n\t\tif (this.loadState !== \"initialized\" || this.rawState.loadingStatus !== ListLoadingState.Idle) {\n\t\t\treturn\n\t\t}\n\t\tawait this.doLoad()\n\t}\n\n\tasync retryLoading() {\n\t\tif (this.loadState !== \"initialized\" || this.rawState.loadingStatus !== ListLoadingState.ConnectionLost) {\n\t\t\treturn\n\t\t}\n\t\tawait this.doLoad()\n\t}\n\n\tprivate async doLoad() {\n\t\tthis.updateState({ loadingStatus: ListLoadingState.Loading })\n\t\tthis.loading = Promise.resolve().then(async () => {\n\t\t\tconst lastItem = last(this.rawState.unfilteredItems)\n\t\t\ttry {\n\t\t\t\tconst { items: newItems, complete } = await this.config.fetch(lastItem ? getElementId(lastItem) : this.config.topId, PageSize)\n\t\t\t\t// if the loading was cancelled in the meantime, don't insert anything so that it's not confusing\n\t\t\t\tif (this.state.loadingStatus === ListLoadingState.ConnectionLost) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst newUnfilteredItems = [...this.rawState.unfilteredItems, ...newItems]\n\t\t\t\tnewUnfilteredItems.sort(this.config.sortCompare)\n\n\t\t\t\tconst newFilteredItems = [...this.rawState.filteredItems, ...this.applyFilter(newItems)]\n\t\t\t\tnewFilteredItems.sort(this.config.sortCompare)\n\n\t\t\t\tconst loadingStatus = complete ? ListLoadingState.Done : ListLoadingState.Idle\n\t\t\t\tthis.updateState({ loadingStatus, unfilteredItems: newUnfilteredItems, filteredItems: newFilteredItems })\n\t\t\t} catch (e) {\n\t\t\t\tthis.updateState({ loadingStatus: ListLoadingState.ConnectionLost })\n\t\t\t\tif (!isOfflineError(e)) {\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\treturn this.loading\n\t}\n\n\tprivate applyFilter(newItems: ReadonlyArray<ElementType>): Array<ElementType> {\n\t\treturn newItems.filter(this.filter ?? (() => true))\n\t}\n\n\tsetFilter(filter: ListFilter<ElementType> | null) {\n\t\tthis.filter = filter\n\t\tthis.reapplyFilter()\n\t}\n\n\treapplyFilter() {\n\t\tconst newFilteredItems = this.applyFilter(this.rawState.unfilteredItems)\n\n\t\tconst newSelectedItems = new Set(this.applyFilter([...this.state.selectedItems]))\n\n\t\tthis.updateState({ filteredItems: newFilteredItems, selectedItems: newSelectedItems })\n\t}\n\n\tisFiltered(): boolean {\n\t\treturn this.filter != null\n\t}\n\n\tasync entityEventReceived(elementId: Id, operation: OperationType): Promise<void> {\n\t\tif (operation === OperationType.CREATE || operation === OperationType.UPDATE) {\n\t\t\t// load the element without range checks for now\n\t\t\tconst entity = await this.config.loadSingle(elementId)\n\t\t\tif (!entity) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Wait for any pending loading\n\t\t\treturn settledThen(this.loading, () => {\n\t\t\t\tif (operation === OperationType.CREATE) {\n\t\t\t\t\tif (\n\t\t\t\t\t\tthis.rawState.loadingStatus === ListLoadingState.Done ||\n\t\t\t\t\t\t// new element is in the loaded range or newer than the first element\n\t\t\t\t\t\t(this.rawState.unfilteredItems.length > 0 && this.config.sortCompare(entity, lastThrow(this.rawState.unfilteredItems)) < 0)\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.addToLoadedEntities(entity)\n\t\t\t\t\t}\n\t\t\t\t} else if (operation === OperationType.UPDATE) {\n\t\t\t\t\tthis.updateLoadedEntity(entity)\n\t\t\t\t}\n\t\t\t})\n\t\t} else if (operation === OperationType.DELETE) {\n\t\t\t// await this.swipeHandler?.animating\n\t\t\tawait this.deleteLoadedEntity(elementId)\n\t\t}\n\t}\n\n\tprivate addToLoadedEntities(entity: ElementType) {\n\t\tconst id = getElementId(entity)\n\t\tif (this.rawState.unfilteredItems.some((item) => getElementId(item) === id)) {\n\t\t\treturn\n\t\t}\n\n\t\t// can we do something like binary search?\n\t\tconst unfilteredItems = this.rawState.unfilteredItems.concat(entity).sort(this.config.sortCompare)\n\t\tconst filteredItems = this.rawState.filteredItems.concat(this.applyFilter([entity])).sort(this.config.sortCompare)\n\t\tthis.updateState({ filteredItems, unfilteredItems })\n\t}\n\n\tprivate updateLoadedEntity(entity: ElementType) {\n\t\t// We cannot use binary search here because the sort order of items can change based on the entity update and we need to find the position of the\n\t\t// old entity by id in order to remove it.\n\n\t\t// update unfiltered list: find the position, take out the old item and put the updated one\n\t\tconst positionToUpdateUnfiltered = this.rawState.unfilteredItems.findIndex((item) => isSameId(item._id, entity._id))\n\t\tconst unfilteredItems = this.rawState.unfilteredItems.slice()\n\t\tif (positionToUpdateUnfiltered >= 0) {\n\t\t\tunfilteredItems.splice(positionToUpdateUnfiltered, 1, entity)\n\t\t\tunfilteredItems.sort(this.config.sortCompare)\n\t\t}\n\n\t\t// update filtered list & selected items\n\t\tconst positionToUpdateFiltered = this.rawState.filteredItems.findIndex((item) => isSameId(item._id, entity._id))\n\t\tconst filteredItems = this.rawState.filteredItems.slice()\n\t\tconst selectedItems = new Set(this.rawState.selectedItems)\n\t\tif (positionToUpdateFiltered >= 0) {\n\t\t\tconst [oldItem] = filteredItems.splice(positionToUpdateFiltered, 1, entity)\n\t\t\tfilteredItems.sort(this.config.sortCompare)\n\t\t\tif (selectedItems.delete(oldItem)) {\n\t\t\t\tselectedItems.add(entity)\n\t\t\t}\n\t\t}\n\n\t\t// keep active element up-to-date\n\t\tconst activeElementUpdated = this.rawState.activeElement != null && isSameId(this.rawState.activeElement._id, entity._id)\n\t\tconst newActiveElement = activeElementUpdated ? this.rawState.activeElement : this.rawState.activeElement\n\n\t\tif (positionToUpdateUnfiltered !== -1 || positionToUpdateFiltered !== -1 || activeElementUpdated) {\n\t\t\tthis.updateState({ unfilteredItems, filteredItems, selectedItems, activeElement: newActiveElement })\n\t\t}\n\n\t\t// keep anchor up-to-date\n\t\tif (this.rangeSelectionAnchorElement != null && isSameId(this.rangeSelectionAnchorElement._id, entity._id)) {\n\t\t\tthis.rangeSelectionAnchorElement = entity\n\t\t}\n\t}\n\n\tprivate deleteLoadedEntity(elementId: Id): Promise<void> {\n\t\treturn settledThen(this.loading, () => {\n\t\t\tconst entity = this.rawState.filteredItems.find((e) => getElementId(e) === elementId)\n\n\t\t\tconst selectedItems = new Set(this.rawState.selectedItems)\n\n\t\t\tlet newActiveElement\n\n\t\t\tif (entity) {\n\t\t\t\tselectedItems.delete(entity)\n\n\t\t\t\t// select next item in the list\n\t\t\t\tif (\n\t\t\t\t\tthis.getSelectedAsArray().length === 1 &&\n\t\t\t\t\tthis.getSelectedAsArray()[0] === entity &&\n\t\t\t\t\tthis.rawState.filteredItems.length > 1 &&\n\t\t\t\t\t!this.rawState.inMultiselect\n\t\t\t\t) {\n\t\t\t\t\tnewActiveElement =\n\t\t\t\t\t\tentity === last(this.state.items)\n\t\t\t\t\t\t\t? this.state.items[this.state.items.length - 2]\n\t\t\t\t\t\t\t: this.state.items[this.state.items.indexOf(entity) + 1]\n\n\t\t\t\t\tselectedItems.add(newActiveElement)\n\t\t\t\t}\n\n\t\t\t\tconst filteredItems = this.rawState.filteredItems.slice()\n\t\t\t\tremove(filteredItems, entity)\n\t\t\t\tconst unfilteredItems = this.rawState.unfilteredItems.slice()\n\t\t\t\tremove(unfilteredItems, entity)\n\t\t\t\tthis.updateState({ filteredItems, selectedItems, unfilteredItems, activeElement: newActiveElement ?? this.rawState.activeElement })\n\t\t\t}\n\t\t})\n\t}\n\n\tonSingleSelection(item: ElementType): void {\n\t\tthis.updateState({ selectedItems: new Set([item]), inMultiselect: false, activeElement: item })\n\t\tthis.rangeSelectionAnchorElement = item\n\t}\n\n\t/** An element was added to the selection. If multiselect was not on, discard previous single selection and only added selected item to the selection. */\n\tonSingleExclusiveSelection(item: ElementType): void {\n\t\tif (!this.rawState.inMultiselect) {\n\t\t\tthis.updateState({ selectedItems: new Set([item]), inMultiselect: true, activeElement: item })\n\t\t\tthis.rangeSelectionAnchorElement = item\n\t\t} else {\n\t\t\tconst selectedItems = new Set(this.state.selectedItems)\n\t\t\tif (selectedItems.has(item)) {\n\t\t\t\tselectedItems.delete(item)\n\t\t\t} else {\n\t\t\t\tselectedItems.add(item)\n\t\t\t}\n\t\t\tif (selectedItems.size === 0) {\n\t\t\t\tthis.updateState({ selectedItems, inMultiselect: false, activeElement: null })\n\t\t\t\tthis.rangeSelectionAnchorElement = null\n\t\t\t} else {\n\t\t\t\tthis.updateState({ selectedItems, inMultiselect: true, activeElement: item })\n\t\t\t\tthis.rangeSelectionAnchorElement = item\n\t\t\t}\n\t\t}\n\t}\n\n\t/** An element was added to the selection. If multiselect was not on, add previous single selection and newly added selected item to the selection. */\n\tonSingleInclusiveSelection(item: ElementType): void {\n\t\tconst selectedItems = new Set(this.state.selectedItems)\n\t\tif (this.state.inMultiselect && selectedItems.has(item)) {\n\t\t\tselectedItems.delete(item)\n\t\t} else {\n\t\t\tselectedItems.add(item)\n\t\t}\n\n\t\tif (selectedItems.size === 0) {\n\t\t\tthis.updateState({ selectedItems, inMultiselect: false, activeElement: null })\n\t\t\tthis.rangeSelectionAnchorElement = null\n\t\t} else {\n\t\t\tthis.updateState({ selectedItems, inMultiselect: true, activeElement: item })\n\t\t\tthis.rangeSelectionAnchorElement = item\n\t\t}\n\t}\n\n\tasync loadAndSelect(itemId: Id, shouldStop: () => boolean): Promise<ElementType | null> {\n\t\tawait this.waitUtilInit()\n\t\tlet foundItem: ElementType | undefined = undefined\n\t\twhile (\n\t\t\t// if we did find the target mail, stop\n\t\t\t// make sure to call this before shouldStop or we might stop before trying to find an item\n\t\t\t// this can probably be optimized to be binary search in most (all?) cases\n\t\t\t!(foundItem = this.rawState.unfilteredItems.find((item) => getElementId(item) === itemId)) &&\n\t\t\t!shouldStop() &&\n\t\t\t// if we are done loading, stop\n\t\t\tthis.rawState.loadingStatus !== ListLoadingState.Done &&\n\t\t\t// if we are offline, stop\n\t\t\tthis.rawState.loadingStatus !== ListLoadingState.ConnectionLost\n\t\t) {\n\t\t\tawait this.loadMore()\n\t\t}\n\t\tif (foundItem) {\n\t\t\tthis.onSingleSelection(foundItem)\n\t\t}\n\t\treturn foundItem ?? null\n\t}\n\n\tselectRangeTowards(item: ElementType): void {\n\t\tconst selectedItems = new Set(this.state.selectedItems)\n\t\tif (selectedItems.size === 0) {\n\t\t\tselectedItems.add(item)\n\t\t} else {\n\t\t\t// we are trying to find the element that's closest to the click one\n\t\t\t// and after that we will select everything between the closest and the clicked one\n\n\t\t\tconst clickedItemIndex: number = this.state.items.indexOf(item)\n\t\t\tlet nearestSelectedIndex: number | null = null\n\n\t\t\t// find absolute min based on the distance (closest)\n\t\t\tfor (const selectedItem of selectedItems) {\n\t\t\t\tconst currentSelectedItemIndex = this.state.items.indexOf(selectedItem)\n\n\t\t\t\tif (nearestSelectedIndex == null || Math.abs(clickedItemIndex - currentSelectedItemIndex) < Math.abs(clickedItemIndex - nearestSelectedIndex)) {\n\t\t\t\t\tnearestSelectedIndex = currentSelectedItemIndex\n\t\t\t\t}\n\t\t\t}\n\t\t\tassertNonNull(nearestSelectedIndex)\n\n\t\t\tconst itemsToAddToSelection: ElementType[] = []\n\n\t\t\tif (nearestSelectedIndex < clickedItemIndex) {\n\t\t\t\tfor (let i = nearestSelectedIndex + 1; i <= clickedItemIndex; i++) {\n\t\t\t\t\titemsToAddToSelection.push(this.state.items[i])\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (let i = clickedItemIndex; i < nearestSelectedIndex; i++) {\n\t\t\t\t\titemsToAddToSelection.push(this.state.items[i])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsetAddAll(selectedItems, itemsToAddToSelection)\n\t\t}\n\t\tthis.updateState({ selectedItems, inMultiselect: true, activeElement: item })\n\t\tthis.rangeSelectionAnchorElement = item\n\t}\n\n\tselectPrevious(multiselect: boolean) {\n\t\tconst oldActiveItem = this.rawState.activeElement\n\t\tconst newActiveItem =\n\t\t\toldActiveItem == null\n\t\t\t\t? first(this.state.items)\n\t\t\t\t: findLast(this.state.items, (el) => this.config.sortCompare(el, oldActiveItem) < 0) ?? first(this.state.items)\n\t\tif (newActiveItem != null) {\n\t\t\tif (!multiselect) {\n\t\t\t\tthis.onSingleSelection(newActiveItem)\n\t\t\t} else {\n\t\t\t\tconst selectedItems = new Set(this.state.selectedItems)\n\t\t\t\tthis.rangeSelectionAnchorElement = this.rangeSelectionAnchorElement ?? first(this.state.items)\n\t\t\t\tif (!this.rangeSelectionAnchorElement) return\n\n\t\t\t\tconst previousActiveIndex = this.state.activeIndex ?? 0\n\t\t\t\tconst towardsAnchor = this.config.sortCompare(oldActiveItem ?? getFirstOrThrow(this.state.items), this.rangeSelectionAnchorElement) > 0\n\t\t\t\tif (towardsAnchor) {\n\t\t\t\t\t// remove\n\t\t\t\t\tselectedItems.delete(this.state.items[previousActiveIndex])\n\t\t\t\t} else {\n\t\t\t\t\t// add\n\t\t\t\t\tselectedItems.add(newActiveItem)\n\t\t\t\t}\n\t\t\t\tthis.updateState({ activeElement: newActiveItem, selectedItems, inMultiselect: true })\n\t\t\t}\n\t\t}\n\t}\n\n\tselectNext(multiselect: boolean) {\n\t\tconst oldActiveItem = this.rawState.activeElement\n\t\tconst lastItem = last(this.state.items)\n\t\tconst newActiveItem =\n\t\t\toldActiveItem == null\n\t\t\t\t? first(this.state.items)\n\t\t\t\t: lastItem && this.config.sortCompare(lastItem, oldActiveItem) <= 0\n\t\t\t\t? lastItem\n\t\t\t\t: this.state.items.find((el) => this.config.sortCompare(el, oldActiveItem) > 0) ?? first(this.state.items)\n\n\t\tif (newActiveItem != null) {\n\t\t\tif (!multiselect) {\n\t\t\t\tthis.onSingleSelection(newActiveItem)\n\t\t\t} else {\n\t\t\t\tconst selectedItems = new Set(this.state.selectedItems)\n\t\t\t\tthis.rangeSelectionAnchorElement = this.rangeSelectionAnchorElement ?? first(this.state.items)\n\t\t\t\tif (!this.rangeSelectionAnchorElement) return\n\n\t\t\t\tconst previousActiveIndex = this.state.activeIndex ?? 0\n\t\t\t\tconst towardsAnchor = this.config.sortCompare(oldActiveItem ?? getFirstOrThrow(this.state.items), this.rangeSelectionAnchorElement) < 0\n\t\t\t\tif (towardsAnchor) {\n\t\t\t\t\tselectedItems.delete(this.state.items[previousActiveIndex])\n\t\t\t\t} else {\n\t\t\t\t\tselectedItems.add(newActiveItem)\n\t\t\t\t}\n\t\t\t\tthis.updateState({ selectedItems, inMultiselect: true, activeElement: newActiveItem })\n\t\t\t}\n\t\t}\n\t}\n\n\tareAllSelected(): boolean {\n\t\treturn this.rawState.inMultiselect && this.state.selectedItems.size === this.state.items.length\n\t}\n\n\tselectAll() {\n\t\tthis.updateState({ selectedItems: new Set(this.state.items), activeElement: null, inMultiselect: true })\n\t\tthis.rangeSelectionAnchorElement = null\n\t}\n\n\tselectNone() {\n\t\tthis.rangeSelectionAnchorElement = null\n\t\tthis.updateState({ selectedItems: new Set<ElementType>(), inMultiselect: false })\n\t}\n\n\tisItemSelected(itemId: Id): boolean {\n\t\treturn findBy(this.state.selectedItems, (item: ElementType) => getElementId(item) === itemId) != null\n\t}\n\n\treadonly getSelectedAsArray: () => Array<ElementType> = memoizedWithHiddenArgument(\n\t\t() => this.state,\n\t\t(state: ListState<ElementType>) => [...state.selectedItems],\n\t)\n\n\treadonly getUnfilteredAsArray: () => Array<ElementType> = memoizedWithHiddenArgument(\n\t\t() => this.rawState,\n\t\t(state: PrivateListState<ElementType>) => [...state.unfilteredItems],\n\t)\n\n\tenterMultiselect() {\n\t\t// avoid having the viewed element as a preselected one which might be confusing.\n\t\tthis.selectNone()\n\t\tthis.updateState({ inMultiselect: true })\n\t}\n\n\tsort() {\n\t\tconst filteredItems = this.rawState.filteredItems.slice().sort(this.config.sortCompare)\n\t\tconst unfilteredItems = this.rawState.filteredItems.slice().sort(this.config.sortCompare)\n\t\tthis.updateState({ filteredItems, unfilteredItems })\n\t}\n\n\tisLoadedCompletely(): boolean {\n\t\treturn this.rawState.loadingStatus === ListLoadingState.Done\n\t}\n\n\tcancelLoadAll() {\n\t\tif (this.state.loadingAll) {\n\t\t\tthis.updateState({ loadingAll: false })\n\t\t}\n\t}\n\n\tasync loadAll() {\n\t\tif (this.rawState.loadingAll) return\n\n\t\tthis.updateState({ loadingAll: true })\n\n\t\ttry {\n\t\t\twhile (this.rawState.loadingAll && !this.isLoadedCompletely()) {\n\t\t\t\tawait this.loadMore()\n\t\t\t\tthis.selectAll()\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.cancelLoadAll()\n\t\t}\n\t}\n\n\tisEmptyAndDone(): boolean {\n\t\treturn this.state.items.length === 0 && this.state.loadingStatus === ListLoadingState.Done\n\t}\n\n\tstopLoading() {\n\t\tif (this.state.loadingStatus === ListLoadingState.Loading) {\n\t\t\t// We can't really cancel ongoing requests but we can prevent more requests from happening\n\t\t\tthis.updateState({ loadingStatus: ListLoadingState.ConnectionLost })\n\t\t}\n\t}\n}\n\nexport function selectionAttrsForList(listModel: ListModel<ListElement> | null) {\n\treturn {\n\t\tselected: listModel?.areAllSelected() ?? false,\n\t\tselectNone: () => listModel?.selectNone(),\n\t\tselectAll: () => listModel?.selectAll(),\n\t}\n}\n","import { lang } from \"./LanguageViewModel\"\nimport { DAY_IN_MILLIS, pad } from \"@tutao/tutanota-utils\"\nimport type { UserSettingsGroupRoot } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { TimeFormat } from \"../api/common/TutanotaConstants\"\nimport { assertMainOrNode } from \"../api/common/Env\"\nimport { cleanMailAddress } from \"../api/common/utils/CommonCalendarUtils.js\"\n\nassertMainOrNode()\n\nexport function formatMonthWithYear(date: Date): string {\n\treturn lang.formats.monthWithYear.format(date)\n}\n\nexport function formatMonthWithFullYear(date: Date): string {\n\treturn lang.formats.monthWithFullYear.format(date)\n}\n\nexport function formatDate(date: Date): string {\n\treturn lang.formats.simpleDate.format(date)\n}\n\nexport function formatDateWithMonth(date: Date): string {\n\treturn lang.formats.dateWithMonth.format(date)\n}\n\nexport function formatDateWithWeekday(date: Date): string {\n\tif (date.getFullYear() === new Date().getFullYear()) {\n\t\treturn lang.formats.dateWithWeekday.format(date)\n\t} else {\n\t\treturn lang.formats.dateWithWeekdayAndYear.format(date)\n\t}\n}\n\nexport function formatDateWithWeekdayAndYear(date: Date): string {\n\treturn lang.formats.dateWithWeekdayAndYear.format(date)\n}\n\nexport function formatDateWithWeekdayAndYearLong(date: Date): string {\n\treturn lang.formats.dateWithWeekdayAndYearLong.format(date)\n}\n\nexport function formatDateTimeFromYesterdayOn(date: Date): string {\n\tlet dateString\n\tlet startOfToday = new Date().setHours(0, 0, 0, 0)\n\tlet startOfYesterday = startOfToday - 1000 * 60 * 60 * 24\n\n\tif (date.getTime() >= startOfToday) {\n\t\tdateString = \"\"\n\t} else if (startOfToday > date.getTime() && date.getTime() >= startOfYesterday) {\n\t\tdateString = lang.get(\"yesterday_label\")\n\t} else {\n\t\tdateString = formatDateWithWeekday(date)\n\t}\n\n\treturn (dateString + \" \" + formatTime(date)).trim()\n}\n\nexport function formatTimeOrDateOrYesterday(date: Date): string {\n\tconst startOfToday = new Date().setHours(0, 0, 0, 0)\n\tif (date.getTime() >= startOfToday) {\n\t\treturn formatTime(date)\n\t} else if (date.getTime() >= startOfToday - DAY_IN_MILLIS) {\n\t\treturn lang.get(\"yesterday_label\")\n\t} else if (date.getFullYear() === new Date().getFullYear()) {\n\t\treturn lang.formats.dateWithoutYear.format(date)\n\t} else {\n\t\treturn lang.formats.dateWithMonth.format(date)\n\t}\n}\n\nexport function formatTime(date: Date): string {\n\treturn lang.formats.time.format(date)\n}\n\nexport function formatDateTime(date: Date): string {\n\treturn lang.formats.dateTime.format(date)\n}\n\nexport function formatDateTimeShort(date: Date): string {\n\treturn lang.formats.dateTimeShort.format(date)\n}\n\nexport function formatDateWithWeekdayAndTime(date: Date): string {\n\treturn lang.formats.dateWithWeekdayAndTime.format(date)\n}\n\nexport function formatDateWithTimeIfNotEven(date: Date): string {\n\tif (\n\t\t(date.getHours() === 0 && date.getMinutes() === 0) || // If it's beginning of the day\n\t\t(date.getHours() === 23 && date.getMinutes() === 59 && date.getSeconds() === 59)\n\t) {\n\t\t// or the end of the day\n\t\treturn formatDate(date)\n\t} else {\n\t\treturn formatDateTimeShort(date)\n\t}\n}\n\nexport function formatWeekdayShort(date: Date): string {\n\treturn lang.formats.weekdayShort.format(date)\n}\n\nexport function formatWeekdayNarrow(date: Date): string {\n\treturn lang.formats.weekdayNarrow.format(date)\n}\n\nexport function dateWithWeekdayWoMonth(date: Date): string {\n\treturn lang.formats.dateWithWeekdayWoMonth.format(date)\n}\n\n/**\n * Formats the given size in bytes to a better human readable string using B, KB, MB, GB, TB.\n */\nexport function formatStorageSize(sizeInBytes: number): string {\n\tvar units = [\"B\", \"kB\", \"MB\", \"GB\", \"TB\"]\n\tvar narrowNoBreakSpace = \" \" // this space is the special unicode narrow no-break character\n\n\tvar unitIndex = 0\n\n\twhile (sizeInBytes >= 1000) {\n\t\tsizeInBytes /= 1000 // we use 1000 instead of 1024\n\n\t\tunitIndex++\n\t}\n\n\t// round to 1 digit after comma\n\tsizeInBytes = Math.floor(sizeInBytes * 10) / 10\n\treturn sizeInBytes + narrowNoBreakSpace + units[unitIndex]\n}\n\nexport function urlEncodeHtmlTags(text: string): string {\n\treturn text.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\").replace(/'/g, \"&#039;\")\n}\n\nexport function getHourCycle(userSettings: UserSettingsGroupRoot): \"h12\" | \"h23\" {\n\treturn userSettings.timeFormat === TimeFormat.TWELVE_HOURS ? \"h12\" : \"h23\"\n}\n\nexport function timeStringFromParts(hours: number, minutes: number, amPm: boolean): string {\n\tlet minutesString = pad(minutes, 2)\n\n\tif (amPm) {\n\t\tif (hours === 0) {\n\t\t\treturn `12:${minutesString} am`\n\t\t} else if (hours === 12) {\n\t\t\treturn `12:${minutesString} pm`\n\t\t} else if (hours > 12) {\n\t\t\treturn `${hours - 12}:${minutesString} pm`\n\t\t} else {\n\t\t\treturn `${hours}:${minutesString} am`\n\t\t}\n\t} else {\n\t\tlet hoursString = pad(hours, 2)\n\t\treturn hoursString + \":\" + minutesString\n\t}\n}\n\nexport function formatMailAddressFromParts(name: string, domain: string): string {\n\treturn cleanMailAddress(`${name}@${domain}`)\n}\n","import { FeatureType } from \"../../api/common/TutanotaConstants\"\nimport { locator } from \"../../api/main/MainLocator.js\"\nimport { LoginController } from \"../../api/main/LoginController.js\"\n\nexport function showUpgradeDialog() {\n\timport(\"../../subscription/UpgradeSubscriptionWizard.js\").then((upgradeWizard) => upgradeWizard.showUpgradeWizard(locator.logins))\n}\n\nexport function showSupportDialog(logins: LoginController) {\n\timport(\"../../support/SupportDialog.js\").then((supportModule) => supportModule.showSupportDialog(logins))\n}\n\nexport function isNewMailActionAvailable(): boolean {\n\treturn locator.logins.isInternalUserLoggedIn() && !locator.logins.isEnabled(FeatureType.ReplyOnly)\n}\n","import { downcast } from \"@tutao/tutanota-utils\"\nimport { TutanotaError } from \"../../api/common/error/TutanotaError\"\n\nexport type Parser<T> = (arg0: StringIterator) => T\n\nexport class ParserError extends TutanotaError {\n\tfilename: string | null\n\n\tconstructor(message: string, filename?: string) {\n\t\tsuper(\"ParserError\", message)\n\t\tthis.filename = filename ?? null\n\t}\n}\n\nexport const combineParsers: (<A, B>(arg0: Parser<A>, arg1: Parser<B>) => Parser<[A, B]>) &\n\t(<A, B, C>(arg0: Parser<A>, arg1: Parser<B>, arg2: Parser<C>) => Parser<[A, B, C]>) &\n\t(<A, B, C, D>(arg0: Parser<A>, arg1: Parser<B>, arg2: Parser<C>, arg3: Parser<D>) => Parser<[A, B, C, D]>) &\n\t(<A, B, C, D, E>(arg0: Parser<A>, arg1: Parser<B>, arg2: Parser<C>, arg3: Parser<D>, arg4: Parser<E>) => Parser<[A, B, C, D, E]>) = downcast(\n\t(...parsers: any[]) =>\n\t\t(iterator: StringIterator) =>\n\t\t\tparsers.map((p) => p(iterator)),\n)\n\nexport function makeCharacterParser(character: string): Parser<string> {\n\treturn (iterator: StringIterator) => {\n\t\tlet value = iterator.peek()\n\n\t\tif (value === character) {\n\t\t\titerator.next()\n\t\t\treturn value\n\t\t}\n\n\t\tconst sliceStart = Math.max(iterator.position - 10, 0)\n\t\tconst sliceEnd = Math.min(iterator.position + 10, iterator.iteratee.length - 1)\n\t\tthrow new ParserError(`expected character ${character} got ${value} near ${iterator.iteratee.slice(sliceStart, sliceEnd)}`)\n\t}\n}\n\nexport function makeNotCharacterParser(character: string): Parser<string> {\n\treturn (iterator: StringIterator) => {\n\t\tlet value = iterator.peek()\n\n\t\tif (value && value !== character) {\n\t\t\titerator.next()\n\t\t\treturn value\n\t\t}\n\n\t\tconst sliceStart = Math.max(iterator.position - 10, 0)\n\t\tconst sliceEnd = Math.min(iterator.position + 10, iterator.iteratee.length - 1)\n\t\tthrow new ParserError(`expected character ${character} got ${value} near ${iterator.iteratee.slice(sliceStart, sliceEnd)}`)\n\t}\n}\n\nexport function makeZeroOrMoreParser<T>(anotherParser: Parser<T>): Parser<Array<T>> {\n\treturn (iterator: StringIterator) => {\n\t\tconst result: T[] = []\n\n\t\ttry {\n\t\t\tlet parseResult = anotherParser(iterator)\n\n\t\t\twhile (true) {\n\t\t\t\tresult.push(parseResult)\n\t\t\t\tparseResult = anotherParser(iterator)\n\t\t\t}\n\t\t} catch (e) {}\n\n\t\treturn result\n\t}\n}\n\nexport function mapParser<T, R>(parser: Parser<T>, mapper: (arg0: T) => R): Parser<R> {\n\treturn (iterator: StringIterator) => {\n\t\treturn mapper(parser(iterator))\n\t}\n}\n\nexport function makeOneOrMoreParser<T>(parser: Parser<T>): Parser<Array<T>> {\n\treturn mapParser(makeZeroOrMoreParser(parser), (value: Array<T>) => {\n\t\tif (value.length === 0) {\n\t\t\tthrow new ParserError(\"Expected at least one value, got none\")\n\t\t}\n\n\t\treturn value\n\t})\n}\n\nexport function maybeParse<T>(parser: Parser<T>): Parser<T | null> {\n\treturn (iterator) => {\n\t\ttry {\n\t\t\treturn parser(iterator)\n\t\t} catch (e) {\n\t\t\treturn null\n\t\t}\n\t}\n}\n\nexport function makeSeparatedByParser<S, T>(separatorParser: Parser<S>, valueParser: Parser<T>): Parser<Array<T>> {\n\treturn (iterator) => {\n\t\tconst result: T[] = []\n\t\tresult.push(valueParser(iterator))\n\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tseparatorParser(iterator)\n\t\t\t} catch (e) {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tresult.push(valueParser(iterator))\n\t\t}\n\n\t\treturn result\n\t}\n}\n\nexport function makeEitherParser<A, B>(parserA: Parser<A>, parserB: Parser<B>): Parser<A | B> {\n\treturn (iterator) => {\n\t\tconst iteratorPosition = iterator.position\n\n\t\ttry {\n\t\t\treturn parserA(iterator)\n\t\t} catch (e) {\n\t\t\tif (e instanceof ParserError) {\n\t\t\t\titerator.position = iteratorPosition\n\t\t\t\treturn parserB(iterator)\n\t\t\t}\n\n\t\t\tthrow e\n\t\t}\n\t}\n}\n\nexport function makeOneOfCharactersParser(allowed: Array<string>): Parser<string> {\n\treturn (iterator: StringIterator) => {\n\t\tconst value = iterator.peek()\n\n\t\tif (value && allowed.includes(value)) {\n\t\t\titerator.next()\n\t\t\treturn value\n\t\t}\n\n\t\tthrow new ParserError(`Expected one of ${allowed.map((c) => `\"${c}\"`).join(\", \")}, but got \"${value}\\n${context(iterator, iterator.position, 10)}\"`)\n\t}\n}\n\nexport function makeNotOneOfCharactersParser(notAllowed: Array<string>): Parser<string> {\n\treturn (iterator: StringIterator) => {\n\t\tconst value = iterator.peek()\n\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow new ParserError(\"unexpected end of input\")\n\t\t}\n\n\t\tif (!notAllowed.includes(value)) {\n\t\t\titerator.next()\n\t\t\treturn value\n\t\t}\n\n\t\tthrow new ParserError(`Expected none of ${notAllowed.map((c) => `\"${c}\"`).join(\", \")}, but got \"${value}\"\\n${context(iterator, iterator.position, 10)}`)\n\t}\n}\n\nexport const numberParser: Parser<number> = mapParser(\n\tmakeOneOrMoreParser(makeOneOfCharactersParser([\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"])),\n\t(values) => parseInt(values.join(\"\"), 10),\n)\n\nexport class StringIterator {\n\titeratee: string\n\tposition: number = -1\n\n\tconstructor(iteratee: string) {\n\t\tthis.iteratee = iteratee\n\t}\n\n\tnext(): IteratorResult<string> {\n\t\tconst value = this.iteratee[++this.position]\n\t\tconst done: boolean = this.position >= this.iteratee.length\n\t\treturn done\n\t\t\t? {\n\t\t\t\t\tdone: true,\n\t\t\t\t\tvalue: undefined,\n\t\t\t  }\n\t\t\t: {\n\t\t\t\t\tdone: false,\n\t\t\t\t\tvalue,\n\t\t\t  }\n\t}\n\n\tpeek(): string | null {\n\t\treturn this.iteratee[this.position + 1] ?? null\n\t}\n}\n\nfunction context(iterator: StringIterator, contextCentre: number, contextRadius: number = 10): string {\n\tconst sliceStart = Math.max(contextCentre - contextRadius, 0)\n\tconst sliceEnd = Math.min(contextCentre + contextRadius, iterator.iteratee.length - 1)\n\tconst sliceLength = sliceEnd - sliceStart\n\tconst actualPosition = contextCentre - (2 * contextRadius - sliceLength)\n\treturn iterator.iteratee.slice(sliceStart, sliceEnd)\n}\n","import { isMailAddress } from \"../FormatValidator\"\nimport { PartialRecipient } from \"../../api/common/recipients/Recipient\"\n\nexport type ParsedMailto = {\n\trecipients: {\n\t\tto?: PartialRecipient[]\n\t\tcc?: PartialRecipient[]\n\t\tbcc?: PartialRecipient[]\n\t}\n\tsubject: string | null\n\tbody: string | null\n\tattach: Array<string> | null\n}\n\n/**\n * takes a URL of the form mailto:a@b.c?body=hello%20world&attach=file:///home/user/cute%20cat.jpg&attach=file:///home/user/ugly%20dog.jpg\n * and returns an object representing the structured information that should be passed to the mail editor for this URL\n *\n * if a param is not given, it is set to null. if it is given, but empty, it will be set to an empty string/array.\n *\n * @param mailtoUrl {string}\n * @returns {ParsedMailto}\n */\nexport function parseMailtoUrl(mailtoUrl: string): ParsedMailto {\n\tlet url = new URL(mailtoUrl)\n\n\tconst createMailAddressFromString = (address: string): PartialRecipient | null => {\n\t\tconst nameAndMailAddress = stringToNameAndMailAddress(address)\n\t\tif (!nameAndMailAddress) return null\n\t\treturn {\n\t\t\tname: nameAndMailAddress.name,\n\t\t\taddress: nameAndMailAddress.mailAddress,\n\t\t}\n\t}\n\n\tconst addresses = url.pathname\n\t\t.split(\",\")\n\t\t.map((address) => {\n\t\t\tif (!address) return null\n\t\t\tconst decodedAddress = decodeURIComponent(address)\n\t\t\tif (!decodedAddress) return null\n\t\t\treturn createMailAddressFromString(decodedAddress)\n\t\t})\n\t\t.filter(Boolean)\n\tconst result: any = {\n\t\trecipients: {\n\t\t\tto: addresses.length > 0 ? addresses : undefined,\n\t\t\tcc: undefined,\n\t\t\tbcc: undefined,\n\t\t},\n\t\tattach: null,\n\t\tsubject: null,\n\t\tbody: null,\n\t}\n\t// @ts-ignore Missing definition\n\tif (!url.searchParams || typeof url.searchParams.entries !== \"function\") return result\n\n\t// @ts-ignore\n\tfor (let pair of url.searchParams.entries()) {\n\t\tlet paramName = pair[0].toLowerCase()\n\t\tlet paramValue = pair[1]\n\n\t\tswitch (paramName) {\n\t\t\tcase \"subject\":\n\t\t\t\tresult.subject = paramValue\n\t\t\t\tbreak\n\n\t\t\tcase \"body\":\n\t\t\t\tresult.body = paramValue.replace(/\\r\\n/g, \"<br>\").replace(/\\n/g, \"<br>\")\n\t\t\t\tbreak\n\n\t\t\tcase \"to\":\n\t\t\tcase \"cc\":\n\t\t\tcase \"bcc\":\n\t\t\t\tif (result.recipients[paramName] == null) result.recipients[paramName] = []\n\t\t\t\tconst nextAddresses = paramValue\n\t\t\t\t\t.split(\",\")\n\t\t\t\t\t.map((address: string) => createMailAddressFromString(address))\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\tresult.recipients[paramName].push(...nextAddresses)\n\t\t\t\tbreak\n\n\t\t\tcase \"attach\":\n\t\t\t\tif (result.attach == null) result.attach = []\n\t\t\t\tresult.attach.push(paramValue)\n\t\t\t\tbreak\n\n\t\t\tdefault:\n\t\t\t\tconsole.warn(\"unexpected mailto param, ignoring\")\n\t\t}\n\t}\n\n\treturn result\n}\n\n/**\n * Parses the given string for a name and mail address. The following formats are recognized: [name][<]mailAddress[>]\n * Additionally, whitespaces at any positions outside name and mailAddress are ignored.\n * @param string The string to check.\n * @return an object with the attributes \"name\" and \"mailAddress\" or null if nothing was found.\n */\nexport function stringToNameAndMailAddress(string: string):\n\t| {\n\t\t\tname: string\n\t\t\tmailAddress: string\n\t  }\n\t| null\n\t| undefined {\n\tstring = string.trim()\n\n\tif (string === \"\") {\n\t\treturn null\n\t}\n\n\tlet startIndex = string.indexOf(\"<\")\n\n\tif (startIndex !== -1) {\n\t\tconst endIndex = string.indexOf(\">\", startIndex)\n\n\t\tif (endIndex === -1) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst cleanedMailAddress = getCleanedMailAddress(string.substring(startIndex + 1, endIndex))\n\n\t\tif (cleanedMailAddress == null || !isMailAddress(cleanedMailAddress, false)) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst name = string.substring(0, startIndex).trim()\n\t\treturn {\n\t\t\tname: name,\n\t\t\tmailAddress: cleanedMailAddress,\n\t\t}\n\t} else {\n\t\tstartIndex = string.lastIndexOf(\" \")\n\t\tstartIndex++\n\t\tconst cleanedMailAddress = getCleanedMailAddress(string.substring(startIndex))\n\n\t\tif (cleanedMailAddress == null || !isMailAddress(cleanedMailAddress, false)) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst name = string.substring(0, startIndex).trim()\n\t\treturn {\n\t\t\tname: name,\n\t\t\tmailAddress: cleanedMailAddress,\n\t\t}\n\t}\n}\n\n/**\n * Returns a cleaned mail address from the input mail address. Removes leading or trailing whitespaces and converters\n * the address to lower case.\n * @param mailAddress The input mail address.\n * @return The cleaned mail address.\n */\nexport function getCleanedMailAddress(mailAddress: string): string | null {\n\tvar cleanedMailAddress = mailAddress.toLowerCase().trim()\n\n\tif (isMailAddress(cleanedMailAddress, false)) {\n\t\treturn cleanedMailAddress\n\t}\n\n\treturn null\n}\n\nexport function getDomainPart(mailAddress: string): string | null {\n\tconst cleanedMailAddress = getCleanedMailAddress(mailAddress)\n\n\tif (cleanedMailAddress) {\n\t\tconst parts = mailAddress.split(\"@\")\n\n\t\tif (parts.length === 2) {\n\t\t\treturn parts[1]\n\t\t} else {\n\t\t\treturn null\n\t\t}\n\t} else {\n\t\treturn null\n\t}\n}\n\n/**\n * Parses the given string for a fist name and a last name separated by whitespace. If there is only one part it is regarded as first name. If there are more than two parts, only the first one is regarded as first name.\n * @param fullName The full name to check.\n * @return Returns an object with the attributes \"firstName\" and \"lastName\".\n */\nexport function fullNameToFirstAndLastName(fullName: string): {\n\tfirstName: string\n\tlastName: string\n} {\n\tfullName = fullName.trim()\n\n\tif (fullName === \"\") {\n\t\treturn {\n\t\t\tfirstName: \"\",\n\t\t\tlastName: \"\",\n\t\t}\n\t}\n\n\tvar separator = fullName.indexOf(\" \")\n\n\tif (separator !== -1) {\n\t\treturn {\n\t\t\tfirstName: fullName.substring(0, separator),\n\t\t\tlastName: fullName.substring(separator + 1),\n\t\t}\n\t} else {\n\t\treturn {\n\t\t\tfirstName: fullName,\n\t\t\tlastName: \"\",\n\t\t}\n\t}\n}\n\n/**\n * Parses the given email address for a fist name and a last name separated by whitespace, comma, dot or underscore.\n * @param mailAddress The email address to check.\n * @return Returns an object with the attributes \"firstName\" and \"lastName\".\n */\nexport function mailAddressToFirstAndLastName(mailAddress: string): {\n\tfirstName: string\n\tlastName: string\n} {\n\tconst addr = mailAddress.substring(0, mailAddress.indexOf(\"@\"))\n\tlet nameData\n\n\tif (addr.indexOf(\".\") !== -1) {\n\t\tnameData = addr.split(\".\")\n\t} else if (addr.indexOf(\"_\") !== -1) {\n\t\tnameData = addr.split(\"_\")\n\t} else if (addr.indexOf(\"-\") !== -1) {\n\t\tnameData = addr.split(\"-\")\n\t} else {\n\t\tnameData = [addr]\n\t}\n\n\t// first character upper case\n\tfor (let i = 0; i < nameData.length; i++) {\n\t\tif (nameData[i].length > 0) {\n\t\t\tnameData[i] = nameData[i].substring(0, 1).toUpperCase() + nameData[i].substring(1)\n\t\t}\n\t}\n\n\treturn {\n\t\tfirstName: nameData[0],\n\t\tlastName: nameData.slice(1).join(\" \"),\n\t}\n}\n","import type { Contact, EncryptedMailAddress, InboxRule, Mail, MailFolder, TutanotaProperties } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport {\n\tcreateContact,\n\tcreateContactMailAddress,\n\tcreateEncryptedMailAddress,\n\tMailBodyTypeRef,\n\tMailDetailsBlobTypeRef,\n\tMailDetailsDraftTypeRef,\n\tMailHeadersTypeRef,\n} from \"../../api/entities/tutanota/TypeRefs.js\"\nimport {\n\tContactAddressType,\n\tConversationType,\n\tgetMailFolderType,\n\tGroupType,\n\tMailFolderType,\n\tMailState,\n\tMAX_ATTACHMENT_SIZE,\n\tReplyType,\n\tTUTANOTA_MAIL_ADDRESS_DOMAINS,\n} from \"../../api/common/TutanotaConstants\"\nimport { assertNotNull, contains, endsWith, first, neverNull } from \"@tutao/tutanota-utils\"\nimport { assertMainOrNode, isDesktop } from \"../../api/common/Env\"\nimport type { LoginController } from \"../../api/main/LoginController\"\nimport type { Language, TranslationKey } from \"../../misc/LanguageViewModel\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport { Icons } from \"../../gui/base/icons/Icons\"\nimport type { MailboxDetail } from \"./MailModel\"\nimport { MailModel } from \"./MailModel\"\nimport type { AllIcons } from \"../../gui/base/Icon\"\nimport type { GroupInfo, User } from \"../../api/entities/sys/TypeRefs.js\"\nimport { CustomerPropertiesTypeRef } from \"../../api/entities/sys/TypeRefs.js\"\nimport type { UserController } from \"../../api/main/UserController\"\nimport type { EntityClient } from \"../../api/common/EntityClient\"\nimport { getEnabledMailAddressesForGroupInfo, getGroupInfoDisplayName } from \"../../api/common/utils/GroupUtils\"\nimport { fullNameToFirstAndLastName, mailAddressToFirstAndLastName } from \"../../misc/parsing/MailAddressParser\"\nimport type { Attachment } from \"../editor/SendMailModel\"\nimport { elementIdPart, getListId, listIdPart } from \"../../api/common/utils/EntityUtils\"\nimport { isDetailsDraft, isLegacyMail, MailWrapper } from \"../../api/common/MailWrapper.js\"\nimport { getLegacyMailHeaders, getMailHeaders } from \"../../api/common/utils/Utils.js\"\nimport { FolderSystem } from \"../../api/common/mail/FolderSystem.js\"\nimport { ListFilter } from \"../../misc/ListModel.js\"\n\nassertMainOrNode()\nexport const LINE_BREAK = \"<br>\"\n\nexport function isTutanotaMailAddress(mailAddress: string): boolean {\n\treturn TUTANOTA_MAIL_ADDRESS_DOMAINS.some((tutaDomain) => mailAddress.endsWith(\"@\" + tutaDomain))\n}\n\n/**\n * Creates a contact with an email address and a name.\n * @param mailAddress The mail address of the contact. Type is OTHER.\n * @param name The name of the contact. If an empty string is provided, the name is parsed from the mail address.\n * @return The contact.\n */\nexport function createNewContact(user: User, mailAddress: string, name: string): Contact {\n\t// prepare some contact information. it is only saved if the mail is sent securely\n\t// use the name or mail address to extract first and last name. first part is used as first name, all other parts as last name\n\tlet firstAndLastName = name.trim() !== \"\" ? fullNameToFirstAndLastName(name) : mailAddressToFirstAndLastName(mailAddress)\n\tlet contact = createContact()\n\tcontact._owner = user._id\n\tcontact._ownerGroup = assertNotNull(\n\t\tuser.memberships.find((m) => m.groupType === GroupType.Contact),\n\t\t\"called createNewContact as user without contact group mship\",\n\t).group\n\tcontact.firstName = firstAndLastName.firstName\n\tcontact.lastName = firstAndLastName.lastName\n\tlet ma = createContactMailAddress()\n\tma.address = mailAddress\n\tma.type = ContactAddressType.OTHER\n\tma.customTypeName = \"\"\n\tcontact.mailAddresses.push(ma)\n\treturn contact\n}\n\nexport function getMailAddressDisplayText(name: string | null, mailAddress: string, preferNameOnly: boolean): string {\n\tif (!name) {\n\t\treturn mailAddress\n\t} else if (preferNameOnly) {\n\t\treturn name\n\t} else {\n\t\treturn name + \" <\" + mailAddress + \">\"\n\t}\n}\n\nexport function getSenderHeading(mail: Mail, preferNameOnly: boolean) {\n\tif (isExcludedMailAddress(mail.sender.address)) {\n\t\treturn \"\"\n\t} else {\n\t\treturn getMailAddressDisplayText(mail.sender.name, mail.sender.address, preferNameOnly)\n\t}\n}\n\nexport function getSenderAddressDisplay(mail: Mail): string {\n\tif (isExcludedMailAddress(mail.sender.address)) {\n\t\treturn \"\"\n\t} else {\n\t\treturn mail.sender.address\n\t}\n}\n\nexport function getRecipientHeading(mail: Mail, preferNameOnly: boolean) {\n\tif (isLegacyMail(mail)) {\n\t\tconst allRecipients = mail.toRecipients.concat(mail.ccRecipients).concat(mail.bccRecipients)\n\n\t\tif (allRecipients.length > 0) {\n\t\t\treturn getMailAddressDisplayText(allRecipients[0].name, allRecipients[0].address, preferNameOnly) + (allRecipients.length > 1 ? \", ...\" : \"\")\n\t\t} else {\n\t\t\treturn \"\"\n\t\t}\n\t} else {\n\t\tlet recipientCount = parseInt(mail.recipientCount)\n\t\tif (recipientCount > 0) {\n\t\t\tlet recipient = neverNull(mail.firstRecipient)\n\t\t\treturn getMailAddressDisplayText(recipient.name, recipient.address, preferNameOnly) + (recipientCount > 1 ? \", ...\" : \"\")\n\t\t} else {\n\t\t\treturn \"\"\n\t\t}\n\t}\n}\n\nexport function getSenderOrRecipientHeading(mail: Mail, preferNameOnly: boolean): string {\n\tif (mail.state === MailState.RECEIVED) {\n\t\treturn getSenderHeading(mail, preferNameOnly)\n\t} else {\n\t\treturn getRecipientHeading(mail, preferNameOnly)\n\t}\n}\n\nexport function getSenderOrRecipientHeadingTooltip(mail: Mail): string {\n\tif (isTutanotaTeamMail(mail) && !isExcludedMailAddress(mail.sender.address)) {\n\t\treturn lang.get(\"tutaoInfo_msg\")\n\t} else {\n\t\treturn \"\"\n\t}\n}\n\nexport function isTutanotaTeamMail(mail: Mail): boolean {\n\treturn mail.confidential && mail.state === MailState.RECEIVED && endsWith(mail.sender.address, \"@tutao.de\")\n}\n\n/** this is used for contact form messages to prevent them from being sent to recipients that are not the contact forms target mail group */\nexport function areParticipantsRestricted(mail: Mail): boolean {\n\tconst { restrictions } = mail\n\treturn restrictions != null && restrictions.participantGroupInfos.length > 0\n}\n\nexport function isExcludedMailAddress(mailAddress: string): boolean {\n\treturn mailAddress === \"no-reply@tutao.de\"\n}\n\n/**\n * @return {string} default mail address\n */\nexport function getDefaultSenderFromUser({ props, userGroupInfo }: UserController): string {\n\treturn props.defaultSender && contains(getEnabledMailAddressesForGroupInfo(userGroupInfo), props.defaultSender)\n\t\t? props.defaultSender\n\t\t: neverNull(userGroupInfo.mailAddress)\n}\n\nexport function getFolderName(folder: MailFolder): string {\n\tswitch (folder.folderType) {\n\t\tcase \"0\":\n\t\t\treturn folder.name\n\n\t\tcase \"1\":\n\t\t\treturn lang.get(\"received_action\")\n\n\t\tcase \"2\":\n\t\t\treturn lang.get(\"sent_action\")\n\n\t\tcase \"3\":\n\t\t\treturn lang.get(\"trash_action\")\n\n\t\tcase \"4\":\n\t\t\treturn lang.get(\"archive_action\")\n\n\t\tcase \"5\":\n\t\t\treturn lang.get(\"spam_action\")\n\n\t\tcase \"6\":\n\t\t\treturn lang.get(\"draft_action\")\n\n\t\tdefault:\n\t\t\t// do not throw an error - new system folders may cause problems\n\t\t\t//throw new Error(\"illegal folder type: \" + this.folder.getFolderType())\n\t\t\treturn \"\"\n\t}\n}\n\nexport function getFolderIconByType(folderType: MailFolderType): AllIcons {\n\tswitch (folderType) {\n\t\tcase MailFolderType.CUSTOM:\n\t\t\treturn Icons.Folder\n\n\t\tcase MailFolderType.INBOX:\n\t\t\treturn Icons.Inbox\n\n\t\tcase MailFolderType.SENT:\n\t\t\treturn Icons.Send\n\n\t\tcase MailFolderType.TRASH:\n\t\t\treturn Icons.TrashBin\n\n\t\tcase MailFolderType.ARCHIVE:\n\t\t\treturn Icons.Archive\n\n\t\tcase MailFolderType.SPAM:\n\t\t\treturn Icons.Spam\n\n\t\tcase MailFolderType.DRAFT:\n\t\t\treturn Icons.Draft\n\n\t\tdefault:\n\t\t\treturn Icons.Folder\n\t}\n}\n\nexport function getFolderIcon(folder: MailFolder): AllIcons {\n\treturn getFolderIconByType(getMailFolderType(folder))\n}\n\nexport function getEnabledMailAddressesWithUser(mailboxDetail: MailboxDetail, userGroupInfo: GroupInfo): Array<string> {\n\tif (isUserMailbox(mailboxDetail)) {\n\t\treturn getEnabledMailAddressesForGroupInfo(userGroupInfo)\n\t} else {\n\t\treturn getEnabledMailAddressesForGroupInfo(mailboxDetail.mailGroupInfo)\n\t}\n}\n\nexport function isUserMailbox(mailboxDetails: MailboxDetail): boolean {\n\treturn mailboxDetails.mailGroup != null && mailboxDetails.mailGroup.user != null\n}\n\nexport function getDefaultSender(logins: LoginController, mailboxDetails: MailboxDetail): string {\n\tif (isUserMailbox(mailboxDetails)) {\n\t\tlet props = logins.getUserController().props\n\t\treturn props.defaultSender && contains(getEnabledMailAddressesWithUser(mailboxDetails, logins.getUserController().userGroupInfo), props.defaultSender)\n\t\t\t? props.defaultSender\n\t\t\t: neverNull(logins.getUserController().userGroupInfo.mailAddress)\n\t} else {\n\t\treturn neverNull(mailboxDetails.mailGroupInfo.mailAddress)\n\t}\n}\n\nexport function getSenderNameForUser(mailboxDetails: MailboxDetail, userController: UserController): string {\n\tif (isUserMailbox(mailboxDetails)) {\n\t\t// external users do not have access to the user group info\n\t\treturn userController.userGroupInfo.name\n\t} else {\n\t\treturn mailboxDetails.mailGroupInfo ? mailboxDetails.mailGroupInfo.name : \"\"\n\t}\n}\n\nexport function getMailboxName(logins: LoginController, mailboxDetails: MailboxDetail): string {\n\tif (!logins.isInternalUserLoggedIn()) {\n\t\treturn lang.get(\"mailbox_label\")\n\t} else if (isUserMailbox(mailboxDetails)) {\n\t\treturn getDefaultSender(logins, mailboxDetails)\n\t} else {\n\t\treturn getGroupInfoDisplayName(assertNotNull(mailboxDetails.mailGroupInfo, \"mailboxDetails without mailGroupInfo?\"))\n\t}\n}\n\nexport interface ImageHandler {\n\tinsertImage(srcAttr: string, attrs?: Record<string, string>): HTMLElement\n}\n\n/**\n * Check if all mails in the selection are drafts. If there are mixed drafts and non-drafts or the array is empty, return true.\n * @param mails\n */\nexport function emptyOrContainsDraftsAndNonDrafts(mails: ReadonlyArray<Mail>): boolean {\n\treturn mails.length === 0 || (mails.some((mail) => mail.state === MailState.DRAFT) && mails.some((mail) => mail.state !== MailState.DRAFT))\n}\n\nexport function copyMailAddress({ address, name }: EncryptedMailAddress): EncryptedMailAddress {\n\treturn createEncryptedMailAddress({\n\t\taddress,\n\t\tname,\n\t})\n}\n\nexport function getTemplateLanguages(sortedLanguages: Array<Language>, entityClient: EntityClient, loginController: LoginController): Promise<Array<Language>> {\n\treturn loginController\n\t\t.getUserController()\n\t\t.loadCustomer()\n\t\t.then((customer) => entityClient.load(CustomerPropertiesTypeRef, neverNull(customer.properties)))\n\t\t.then((customerProperties) => {\n\t\t\treturn sortedLanguages.filter((sL) => customerProperties.notificationMailTemplates.find((nmt) => nmt.language === sL.code))\n\t\t})\n\t\t.catch(() => [])\n}\n\nexport function conversationTypeString(conversationType: ConversationType): string {\n\tlet key: TranslationKey\n\n\tswitch (conversationType) {\n\t\tcase ConversationType.NEW:\n\t\t\tkey = \"newMail_action\"\n\t\t\tbreak\n\n\t\tcase ConversationType.REPLY:\n\t\t\tkey = \"reply_action\"\n\t\t\tbreak\n\n\t\tcase ConversationType.FORWARD:\n\t\t\tkey = \"forward_action\"\n\t\t\tbreak\n\n\t\tdefault:\n\t\t\tkey = \"emptyString_msg\"\n\t}\n\n\treturn lang.get(key)\n}\n\nexport function getExistingRuleForType(props: TutanotaProperties, cleanValue: string, type: string): InboxRule | null {\n\treturn props.inboxRules.find((rule) => type === rule.type && cleanValue === rule.value) ?? null\n}\n\nexport function canDoDragAndDropExport(): boolean {\n\treturn isDesktop()\n}\n\ntype AttachmentSizeCheckResult = {\n\tattachableFiles: Array<Attachment>\n\ttooBigFiles: Array<string>\n}\n\n/**\n * @param files the files that shall be attached.\n * @param maxAttachmentSize the maximum size the new files may have in total to be attached successfully.\n */\nexport function checkAttachmentSize(files: ReadonlyArray<Attachment>, maxAttachmentSize: number = MAX_ATTACHMENT_SIZE): AttachmentSizeCheckResult {\n\tlet totalSize = 0\n\tconst attachableFiles: Array<Attachment> = []\n\tconst tooBigFiles: Array<string> = []\n\tfiles.forEach((file) => {\n\t\tif (totalSize + Number(file.size) > maxAttachmentSize) {\n\t\t\ttooBigFiles.push(file.name)\n\t\t} else {\n\t\t\ttotalSize += Number(file.size)\n\t\t\tattachableFiles.push(file)\n\t\t}\n\t})\n\treturn {\n\t\tattachableFiles,\n\t\ttooBigFiles,\n\t}\n}\n\n/**\n * @returns {boolean} true if the given mail was already replied to. Otherwise false.\n * Note that it also returns true if the mail was replied to AND forwarded.\n */\nexport function isRepliedTo(mail: Mail): boolean {\n\treturn mail.replyType === ReplyType.REPLY || mail.replyType === ReplyType.REPLY_FORWARD\n}\n\nexport enum RecipientField {\n\tTO = \"to\",\n\tCC = \"cc\",\n\tBCC = \"bcc\",\n}\n\nexport type FolderInfo = { level: number; folder: MailFolder }\n\nexport async function getMoveTargetFolderSystems(model: MailModel, mails: readonly Mail[]): Promise<Array<FolderInfo>> {\n\tconst firstMail = first(mails)\n\tif (firstMail == null) return []\n\n\tconst mailboxDetails = await model.getMailboxDetailsForMail(firstMail)\n\tif (mailboxDetails == null) {\n\t\treturn []\n\t}\n\tconst folderSystem = mailboxDetails.folders\n\treturn folderSystem.getIndentedList().filter((f) => f.folder.mails !== getListId(firstMail))\n}\n\nexport const MAX_FOLDER_INDENT_LEVEL = 10\n\nexport function getIndentedFolderNameForDropdown(folderInfo: FolderInfo) {\n\tconst indentLevel = Math.min(folderInfo.level, MAX_FOLDER_INDENT_LEVEL)\n\treturn \". \".repeat(indentLevel) + getFolderName(folderInfo.folder)\n}\n\nexport function getPathToFolderString(folderSystem: FolderSystem, folder: MailFolder, omitLast = false) {\n\tconst folderPath = folderSystem.getPathToFolder(folder._id)\n\tif (omitLast) {\n\t\tfolderPath.pop()\n\t}\n\treturn folderPath.map(getFolderName).join(\" · \")\n}\n\nexport async function loadMailDetails(entityClient: EntityClient, mail: Mail): Promise<MailWrapper> {\n\tif (isLegacyMail(mail)) {\n\t\treturn entityClient.load(MailBodyTypeRef, neverNull(mail.body)).then((b) => MailWrapper.body(mail, b))\n\t} else if (isDetailsDraft(mail)) {\n\t\treturn entityClient.load(MailDetailsDraftTypeRef, neverNull(mail.mailDetailsDraft)).then((d) => MailWrapper.details(mail, d.details))\n\t} else {\n\t\tconst mailDetailsId = neverNull(mail.mailDetails)\n\t\treturn entityClient\n\t\t\t.loadMultiple(MailDetailsBlobTypeRef, listIdPart(mailDetailsId), [elementIdPart(mailDetailsId)])\n\t\t\t.then((d) => MailWrapper.details(mail, d[0].details))\n\t}\n}\n\nexport async function loadMailHeaders(entityClient: EntityClient, mailWrapper: MailWrapper): Promise<string | null> {\n\tif (mailWrapper.isLegacy()) {\n\t\tconst headersId = mailWrapper.getMail().headers\n\t\treturn headersId != null ? getLegacyMailHeaders(await entityClient.load(MailHeadersTypeRef, headersId)) : null\n\t} else {\n\t\tconst details = mailWrapper.getDetails()\n\t\treturn details.headers != null ? getMailHeaders(details.headers) : null\n\t}\n}\n\nexport enum MailFilterType {\n\tUnread,\n\tRead,\n\tWithAttachments,\n}\n\nexport function getMailFilterForType(filter: MailFilterType | null): ListFilter<Mail> | null {\n\tswitch (filter) {\n\t\tcase MailFilterType.Read:\n\t\t\treturn (mail) => !mail.unread\n\t\tcase MailFilterType.Unread:\n\t\t\treturn (mail) => mail.unread\n\t\tcase MailFilterType.WithAttachments:\n\t\t\treturn (mail) => mail.attachments.length > 0\n\t\tcase null:\n\t\t\treturn null\n\t}\n}\n","import type { MailboxProperties } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { ReportMovedMailsType } from \"../api/common/TutanotaConstants\"\nimport { downcast } from \"@tutao/tutanota-utils\"\n\n/**\n * @returns ALWAYS_ASK if not set yet.\n */\nexport function getReportMovedMailsType(props: MailboxProperties | null): ReportMovedMailsType {\n\tif (!props) {\n\t\treturn ReportMovedMailsType.ALWAYS_ASK\n\t}\n\n\treturn downcast(props.reportMovedMails)\n}\n\nexport function getSenderName(mailboxProperties: MailboxProperties, senderAddress: string): string | null {\n\treturn mailboxProperties.mailAddressProperties.find((a) => a.mailAddress === senderAddress)?.senderName ?? null\n}\n","import m, { ClassComponent, Vnode } from \"mithril\"\nimport { stateBgActive, stateBgHover } from \"./builtinThemes.js\"\nimport { theme } from \"./theme.js\"\nimport { styles } from \"./styles.js\"\nimport { px, size } from \"./size.js\"\nimport { DefaultAnimationTime } from \"./animation/Animations.js\"\n\n/** A function that can adjust the style of the selectable row. */\nexport type SelectableRowSelectedSetter = (selected: boolean, isInMultiselect: boolean) => unknown\n\nexport interface SelectableRowContainerAttrs {\n\t/** This function will be called with a setter for the row style once it's available. */\n\tonSelectedChangeRef?: (changer: SelectableRowSelectedSetter) => unknown\n}\n\nexport class SelectableRowContainer implements ClassComponent<SelectableRowContainerAttrs> {\n\tprivate dom: HTMLElement | null = null\n\tprivate selected: boolean = false\n\tprivate isInMultiselect: boolean = false\n\n\tview({ attrs, children }: Vnode<SelectableRowContainerAttrs>) {\n\t\treturn m(\n\t\t\t\".flex.mb-xs.border-radius.pt-m.pb-m.pl.pr.ml-s\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\tpaddingTop: \"14px\",\n\t\t\t\t\tpaddingBottom: \"12px\",\n\t\t\t\t\t// this is an adjustment to keep tha columns aligned, space between columns is too big otherwise.\n\t\t\t\t\t// this is an obscure place to put it and ideally should not be done here or should be passed down here.\n\t\t\t\t\tmarginRight: styles.isSingleColumnLayout() ? px(size.hpad_small) : \"0\",\n\t\t\t\t\ttransition: `background 200ms`,\n\t\t\t\t},\n\t\t\t\toncreate: ({ dom }) => {\n\t\t\t\t\tthis.dom = dom as HTMLElement\n\t\t\t\t\tattrs.onSelectedChangeRef?.((selected, isInMultiselect) => {\n\t\t\t\t\t\tthis.selected = selected\n\t\t\t\t\t\tthis.isInMultiselect = isInMultiselect\n\t\t\t\t\t\tthis.updateDomBg()\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tonpointerdown: () => {\n\t\t\t\t\tif (this.dom) this.dom.style.backgroundColor = stateBgActive\n\t\t\t\t},\n\t\t\t\tonpointerup: this.updateDomBg,\n\t\t\t\tonpointercancel: this.updateDomBg,\n\t\t\t\tonpointerleave: this.updateDomBg,\n\t\t\t},\n\t\t\tchildren,\n\t\t)\n\t}\n\n\tprivate updateDomBg = () => {\n\t\t// in single column layout the \"current element\" selection is not meaningful and is even annoying\n\t\tconst highlight = styles.isSingleColumnLayout() ? this.isInMultiselect && this.selected : this.selected\n\t\tif (this.dom) {\n\t\t\tthis.dom.style.backgroundColor = highlight ? stateBgHover : theme.list_bg\n\t\t}\n\t}\n}\n\nexport function setVisibility(dom: HTMLElement, visible: boolean) {\n\tdom.style.display = visible ? \"\" : \"none\"\n}\n\nexport function checkboxOpacity(dom: HTMLInputElement, selected: boolean) {\n\tif (selected) {\n\t\tdom.classList.remove(\"list-checkbox\")\n\t} else {\n\t\tdom.classList.add(\"list-checkbox\")\n\t}\n}\n\nexport function shouldAlwaysShowMultiselectCheckbox() {\n\treturn !styles.isUsingBottomNavigation()\n}\n\n// delay by 2 frames roughly so that the browser has time to do heavy stuff with layout\nexport const selectableRowAnimParams: KeyframeAnimationOptions = { duration: DefaultAnimationTime, easing: \"ease-in-out\", fill: \"forwards\", delay: 36 }\nexport const scaleXHide = \"scaleX(0)\"\nexport const scaleXShow = \"scaleX(1)\"\n","import { styles } from \"./styles.js\"\n\nexport function responsiveCardHMargin() {\n\treturn styles.isSingleColumnLayout() ? \"mlr\" : \"mlr-l\"\n}\n\nexport function responsiveCardHPadding() {\n\treturn styles.isSingleColumnLayout() ? \"plr\" : \"plr-l\"\n}\n","import type { MailboxDetail } from \"../../mail/model/MailModel\"\nimport { getEnabledMailAddressesWithUser, getMailboxName } from \"../../mail/model/MailUtils\"\nimport type { LoginController } from \"../../api/main/LoginController\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport { PartialRecipient } from \"../../api/common/recipients/Recipient\"\n\nassertMainOrNode()\nexport const PASSWORD_MAX_VALUE = 100\nexport const PASSWORD_MIN_VALUE = 0\nexport const PASSWORD_MIN_SECURE_VALUE = 80\nexport const _BAD_SEQUENCES = [\n\t\"^1234567890ß´\",\n\t'°!\"§$%&/()=?`',\n\t\"qwertzuiopü+\",\n\t\"QWERTZUIOPÜ*\",\n\t\"asdfghjklöä#\",\n\t\"ASDFGHJKLÖÄ'\",\n\t\"<yxcvbnm,.-\",\n\t\">YXCVBNM:_\",\n\t\"`1234567890-=\",\n\t\"~!@#$%^&*()_+\",\n\t\"qwertyuiop[]\",\n\t\"QWERTYUIOP{}\",\n\t\"asdfghjkl'\\\\\",\n\t'ASDFGHJKL:\"|',\n\t\"\\\\zxcvbnm,./\",\n\t\"|ZXCVBNM<>?\",\n\t\"abcdefghijklmnopqrstuvwxyz\",\n\t\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\",\n]\nconst _BAD_STRINGS = [\"passwort\", \"Passwort\", \"password\", \"Password\", \"tutanota\", \"Tutanota\", \"free\", \"Free\", \"starter\", \"Starter\", \"Test\", \"test\"]\n\n/**\n * Checks how secure the given password is. The following password characteristics decrease the password strength:\n * - irregular distribution of characters across the character classes lower case, upper case, digit, other\n * - consecutive characters of the same class\n * - same chars\n * - same consecutive chars\n * - keyboard (german/english) or alphabet sequences\n * - bad strings (statically defined and passed to function in badStrings)\n * @param password The password to check.\n * @param badStrings Strings that reduce the strength of the password.\n * @return A number from 0 to 100.\n */\nexport function getPasswordStrength(password: string, badStrings: string[]): number {\n\tif (password.length === 0) return 0\n\n\t// calculate the characteristics of the password\n\tlet nbrOfLowerChars = _getNbrOfOccurrences(password, /[a-z ]/g)\n\n\tlet nbrOfConsecutiveLowerChars = Math.max(0, _getLongestResult(password, /[a-z ]*/g) - 2) // consecutive chars > 2\n\n\tlet nbrOfUpperChars = _getNbrOfOccurrences(password, /[A-Z]/g)\n\n\tlet nbrOfConsecutiveUpperChars = Math.max(0, _getLongestResult(password, /[A-Z]*/g) - 2)\n\n\tlet nbrOfDigits = _getNbrOfOccurrences(password, /[0-9]/g)\n\n\tlet nbrOfConsecutiveDigits = Math.max(0, _getLongestResult(password, /[0-9]*/g) - 2)\n\tlet nbrOfOtherChars = password.length - nbrOfDigits - nbrOfLowerChars - nbrOfUpperChars\n\tlet nbrOfConsecutiveOtherChars = Math.max(0, _getLongestResult(password, /[^a-z A-Z0-9]*/g) - 2)\n\tlet nbrOfConsecutiveSame = Math.max(0, _getLongestResult(password, /(.)\\1+/g) - 2)\n\tlet minNbrOfCharsPerType = password.length / 4 // best is 1/4 lower case, 1/4 upper case, 1/4 digits, 1/4 other chars\n\n\t// all these values decrease the strength\n\tlet nbrOfMissingLowerChars = Math.max(0, minNbrOfCharsPerType - nbrOfLowerChars)\n\tlet nbrOfMissingUpperChars = Math.max(0, minNbrOfCharsPerType - nbrOfUpperChars)\n\tlet nbrOfMissingDigits = Math.max(0, minNbrOfCharsPerType - nbrOfDigits)\n\tlet nbrOfMissingOtherChars = Math.max(0, minNbrOfCharsPerType - nbrOfOtherChars)\n\n\tlet nbrOfSameChars = _getNbrOfSameChars(password)\n\n\tlet nbrOfSequenceDigits = _getNbrOfSequenceChars(password, _BAD_SEQUENCES, true)\n\n\tlet nbrOfBadStringDigits = _getNbrOfSequenceChars(password, badStrings.concat(_BAD_STRINGS), false)\n\n\tlet strength = password.length * 11 // 11 = strength per character without reduction\n\n\tstrength -= nbrOfMissingLowerChars * 3\n\tstrength -= nbrOfMissingUpperChars * 3\n\tstrength -= nbrOfMissingDigits * 3\n\tstrength -= nbrOfMissingOtherChars * 3\n\tstrength -= nbrOfConsecutiveLowerChars * 2\n\tstrength -= nbrOfConsecutiveUpperChars * 2\n\tstrength -= nbrOfConsecutiveDigits * 2\n\tstrength -= nbrOfConsecutiveOtherChars * 2\n\tstrength -= nbrOfConsecutiveSame * 2\n\tstrength -= nbrOfSameChars * 5\n\tstrength -= nbrOfSequenceDigits * 4\n\tstrength -= nbrOfBadStringDigits * 4\n\treturn Math.min(PASSWORD_MAX_VALUE, Math.max(PASSWORD_MIN_VALUE, Math.round(strength)))\n}\n\nexport function getPasswordStrengthForUser(password: string, recipientInfo: PartialRecipient, mailboxDetails: MailboxDetail, logins: LoginController): number {\n\tlet reserved = getEnabledMailAddressesWithUser(mailboxDetails, logins.getUserController().userGroupInfo).concat(\n\t\tgetMailboxName(logins, mailboxDetails),\n\t\trecipientInfo.address,\n\t\trecipientInfo.name ?? \"\",\n\t)\n\treturn Math.min(PASSWORD_MAX_VALUE, getPasswordStrength(password, reserved))\n}\n\nexport function scaleToVisualPasswordStrength(passwordStrength: number): number {\n\tconst scale = PASSWORD_MIN_SECURE_VALUE / 100\n\treturn Math.min(PASSWORD_MAX_VALUE, passwordStrength / scale)\n}\n\nexport function isSecurePassword(passwordStrength: number): boolean {\n\treturn passwordStrength >= PASSWORD_MIN_SECURE_VALUE\n}\n\n/**\n * Provides the number of repetitions of any characters in the given password at any position.\n * @param password The password to check.\n * @returns The number of same characters.\n */\nfunction _getNbrOfSameChars(password: string): number {\n\tconst characterObject = new Set<string>()\n\n\tfor (const c of password) {\n\t\tcharacterObject.add(c)\n\t}\n\n\treturn password.length - characterObject.size\n}\n\n/**\n * Provides the number of chars in the given password that contains parts (> 2 characters) of the given sequences.\n * @param password The password to check.\n * @param sequences The sequences to check.\n * @param reverseToo If true, also all reverse sequences are checked.\n * @returns The number of chars that match any sequences.\n */\nexport function _getNbrOfSequenceChars(password: string, sequences: string[], reverseToo: boolean): number {\n\t// all sequences to the list of checked sequences s. also add all reverse sequences if requested\n\tlet s = sequences\n\n\tif (reverseToo) {\n\t\ts = sequences.concat(sequences.map((s1) => s1.split(\"\").reverse().join(\"\")))\n\t}\n\n\tlet MIN_SEQUENCE_LEN = 3\n\tlet nbrOfSequenceDigits = 0\n\n\t// check the part of the password (substringToCheck) from i to i+sequenceLen in a loop\n\tfor (let i = 0; i < password.length - MIN_SEQUENCE_LEN; i++) {\n\t\tlet maxFoundLen = 0\n\n\t\tfor (let sequenceLen = MIN_SEQUENCE_LEN; i + sequenceLen <= password.length; sequenceLen++) {\n\t\t\tlet substringToCheck = password.substring(i, i + sequenceLen)\n\n\t\t\tfor (let a = 0; a < s.length; a++) {\n\t\t\t\tif (s[a].indexOf(substringToCheck) !== -1) {\n\t\t\t\t\tmaxFoundLen = sequenceLen\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (maxFoundLen > 0) {\n\t\t\tnbrOfSequenceDigits += maxFoundLen\n\t\t\ti += maxFoundLen - 1 // skip the found sequence. -1 because the for loop also decreases by 1\n\t\t}\n\t}\n\n\treturn nbrOfSequenceDigits\n}\n\n/**\n * Gets the number of occurrences of the given regular expression in the given string.\n * @param string The string to check.\n * @param regexp The reqular expression to check against.\n * @return The number of occurrences.\n */\nfunction _getNbrOfOccurrences(string: string, regexp: RegExp): number {\n\tlet result = string.match(regexp)\n\treturn result ? result.length : 0\n}\n\n/**\n * Gets the number of characters in the longest result when checking the given string against the given regular expression.\n * @param string The string to check.\n * @param regexp The reqular expression to check against.\n * @returns The number of characters of the longest result.\n */\nfunction _getLongestResult(string: string, regexp: RegExp): number {\n\tlet result = string.match(regexp)\n\treturn result ? result.reduce((max, val) => Math.max(max, val.length), 0) : 0\n}\n","import type { OutOfOfficeNotification } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { OutOfOfficeNotificationTypeRef } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { formatDate } from \"./Formatter\"\nimport { lang } from \"./LanguageViewModel\"\nimport { locator } from \"../api/main/MainLocator\"\nimport { MailboxGroupRootTypeRef } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { getDayShifted } from \"@tutao/tutanota-utils\"\n\n/**\n * Returns true if notifications are currently sent.\n */\nexport function isNotificationCurrentlyActive(notification: OutOfOfficeNotification, currentDate: Date): boolean {\n\tif (notification.enabled) {\n\t\tif (notification.startDate && !notification.endDate) {\n\t\t\treturn currentDate >= notification.startDate\n\t\t} else if (notification.startDate && notification.endDate) {\n\t\t\treturn currentDate >= notification.startDate && currentDate < notification.endDate\n\t\t} else {\n\t\t\t// no dates specified but enabled\n\t\t\treturn true\n\t\t}\n\t} else {\n\t\treturn false\n\t}\n}\n\nexport function formatActivateState(notification: OutOfOfficeNotification | null): string {\n\tif (notification && notification.enabled) {\n\t\tvar timeRange = \"\"\n\n\t\tif (notification.startDate) {\n\t\t\ttimeRange += \" (\" + formatDate(notification.startDate)\n\n\t\t\tif (notification.endDate) {\n\t\t\t\t// end dates are stored as the beginning of the following date. We subtract one day to show the correct date to the user.\n\t\t\t\tconst shiftedEndDate = getDayShifted(notification.endDate, -1)\n\t\t\t\ttimeRange += \" - \" + formatDate(shiftedEndDate)\n\t\t\t}\n\n\t\t\ttimeRange += \")\"\n\t\t}\n\n\t\treturn lang.get(\"activated_label\") + timeRange\n\t} else {\n\t\treturn lang.get(\"deactivated_label\")\n\t}\n}\n\n/**\n *\n * @param organizationMessageEnabled true if a special messagesfor senders from the same organization is setup\n * @returns {string} the label for default notifications (depends on whether only default notifications or both default and same organization notifications are enabled)\n */\nexport function getDefaultNotificationLabel(organizationMessageEnabled: boolean): string {\n\tif (organizationMessageEnabled) {\n\t\treturn lang.get(\"outOfOfficeExternal_msg\")\n\t} else {\n\t\treturn lang.get(\"outOfOfficeEveryone_msg\")\n\t}\n}\n\n/**\n * Loads the out of office notification from the server and shifts the end date (from the first second of the following day to the first second of the last day)\n * which is needed to display the correct end date.\n */\nexport function loadOutOfOfficeNotification(): Promise<OutOfOfficeNotification | null> {\n\tconst mailMembership = locator.logins.getUserController().getUserMailGroupMembership()\n\treturn locator.entityClient.load(MailboxGroupRootTypeRef, mailMembership.group).then((grouproot) => {\n\t\tif (grouproot.outOfOfficeNotification) {\n\t\t\treturn locator.entityClient.load<OutOfOfficeNotification>(OutOfOfficeNotificationTypeRef, grouproot.outOfOfficeNotification)\n\t\t} else {\n\t\t\treturn null\n\t\t}\n\t})\n}\n","import type { SelectorItem } from \"../base/DropDownSelector.js\"\nimport type { TranslationKey } from \"../../misc/LanguageViewModel\"\nimport { locator } from \"../../api/main/MainLocator\"\n\nexport async function showSpellcheckLanguageDialog(): Promise<string> {\n\tconst { DesktopConfigKey } = await import(\"../../desktop/config/ConfigKeys\")\n\tconst current = await getCurrentSpellcheckLanguage()\n\tconst { Dialog } = await import(\"../base/Dialog.js\")\n\tconst items = await getItems()\n\t// this is a language code\n\tconst newLang = await Dialog.showDropDownSelectionDialog(\"spelling_label\", \"language_label\", null, items, current)\n\tawait locator.desktopSettingsFacade.setStringConfigValue(DesktopConfigKey.spellcheck, newLang)\n\t// return displayable language name\n\tconst selectedItem = items.find((i) => i.value === newLang)\n\treturn selectedItem ? selectedItem.name : items[0].name\n}\n\nexport async function getCurrentSpellcheckLanguageLabel(): Promise<string> {\n\tconst current = await getCurrentSpellcheckLanguage()\n\tconst items = await getItems()\n\tconst selectedItem = items.find((i) => i.value === current)\n\treturn selectedItem ? selectedItem.name : items[0].name\n}\n\nasync function getCurrentSpellcheckLanguage(): Promise<string> {\n\tconst { DesktopConfigKey } = await import(\"../../desktop/config/ConfigKeys\")\n\treturn (await locator.desktopSettingsFacade.getStringConfigValue(DesktopConfigKey.spellcheck)) ?? \"\"\n}\n\nasync function getItems(): Promise<Array<SelectorItem<string>>> {\n\tconst { languages, lang } = await import(\"../../misc/LanguageViewModel.js\")\n\tconst options = await locator.desktopSettingsFacade.getSpellcheckLanguages()\n\treturn [\n\t\t{\n\t\t\tname: lang.get(\"comboBoxSelectionNone_msg\"),\n\t\t\tvalue: \"\",\n\t\t},\n\t\t...options\n\t\t\t.map((code) => {\n\t\t\t\tconst [langCode, locale] = code.split(\"-\")\n\t\t\t\t// first, find the name for a language given a locale with a perfect match\n\t\t\t\tconst language =\n\t\t\t\t\tlanguages.find((language) => locale && language.code === `${langCode}_${locale.toLowerCase()}`) || // find the name for a language without a locale, with a perfect match\n\t\t\t\t\tlanguages.find((language) => language.code === langCode) || // try to get a missing one before splitting\n\t\t\t\t\tgetMissingLanguage(langCode) || // the code given by electron doesn't always have a locale when we do,\n\t\t\t\t\t// e.g. for Persian we have \"fa_ir\" in LanguageViewModel, but electron only gives us \"fa\"\n\t\t\t\t\tlanguages.find((language) => language.code.slice(0, 2) === langCode)\n\t\t\t\tconst textId = language?.textId\n\t\t\t\tconst name = textId ? lang.get(textId) + ` (${code})` : code\n\t\t\t\treturn {\n\t\t\t\t\tname,\n\t\t\t\t\tvalue: code,\n\t\t\t\t}\n\t\t\t})\n\t\t\t.sort((a, b) => a.name.localeCompare(b.name)),\n\t]\n}\n\n/**\n * Electron has a different selection of spellchecker languages from what our client supports,\n * so we can't get all of the names from the LanguageViewModel\n */\nfunction getMissingLanguage(code: string): { textId: TranslationKey; code: string } | null {\n\tconst mapping: Record<string, TranslationKey> = {\n\t\taf: \"languageAfrikaans_label\",\n\t\tcy: \"languageWelsh_label\",\n\t\tfo: \"languageFaroese_label\",\n\t\thy: \"languageArmenian_label\",\n\t\tnb: \"languageNorwegianBokmal_label\",\n\t\tsh: \"languageSerboCroatian_label\",\n\t\tsq: \"languageAlbanian_label\",\n\t\tta: \"languageTamil_label\",\n\t\ttg: \"languageTajik_label\",\n\t\tpt: \"languagePortugese_label\",\n\t}\n\tconst id = mapping[code]\n\treturn id ? { textId: id, code } : null\n}\n","import m from \"mithril\"\nimport type { GroupInfo } from \"../../api/entities/sys/TypeRefs.js\"\nimport { GroupInfoTypeRef, WhitelabelChildTypeRef } from \"../../api/entities/sys/TypeRefs.js\"\nimport { assertNotNull, filterInt, getDayShifted, getStartOfDay, isSameTypeRef, neverNull } from \"@tutao/tutanota-utils\"\nimport { RouteSetFn, throttleRoute } from \"../../misc/RouteChange\"\nimport type { SearchRestriction } from \"../../api/worker/search/SearchTypes\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport { TranslationKey } from \"../../misc/LanguageViewModel\"\nimport { ContactTypeRef, MailTypeRef } from \"../../api/entities/tutanota/TypeRefs\"\nimport { typeModels } from \"../../api/entities/tutanota/TypeModels.js\"\nimport { locator } from \"../../api/main/MainLocator.js\"\n\nassertMainOrNode()\n\nconst FIXED_FREE_SEARCH_DAYS = 28\n\nexport const SEARCH_CATEGORIES = [\n\t{\n\t\tname: \"mail\",\n\t\ttypeRef: MailTypeRef,\n\t},\n\t{\n\t\tname: \"contact\",\n\t\ttypeRef: ContactTypeRef,\n\t},\n\t{\n\t\tname: \"whitelabelchild\",\n\t\ttypeRef: WhitelabelChildTypeRef,\n\t},\n]\n\ninterface SearchMailField {\n\treadonly textId: TranslationKey\n\treadonly field: string | null\n\treadonly attributeIds: number[] | null\n}\n\nexport const SEARCH_MAIL_FIELDS: ReadonlyArray<SearchMailField> = [\n\t{\n\t\ttextId: \"all_label\",\n\t\tfield: null,\n\t\tattributeIds: null,\n\t},\n\t{\n\t\ttextId: \"subject_label\",\n\t\tfield: \"subject\",\n\t\tattributeIds: [typeModels.Mail.values[\"subject\"].id as number],\n\t},\n\t{\n\t\ttextId: \"mailBody_label\",\n\t\tfield: \"body\",\n\t\tattributeIds: [typeModels.Mail.associations[\"body\"].id as number],\n\t},\n\t{\n\t\ttextId: \"from_label\",\n\t\tfield: \"from\",\n\t\tattributeIds: [typeModels.Mail.associations[\"sender\"].id as number],\n\t},\n\t{\n\t\ttextId: \"to_label\",\n\t\tfield: \"to\",\n\t\tattributeIds: [\n\t\t\ttypeModels.Mail.associations[\"toRecipients\"].id as number,\n\t\t\ttypeModels.Mail.associations[\"ccRecipients\"].id as number,\n\t\t\ttypeModels.Mail.associations[\"bccRecipients\"].id as number,\n\t\t],\n\t},\n\t{\n\t\ttextId: \"attachmentName_label\",\n\t\tfield: \"attachment\",\n\t\tattributeIds: [typeModels.Mail.associations[\"attachments\"].id as number],\n\t},\n]\n\nconst routeSetThrottled: RouteSetFn = throttleRoute()\n\nexport function setSearchUrl(url: string) {\n\tif (url !== m.route.get()) {\n\t\trouteSetThrottled(url, {})\n\t}\n}\n\nexport function searchCategoryForRestriction(restriction: SearchRestriction): string {\n\treturn assertNotNull(SEARCH_CATEGORIES.find((c) => isSameTypeRef(c.typeRef, restriction.type))).name\n}\n\nexport function getSearchUrl(\n\tquery: string | null,\n\trestriction: SearchRestriction,\n\tselectedId?: Id,\n): {\n\tpath: string\n\tparams: Record<string, string | number>\n} {\n\tconst category = searchCategoryForRestriction(restriction)\n\tconst params: Record<string, string | number> = {\n\t\tquery: query ?? \"\",\n\t\tcategory,\n\t}\n\t// a bit annoying but avoids putting unnecessary things into the url (if we woudl put undefined into it)\n\tif (restriction.start) {\n\t\tparams.start = restriction.start\n\t}\n\tif (restriction.end) {\n\t\tparams.end = restriction.end\n\t}\n\tif (restriction.listId) {\n\t\tparams.list = restriction.listId\n\t}\n\tif (restriction.field) {\n\t\tparams.field = restriction.field\n\t}\n\treturn {\n\t\tpath: \"/search/:category\" + (selectedId ? \"/\" + selectedId : \"\"),\n\t\tparams: params,\n\t}\n}\n\nexport function getFreeSearchStartDate(): Date {\n\treturn getStartOfDay(getDayShifted(new Date(), -FIXED_FREE_SEARCH_DAYS))\n}\n\n/**\n * Adjusts the restriction according to the account type if necessary\n */\nexport function createRestriction(\n\tsearchCategory: string,\n\tstart: number | null,\n\tend: number | null,\n\tfield: string | null,\n\tlistId: string | null,\n): SearchRestriction {\n\tif (locator.logins.getUserController().isFreeAccount() && searchCategory === \"mail\") {\n\t\tstart = null\n\t\tend = getFreeSearchStartDate().getTime()\n\t\tfield = null\n\t\tlistId = null\n\t}\n\n\tlet r: SearchRestriction = {\n\t\ttype: neverNull(SEARCH_CATEGORIES.find((c) => c.name === searchCategory)).typeRef,\n\t\tstart: start,\n\t\tend: end,\n\t\tfield: null,\n\t\tattributeIds: null,\n\t\tlistId: listId,\n\t}\n\n\tif (field && searchCategory === \"mail\") {\n\t\tlet fieldData = SEARCH_MAIL_FIELDS.find((f) => f.field === field)\n\n\t\tif (fieldData) {\n\t\t\tr.field = field\n\t\t\tr.attributeIds = fieldData.attributeIds\n\t\t}\n\t} else if (field && searchCategory === \"contact\") {\n\t\tif (field === \"recipient\") {\n\t\t\tr.field = field\n\t\t\tr.attributeIds = [\n\t\t\t\ttypeModels.Contact.values[\"firstName\"].id,\n\t\t\t\ttypeModels.Contact.values[\"lastName\"].id,\n\t\t\t\ttypeModels.Contact.associations[\"mailAddresses\"].id,\n\t\t\t]\n\t\t} else if (field === \"mailAddress\") {\n\t\t\tr.field = field\n\t\t\tr.attributeIds = [typeModels.Contact.associations[\"mailAddresses\"].id]\n\t\t}\n\t}\n\n\treturn r\n}\n\n/**\n * Adjusts the restriction according to the account type if necessary\n */\nexport function getRestriction(route: string): SearchRestriction {\n\tlet category: string\n\tlet start: number | null = null\n\tlet end: number | null = null\n\tlet field: string | null = null\n\tlet listId: string | null = null\n\n\tif (route.startsWith(\"/mail\") || route.startsWith(\"/search/mail\")) {\n\t\tcategory = \"mail\"\n\n\t\tif (route.startsWith(\"/search/mail\")) {\n\t\t\ttry {\n\t\t\t\t// mithril will parse boolean but not numbers\n\t\t\t\tconst { params } = m.parsePathname(route)\n\t\t\t\tif (typeof params[\"start\"] === \"string\") {\n\t\t\t\t\tstart = filterInt(params[\"start\"])\n\t\t\t\t}\n\n\t\t\t\tif (typeof params[\"end\"] === \"string\") {\n\t\t\t\t\tend = filterInt(params[\"end\"])\n\t\t\t\t}\n\n\t\t\t\tif (typeof params[\"field\"] === \"string\") {\n\t\t\t\t\tconst fieldString = params[\"field\"]\n\t\t\t\t\tfield = SEARCH_MAIL_FIELDS.find((f) => f.field === fieldString)?.field ?? null\n\t\t\t\t}\n\n\t\t\t\tif (typeof params[\"list\"] === \"string\") {\n\t\t\t\t\tlistId = params[\"list\"]\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tconsole.log(\"invalid query: \" + route, e)\n\t\t\t}\n\t\t}\n\t} else if (route.startsWith(\"/contact\") || route.startsWith(\"/search/contact\")) {\n\t\tcategory = \"contact\"\n\t} else if (route.startsWith(\"/settings/whitelabelaccounts\")) {\n\t\tcategory = \"whitelabelchild\"\n\t} else {\n\t\tthrow new Error(\"invalid type \" + route)\n\t}\n\n\treturn createRestriction(category, start, end, field, listId)\n}\n\nexport function isAdministratedGroup(localAdminGroupIds: Id[], gi: GroupInfo): boolean {\n\tif (gi.localAdmin && localAdminGroupIds.indexOf(gi.localAdmin) !== -1) {\n\t\treturn true // group is administrated by local admin group of this user\n\t} else if (localAdminGroupIds.indexOf(gi.group) !== -1) {\n\t\treturn true // group is one of the local admin groups of this user\n\t} else {\n\t\treturn false\n\t}\n}\n","import stream from \"mithril/stream\"\nimport Stream from \"mithril/stream\"\nimport { MailTypeRef } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { NOTHING_INDEXED_TIMESTAMP } from \"../../api/common/TutanotaConstants\"\nimport { DbError } from \"../../api/common/error/DbError\"\nimport type { SearchIndexStateInfo, SearchRestriction, SearchResult } from \"../../api/worker/search/SearchTypes\"\nimport { arrayEquals, isSameTypeRef, ofClass } from \"@tutao/tutanota-utils\"\nimport type { SearchFacade } from \"../../api/worker/search/SearchFacade\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport { GroupInfo, WhitelabelChild } from \"../../api/entities/sys/TypeRefs.js\"\n\nassertMainOrNode()\nexport type SearchQuery = {\n\tquery: string\n\trestriction: SearchRestriction\n\tminSuggestionCount: number\n\tmaxResults: number | null\n}\n\nexport class SearchModel {\n\tresult: Stream<SearchResult | null>\n\tindexState: Stream<SearchIndexStateInfo>\n\tlastQuery: Stream<string | null>\n\tindexingSupported: boolean\n\tlastSelectedGroupInfoResult: Stream<GroupInfo>\n\tlastSelectedWhitelabelChildrenInfoResult: Stream<WhitelabelChild>\n\t_searchFacade: SearchFacade\n\t_lastQuery: SearchQuery | null\n\t_lastSearchPromise: Promise<SearchResult | void>\n\t_groupInfoRestrictionListId: Id | null\n\n\tconstructor(searchFacade: SearchFacade) {\n\t\tthis._searchFacade = searchFacade\n\t\tthis.result = stream()\n\t\tthis.lastQuery = stream<string | null>(\"\")\n\t\tthis.lastSelectedGroupInfoResult = stream()\n\t\tthis.lastSelectedWhitelabelChildrenInfoResult = stream()\n\t\tthis.indexingSupported = true\n\t\tthis.indexState = stream<SearchIndexStateInfo>({\n\t\t\tinitializing: true,\n\t\t\tmailIndexEnabled: false,\n\t\t\tprogress: 0,\n\t\t\tcurrentMailIndexTimestamp: NOTHING_INDEXED_TIMESTAMP,\n\t\t\taimedMailIndexTimestamp: NOTHING_INDEXED_TIMESTAMP,\n\t\t\tindexedMailCount: 0,\n\t\t\tfailedIndexingUpTo: null,\n\t\t})\n\t\tthis._lastQuery = null\n\t\tthis._lastSearchPromise = Promise.resolve(undefined)\n\t\tthis._groupInfoRestrictionListId = null\n\t}\n\n\tasync search(searchQuery: SearchQuery): Promise<SearchResult | void> {\n\t\tif (this._lastQuery && searchQueryEquals(searchQuery, this._lastQuery)) {\n\t\t\treturn this._lastSearchPromise\n\t\t}\n\n\t\tthis._lastQuery = searchQuery\n\t\tconst { query, restriction, minSuggestionCount, maxResults } = searchQuery\n\t\tthis.lastQuery(query)\n\t\tlet result = this.result()\n\n\t\tif (result && !isSameTypeRef(restriction.type, result.restriction.type)) {\n\t\t\t// reset the result in case only the search type has changed\n\t\t\tthis.result(null)\n\t\t} else if (this.indexState().progress > 0 && result && isSameTypeRef(MailTypeRef, result.restriction.type)) {\n\t\t\t// reset the result if indexing is in progress and the current search result is of type mail\n\t\t\tthis.result(null)\n\t\t}\n\n\t\tif (query.trim() === \"\") {\n\t\t\t// if there was an empty query, just send empty result\n\t\t\tconst result: SearchResult = {\n\t\t\t\tquery: query,\n\t\t\t\trestriction: restriction,\n\t\t\t\tresults: [],\n\t\t\t\tcurrentIndexTimestamp: this.indexState().currentMailIndexTimestamp,\n\t\t\t\tlastReadSearchIndexRow: [],\n\t\t\t\tmaxResults: 0,\n\t\t\t\tmatchWordOrder: false,\n\t\t\t\tmoreResults: [],\n\t\t\t\tmoreResultsEntries: [],\n\t\t\t}\n\t\t\tthis.result(result)\n\t\t\tthis._lastSearchPromise = Promise.resolve(result)\n\t\t} else {\n\t\t\tthis._lastSearchPromise = this._searchFacade\n\t\t\t\t.search(query, restriction, minSuggestionCount, maxResults ?? undefined)\n\t\t\t\t.then((result) => {\n\t\t\t\t\tthis.result(result)\n\t\t\t\t\treturn result\n\t\t\t\t})\n\t\t\t\t.catch(\n\t\t\t\t\tofClass(DbError, (e) => {\n\t\t\t\t\t\tconsole.log(\"DBError while search\", e)\n\n\t\t\t\t\t\tif (isSameTypeRef(MailTypeRef, restriction.type) && !this.indexState().mailIndexEnabled) {\n\t\t\t\t\t\t\tconsole.log(\"Mail indexing was disabled, ignoring DBError\")\n\t\t\t\t\t\t\tthis.result(null)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow e\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t}\n\n\t\treturn this._lastSearchPromise\n\t}\n\n\tisNewSearch(query: string, restriction: SearchRestriction): boolean {\n\t\tlet result = this.result()\n\n\t\tif (result == null) {\n\t\t\treturn true\n\t\t}\n\n\t\tif (query !== result.query) {\n\t\t\treturn true\n\t\t}\n\n\t\tif (result.restriction === restriction) {\n\t\t\t// both are the same instance\n\t\t\treturn false\n\t\t}\n\n\t\treturn !isSameSearchRestriction(restriction, result.restriction)\n\t}\n\n\t// TODO: remove this and take the list id from the url as soon as the list id is included in user and group settings\n\tsetGroupInfoRestrictionListId(listId: Id) {\n\t\tthis._groupInfoRestrictionListId = listId\n\t}\n\n\tgetGroupInfoRestrictionListId(): Id | null {\n\t\treturn this._groupInfoRestrictionListId\n\t}\n}\n\nfunction searchQueryEquals(a: SearchQuery, b: SearchQuery) {\n\treturn (\n\t\ta.query === b.query &&\n\t\tisSameSearchRestriction(a.restriction, b.restriction) &&\n\t\ta.minSuggestionCount === b.minSuggestionCount &&\n\t\ta.maxResults === b.maxResults\n\t)\n}\n\nexport function isSameSearchRestriction(a: SearchRestriction, b: SearchRestriction): boolean {\n\tconst isSameAttributeIds = a.attributeIds === b.attributeIds || (!!a.attributeIds && !!b.attributeIds && arrayEquals(a.attributeIds, b.attributeIds))\n\treturn isSameTypeRef(a.type, b.type) && a.start === b.start && a.end === b.end && a.field === b.field && isSameAttributeIds && a.listId === b.listId\n}\n\nexport function areResultsForTheSameQuery(a: SearchResult, b: SearchResult) {\n\treturn a.query === b.query && isSameSearchRestriction(a.restriction, b.restriction)\n}\n\nexport function hasMoreResults(searchResult: SearchResult): boolean {\n\treturn (\n\t\tsearchResult.moreResults.length > 0 ||\n\t\t(searchResult.lastReadSearchIndexRow.length > 0 && searchResult.lastReadSearchIndexRow.every(([word, id]) => id !== 0))\n\t)\n}\n","import type { InboxRule, Mail, MailDetails, MoveMailData } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { createMoveMailData, MailDetailsBlobTypeRef, MailHeadersTypeRef } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { InboxRuleType, MailFolderType, MAX_NBR_MOVE_DELETE_MAIL_SERVICE } from \"../../api/common/TutanotaConstants\"\nimport { isDomainName, isRegularExpression } from \"../../misc/FormatValidator\"\nimport { getLegacyMailHeaders, getMailHeaders } from \"../../api/common/utils/Utils\"\nimport { assertNotNull, asyncFind, debounce, neverNull, ofClass, promiseMap, splitInChunks } from \"@tutao/tutanota-utils\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport type { MailboxDetail } from \"./MailModel\"\nimport { LockedError, NotFoundError, PreconditionFailedError } from \"../../api/common/error/RestError\"\nimport type { SelectorItemList } from \"../../gui/base/DropDownSelector.js\"\nimport { EntityClient } from \"../../api/common/EntityClient\"\nimport { elementIdPart, getElementId, getListId, isSameId, listIdPart } from \"../../api/common/utils/EntityUtils\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport { MailFacade } from \"../../api/worker/facades/lazy/MailFacade.js\"\nimport { isLegacyMail } from \"../../api/common/MailWrapper.js\"\nimport { assertSystemFolderOfType } from \"../../api/common/mail/CommonMailUtils.js\"\nimport { LoginController } from \"../../api/main/LoginController.js\"\n\nassertMainOrNode()\nconst moveMailDataPerFolder: MoveMailData[] = []\nconst DEBOUNCE_FIRST_MOVE_MAIL_REQUEST_MS = 200\nlet applyingRules = false // used to avoid concurrent application of rules (-> requests to locked service)\n\nasync function sendMoveMailRequest(mailFacade: MailFacade): Promise<void> {\n\tif (moveMailDataPerFolder.length) {\n\t\tconst moveToTargetFolder = assertNotNull(moveMailDataPerFolder.shift())\n\t\tconst mailChunks = splitInChunks(MAX_NBR_MOVE_DELETE_MAIL_SERVICE, moveToTargetFolder.mails)\n\t\tawait promiseMap(mailChunks, (mailChunk) => {\n\t\t\tmoveToTargetFolder.mails = mailChunk\n\t\t\treturn mailFacade.moveMails(mailChunk, moveToTargetFolder.targetFolder)\n\t\t})\n\t\t\t.catch(\n\t\t\t\tofClass(LockedError, (e) => {\n\t\t\t\t\t//LockedError should no longer be thrown!?!\n\t\t\t\t\tconsole.log(\"moving mail failed\", e, moveToTargetFolder)\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.catch(\n\t\t\t\tofClass(PreconditionFailedError, (e) => {\n\t\t\t\t\t// move mail operation may have been locked by other process\n\t\t\t\t\tconsole.log(\"moving mail failed\", e, moveToTargetFolder)\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.finally(() => {\n\t\t\t\treturn sendMoveMailRequest(mailFacade)\n\t\t\t})\n\t} //We are done and unlock for future requests\n}\n\n// We throttle the moveMail requests to a rate of 50ms\n// Each target folder requires one request\nconst applyMatchingRules = debounce(DEBOUNCE_FIRST_MOVE_MAIL_REQUEST_MS, (mailFacade: MailFacade) => {\n\tif (applyingRules) return\n\t// We lock to avoid concurrent requests\n\tapplyingRules = true\n\tsendMoveMailRequest(mailFacade).finally(() => {\n\t\tapplyingRules = false\n\t})\n})\n\nexport function getInboxRuleTypeNameMapping(): SelectorItemList<string> {\n\treturn [\n\t\t{\n\t\t\tvalue: InboxRuleType.FROM_EQUALS,\n\t\t\tname: lang.get(\"inboxRuleSenderEquals_action\"),\n\t\t},\n\t\t{\n\t\t\tvalue: InboxRuleType.RECIPIENT_TO_EQUALS,\n\t\t\tname: lang.get(\"inboxRuleToRecipientEquals_action\"),\n\t\t},\n\t\t{\n\t\t\tvalue: InboxRuleType.RECIPIENT_CC_EQUALS,\n\t\t\tname: lang.get(\"inboxRuleCCRecipientEquals_action\"),\n\t\t},\n\t\t{\n\t\t\tvalue: InboxRuleType.RECIPIENT_BCC_EQUALS,\n\t\t\tname: lang.get(\"inboxRuleBCCRecipientEquals_action\"),\n\t\t},\n\t\t{\n\t\t\tvalue: InboxRuleType.SUBJECT_CONTAINS,\n\t\t\tname: lang.get(\"inboxRuleSubjectContains_action\"),\n\t\t},\n\t\t{\n\t\t\tvalue: InboxRuleType.MAIL_HEADER_CONTAINS,\n\t\t\tname: lang.get(\"inboxRuleMailHeaderContains_action\"),\n\t\t},\n\t]\n}\n\nexport function getInboxRuleTypeName(type: string): string {\n\tlet typeNameMapping = getInboxRuleTypeNameMapping().find((t) => t.value === type)\n\treturn typeNameMapping != null ? typeNameMapping.name : \"\"\n}\n\nexport class InboxRuleHandler {\n\tconstructor(private readonly mailFacade: MailFacade, private readonly entityClient: EntityClient, private readonly logins: LoginController) {}\n\n\t/**\n\t * Checks the mail for an existing inbox rule and moves the mail to the target folder of the rule.\n\t * @returns true if a rule matches otherwise false\n\t */\n\tasync findAndApplyMatchingRule(mailboxDetail: MailboxDetail, mail: Mail, applyRulesOnServer: boolean): Promise<IdTuple | null> {\n\t\tif (mail._errors || !mail.unread || !isInboxList(mailboxDetail, getListId(mail)) || !this.logins.getUserController().isPremiumAccount()) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst inboxRule = await _findMatchingRule(this.entityClient, mail, this.logins.getUserController().props.inboxRules)\n\t\tif (inboxRule) {\n\t\t\tlet targetFolder = mailboxDetail.folders.getFolderById(inboxRule.targetFolder)\n\n\t\t\tif (targetFolder && targetFolder.folderType !== MailFolderType.INBOX) {\n\t\t\t\tif (applyRulesOnServer) {\n\t\t\t\t\tlet moveMailData = moveMailDataPerFolder.find((folderMoveMailData) => isSameId(folderMoveMailData.targetFolder, inboxRule.targetFolder))\n\n\t\t\t\t\tif (moveMailData) {\n\t\t\t\t\t\tmoveMailData.mails.push(mail._id)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmoveMailData = createMoveMailData()\n\t\t\t\t\t\tmoveMailData.targetFolder = inboxRule.targetFolder\n\t\t\t\t\t\tmoveMailData.mails.push(mail._id)\n\t\t\t\t\t\tmoveMailDataPerFolder.push(moveMailData)\n\t\t\t\t\t}\n\n\t\t\t\t\tapplyMatchingRules(this.mailFacade)\n\t\t\t\t}\n\n\t\t\t\treturn [targetFolder.mails, getElementId(mail)]\n\t\t\t} else {\n\t\t\t\treturn null\n\t\t\t}\n\t\t} else {\n\t\t\treturn null\n\t\t}\n\t}\n}\n\n/**\n * Finds the first matching inbox rule for the mail and returns it.\n * export only for testing\n */\nexport async function _findMatchingRule(entityClient: EntityClient, mail: Mail, rules: InboxRule[]): Promise<InboxRule | null> {\n\treturn asyncFind(rules, (rule) => checkInboxRule(entityClient, mail, rule)).then((v) => v ?? null)\n}\n\nasync function getMailDetails(entityClient: EntityClient, mail: Mail): Promise<MailDetails | null> {\n\tif (!isLegacyMail(mail)) {\n\t\ttry {\n\t\t\tlet mailDetailsBlobId = neverNull(mail.mailDetails)\n\t\t\tlet mailDetailsBlobs = await entityClient.loadMultiple(MailDetailsBlobTypeRef, listIdPart(mailDetailsBlobId), [elementIdPart(mailDetailsBlobId)])\n\t\t\treturn mailDetailsBlobs[0].details\n\t\t} catch (e) {\n\t\t\tif (!(e instanceof NotFoundError)) {\n\t\t\t\t// Does the outer catch already handle this case?\n\t\t\t\tconsole.error(\"Error processing inbox rule:\", e.message)\n\t\t\t}\n\t\t}\n\t}\n\treturn Promise.resolve(null)\n}\n\nasync function checkInboxRule(entityClient: EntityClient, mail: Mail, inboxRule: InboxRule): Promise<boolean> {\n\tconst ruleType = inboxRule.type\n\ttry {\n\t\tif (ruleType === InboxRuleType.FROM_EQUALS) {\n\t\t\tlet mailAddresses = [mail.sender.address]\n\n\t\t\tif (mail.differentEnvelopeSender) {\n\t\t\t\tmailAddresses.push(mail.differentEnvelopeSender)\n\t\t\t}\n\n\t\t\treturn _checkEmailAddresses(mailAddresses, inboxRule)\n\t\t} else if (ruleType === InboxRuleType.RECIPIENT_TO_EQUALS) {\n\t\t\tconst details = await getMailDetails(entityClient, mail)\n\t\t\tconst toRecipients = details !== null ? details.recipients.toRecipients : mail.toRecipients\n\t\t\treturn _checkEmailAddresses(\n\t\t\t\ttoRecipients.map((m) => m.address),\n\t\t\t\tinboxRule,\n\t\t\t)\n\t\t} else if (ruleType === InboxRuleType.RECIPIENT_CC_EQUALS) {\n\t\t\tconst details = await getMailDetails(entityClient, mail)\n\t\t\tconst ccRecipients = details !== null ? details.recipients.ccRecipients : mail.ccRecipients\n\t\t\treturn _checkEmailAddresses(\n\t\t\t\tccRecipients.map((m) => m.address),\n\t\t\t\tinboxRule,\n\t\t\t)\n\t\t} else if (ruleType === InboxRuleType.RECIPIENT_BCC_EQUALS) {\n\t\t\tconst details = await getMailDetails(entityClient, mail)\n\t\t\tconst bccRecipients = details !== null ? details.recipients.ccRecipients : mail.bccRecipients\n\t\t\treturn _checkEmailAddresses(\n\t\t\t\tbccRecipients.map((m) => m.address),\n\t\t\t\tinboxRule,\n\t\t\t)\n\t\t} else if (ruleType === InboxRuleType.SUBJECT_CONTAINS) {\n\t\t\treturn _checkContainsRule(mail.subject, inboxRule)\n\t\t} else if (ruleType === InboxRuleType.MAIL_HEADER_CONTAINS) {\n\t\t\tif (isLegacyMail(mail) && mail.headers) {\n\t\t\t\treturn entityClient\n\t\t\t\t\t.load(MailHeadersTypeRef, mail.headers)\n\t\t\t\t\t.then((mailHeaders) => {\n\t\t\t\t\t\treturn _checkContainsRule(getLegacyMailHeaders(mailHeaders), inboxRule)\n\t\t\t\t\t})\n\t\t\t\t\t.catch((e) => {\n\t\t\t\t\t\tif (!(e instanceof NotFoundError)) {\n\t\t\t\t\t\t\t// Does the outer catch already handle this case?\n\t\t\t\t\t\t\tconsole.error(\"Error processing inbox rule:\", e.message)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn false\n\t\t\t\t\t})\n\t\t\t} else if (!isLegacyMail(mail)) {\n\t\t\t\tconst details = await getMailDetails(entityClient, mail)\n\t\t\t\tif (details?.headers != null) {\n\t\t\t\t\treturn _checkContainsRule(getMailHeaders(details.headers), inboxRule)\n\t\t\t\t} else {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn false\n\t\t\t}\n\t\t} else {\n\t\t\tconsole.warn(\"Unknown rule type: \", inboxRule.type)\n\t\t\treturn false\n\t\t}\n\t} catch (e) {\n\t\tconsole.error(\"Error processing inbox rule:\", e.message)\n\t\treturn false\n\t}\n}\n\nfunction _checkContainsRule(value: string, inboxRule: InboxRule): boolean {\n\treturn (isRegularExpression(inboxRule.value) && _matchesRegularExpression(value, inboxRule)) || value.includes(inboxRule.value)\n}\n\n/** export for test. */\nexport function _matchesRegularExpression(value: string, inboxRule: InboxRule): boolean {\n\tif (isRegularExpression(inboxRule.value)) {\n\t\tlet flags = inboxRule.value.replace(/.*\\/([gimsuy]*)$/, \"$1\")\n\t\tlet pattern = inboxRule.value.replace(new RegExp(\"^/(.*?)/\" + flags + \"$\"), \"$1\")\n\t\tlet regExp = new RegExp(pattern, flags)\n\t\treturn regExp.test(value)\n\t}\n\n\treturn false\n}\n\nfunction _checkEmailAddresses(mailAddresses: string[], inboxRule: InboxRule): boolean {\n\tconst mailAddress = mailAddresses.find((mailAddress) => {\n\t\tlet cleanMailAddress = mailAddress.toLowerCase().trim()\n\n\t\tif (isRegularExpression(inboxRule.value)) {\n\t\t\treturn _matchesRegularExpression(cleanMailAddress, inboxRule)\n\t\t} else if (isDomainName(inboxRule.value)) {\n\t\t\tlet domain = cleanMailAddress.split(\"@\")[1]\n\t\t\treturn domain === inboxRule.value\n\t\t} else {\n\t\t\treturn cleanMailAddress === inboxRule.value\n\t\t}\n\t})\n\treturn mailAddress != null\n}\n\nexport function isInboxList(mailboxDetail: MailboxDetail, listId: Id): boolean {\n\treturn isSameId(listId, assertSystemFolderOfType(mailboxDetail.folders, MailFolderType.INBOX).mails)\n}\n","import { startsWith } from \"@tutao/tutanota-utils\"\n\nconst DOMAIN_PART_REGEX = \"[\\\\w\\\\-\\\\+_]+\"\nconst DOMAIN_REGEXP = new RegExp(`^${DOMAIN_PART_REGEX}\\\\.${DOMAIN_PART_REGEX}(\\\\.${DOMAIN_PART_REGEX})*\\\\s*$`)\nconst DOMAIN_OR_TLD_REGEXP = new RegExp(`^(${DOMAIN_PART_REGEX}.)*${DOMAIN_PART_REGEX}$`)\nconst STRICT_USERNAME_MAIL_ADDR_REGEXP = new RegExp(\n\t`^\\\\s*${DOMAIN_PART_REGEX}(\\\\.${DOMAIN_PART_REGEX})*\\\\@${DOMAIN_PART_REGEX}\\\\.${DOMAIN_PART_REGEX}(\\\\.${DOMAIN_PART_REGEX})*\\\\s*$`,\n)\nconst EMAIL_ADDR_REGEXP = new RegExp(`^[^\\\\s\\\\@]+\\\\@${DOMAIN_PART_REGEX}\\\\.${DOMAIN_PART_REGEX}(\\\\.${DOMAIN_PART_REGEX})*\\\\s*$`)\n\n/**\n * Checks if the given string is a valid email address format.\n * @param string The string to check.\n * @param strictUserName If true checks that the part before the @ is not longer than 64 characters and does not contain special characters.\n * @return If the string is an email address.\n */\nexport function isMailAddress(string: string, strictUserName: boolean): boolean {\n\t/* KEEP IN SYNC WITH JAVA VERSION IN PhoneNumberUtils.js (except uppercase) */\n\t// check trailing whitespaces because they are not covered by the following regexp\n\t// allow uppercase addresses in input check, convert them before sending to server.\n\tif (string == null || string !== string.trim()) {\n\t\treturn false\n\t}\n\n\tif (string.indexOf(\"-\") === 0) {\n\t\treturn false\n\t}\n\n\tif (\n\t\tstring.indexOf(\",\") !== -1 ||\n\t\tstring.indexOf(\"(\") !== -1 ||\n\t\tstring.indexOf(\")\") !== -1 ||\n\t\tstring.indexOf(\":\") !== -1 ||\n\t\tstring.indexOf(\";\") !== -1 ||\n\t\tstring.indexOf(\"<\") !== -1 ||\n\t\tstring.indexOf(\">\") !== -1 ||\n\t\tstring.indexOf(\"[\") !== -1 ||\n\t\tstring.indexOf(\"]\") !== -1 ||\n\t\tstring.indexOf(\"\\\\\") !== -1\n\t) {\n\t\treturn false\n\t}\n\n\t// check lengths (see https://tools.ietf.org/html/rfc5321#section-4.5.3)\n\tif (string.length > 254) {\n\t\t// 256 minus \"<\" and \">\" of the path\n\t\treturn false\n\t}\n\n\tif (strictUserName) {\n\t\tif (string.indexOf(\"@\") > 64) {\n\t\t\treturn false\n\t\t}\n\n\t\t// see https://web.archive.org/web/20180813043723/http://ntt.cc/2008/05/10/over-10-useful-javascript-regular-expression-functions-to-improve-your-web-applications-efficiency.html\n\t\treturn STRICT_USERNAME_MAIL_ADDR_REGEXP.test(string)\n\t} else {\n\t\t// see https://web.archive.org/web/20180813043723/http://ntt.cc/2008/05/10/over-10-useful-javascript-regular-expression-functions-to-improve-your-web-applications-efficiency.html\n\t\treturn EMAIL_ADDR_REGEXP.test(string)\n\t}\n}\n\n/**\n * Checks if the given string is a valid domain name.\n * @param {string} domainName The string to check.\n * @return {boolean} If the string is a domain name.\n */\nexport function isDomainName(domainName: string): boolean {\n\tif (domainName == null || domainName !== domainName.trim()) {\n\t\treturn false\n\t}\n\n\tif (startsWith(domainName, \"-\")) {\n\t\treturn false\n\t}\n\n\treturn DOMAIN_REGEXP.test(domainName)\n}\n\nexport function isDomainOrTopLevelDomain(value: string): boolean {\n\tif (startsWith(value, \"-\")) {\n\t\treturn false\n\t}\n\n\t// Repeated words ending with dot and word at the end.\n\t// matches test.com and com but not .com\n\treturn DOMAIN_OR_TLD_REGEXP.test(value)\n}\n\n/**\n * Checks if the value is a regular expression, with or without optional flags.\n * @param value The string to check\n */\nexport function isRegularExpression(value: string): boolean {\n\treturn /^\\/.*\\/$/.test(value) || /^\\/.*\\/(?!.*(.)\\1)[gimsuy]+$/.test(value)\n}\n\n/**\n * Determine whether an input string is a valid credit card number\n * https://en.wikipedia.org/wiki/Luhn_algorithm\n * @param input: a string between 6 and 20 chars long that should contain only digits or spaces\n * @returns {boolean}\n */\nexport function isValidCreditCardNumber(input: string): boolean {\n\tconst cleaned = input.match(/^[0123456789 ]+$/)\n\n\tif (!cleaned || cleaned.length !== 1) {\n\t\treturn false\n\t}\n\n\tconst digits = cleaned[0].split(\"\").filter((c) => !/\\s/.test(c))\n\n\tif (digits.length < 6 || digits.length > 20) {\n\t\treturn false\n\t}\n\n\treturn (\n\t\tdigits\n\t\t\t.reverse()\n\t\t\t.map((num) => Number(num))\n\t\t\t.reduce((acc, cur, idx) => {\n\t\t\t\tconst num = idx % 2 === 0 ? cur : cur * 2 - (cur > 4 ? 9 : 0)\n\t\t\t\treturn acc + num\n\t\t\t}, 0) %\n\t\t\t10 ===\n\t\t0\n\t)\n}\n","const WEBSITE_BASE_URL = \"https://tutanota.com\"\n\nexport async function requestFromWebsite(path: string): Promise<Response> {\n\tconst url = new URL(path, WEBSITE_BASE_URL)\n\treturn fetch(url.href)\n}\n","import { decode } from \"cborg\"\nimport { assert, downcast, getFirstOrThrow, partitionAsync, stringToUtf8Uint8Array } from \"@tutao/tutanota-utils\"\nimport type { U2fChallenge, U2fRegisteredDevice, WebauthnResponseData } from \"../../../api/entities/sys/TypeRefs.js\"\nimport { createU2fRegisteredDevice, createWebauthnResponseData, U2fKey } from \"../../../api/entities/sys/TypeRefs.js\"\nimport { WebAuthnFacade } from \"../../../native/common/generatedipc/WebAuthnFacade.js\"\nimport { U2F_APPID, U2f_APPID_SUFFIX, WEBAUTHN_RP_ID } from \"./WebAuthn.js\"\nimport { WebauthnKeyDescriptor } from \"../../../native/common/generatedipc/WebauthnKeyDescriptor.js\"\n\n/** Web authentication entry point for the rest of the app. */\nexport class WebauthnClient {\n\tconstructor(private readonly webauthn: WebAuthnFacade, private readonly clientWebRoot: string) {}\n\n\tisSupported(): Promise<boolean> {\n\t\treturn this.webauthn.isSupported()\n\t}\n\n\t/** Whether it's possible to attempt a challenge. It might not be possible if there are not keys for this domain. */\n\tasync canAttemptChallenge(challenge: U2fChallenge): Promise<{ canAttempt: Array<U2fKey>; cannotAttempt: Array<U2fKey> }> {\n\t\t// Whitelabel keys can ge registered other (whitelabel) domains.\n\t\t// If it's a new Webauthn key it will match rpId, otherwise it will match legacy appId.\n\n\t\t// Partition in keys that might work and which certainly cannot work.\n\t\tconst [canAttempt, cannotAttempt] = await partitionAsync(\n\t\t\tchallenge.keys,\n\t\t\tasync (k) => (await this.webauthn.canAttemptChallengeForRpId(k.appId)) || (await this.webauthn.canAttemptChallengeForU2FAppId(k.appId)),\n\t\t)\n\t\treturn { canAttempt, cannotAttempt }\n\t}\n\n\tasync register(userId: Id, displayName: string): Promise<U2fRegisteredDevice> {\n\t\tconst challenge = this.getChallenge()\n\t\t// this must be at most 64 bytes because the authenticators are allowed to truncate it\n\t\t// https://www.w3.org/TR/webauthn-2/#user-handle\n\t\tconst name = `userId=\"${userId}\"`\n\t\tconst registrationResult = await this.webauthn.register({ challenge, userId, name, displayName, domain: this.clientWebRoot })\n\t\tconst attestationObject = this.parseAttestationObject(registrationResult.attestationObject)\n\t\tconst publicKey = this.parsePublicKey(downcast(attestationObject).authData)\n\n\t\treturn createU2fRegisteredDevice({\n\t\t\tkeyHandle: new Uint8Array(registrationResult.rawId),\n\t\t\t// For Webauthn keys we save rpId into appId. They do not conflict: one of them is json URL, another is domain.\n\t\t\tappId: registrationResult.rpId,\n\t\t\tpublicKey: this.serializePublicKey(publicKey),\n\t\t\tcompromised: false,\n\t\t\tcounter: \"-1\",\n\t\t})\n\t}\n\n\tasync authenticate(challenge: U2fChallenge, signal?: AbortSignal): Promise<WebauthnResponseData> {\n\t\tconst allowedKeys: WebauthnKeyDescriptor[] = challenge.keys.map((key) => {\n\t\t\treturn {\n\t\t\t\tid: key.keyHandle,\n\t\t\t}\n\t\t})\n\n\t\tconst signResult = await this.webauthn.sign({\n\t\t\tchallenge: challenge.challenge,\n\t\t\tkeys: allowedKeys,\n\t\t\tdomain: this.selectAuthenticationUrl(challenge),\n\t\t})\n\n\t\treturn createWebauthnResponseData({\n\t\t\tkeyHandle: new Uint8Array(signResult.rawId),\n\t\t\tclientData: new Uint8Array(signResult.clientDataJSON),\n\t\t\tsignature: new Uint8Array(signResult.signature),\n\t\t\tauthenticatorData: new Uint8Array(signResult.authenticatorData),\n\t\t})\n\t}\n\n\tabortCurrentOperation(): Promise<void> {\n\t\treturn this.webauthn.abortCurrentOperation()\n\t}\n\n\tprivate selectAuthenticationUrl(challenge: U2fChallenge): string {\n\t\t// We need to figure our for which page we need to open authentication based on the keys that user has added because users can register keys for our\n\t\t// domains as well as for whitelabel domains.\n\n\t\tlet selectedClientUrl\n\t\tif (challenge.keys.some((k) => k.appId === WEBAUTHN_RP_ID)) {\n\t\t\t// First, if we find our own key then open web client on our URL.\n\t\t\t// Even if it's a different subdomain of ours it can still match because it is scoped for all tutanota.com subdomains\n\t\t\tselectedClientUrl = this.clientWebRoot\n\t\t} else if (this.clientWebRoot.startsWith(\"http://\")) {\n\t\t\t// for local http server we want the whole origin, including the port\n\t\t\tselectedClientUrl = this.clientWebRoot\n\t\t} else {\n\t\t\t// If it isn't there, look for any Webauthn key. Legacy U2F key ids ends with json subpath.\n\t\t\tconst webauthnKey = challenge.keys.find((k) => !this.isLegacyU2fKey(k))\n\t\t\tif (webauthnKey) {\n\t\t\t\tselectedClientUrl = `https://${webauthnKey.appId}`\n\t\t\t} else if (challenge.keys.some((k) => k.appId === U2F_APPID)) {\n\t\t\t\t// There are only legacy U2F keys but there is one for our domain, take it\n\t\t\t\tselectedClientUrl = this.clientWebRoot\n\t\t\t} else {\n\t\t\t\t// Nothing else worked, select legacy U2F key for whitelabel domain\n\t\t\t\tselectedClientUrl = this.legacyU2fKeyToBaseUrl(getFirstOrThrow(challenge.keys))\n\t\t\t}\n\t\t}\n\t\treturn selectedClientUrl\n\t}\n\n\tprivate isLegacyU2fKey(key: U2fKey): boolean {\n\t\treturn key.appId.endsWith(U2f_APPID_SUFFIX)\n\t}\n\n\tprivate legacyU2fKeyToBaseUrl(key: U2fKey): string {\n\t\tassert(this.isLegacyU2fKey(key), \"Is not a legacy u2f key\")\n\n\t\treturn key.appId.slice(0, -U2f_APPID_SUFFIX.length)\n\t}\n\n\tprivate getChallenge(): Uint8Array {\n\t\t// Should be replaced with our own entropy generator in the future.\n\t\tconst random = new Uint8Array(32)\n\t\tcrypto.getRandomValues(random)\n\t\treturn random\n\t}\n\n\tprivate parseAttestationObject(raw: ArrayBuffer): unknown {\n\t\treturn decode(new Uint8Array(raw))\n\t}\n\n\tprivate parsePublicKey(authData: Uint8Array): Map<number, number | Uint8Array> {\n\t\t// get the length of the credential ID\n\t\tconst dataView = new DataView(new ArrayBuffer(2))\n\t\tconst idLenBytes = authData.slice(53, 55)\n\t\tidLenBytes.forEach((value, index) => dataView.setUint8(index, value))\n\t\tconst credentialIdLength = dataView.getUint16(0)\n\t\t// get the public key object\n\t\tconst publicKeyBytes = authData.slice(55 + credentialIdLength)\n\t\t// the publicKeyBytes are encoded again as CBOR\n\t\t// We have to use maps here because keys are numeric and cborg only allows them in maps\n\t\treturn decode(new Uint8Array(publicKeyBytes.buffer), {\n\t\t\tuseMaps: true,\n\t\t})\n\t}\n\n\tprivate serializePublicKey(publicKey: Map<number, number | Uint8Array>): Uint8Array {\n\t\tconst encoded = new Uint8Array(65)\n\t\tencoded[0] = 0x04\n\t\tconst x = publicKey.get(-2)\n\t\tconst y = publicKey.get(-3)\n\n\t\tif (!(x instanceof Uint8Array) || !(y instanceof Uint8Array)) {\n\t\t\tthrow new Error(\"Public key is in unknown format\")\n\t\t}\n\n\t\tencoded.set(x, 1)\n\t\tencoded.set(y, 33)\n\t\treturn encoded\n\t}\n}\n\n/** authenticators are allowed to truncate strings to this length */\nconst WEBAUTHN_STRING_MAX_BYTE_LENGTH = 64\n\n/**\n * some authenticators truncate this and others refuse to be registered\n * at all if this validation does not pass.\n *\n * Note: technically, we'd also be supposed to encode text direction and a language\n * code into the display name.\n */\nexport function validateWebauthnDisplayName(displayName: string): boolean {\n\treturn WEBAUTHN_STRING_MAX_BYTE_LENGTH - stringToUtf8Uint8Array(displayName).byteLength >= 0\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport { Dialog } from \"../../gui/base/Dialog\"\nimport { PasswordGenerator } from \"./PasswordGenerator\"\nimport { Button, ButtonType } from \"../../gui/base/Button.js\"\nimport { locator } from \"../../api/main/MainLocator\"\nimport { px } from \"../../gui/size\"\nimport { copyToClipboard } from \"../ClipboardUtils\"\nimport { lang } from \"../LanguageViewModel.js\"\n\nlet dictionary: string[] | null = null\n\n/**\n * Show a dialog to generate a random passphrase\n * @returns a promise containing the generated password\n */\nexport async function showPasswordGeneratorDialog(): Promise<string> {\n\tif (dictionary == null) {\n\t\tconst appState = window.tutao.appState\n\t\tconst baseUrl = location.protocol + \"//\" + location.hostname + (location.port ? \":\" + location.port : \"\") + appState.prefixWithoutFile\n\t\tdictionary = await fetch(baseUrl + \"/wordlibrary.json\").then((response) => response.json())\n\t}\n\n\tlet password = \"\"\n\tconst pwGenerator = new PasswordGenerator(locator.random, dictionary!)\n\n\treturn new Promise((resolve) => {\n\t\tconst insertPasswordOkAction = () => {\n\t\t\tresolve(password)\n\t\t\tdialog.close()\n\t\t}\n\n\t\tconst updateAction = async () => {\n\t\t\tpassword = await pwGenerator.generateRandomPassphrase()\n\t\t\tm.redraw()\n\t\t}\n\n\t\tupdateAction()\n\n\t\tconst dialog = Dialog.showActionDialog({\n\t\t\ttitle: () => \"Passphrase\",\n\t\t\tchild: {\n\t\t\t\tview: () =>\n\t\t\t\t\tm(PasswordGeneratorDialog, {\n\t\t\t\t\t\tokAction: insertPasswordOkAction,\n\t\t\t\t\t\tupdateAction,\n\t\t\t\t\t\tpassword,\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\tokAction: null,\n\t\t})\n\t})\n}\n\ninterface PasswordGeneratorDialogAttrs {\n\tokAction: () => void\n\tupdateAction: () => void\n\tpassword: string\n}\n\nclass PasswordGeneratorDialog implements Component<PasswordGeneratorDialogAttrs> {\n\tview(vnode: Vnode<PasswordGeneratorDialogAttrs>): Children {\n\t\tconst { updateAction, okAction, password } = vnode.attrs\n\t\treturn m(\"\", [\n\t\t\tm(\n\t\t\t\t\".editor-border.mt.flex.center-horizontally.center-vertically\",\n\t\t\t\t{\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tminHeight: px(65), // needs 65px for displaying two rows\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tm(\".center.b.monospace\", password),\n\t\t\t),\n\t\t\tm(\".small.mt-xs\", [\n\t\t\t\tlang.get(\"passphraseGeneratorHelp_msg\"),\n\t\t\t\t\" \",\n\t\t\t\tm(\n\t\t\t\t\t\"a\",\n\t\t\t\t\t{\n\t\t\t\t\t\thref: \"https://tutanota.com/faq#passphrase-generator\",\n\t\t\t\t\t\ttarget: \"_blank\",\n\t\t\t\t\t\trel: \"nooopener noreferer\",\n\t\t\t\t\t},\n\t\t\t\t\tlang.get(\"faqEntry_label\"),\n\t\t\t\t),\n\t\t\t]),\n\t\t\tm(\".flex-end\", [\n\t\t\t\tm(Button, {\n\t\t\t\t\tlabel: \"regeneratePassword_action\",\n\t\t\t\t\tclick: () => updateAction(),\n\t\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t\t}),\n\t\t\t\tm(Button, {\n\t\t\t\t\tclick: () => copyToClipboard(password),\n\t\t\t\t\tlabel: \"copy_action\",\n\t\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t\t}),\n\t\t\t]),\n\t\t\tm(\n\t\t\t\t\".flex\",\n\t\t\t\tm(Button, {\n\t\t\t\t\tlabel: \"apply_action\",\n\t\t\t\t\tclick: () => okAction(),\n\t\t\t\t\ttype: ButtonType.Login,\n\t\t\t\t}),\n\t\t\t),\n\t\t])\n\t}\n}\n","import type { CredentialsAndDatabaseKey, CredentialsEncryption, PersistentCredentials } from \"./CredentialsProvider.js\"\nimport { CredentialsProvider } from \"./CredentialsProvider.js\"\nimport { deviceConfig } from \"../DeviceConfig\"\nimport { isApp, isDesktop } from \"../../api/common/Env\"\nimport type { DeviceEncryptionFacade } from \"../../api/worker/facades/DeviceEncryptionFacade\"\nimport { CredentialsKeyProvider } from \"./CredentialsKeyProvider\"\nimport { NativeCredentialsEncryption } from \"./NativeCredentialsEncryption\"\nimport type { NativeInterface } from \"../../native/common/NativeInterface\"\nimport { assertNotNull } from \"@tutao/tutanota-utils\"\nimport { DatabaseKeyFactory } from \"./DatabaseKeyFactory\"\nimport { DefaultCredentialsKeyMigrator, StubCredentialsKeyMigrator } from \"./CredentialsKeyMigrator.js\"\nimport { InterWindowEventFacadeSendDispatcher } from \"../../native/common/generatedipc/InterWindowEventFacadeSendDispatcher.js\"\nimport { SqlCipherFacade } from \"../../native/common/generatedipc/SqlCipherFacade.js\"\n\nexport function usingKeychainAuthentication(): boolean {\n\treturn isApp() || isDesktop()\n}\n\nexport function hasKeychainAuthenticationOptions(): boolean {\n\treturn isApp()\n}\n\n/**\n * Factory method for credentials provider that will return an instance injected with the implementations appropriate for the platform.\n * @param deviceEncryptionFacade\n * @param nativeApp: If {@code usingKeychainAuthentication} would return true, this _must not_ be null\n * @param sqlCipherFacade\n * @param interWindowEventSender\n */\nexport async function createCredentialsProvider(\n\tdeviceEncryptionFacade: DeviceEncryptionFacade,\n\tnativeApp: NativeInterface | null,\n\tsqlCipherFacade: SqlCipherFacade | null,\n\tinterWindowEventSender: InterWindowEventFacadeSendDispatcher | null,\n): Promise<CredentialsProvider> {\n\tif (usingKeychainAuthentication()) {\n\t\tconst { NativeCredentialsFacadeSendDispatcher } = await import(\"../../native/common/generatedipc/NativeCredentialsFacadeSendDispatcher.js\")\n\t\tconst nativeCredentials = new NativeCredentialsFacadeSendDispatcher(assertNotNull(nativeApp))\n\t\tconst credentialsKeyProvider = new CredentialsKeyProvider(nativeCredentials, deviceConfig, deviceEncryptionFacade)\n\t\tconst credentialsEncryption = new NativeCredentialsEncryption(credentialsKeyProvider, deviceEncryptionFacade, nativeCredentials)\n\t\tconst credentialsKeyMigrator = new DefaultCredentialsKeyMigrator(nativeCredentials)\n\t\treturn new CredentialsProvider(\n\t\t\tcredentialsEncryption,\n\t\t\tdeviceConfig,\n\t\t\tcredentialsKeyMigrator,\n\t\t\tnew DatabaseKeyFactory(deviceEncryptionFacade),\n\t\t\tsqlCipherFacade,\n\t\t\tisDesktop() ? interWindowEventSender : null,\n\t\t)\n\t} else {\n\t\treturn new CredentialsProvider(\n\t\t\tnew CredentialsEncryptionStub(),\n\t\t\tdeviceConfig,\n\t\t\tnew StubCredentialsKeyMigrator(),\n\t\t\tnew DatabaseKeyFactory(deviceEncryptionFacade),\n\t\t\tnull,\n\t\t\tnull,\n\t\t)\n\t}\n}\n\n/**\n * This is a temporary stub that we will replace soon by some mechanism that will be able to utilize fingerprint/pin on mobile devices\n * for encryption of login data. Using this implementation does not mean we do not encrypt credentials currently since there is an\n * additional mechanism for credentials encryption using an access key stored server side. This is done in LoginFacade.\n */\n\nclass CredentialsEncryptionStub implements CredentialsEncryption {\n\tasync encrypt({ credentials, databaseKey }: CredentialsAndDatabaseKey): Promise<PersistentCredentials> {\n\t\tconst { encryptedPassword } = credentials\n\n\t\tif (encryptedPassword == null) {\n\t\t\tthrow new Error(\"Trying to encrypt non-persistent credentials\")\n\t\t}\n\n\t\treturn {\n\t\t\tcredentialInfo: {\n\t\t\t\tlogin: credentials.login,\n\t\t\t\tuserId: credentials.userId,\n\t\t\t\ttype: credentials.type,\n\t\t\t},\n\t\t\tencryptedPassword,\n\t\t\taccessToken: credentials.accessToken,\n\t\t\tdatabaseKey: null,\n\t\t}\n\t}\n\n\tasync decrypt(encryptedCredentials: PersistentCredentials): Promise<CredentialsAndDatabaseKey> {\n\t\treturn {\n\t\t\tcredentials: {\n\t\t\t\tlogin: encryptedCredentials.credentialInfo.login,\n\t\t\t\tencryptedPassword: encryptedCredentials.encryptedPassword,\n\t\t\t\taccessToken: encryptedCredentials.accessToken,\n\t\t\t\tuserId: encryptedCredentials.credentialInfo.userId,\n\t\t\t\ttype: encryptedCredentials.credentialInfo.type,\n\t\t\t},\n\t\t\tdatabaseKey: null,\n\t\t}\n\t}\n\n\tasync getSupportedEncryptionModes() {\n\t\treturn []\n\t}\n}\n","import { downcast, identity, isSameTypeRefByAttr, noOp, remove, TypeRef } from \"@tutao/tutanota-utils\"\nimport type { LoginController } from \"./LoginController\"\nimport type { OperationType } from \"../common/TutanotaConstants\"\nimport stream from \"mithril/stream\"\nimport Stream from \"mithril/stream\"\nimport { assertMainOrNode } from \"../common/Env\"\nimport { EntityUpdate, WebsocketCounterData } from \"../entities/sys/TypeRefs\"\nimport { SomeEntity } from \"../common/EntityTypes.js\"\nimport { isSameId } from \"../common/utils/EntityUtils.js\"\n\nassertMainOrNode()\nexport type EntityUpdateData = {\n\tapplication: string\n\ttype: string\n\tinstanceListId: string\n\tinstanceId: string\n\toperation: OperationType\n}\nexport type EntityEventsListener = (updates: ReadonlyArray<EntityUpdateData>, eventOwnerGroupId: Id) => Promise<any>\n\nexport function isUpdateForTypeRef(typeRef: TypeRef<unknown>, update: EntityUpdateData): boolean {\n\treturn isSameTypeRefByAttr(typeRef, update.application, update.type)\n}\n\nexport function isUpdateFor<T extends SomeEntity>(entity: T, update: EntityUpdateData): boolean {\n\tconst typeRef = entity._type as TypeRef<T>\n\treturn (\n\t\tisUpdateForTypeRef(typeRef, update) &&\n\t\t(update.instanceListId === \"\" ? isSameId(update.instanceId, entity._id) : isSameId([update.instanceListId, update.instanceId], entity._id))\n\t)\n}\n\nexport type ExposedEventController = Pick<EventController, \"onEntityUpdateReceived\" | \"onCountersUpdateReceived\">\n\nconst TAG = \"[EventController]\"\n\nexport class EventController {\n\tprivate countersStream: Stream<WebsocketCounterData> = stream()\n\tprivate entityListeners: Set<EntityEventsListener> = new Set()\n\n\tconstructor(private readonly logins: LoginController) {}\n\n\taddEntityListener(listener: EntityEventsListener) {\n\t\tif (this.entityListeners.has(listener)) {\n\t\t\tconsole.warn(TAG, \"Adding the same listener twice!\")\n\t\t} else {\n\t\t\tthis.entityListeners.add(listener)\n\t\t}\n\t}\n\n\tremoveEntityListener(listener: EntityEventsListener) {\n\t\tconst wasRemoved = this.entityListeners.delete(listener)\n\t\tif (!wasRemoved) {\n\t\t\tconsole.warn(TAG, \"Could not remove listener, possible leak?\", listener)\n\t\t}\n\t}\n\n\tgetCountersStream(): Stream<WebsocketCounterData> {\n\t\t// Create copy so it's never ended\n\t\treturn this.countersStream.map(identity)\n\t}\n\n\tasync onEntityUpdateReceived(entityUpdates: ReadonlyArray<EntityUpdate>, eventOwnerGroupId: Id): Promise<void> {\n\t\tlet loginsUpdates = Promise.resolve()\n\n\t\tif (this.logins.isUserLoggedIn()) {\n\t\t\t// the UserController must be notified first as other event receivers depend on it to be up-to-date\n\t\t\tloginsUpdates = this.logins.getUserController().entityEventsReceived(entityUpdates as ReadonlyArray<EntityUpdateData>, eventOwnerGroupId)\n\t\t}\n\n\t\treturn loginsUpdates\n\t\t\t.then(async () => {\n\t\t\t\t// sequentially to prevent parallel loading of instances\n\t\t\t\tfor (const listener of this.entityListeners) {\n\t\t\t\t\tlet entityUpdatesData: Array<EntityUpdateData> = downcast(entityUpdates)\n\t\t\t\t\tawait listener(entityUpdatesData, eventOwnerGroupId)\n\t\t\t\t}\n\t\t\t})\n\t\t\t.then(noOp)\n\t}\n\n\tasync onCountersUpdateReceived(update: WebsocketCounterData): Promise<void> {\n\t\tthis.countersStream(update)\n\t}\n}\n","import { CryptoError } from \"../common/error/CryptoError\"\nimport type { Commands, Transport } from \"../common/MessageDispatcher\"\nimport { MessageDispatcher, Request, WorkerTransport } from \"../common/MessageDispatcher\"\nimport { assertMainOrNode } from \"../common/Env\"\nimport type { IMainLocator } from \"./MainLocator\"\nimport { client } from \"../../misc/ClientDetector\"\nimport type { DeferredObject } from \"@tutao/tutanota-utils\"\nimport { defer, downcast } from \"@tutao/tutanota-utils\"\nimport { objToError } from \"../common/utils/Utils\"\nimport { handleUncaughtError } from \"../../misc/ErrorHandler\"\nimport type { MainInterface, WorkerInterface } from \"../worker/WorkerImpl\"\nimport { DelayedImpls, exposeLocalDelayed, exposeRemote } from \"../common/WorkerProxy\"\nimport type { RestClient } from \"../worker/rest/RestClient\"\nimport { EntropyDataChunk } from \"../worker/facades/EntropyFacade.js\"\n\nassertMainOrNode()\n\ntype ProgressUpdater = (progress: number) => unknown\ntype MainRequest = Request<MainRequestType>\n\nexport const enum WsConnectionState {\n\tconnecting,\n\tconnected,\n\tterminated,\n}\n\nexport class WorkerClient {\n\tprivate _deferredInitialized: DeferredObject<void> = defer()\n\tprivate _isInitialized: boolean = false\n\n\tprivate _dispatcher!: MessageDispatcher<WorkerRequestType, MainRequestType>\n\n\tconstructor() {\n\t\tthis.initialized.then(() => {\n\t\t\tthis._isInitialized = true\n\t\t})\n\t}\n\n\tget initialized(): Promise<void> {\n\t\treturn this._deferredInitialized.promise\n\t}\n\n\tasync init(locator: IMainLocator): Promise<void> {\n\t\tif (env.mode !== \"Test\") {\n\t\t\tconst { prefixWithoutFile } = window.tutao.appState\n\t\t\t// In apps/desktop we load HTML file and url ends on path/index.html so we want to load path/WorkerBootstrap.js.\n\t\t\t// In browser we load at domain.com or localhost/path (locally) and we want to load domain.com/WorkerBootstrap.js or\n\t\t\t// localhost/path/WorkerBootstrap.js respectively.\n\t\t\t// Service worker has similar logic but it has luxury of knowing that it's served as sw.js.\n\t\t\tconst workerUrl = prefixWithoutFile + \"/worker-bootstrap.js\"\n\t\t\tconst worker = new Worker(workerUrl)\n\t\t\tthis._dispatcher = new MessageDispatcher(new WorkerTransport(worker), this.queueCommands(locator))\n\t\t\tawait this._dispatcher.postRequest(new Request(\"setup\", [window.env, this.getInitialEntropy(), client.browserData()]))\n\n\t\t\tworker.onerror = (e: any) => {\n\t\t\t\tthrow new CryptoError(\"could not setup worker\", e)\n\t\t\t}\n\t\t} else {\n\t\t\t// node: we do not use workers but connect the client and the worker queues directly with each other\n\t\t\t// attention: do not load directly with require() here because in the browser SystemJS would load the WorkerImpl in the client although this code is not executed\n\t\t\t// @ts-ignore\n\t\t\tconst WorkerImpl = globalThis.testWorker\n\t\t\tconst workerImpl = new WorkerImpl(this, true)\n\t\t\tawait workerImpl.init(client.browserData())\n\t\t\tworkerImpl._queue._transport = {\n\t\t\t\tpostMessage: (msg: any) => this._dispatcher.handleMessage(msg),\n\t\t\t}\n\t\t\tthis._dispatcher = new MessageDispatcher(\n\t\t\t\t{\n\t\t\t\t\tpostMessage: function (msg: any) {\n\t\t\t\t\t\tworkerImpl._queue.handleMessage(msg)\n\t\t\t\t\t},\n\t\t\t\t} as Transport<WorkerRequestType, MainRequestType>,\n\t\t\t\tthis.queueCommands(locator),\n\t\t\t)\n\t\t}\n\n\t\tthis._deferredInitialized.resolve()\n\t}\n\n\tqueueCommands(locator: IMainLocator): Commands<MainRequestType> {\n\t\treturn {\n\t\t\texecNative: (message: MainRequest) => locator.native.invokeNative(downcast(message.args[0]), downcast(message.args[1])),\n\t\t\terror: (message: MainRequest) => {\n\t\t\t\thandleUncaughtError(objToError(message.args[0]))\n\t\t\t\treturn Promise.resolve()\n\t\t\t},\n\t\t\tfacade: exposeLocalDelayed<DelayedImpls<MainInterface>, MainRequestType>({\n\t\t\t\tasync loginListener() {\n\t\t\t\t\treturn locator.loginListener\n\t\t\t\t},\n\t\t\t\tasync wsConnectivityListener() {\n\t\t\t\t\treturn locator.connectivityModel\n\t\t\t\t},\n\t\t\t\tasync progressTracker() {\n\t\t\t\t\treturn locator.progressTracker\n\t\t\t\t},\n\t\t\t\tasync eventController() {\n\t\t\t\t\treturn locator.eventController\n\t\t\t\t},\n\t\t\t\tasync operationProgressTracker() {\n\t\t\t\t\treturn locator.operationProgressTracker\n\t\t\t\t},\n\t\t\t\tasync infoMessageHandler() {\n\t\t\t\t\treturn locator.infoMessageHandler\n\t\t\t\t},\n\t\t\t}),\n\t\t}\n\t}\n\n\tgetWorkerInterface(): WorkerInterface {\n\t\treturn exposeRemote<WorkerInterface>(async (request) => this._postRequest(request))\n\t}\n\n\trestRequest(...args: Parameters<RestClient[\"request\"]>): Promise<any | null> {\n\t\treturn this._postRequest(new Request(\"restRequest\", Array.from(arguments)))\n\t}\n\n\t/** @private visible for tests */\n\tasync _postRequest(msg: Request<WorkerRequestType>): Promise<any> {\n\t\tawait this.initialized\n\t\treturn this._dispatcher.postRequest(msg)\n\t}\n\n\treset(): Promise<void> {\n\t\treturn this._postRequest(new Request(\"reset\", []))\n\t}\n\n\t/**\n\t * Add data from either secure random source or Math.random as entropy.\n\t */\n\tprivate getInitialEntropy(): Array<EntropyDataChunk> {\n\t\tconst valueList = new Uint32Array(16)\n\t\tcrypto.getRandomValues(valueList)\n\t\tconst entropy: Array<EntropyDataChunk> = []\n\n\t\tfor (let i = 0; i < valueList.length; i++) {\n\t\t\t// 32 because we have 32-bit values Uint32Array\n\t\t\tentropy.push({\n\t\t\t\tsource: \"random\",\n\t\t\t\tentropy: 32,\n\t\t\t\tdata: valueList[i],\n\t\t\t})\n\t\t}\n\n\t\treturn entropy\n\t}\n}\n\nexport function bootstrapWorker(locator: IMainLocator): WorkerClient {\n\tconst worker = new WorkerClient()\n\tconst start = Date.now()\n\tworker.init(locator).then(() => console.log(\"worker init time (ms):\", Date.now() - start))\n\treturn worker\n}\n","/// <reference lib=\"dom\" /> // fixes MouseEvent conflict with react\nimport { assertMainOrNode } from \"../common/Env\"\nimport type { EntropySource } from \"@tutao/tutanota-crypto\"\nimport type { EntropyDataChunk, EntropyFacade } from \"../worker/facades/EntropyFacade.js\"\nimport { Scheduler } from \"../common/utils/Scheduler.js\"\n\nassertMainOrNode()\n\nexport type EntropyCallback = (data: number, entropy: number, source: EntropySource) => unknown\n\n/**\n * Automatically collects entropy from various events and sends it to the randomizer in the worker regularly.\n */\nexport class EntropyCollector {\n\t// accessible from test case\n\tstatic readonly SEND_INTERVAL: number = 5000\n\n\tprivate stopped: boolean = true\n\t// the entropy is cached and transmitted to the worker in defined intervals\n\tprivate entropyCache: EntropyDataChunk[] = []\n\n\tconstructor(private readonly entropyFacade: EntropyFacade, private readonly scheduler: Scheduler, private readonly window: Window) {}\n\n\tprivate mouse = (e: MouseEvent) => {\n\t\tconst value = e.clientX ^ e.clientY\n\n\t\tthis.addEntropy(value, 2, \"mouse\")\n\t}\n\n\tprivate keyDown = (e: KeyboardEvent) => {\n\t\tconst value = e.keyCode\n\n\t\tthis.addEntropy(value, 2, \"key\")\n\t}\n\n\tprivate touch = (e: TouchEvent) => {\n\t\tconst value = e.touches[0].clientX ^ e.touches[0].clientY\n\n\t\tthis.addEntropy(value, 2, \"touch\")\n\t}\n\n\t/** e is a DeviceMotionEvent but it's typed in a very annoying way */\n\tprivate accelerometer = (e: any) => {\n\t\tif (e.accelerationIncludingGravity) {\n\t\t\tthis.addEntropy(e.accelerationIncludingGravity.x ^ e.accelerationIncludingGravity.y ^ e.accelerationIncludingGravity.z, 2, \"accel\")\n\t\t}\n\n\t\tthis.addEntropy(this.window.screen.orientation.angle, 0, \"accel\")\n\t}\n\n\t/**\n\t * Adds entropy to the random number generator algorithm\n\t * @param data Any number value.\n\t * @param entropy The amount of entropy in the number in bit.\n\t * @param source The source of the number. One of RandomizerInterface.ENTROPY_SRC_*.\n\t */\n\tprivate addEntropy(data: number, entropy: number, source: EntropySource) {\n\t\tif (data) {\n\t\t\tthis.entropyCache.push({\n\t\t\t\tsource: source,\n\t\t\t\tentropy: entropy,\n\t\t\t\tdata: data,\n\t\t\t})\n\t\t}\n\n\t\tif (this.window.performance && typeof window.performance.now === \"function\") {\n\t\t\tthis.entropyCache.push({\n\t\t\t\tsource: \"time\",\n\t\t\t\tentropy: 2,\n\t\t\t\tdata: this.window.performance.now(),\n\t\t\t})\n\t\t} else {\n\t\t\tthis.entropyCache.push({\n\t\t\t\tsource: \"time\",\n\t\t\t\tentropy: 2,\n\t\t\t\tdata: new Date().valueOf(),\n\t\t\t})\n\t\t}\n\t}\n\n\tstart() {\n\t\tthis.addPerformanceTimingValues()\n\n\t\tthis.window.addEventListener(\"mousemove\", this.mouse)\n\t\tthis.window.addEventListener(\"click\", this.mouse)\n\t\tthis.window.addEventListener(\"touchstart\", this.touch)\n\t\tthis.window.addEventListener(\"touchmove\", this.touch)\n\t\tthis.window.addEventListener(\"keydown\", this.keyDown)\n\t\tthis.window.addEventListener(\"devicemotion\", this.accelerometer)\n\n\t\tthis.scheduler.schedulePeriodic(() => this.sendEntropyToWorker(), EntropyCollector.SEND_INTERVAL)\n\t\tthis.stopped = false\n\t}\n\n\tprivate addPerformanceTimingValues() {\n\t\tif (!this.window.performance) return\n\t\tconst entries = this.window.performance.getEntries()\n\t\tlet added: number[] = []\n\t\tfor (const entry of entries.map((e) => e.toJSON())) {\n\t\t\tfor (let key in entry) {\n\t\t\t\tconst value = entry[key]\n\t\t\t\tif (typeof value === \"number\" && value !== 0) {\n\t\t\t\t\tif (added.indexOf(value) === -1) {\n\t\t\t\t\t\tthis.addEntropy(value, 1, \"static\")\n\t\t\t\t\t\tadded.push(value)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Add data from secure random source as entropy.\n\t */\n\tprivate addNativeRandomValues(nbrOf32BitValues: number) {\n\t\tlet valueList = new Uint32Array(nbrOf32BitValues)\n\t\tthis.window.crypto.getRandomValues(valueList)\n\n\t\tfor (let i = 0; i < valueList.length; i++) {\n\t\t\t// 32 because we have 32-bit values Uint32Array\n\t\t\tthis.addEntropy(valueList[i], 32, \"random\")\n\t\t}\n\t}\n\n\tprivate sendEntropyToWorker() {\n\t\tif (this.entropyCache.length > 0) {\n\t\t\tthis.addNativeRandomValues(1)\n\n\t\t\tthis.entropyFacade.addEntropy(this.entropyCache)\n\n\t\t\tthis.entropyCache = []\n\t\t}\n\t}\n\n\tstop() {\n\t\tthis.stopped = true\n\t\tthis.window.removeEventListener(\"mousemove\", this.mouse)\n\t\tthis.window.removeEventListener(\"mouseclick\", this.mouse)\n\t\tthis.window.removeEventListener(\"touchstart\", this.touch)\n\t\tthis.window.removeEventListener(\"touchmove\", this.touch)\n\t\tthis.window.removeEventListener(\"keydown\", this.keyDown)\n\t\tthis.window.removeEventListener(\"devicemotion\", this.accelerometer)\n\t}\n}\n","import { TutanotaError } from \"../common/error/TutanotaError\"\nimport type { TranslationKeyType } from \"../../misc/TranslationKey\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport type { lazy } from \"@tutao/tutanota-utils\"\nimport { assertMainOrNode } from \"../common/Env\"\n\nassertMainOrNode()\n\nexport class UserError extends TutanotaError {\n\tconstructor(message: TranslationKeyType | lazy<string>) {\n\t\tsuper(\"UserError\", lang.getMaybeLazy(message))\n\t}\n}\n","import m from \"mithril\"\nimport stream from \"mithril/stream\"\nimport Stream from \"mithril/stream\"\nimport { containsEventOfType } from \"../../api/common/utils/Utils\"\nimport { assertNotNull, groupBy, lazyMemoized, neverNull, noOp, ofClass, promiseMap, splitInChunks } from \"@tutao/tutanota-utils\"\nimport type { Mail, MailBox, MailboxGroupRoot, MailboxProperties, MailFolder } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport {\n\tcreateMailAddressProperties,\n\tcreateMailboxProperties,\n\tMailboxGroupRootTypeRef,\n\tMailboxPropertiesTypeRef,\n\tMailBoxTypeRef,\n\tMailFolderTypeRef,\n\tMailTypeRef,\n} from \"../../api/entities/tutanota/TypeRefs.js\"\nimport type { Group, GroupInfo, GroupMembership, WebsocketCounterData } from \"../../api/entities/sys/TypeRefs.js\"\nimport { GroupInfoTypeRef, GroupTypeRef, UserTypeRef } from \"../../api/entities/sys/TypeRefs.js\"\nimport type { MailReportType } from \"../../api/common/TutanotaConstants\"\nimport {\n\tFeatureType,\n\tGroupType,\n\tMailFolderType,\n\tMAX_NBR_MOVE_DELETE_MAIL_SERVICE,\n\tOperationType,\n\tReportMovedMailsType,\n} from \"../../api/common/TutanotaConstants\"\nimport type { EntityUpdateData } from \"../../api/main/EventController\"\nimport { EventController, isUpdateForTypeRef } from \"../../api/main/EventController\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport { Notifications } from \"../../gui/Notifications\"\nimport { EntityClient } from \"../../api/common/EntityClient\"\nimport { elementIdPart, GENERATED_MAX_ID, getElementId, getListId, isSameId, listIdPart } from \"../../api/common/utils/EntityUtils\"\nimport { LockedError, NotFoundError, PreconditionFailedError } from \"../../api/common/error/RestError\"\nimport type { MailFacade } from \"../../api/worker/facades/lazy/MailFacade.js\"\nimport { LoginController } from \"../../api/main/LoginController.js\"\nimport { areParticipantsRestricted, getEnabledMailAddressesWithUser } from \"./MailUtils.js\"\nimport { ProgrammingError } from \"../../api/common/error/ProgrammingError.js\"\nimport { WebsocketConnectivityModel } from \"../../misc/WebsocketConnectivityModel.js\"\nimport { FolderSystem } from \"../../api/common/mail/FolderSystem.js\"\nimport { UserError } from \"../../api/main/UserError.js\"\nimport { assertSystemFolderOfType, isSpamOrTrashFolder } from \"../../api/common/mail/CommonMailUtils.js\"\nimport { InboxRuleHandler } from \"./InboxRuleHandler.js\"\n\nexport type MailboxDetail = {\n\tmailbox: MailBox\n\tfolders: FolderSystem\n\tmailGroupInfo: GroupInfo\n\tmailGroup: Group\n\tmailboxGroupRoot: MailboxGroupRoot\n}\n\nexport type MailboxCounters = Record<Id, Record<string, number>>\n\nexport class MailModel {\n\t/** Empty stream until init() is finished, exposed mostly for map()-ing, use getMailboxDetails to get a promise */\n\treadonly mailboxDetails: Stream<MailboxDetail[]> = stream()\n\treadonly mailboxCounters: Stream<MailboxCounters> = stream({})\n\tprivate initialization: Promise<void> | null = null\n\t/**\n\t * Map from MailboxGroupRoot id to MailboxProperties\n\t * A way to avoid race conditions in case we try to create mailbox properties from multiple places.\n\t *\n\t */\n\tprivate mailboxPropertiesPromises: Map<Id, Promise<MailboxProperties>> = new Map()\n\n\tconstructor(\n\t\tprivate readonly notifications: Notifications,\n\t\tprivate readonly eventController: EventController,\n\t\tprivate readonly connectivityModel: WebsocketConnectivityModel,\n\t\tprivate readonly mailFacade: MailFacade,\n\t\tprivate readonly entityClient: EntityClient,\n\t\tprivate readonly logins: LoginController,\n\t\tprivate readonly inboxRuleHandler: InboxRuleHandler,\n\t) {}\n\n\t// only init listeners once\n\tprivate readonly initListeners = lazyMemoized(() => {\n\t\tthis.eventController.addEntityListener((updates) => this.entityEventsReceived(updates))\n\n\t\tthis.eventController.getCountersStream().map((update) => {\n\t\t\tthis._mailboxCountersUpdates(update)\n\t\t})\n\t})\n\n\tinit(): Promise<void> {\n\t\t// if we are in the process of loading do not start another one in parallel\n\t\tif (this.initialization) {\n\t\t\treturn this.initialization\n\t\t}\n\t\tthis.initListeners()\n\n\t\treturn this._init()\n\t}\n\n\tprivate _init(): Promise<void> {\n\t\tconst mailGroupMemberships = this.logins.getUserController().getMailGroupMemberships()\n\t\tconst mailBoxDetailsPromises = mailGroupMemberships.map((m) => this.mailboxDetailsFromMembership(m))\n\t\tthis.initialization = Promise.all(mailBoxDetailsPromises).then((details) => {\n\t\t\tthis.mailboxDetails(details)\n\t\t})\n\t\treturn this.initialization.catch((e) => {\n\t\t\tconsole.warn(\"mail model initialization failed!\", e)\n\t\t\tthis.initialization = null\n\t\t\tthrow e\n\t\t})\n\t}\n\n\t/**\n\t * load mailbox details from a mailgroup membership\n\t */\n\tprivate async mailboxDetailsFromMembership(membership: GroupMembership): Promise<MailboxDetail> {\n\t\tconst [mailboxGroupRoot, mailGroupInfo, mailGroup] = await Promise.all([\n\t\t\tthis.entityClient.load(MailboxGroupRootTypeRef, membership.group),\n\t\t\tthis.entityClient.load(GroupInfoTypeRef, membership.groupInfo),\n\t\t\tthis.entityClient.load(GroupTypeRef, membership.group),\n\t\t])\n\t\tconst mailbox = await this.entityClient.load(MailBoxTypeRef, mailboxGroupRoot.mailbox)\n\t\tconst folders = await this.loadFolders(neverNull(mailbox.folders).folders)\n\t\treturn {\n\t\t\tmailbox,\n\t\t\tfolders: new FolderSystem(folders),\n\t\t\tmailGroupInfo,\n\t\t\tmailGroup,\n\t\t\tmailboxGroupRoot,\n\t\t}\n\t}\n\n\tprivate loadFolders(folderListId: Id): Promise<MailFolder[]> {\n\t\treturn this.entityClient.loadAll(MailFolderTypeRef, folderListId).then((folders) => {\n\t\t\treturn folders.filter((f) => {\n\t\t\t\t// We do not show spam or archive for external users\n\t\t\t\tif (!this.logins.isInternalUserLoggedIn() && (f.folderType === MailFolderType.SPAM || f.folderType === MailFolderType.ARCHIVE)) {\n\t\t\t\t\treturn false\n\t\t\t\t} else if (this.logins.isEnabled(FeatureType.InternalCommunication) && f.folderType === MailFolderType.SPAM) {\n\t\t\t\t\treturn false\n\t\t\t\t} else {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Get the list of MailboxDetails that this user has access to from their memberships.\n\t *\n\t * Will wait for successful initialization.\n\t */\n\tasync getMailboxDetails(): Promise<Array<MailboxDetail>> {\n\t\t// If details are there, use them\n\t\tif (this.mailboxDetails()) {\n\t\t\treturn this.mailboxDetails()\n\t\t} else {\n\t\t\t// If they are not there, trigger loading again (just in case) but do not fail and wait until we actually have the details.\n\t\t\t// This is so that the rest of the app is not in the broken state if details fail to load but is just waiting until the success.\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tthis.init()\n\t\t\t\tconst end = this.mailboxDetails.map((details) => {\n\t\t\t\t\tresolve(details)\n\t\t\t\t\tend.end(true)\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t}\n\n\tgetMailboxDetailsForMail(mail: Mail): Promise<MailboxDetail | null> {\n\t\treturn this.getMailboxDetailsForMailListId(mail._id[0])\n\t}\n\n\tasync getMailboxDetailsForMailListId(mailListId: Id): Promise<MailboxDetail | null> {\n\t\tconst mailboxDetails = await this.getMailboxDetails()\n\t\tconst detail = mailboxDetails.find((md) => md.folders.getFolderByMailListId(mailListId)) ?? null\n\t\tif (detail == null) {\n\t\t\tconsole.warn(\"Mailbox detail for mail list does not exist\", mailListId)\n\t\t}\n\t\treturn detail\n\t}\n\n\tasync getMailboxDetailsForMailGroup(mailGroupId: Id): Promise<MailboxDetail> {\n\t\tconst mailboxDetails = await this.getMailboxDetails()\n\t\treturn assertNotNull(\n\t\t\tmailboxDetails.find((md) => mailGroupId === md.mailGroup._id),\n\t\t\t\"No mailbox details for mail group\",\n\t\t)\n\t}\n\n\tasync getUserMailboxDetails(): Promise<MailboxDetail> {\n\t\tconst userMailGroupMembership = this.logins.getUserController().getUserMailGroupMembership()\n\t\tconst mailboxDetails = await this.getMailboxDetails()\n\t\treturn assertNotNull(mailboxDetails.find((md) => md.mailGroup._id === userMailGroupMembership.group))\n\t}\n\n\tgetMailboxFolders(mail: Mail): Promise<FolderSystem | null> {\n\t\treturn this.getMailboxDetailsForMail(mail).then((md) => md && md.folders)\n\t}\n\n\tgetMailFolder(mailListId: Id): MailFolder | null {\n\t\tconst mailboxDetails = this.mailboxDetails() || []\n\n\t\tfor (let detail of mailboxDetails) {\n\t\t\tconst f = detail.folders.getFolderByMailListId(mailListId)\n\t\t\tif (f) {\n\t\t\t\treturn f\n\t\t\t}\n\t\t}\n\n\t\treturn null\n\t}\n\n\t/**\n\t * Sends the given folder and all its descendants to the spam folder, reporting mails (if applicable) and removes any empty folders\n\t */\n\tasync sendFolderToSpam(folder: MailFolder): Promise<void> {\n\t\tconst mailboxDetail = await this.getMailboxDetailsForMailListId(folder.mails)\n\t\tif (mailboxDetail == null) {\n\t\t\treturn\n\t\t}\n\n\t\tlet deletedFolder = await this.removeAllEmpty(mailboxDetail, folder)\n\t\tif (!deletedFolder) {\n\t\t\treturn this.mailFacade.updateMailFolderParent(folder, assertSystemFolderOfType(mailboxDetail.folders, MailFolderType.SPAM)._id)\n\t\t}\n\t}\n\n\tasync reportMails(reportType: MailReportType, mails: ReadonlyArray<Mail>): Promise<void> {\n\t\tfor (const mail of mails) {\n\t\t\tawait this.mailFacade.reportMail(mail, reportType).catch(ofClass(NotFoundError, (e) => console.log(\"mail to be reported not found\", e)))\n\t\t}\n\t}\n\n\t/**\n\t * Finally deletes all given mails. Caller must ensure that mails are only from one folder\n\t */\n\tasync _moveMails(mails: Mail[], targetMailFolder: MailFolder): Promise<void> {\n\t\tlet moveMails = mails.filter(\n\t\t\t(m) =>\n\t\t\t\tm._id[0] !== targetMailFolder.mails &&\n\t\t\t\ttargetMailFolder._ownerGroup === m._ownerGroup &&\n\t\t\t\t// there is a chance to get here without going through that check when using multiselect, so checking again here.\n\t\t\t\t!areParticipantsRestricted(m),\n\t\t) // prevent moving mails between mail boxes.\n\n\t\t// Do not move if target is the same as the current mailFolder\n\t\tconst sourceMailFolder = this.getMailFolder(getListId(mails[0]))\n\n\t\tif (moveMails.length > 0 && sourceMailFolder && !isSameId(targetMailFolder._id, sourceMailFolder._id)) {\n\t\t\tconst mailChunks = splitInChunks(\n\t\t\t\tMAX_NBR_MOVE_DELETE_MAIL_SERVICE,\n\t\t\t\tmails.map((m) => m._id),\n\t\t\t)\n\n\t\t\tfor (const mailChunk of mailChunks) {\n\t\t\t\tawait this.mailFacade.moveMails(mailChunk, targetMailFolder._id)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Preferably use moveMails() in MailGuiUtils.js which has built-in error handling\n\t * @throws PreconditionFailedError or LockedError if operation is locked on the server\n\t */\n\tasync moveMails(mails: ReadonlyArray<Mail>, targetMailFolder: MailFolder): Promise<void> {\n\t\tconst mailsPerFolder = groupBy(mails, (mail) => {\n\t\t\treturn getListId(mail)\n\t\t})\n\n\t\tfor (const [listId, mails] of mailsPerFolder) {\n\t\t\tconst sourceMailFolder = this.getMailFolder(listId)\n\n\t\t\tif (sourceMailFolder) {\n\t\t\t\tawait this._moveMails(mails, targetMailFolder)\n\t\t\t} else {\n\t\t\t\tconsole.log(\"Move mail: no mail folder for list id\", listId)\n\t\t\t}\n\t\t}\n\t}\n\n\tisMovingMailsAllowed(): boolean {\n\t\treturn this.logins.getUserController().isInternalUser()\n\t}\n\n\tisExportingMailsAllowed(): boolean {\n\t\treturn !this.logins.isEnabled(FeatureType.DisableMailExport)\n\t}\n\n\tasync markMails(mails: readonly Mail[], unread: boolean): Promise<void> {\n\t\tawait promiseMap(\n\t\t\tmails,\n\t\t\tasync (mail) => {\n\t\t\t\tif (mail.unread !== unread) {\n\t\t\t\t\tmail.unread = unread\n\t\t\t\t\treturn this.entityClient.update(mail).catch(ofClass(NotFoundError, noOp)).catch(ofClass(LockedError, noOp))\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ concurrency: 5 },\n\t\t)\n\t}\n\n\t/**\n\t * Finally deletes the given mails if they are already in the trash or spam folders,\n\t * otherwise moves them to the trash folder.\n\t * A deletion confirmation must have been show before.\n\t */\n\tasync deleteMails(mails: ReadonlyArray<Mail>): Promise<void> {\n\t\tconst mailsPerFolder = groupBy(mails, (mail) => {\n\t\t\treturn getListId(mail)\n\t\t})\n\n\t\tif (mails.length === 0) {\n\t\t\treturn\n\t\t}\n\t\tconst folders = await this.getMailboxFolders(mails[0])\n\t\tif (folders == null) {\n\t\t\treturn\n\t\t}\n\t\tconst trashFolder = assertNotNull(folders.getSystemFolderByType(MailFolderType.TRASH))\n\n\t\tfor (const [listId, mails] of mailsPerFolder) {\n\t\t\tconst sourceMailFolder = this.getMailFolder(listId)\n\n\t\t\tif (sourceMailFolder) {\n\t\t\t\tif (isSpamOrTrashFolder(folders, sourceMailFolder)) {\n\t\t\t\t\tawait this._finallyDeleteMails(mails)\n\t\t\t\t} else {\n\t\t\t\t\tawait this._moveMails(mails, trashFolder)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconsole.log(\"Delete mail: no mail folder for list id\", listId)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Finally deletes all given mails. Caller must ensure that mails are only from one folder and the folder must allow final delete operation.\n\t */\n\tasync _finallyDeleteMails(mails: Mail[]): Promise<void> {\n\t\tif (!mails.length) return Promise.resolve()\n\t\tconst mailFolder = neverNull(this.getMailFolder(getListId(mails[0])))\n\t\tconst mailIds = mails.map((m) => m._id)\n\t\tconst mailChunks = splitInChunks(MAX_NBR_MOVE_DELETE_MAIL_SERVICE, mailIds)\n\n\t\tfor (const mailChunk of mailChunks) {\n\t\t\tawait this.mailFacade.deleteMails(mailChunk, mailFolder._id)\n\t\t}\n\t}\n\n\tasync entityEventsReceived(updates: ReadonlyArray<EntityUpdateData>): Promise<void> {\n\t\tfor (const update of updates) {\n\t\t\tif (isUpdateForTypeRef(MailFolderTypeRef, update)) {\n\t\t\t\tawait this._init()\n\t\t\t\tm.redraw()\n\t\t\t} else if (isUpdateForTypeRef(GroupInfoTypeRef, update)) {\n\t\t\t\tif (update.operation === OperationType.UPDATE) {\n\t\t\t\t\tawait this._init()\n\t\t\t\t\tm.redraw\n\t\t\t\t}\n\t\t\t} else if (isUpdateForTypeRef(UserTypeRef, update)) {\n\t\t\t\tif (update.operation === OperationType.UPDATE && isSameId(this.logins.getUserController().user._id, update.instanceId)) {\n\t\t\t\t\tconst updatedUser = await this.entityClient.load(UserTypeRef, update.instanceId)\n\t\t\t\t\tlet newMemberships = updatedUser.memberships.filter((membership) => membership.groupType === GroupType.Mail)\n\t\t\t\t\tconst mailboxDetails = await this.getMailboxDetails()\n\n\t\t\t\t\tif (newMemberships.length !== mailboxDetails.length) {\n\t\t\t\t\t\tawait this._init()\n\t\t\t\t\t\tm.redraw()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (isUpdateForTypeRef(MailTypeRef, update) && update.operation === OperationType.CREATE) {\n\t\t\t\tconst folder = this.getMailFolder(update.instanceListId)\n\n\t\t\t\tif (folder && folder.folderType === MailFolderType.INBOX && !containsEventOfType(updates, OperationType.DELETE, update.instanceId)) {\n\t\t\t\t\t// If we don't find another delete operation on this email in the batch, then it should be a create operation,\n\t\t\t\t\t// otherwise it's a move\n\t\t\t\t\tconst mailId: IdTuple = [update.instanceListId, update.instanceId]\n\t\t\t\t\tconst mail = await this.entityClient.load(MailTypeRef, mailId)\n\t\t\t\t\tawait this.getMailboxDetailsForMailListId(update.instanceListId)\n\t\t\t\t\t\t.then((mailboxDetail) => {\n\t\t\t\t\t\t\t// We only apply rules on server if we are the leader in case of incoming messages\n\t\t\t\t\t\t\treturn mailboxDetail && this.inboxRuleHandler.findAndApplyMatchingRule(mailboxDetail, mail, this.connectivityModel.isLeader())\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.then((newId) => this._showNotification(newId || mailId))\n\t\t\t\t\t\t.catch(noOp)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t_mailboxCountersUpdates(counters: WebsocketCounterData) {\n\t\tconst normalized = this.mailboxCounters() || {}\n\t\tconst group = normalized[counters.mailGroup] || {}\n\t\tcounters.counterValues.forEach((value) => {\n\t\t\tgroup[value.mailListId] = Number(value.count) || 0\n\t\t})\n\t\tnormalized[counters.mailGroup] = group\n\t\tthis.mailboxCounters(normalized)\n\t}\n\n\t_showNotification(mailId: IdTuple) {\n\t\tthis.notifications.showNotification(\n\t\t\tlang.get(\"newMails_msg\"),\n\t\t\t{\n\t\t\t\tactions: [],\n\t\t\t},\n\t\t\t(_) => {\n\t\t\t\tm.route.set(`/mail/${listIdPart(mailId)}/${elementIdPart(mailId)}`)\n\t\t\t\twindow.focus()\n\t\t\t},\n\t\t)\n\t}\n\n\tgetCounterValue(listId: Id): Promise<number | null> {\n\t\treturn this.getMailboxDetailsForMailListId(listId)\n\t\t\t.then((mailboxDetails) => {\n\t\t\t\tif (mailboxDetails == null) {\n\t\t\t\t\treturn null\n\t\t\t\t} else {\n\t\t\t\t\tconst counters = this.mailboxCounters()\n\t\t\t\t\tconst mailGroupCounter = counters[mailboxDetails.mailGroup._id]\n\t\t\t\t\treturn mailGroupCounter && mailGroupCounter[listId]\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch(() => null)\n\t}\n\n\tcheckMailForPhishing(\n\t\tmail: Mail,\n\t\tlinks: Array<{\n\t\t\thref: string\n\t\t\tinnerHTML: string\n\t\t}>,\n\t): Promise<boolean> {\n\t\treturn this.mailFacade.checkMailForPhishing(mail, links)\n\t}\n\n\t/**\n\t * Sends the given folder and all its descendants to the trash folder, removes any empty folders\n\t */\n\tasync trashFolderAndSubfolders(folder: MailFolder): Promise<void> {\n\t\tconst mailboxDetail = await this.getMailboxDetailsForMailListId(folder.mails)\n\t\tif (mailboxDetail == null) {\n\t\t\treturn\n\t\t}\n\t\tlet deletedFolder = await this.removeAllEmpty(mailboxDetail, folder)\n\t\tif (!deletedFolder) {\n\t\t\tconst trash = assertSystemFolderOfType(mailboxDetail.folders, MailFolderType.TRASH)\n\t\t\treturn this.mailFacade.updateMailFolderParent(folder, trash._id)\n\t\t}\n\t}\n\n\t/**\n\t * This is called when moving a folder to SPAM or TRASH, which do not allow empty folders (since only folders that contain mail are allowed)\n\t */\n\tprivate async removeAllEmpty(mailboxDetail: MailboxDetail, folder: MailFolder): Promise<boolean> {\n\t\t// sort descendants deepest first so that we can clean them up before checking their ancestors\n\t\tconst descendants = mailboxDetail.folders.getDescendantFoldersOfParent(folder._id).sort((l, r) => r.level - l.level)\n\n\t\t// we completely delete empty folders\n\t\tlet someNonEmpty = false\n\t\t// we don't update folder system quickly enough so we keep track of deleted folders here and consider them \"empty\" when all their children are here\n\t\tconst deleted = new Set<Id>()\n\t\tfor (const descendant of descendants) {\n\t\t\t// Only load one mail, if there is even one we won't remove\n\t\t\tif (\n\t\t\t\t(await this.entityClient.loadRange(MailTypeRef, descendant.folder.mails, GENERATED_MAX_ID, 1, true)).length === 0 &&\n\t\t\t\tmailboxDetail.folders.getCustomFoldersOfParent(descendant.folder._id).every((f) => deleted.has(getElementId(f)))\n\t\t\t) {\n\t\t\t\tdeleted.add(getElementId(descendant.folder))\n\t\t\t\tawait this.finallyDeleteCustomMailFolder(descendant.folder)\n\t\t\t} else {\n\t\t\t\tsomeNonEmpty = true\n\t\t\t}\n\t\t}\n\t\t// Only load one mail, if there is even one we won't remove\n\t\tif (\n\t\t\t(await this.entityClient.loadRange(MailTypeRef, folder.mails, GENERATED_MAX_ID, 1, true)).length === 0 &&\n\t\t\tmailboxDetail.folders.getCustomFoldersOfParent(folder._id).every((f) => deleted.has(getElementId(f))) &&\n\t\t\t!someNonEmpty\n\t\t) {\n\t\t\tawait this.finallyDeleteCustomMailFolder(folder)\n\t\t\treturn true\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tpublic async finallyDeleteCustomMailFolder(folder: MailFolder): Promise<void> {\n\t\tif (folder.folderType !== MailFolderType.CUSTOM) {\n\t\t\tthrow new ProgrammingError(\"Cannot delete non-custom folder: \" + String(folder._id))\n\t\t}\n\n\t\treturn await this.mailFacade\n\t\t\t.deleteFolder(folder._id)\n\t\t\t.catch(ofClass(NotFoundError, () => console.log(\"mail folder already deleted\")))\n\t\t\t.catch(\n\t\t\t\tofClass(PreconditionFailedError, () => {\n\t\t\t\t\tthrow new UserError(\"operationStillActive_msg\")\n\t\t\t\t}),\n\t\t\t)\n\t}\n\n\tasync fixupCounterForMailList(listId: Id, unreadMails: number) {\n\t\tconst mailboxDetails = await this.getMailboxDetailsForMailListId(listId)\n\t\tmailboxDetails && (await this.mailFacade.fixupCounterForMailList(mailboxDetails.mailGroup._id, listId, unreadMails))\n\t}\n\n\tasync clearFolder(folder: MailFolder): Promise<void> {\n\t\tawait this.mailFacade.clearFolder(folder._id)\n\t}\n\n\tasync unsubscribe(mail: Mail, recipient: string, headers: string[]) {\n\t\tawait this.mailFacade.unsubscribe(mail._id, recipient, headers)\n\t}\n\n\tasync getMailboxProperties(mailboxGroupRoot: MailboxGroupRoot): Promise<MailboxProperties> {\n\t\t// MailboxProperties is an encrypted instance that is created lazily. When we create it the reference is automatically written to the MailboxGroupRoot.\n\t\t// Unfortunately we will only get updated new MailboxGroupRoot with the next EntityUpdate.\n\t\t// To prevent parallel creation attempts we do two things:\n\t\t//  - we save the loading promise to avoid calling setup() twice in parallel\n\t\t//  - we set mailboxProperties reference manually (we could save the id elsewhere but it's easier this way)\n\n\t\t// If we are already loading/creating, just return it to avoid races\n\t\tconst existingPromise = this.mailboxPropertiesPromises.get(mailboxGroupRoot._id)\n\t\tif (existingPromise) {\n\t\t\treturn existingPromise\n\t\t}\n\n\t\tconst promise: Promise<MailboxProperties> = this.loadOrCreateMailboxProperties(mailboxGroupRoot)\n\t\tthis.mailboxPropertiesPromises.set(mailboxGroupRoot._id, promise)\n\t\treturn promise.finally(() => this.mailboxPropertiesPromises.delete(mailboxGroupRoot._id))\n\t}\n\n\tprivate async loadOrCreateMailboxProperties(mailboxGroupRoot: MailboxGroupRoot): Promise<MailboxProperties> {\n\t\tif (!mailboxGroupRoot.mailboxProperties) {\n\t\t\tmailboxGroupRoot.mailboxProperties = await this.entityClient\n\t\t\t\t.setup(\n\t\t\t\t\tnull,\n\t\t\t\t\tcreateMailboxProperties({\n\t\t\t\t\t\t_ownerGroup: mailboxGroupRoot._ownerGroup,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.catch(\n\t\t\t\t\tofClass(PreconditionFailedError, (e) => {\n\t\t\t\t\t\t// We try to prevent race conditions but they can still happen with multiple clients trying ot create mailboxProperties at the same time.\n\t\t\t\t\t\t// We send special precondition from the server with an existing id.\n\t\t\t\t\t\tif (e.data && e.data.startsWith(\"exists:\")) {\n\t\t\t\t\t\t\tconst existingId = e.data.substring(\"exists:\".length)\n\t\t\t\t\t\t\tconsole.log(\"mailboxProperties already exists\", existingId)\n\t\t\t\t\t\t\treturn existingId\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow new ProgrammingError(`Could not create mailboxProperties, precondition: ${e.data}`)\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t}\n\t\tconst mailboxProperties = await this.entityClient.load(MailboxPropertiesTypeRef, mailboxGroupRoot.mailboxProperties)\n\t\tif (mailboxProperties.mailAddressProperties.length === 0) {\n\t\t\tawait this.migrateFromOldSenderName(mailboxGroupRoot, mailboxProperties)\n\t\t}\n\t\treturn mailboxProperties\n\t}\n\n\t/** If there was no sender name configured before take the user's name and assign it to all email addresses. */\n\tprivate async migrateFromOldSenderName(mailboxGroupRoot: MailboxGroupRoot, mailboxProperties: MailboxProperties) {\n\t\tconst userGroupInfo = this.logins.getUserController().userGroupInfo\n\t\tconst legacySenderName = userGroupInfo.name\n\t\tconst mailboxDetails = await this.getMailboxDetailsForMailGroup(mailboxGroupRoot._id)\n\t\tconst mailAddresses = getEnabledMailAddressesWithUser(mailboxDetails, userGroupInfo)\n\t\tfor (const mailAddress of mailAddresses) {\n\t\t\tmailboxProperties.mailAddressProperties.push(\n\t\t\t\tcreateMailAddressProperties({\n\t\t\t\t\tmailAddress,\n\t\t\t\t\tsenderName: legacySenderName,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t\tawait this.entityClient.update(mailboxProperties)\n\t}\n\n\tasync saveReportMovedMails(mailboxGroupRoot: MailboxGroupRoot, reportMovedMails: ReportMovedMailsType): Promise<MailboxProperties> {\n\t\tconst mailboxProperties = await this.loadOrCreateMailboxProperties(mailboxGroupRoot)\n\t\tmailboxProperties.reportMovedMails = reportMovedMails\n\t\tawait this.entityClient.update(mailboxProperties)\n\t\treturn mailboxProperties\n\t}\n}\n","import { noOp } from \"@tutao/tutanota-utils\"\nimport { isApp, isDesktop } from \"../api/common/Env\"\nimport { NotificationIcon } from \"./base/icons/Icons\"\nimport type { clickHandler } from \"./base/GuiUtils\"\n\nexport class Notifications {\n\tshowNotification(title: string, options?: NotificationOptions, onclick: Notification[\"onclick\"] = noOp): Notification | null {\n\t\tif (!isApp() && typeof window.Notification !== \"undefined\" && window.Notification.permission === \"granted\") {\n\t\t\ttry {\n\t\t\t\tconst actualOptions: NotificationOptions = Object.assign(\n\t\t\t\t\t{},\n\t\t\t\t\t{\n\t\t\t\t\t\ticon: NotificationIcon,\n\t\t\t\t\t},\n\t\t\t\t\toptions,\n\t\t\t\t)\n\t\t\t\tconst notification = new window.Notification(title, actualOptions)\n\t\t\t\tnotification.onclick = onclick\n\t\t\t\treturn notification\n\t\t\t} catch (e) {\n\t\t\t\t// new Notification() throws an error in new chrome browsers on android devices.\n\t\t\t\t// According to the error message ServiceWorkerRegistration.showNotification() should be used instead.\n\t\t\t\t// This is currently not available on our test devices, so ignore notification errors.\n\t\t\t\t// Setails: http://stackoverflow.com/questions/29774836/failed-to-construct-notification-illegal-constructor\n\t\t\t\tconsole.warn(\"notification error\", e)\n\t\t\t}\n\t\t}\n\n\t\treturn null\n\t}\n\n\t/**\n\t * Requests user permission if notifications are supported\n\t * @returns {Promise<boolean>} resolves to \"true\" if we can send notifications.\n\t */\n\trequestPermission(): void {\n\t\tif (isDesktop() || isApp() || typeof Notification === \"undefined\") {\n\t\t\treturn\n\t\t}\n\n\t\ttry {\n\t\t\tif (window.Notification.permission !== \"denied\") {\n\t\t\t\twindow.Notification.requestPermission()\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.log(\"request notification permission error\", e)\n\t\t}\n\t}\n}\n\nexport const notifications: Notifications = new Notifications()\n","import type { DeferredObject } from \"@tutao/tutanota-utils\"\nimport { assertNotNull, clone, defer, downcast, filterInt, getFromMap, LazyLoaded, noOp } from \"@tutao/tutanota-utils\"\nimport { CalendarMethod, FeatureType, GroupType, OperationType } from \"../../api/common/TutanotaConstants\"\nimport type { EntityUpdateData } from \"../../api/main/EventController\"\nimport { EventController, isUpdateForTypeRef } from \"../../api/main/EventController\"\nimport type { AlarmInfo, DateWrapper, Group, GroupInfo, User, UserAlarmInfo } from \"../../api/entities/sys/TypeRefs.js\"\nimport {\n\tcreateDateWrapper,\n\tcreateMembershipRemoveData,\n\tGroupInfoTypeRef,\n\tGroupMembership,\n\tGroupTypeRef,\n\tUserAlarmInfoTypeRef,\n} from \"../../api/entities/sys/TypeRefs.js\"\nimport {\n\tCalendarEvent,\n\tCalendarEventTypeRef,\n\tCalendarEventUpdate,\n\tCalendarEventUpdateTypeRef,\n\tCalendarGroupRoot,\n\tCalendarGroupRootTypeRef,\n\tCalendarRepeatRule,\n\tcreateGroupSettings,\n\tFileTypeRef,\n} from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { isApp, isDesktop } from \"../../api/common/Env\"\nimport type { LoginController } from \"../../api/main/LoginController\"\nimport { LockedError, NotAuthorizedError, NotFoundError, PreconditionFailedError } from \"../../api/common/error/RestError\"\nimport type { ParsedCalendarData } from \"../export/CalendarImporter\"\nimport { ParserError } from \"../../misc/parsing/ParserCombinator\"\nimport { ProgressTracker } from \"../../api/main/ProgressTracker\"\nimport type { IProgressMonitor } from \"../../api/common/utils/ProgressMonitor\"\nimport { EntityClient } from \"../../api/common/EntityClient\"\nimport type { MailModel } from \"../../mail/model/MailModel\"\nimport { elementIdPart, getElementId, isSameId, listIdPart, removeTechnicalFields } from \"../../api/common/utils/EntityUtils\"\nimport type { AlarmScheduler } from \"../date/AlarmScheduler\"\nimport type { Notifications } from \"../../gui/Notifications\"\nimport m from \"mithril\"\nimport type { CalendarFacade } from \"../../api/worker/facades/lazy/CalendarFacade.js\"\nimport { IServiceExecutor } from \"../../api/common/ServiceRequest\"\nimport { MembershipService } from \"../../api/entities/sys/Services\"\nimport { FileController } from \"../../file/FileController\"\nimport { findAttendeeInAddresses } from \"../../api/common/utils/CommonCalendarUtils.js\"\n\nconst TAG = \"[CalendarModel]\"\n\nexport type CalendarInfo = {\n\tgroupRoot: CalendarGroupRoot\n\t// We use LazyLoaded so that we don't get races for loading these events which is\n\t// 1. Good because loading them twice is not optimal\n\t// 2. Event identity is required by some functions (e.g. when determining week events)\n\tlongEvents: LazyLoaded<Array<CalendarEvent>>\n\tgroupInfo: GroupInfo\n\tgroup: Group\n\tshared: boolean\n}\n\nexport class CalendarModel {\n\t/** Map from calendar event element id to the deferred object with a promise of getting CREATE event for this calendar event */\n\tprivate pendingAlarmRequests: Map<string, DeferredObject<void>> = new Map()\n\tprivate readonly userAlarmToAlarmInfo: Map<string, string> = new Map()\n\n\tconstructor(\n\t\tprivate readonly notifications: Notifications,\n\t\tprivate readonly alarmScheduler: () => Promise<AlarmScheduler>,\n\t\teventController: EventController,\n\t\tprivate readonly serviceExecutor: IServiceExecutor,\n\t\tprivate readonly logins: LoginController,\n\t\tprivate readonly progressTracker: ProgressTracker,\n\t\tprivate readonly entityClient: EntityClient,\n\t\tprivate readonly mailModel: MailModel,\n\t\tprivate readonly calendarFacade: CalendarFacade,\n\t\tprivate readonly fileController: FileController,\n\t) {\n\t\tif (isApp()) return\n\t\teventController.addEntityListener((updates) => this.entityEventsReceived(updates))\n\t}\n\n\tasync createEvent(event: CalendarEvent, alarmInfos: ReadonlyArray<AlarmInfo>, zone: string, groupRoot: CalendarGroupRoot): Promise<void> {\n\t\tawait this.doCreate(event, zone, groupRoot, alarmInfos)\n\t}\n\n\t/** Update existing event when time did not change */\n\tasync updateEvent(\n\t\tnewEvent: CalendarEvent,\n\t\tnewAlarms: ReadonlyArray<AlarmInfo>,\n\t\tzone: string,\n\t\tgroupRoot: CalendarGroupRoot,\n\t\texistingEvent: CalendarEvent,\n\t): Promise<CalendarEvent> {\n\t\tif (existingEvent._id == null) {\n\t\t\tthrow new Error(\"Invalid existing event for update: no id\")\n\t\t}\n\n\t\tif (existingEvent.uid != null && newEvent.uid !== existingEvent.uid) {\n\t\t\tthrow new Error(\"Invalid existing event for update: mismatched uids.\")\n\t\t}\n\n\t\tif (\n\t\t\texistingEvent._ownerGroup !== groupRoot._id ||\n\t\t\tnewEvent.startTime.getTime() !== existingEvent.startTime.getTime() ||\n\t\t\t!repeatRulesEqual(newEvent.repeatRule, existingEvent.repeatRule)\n\t\t) {\n\t\t\t// We should reload the instance here because session key and permissions are updated when we recreate event.\n\t\t\tawait this.doCreate(newEvent, zone, groupRoot, newAlarms, existingEvent)\n\t\t\treturn await this.entityClient.load<CalendarEvent>(CalendarEventTypeRef, newEvent._id)\n\t\t} else {\n\t\t\tnewEvent._ownerGroup = groupRoot._id\n\t\t\t// We can't load updated event here because cache is not updated yet. We also shouldn't need to load it, we have the latest\n\t\t\t// version\n\t\t\tawait this.calendarFacade.updateCalendarEvent(newEvent, newAlarms, existingEvent)\n\t\t\treturn newEvent\n\t\t}\n\t}\n\n\t/** Load map from group/groupRoot ID to the calendar info */\n\tasync loadCalendarInfos(progressMonitor: IProgressMonitor): Promise<ReadonlyMap<Id, CalendarInfo>> {\n\t\tconst user = this.logins.getUserController().user\n\n\t\tconst calendarMemberships = user.memberships.filter((m) => m.groupType === GroupType.Calendar)\n\t\tconst notFoundMemberships: GroupMembership[] = []\n\t\tconst groupInstances: Array<[CalendarGroupRoot, GroupInfo, Group]> = []\n\t\tfor (const membership of calendarMemberships) {\n\t\t\ttry {\n\t\t\t\tconst result = await Promise.all([\n\t\t\t\t\tthis.entityClient.load(CalendarGroupRootTypeRef, membership.group),\n\t\t\t\t\tthis.entityClient.load(GroupInfoTypeRef, membership.groupInfo),\n\t\t\t\t\tthis.entityClient.load(GroupTypeRef, membership.group),\n\t\t\t\t])\n\t\t\t\tgroupInstances.push(result)\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof NotFoundError) {\n\t\t\t\t\tnotFoundMemberships.push(membership)\n\t\t\t\t} else {\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t}\n\t\t\tprogressMonitor.workDone(3)\n\t\t}\n\n\t\tconst calendarInfos: Map<Id, CalendarInfo> = new Map()\n\t\tfor (const [groupRoot, groupInfo, group] of groupInstances) {\n\t\t\tcalendarInfos.set(groupRoot._id, {\n\t\t\t\tgroupRoot,\n\t\t\t\tgroupInfo,\n\t\t\t\tlongEvents: new LazyLoaded(() => this.entityClient.loadAll(CalendarEventTypeRef, groupRoot.longEvents), []),\n\t\t\t\tgroup: group,\n\t\t\t\tshared: !isSameId(group.user, user._id),\n\t\t\t})\n\t\t}\n\n\t\t// cleanup inconsistent memberships\n\t\tfor (const mship of notFoundMemberships) {\n\t\t\t// noinspection ES6MissingAwait\n\t\t\tthis.serviceExecutor.delete(MembershipService, createMembershipRemoveData({ user: user._id, group: mship.group }))\n\t\t}\n\t\treturn calendarInfos\n\t}\n\n\tasync loadOrCreateCalendarInfo(progressMonitor: IProgressMonitor): Promise<ReadonlyMap<Id, CalendarInfo>> {\n\t\tconst { findPrivateCalendar } = await import(\"../date/CalendarUtils\")\n\t\tconst calendarInfo = await this.loadCalendarInfos(progressMonitor)\n\n\t\tif (!this.logins.isInternalUserLoggedIn() || findPrivateCalendar(calendarInfo)) {\n\t\t\treturn calendarInfo\n\t\t} else {\n\t\t\tawait this.createCalendar(\"\", null)\n\t\t\treturn await this.loadCalendarInfos(progressMonitor)\n\t\t}\n\t}\n\n\tasync createCalendar(name: string, color: string | null): Promise<void> {\n\t\t// when a calendar group is added, a group membership is added to the user. we might miss this websocket event\n\t\t// during startup if the websocket is not connected fast enough. Therefore, we explicitly update the user\n\t\t// this should be removed once we handle missed events during startup\n\t\tconst { user, group } = await this.calendarFacade.addCalendar(name)\n\t\tthis.logins.getUserController().user = user\n\n\t\tif (color != null) {\n\t\t\tconst { userSettingsGroupRoot } = this.logins.getUserController()\n\n\t\t\tconst newGroupSettings = Object.assign(createGroupSettings(), {\n\t\t\t\tgroup: group._id,\n\t\t\t\tcolor: color,\n\t\t\t})\n\t\t\tuserSettingsGroupRoot.groupSettings.push(newGroupSettings)\n\t\t\tawait this.entityClient.update(userSettingsGroupRoot)\n\t\t}\n\t}\n\n\tprivate async doCreate(\n\t\tevent: CalendarEvent,\n\t\tzone: string,\n\t\tgroupRoot: CalendarGroupRoot,\n\t\talarmInfos: ReadonlyArray<AlarmInfo>,\n\t\texistingEvent?: CalendarEvent,\n\t): Promise<void> {\n\t\t// If the event was copied it might still carry some fields for re-encryption. We can't reuse them.\n\t\tremoveTechnicalFields(event)\n\t\tconst { assignEventId } = await import(\"../date/CalendarUtils\")\n\t\t// if values of the existing events have changed that influence the alarm time then delete the old event and create a new\n\t\t// one.\n\t\tassignEventId(event, zone, groupRoot)\n\t\t// Reset ownerEncSessionKey because it cannot be set for new entity, it will be assigned by the CryptoFacade\n\t\tevent._ownerEncSessionKey = null\n\t\tif (event.repeatRule != null) {\n\t\t\tevent.repeatRule.excludedDates = event.repeatRule.excludedDates.map(({ date }) => createDateWrapper({ date }))\n\t\t}\n\t\t// Reset permissions because server will assign them\n\t\tdowncast(event)._permissions = null\n\t\tevent._ownerGroup = groupRoot._id\n\t\treturn await this.calendarFacade.saveCalendarEvent(event, alarmInfos, existingEvent ?? null)\n\t}\n\n\tasync deleteEvent(event: CalendarEvent): Promise<void> {\n\t\treturn await this.entityClient.erase(event)\n\t}\n\n\tprivate async loadAndProcessCalendarUpdates(): Promise<void> {\n\t\tconst { mailboxGroupRoot } = await this.mailModel.getUserMailboxDetails()\n\t\tconst { calendarEventUpdates } = mailboxGroupRoot\n\t\tif (calendarEventUpdates == null) return\n\n\t\tconst invites = await this.entityClient.loadAll(CalendarEventUpdateTypeRef, calendarEventUpdates.list)\n\t\tfor (const invite of invites) {\n\t\t\t// noinspection ES6MissingAwait\n\t\t\tthis.handleCalendarEventUpdate(invite)\n\t\t}\n\t}\n\n\tprivate async getCalendarDataForUpdate(fileId: IdTuple): Promise<ParsedCalendarData | null> {\n\t\ttry {\n\t\t\tconst file = await this.entityClient.load(FileTypeRef, fileId)\n\t\t\tconst dataFile = await this.fileController.getAsDataFile(file)\n\t\t\tconst { parseCalendarFile } = await import(\"../export/CalendarImporter\")\n\t\t\treturn await parseCalendarFile(dataFile)\n\t\t} catch (e) {\n\t\t\tif (e instanceof ParserError || e instanceof NotFoundError) {\n\t\t\t\tconsole.warn(TAG, \"could not get calendar update data\", e)\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tthrow e\n\t\t}\n\t}\n\n\tprivate async handleCalendarEventUpdate(update: CalendarEventUpdate): Promise<void> {\n\t\ttry {\n\t\t\tconst parsedCalendarData = await this.getCalendarDataForUpdate(update.file)\n\t\t\tif (parsedCalendarData != null) {\n\t\t\t\tawait this.processCalendarUpdate(update.sender, parsedCalendarData)\n\t\t\t}\n\t\t\tawait this.entityClient.erase(update)\n\t\t} catch (e) {\n\t\t\tif (e instanceof NotAuthorizedError) {\n\t\t\t\tconsole.warn(TAG, \"could not process calendar update: not authorized\", e)\n\t\t\t} else if (e instanceof PreconditionFailedError) {\n\t\t\t\tconsole.warn(TAG, \"could not process calendar update: precondition failed\", e)\n\t\t\t} else if (e instanceof LockedError) {\n\t\t\t\tconsole.warn(TAG, \"could not process calendar update: locked\", e)\n\t\t\t} else if (e instanceof NotFoundError) {\n\t\t\t\tconsole.warn(TAG, \"could not process calendar update: not found\", e)\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Processing calendar update - bring events in calendar up-to-date with updates sent via email.\n\t * Calendar updates are currently processed for REPLY, REQUEST and CANCEL calendar types. For REQUEST type the update is only processed\n\t * if there is an existing event.\n\t * For REPLY we update attendee status, for REQUEST we update event and for CANCEL we delete existing event.\n\t *\n\t * public for testing\n\t */\n\tasync processCalendarUpdate(sender: string, calendarData: ParsedCalendarData): Promise<void> {\n\t\tif (calendarData.contents.length !== 1) {\n\t\t\tconsole.log(TAG, `Calendar update with ${calendarData.contents.length} events, ignoring`)\n\t\t\treturn\n\t\t}\n\n\t\tconst { event } = calendarData.contents[0]\n\n\t\tif (event == null || event.uid == null) {\n\t\t\tconsole.log(TAG, \"Invalid event: \", event)\n\t\t\treturn\n\t\t}\n\n\t\tconst dbEvent = await this.calendarFacade.getEventByUid(event.uid)\n\t\tif (dbEvent == null) {\n\t\t\t// event was not found\n\t\t\treturn\n\t\t}\n\n\t\tif (calendarData.method === CalendarMethod.REPLY) {\n\t\t\t// first check if the sender of the email is in the attendee list\n\t\t\tconst replyAttendee = findAttendeeInAddresses(event.attendees, [sender])\n\n\t\t\tif (replyAttendee == null) {\n\t\t\t\tconsole.log(TAG, \"Sender is not among attendees, ignoring\", replyAttendee)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst newEvent = clone(dbEvent)\n\t\t\t// check if the attendee is still in the attendee list of the latest event\n\t\t\tconst dbAttendee = findAttendeeInAddresses(newEvent.attendees, [replyAttendee.address.address])\n\n\t\t\tif (dbAttendee == null) {\n\t\t\t\tconsole.log(TAG, \"Attendee was not found\", dbEvent._id, replyAttendee)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdbAttendee.status = replyAttendee.status\n\t\t\tawait this.doUpdateEvent(dbEvent, newEvent)\n\t\t} else if (calendarData.method === CalendarMethod.REQUEST) {\n\t\t\t// Either initial invite or update\n\t\t\t// then it's an update\n\t\t\tif (dbEvent.organizer == null || dbEvent.organizer.address !== sender) {\n\t\t\t\tconsole.log(TAG, \"REQUEST sent not by organizer, ignoring\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (filterInt(dbEvent.sequence) < filterInt(event.sequence)) {\n\t\t\t\tawait this.updateEventWithExternal(dbEvent, event).then(noOp)\n\t\t\t\treturn\n\t\t\t}\n\t\t} else if (calendarData.method === CalendarMethod.CANCEL) {\n\t\t\tif (dbEvent.organizer == null || dbEvent.organizer.address !== sender) {\n\t\t\t\tconsole.log(TAG, \"CANCEL sent not by organizer, ignoring\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tawait this.entityClient.erase(dbEvent)\n\t\t\treturn\n\t\t}\n\t}\n\n\t/**\n\t * Update {@param dbEvent} stored on the server with {@param event} from the ics file.\n\t */\n\tasync updateEventWithExternal(dbEvent: CalendarEvent, event: CalendarEvent): Promise<CalendarEvent> {\n\t\tconst newEvent = clone(dbEvent)\n\t\tnewEvent.startTime = event.startTime\n\t\tnewEvent.endTime = event.endTime\n\t\tnewEvent.attendees = event.attendees\n\t\tnewEvent.summary = event.summary\n\t\tnewEvent.sequence = event.sequence\n\t\tnewEvent.location = event.location\n\t\tnewEvent.description = event.description\n\t\tnewEvent.organizer = event.organizer\n\t\tnewEvent.repeatRule = event.repeatRule\n\t\treturn await this.doUpdateEvent(dbEvent, newEvent)\n\t}\n\n\tasync doUpdateEvent(dbEvent: CalendarEvent, newEvent: CalendarEvent): Promise<CalendarEvent> {\n\t\tconst [alarms, groupRoot] = await Promise.all([\n\t\t\tthis.loadAlarms(dbEvent.alarmInfos, this.logins.getUserController().user),\n\t\t\tthis.entityClient.load<CalendarGroupRoot>(CalendarGroupRootTypeRef, assertNotNull(dbEvent._ownerGroup)),\n\t\t])\n\t\tconst alarmInfos = alarms.map((a) => a.alarmInfo)\n\t\treturn await this.updateEvent(newEvent, alarmInfos, \"\", groupRoot, dbEvent)\n\t}\n\n\tasync init(): Promise<void> {\n\t\tawait this.scheduleAlarmsLocally()\n\t\tawait this.loadAndProcessCalendarUpdates()\n\t}\n\n\tasync scheduleAlarmsLocally(): Promise<void> {\n\t\tif (!this.localAlarmsEnabled()) return\n\t\tconst eventsWithInfos = await this.calendarFacade.loadAlarmEvents()\n\t\tconst scheduler: AlarmScheduler = await this.alarmScheduler()\n\t\tfor (let { event, userAlarmInfos } of eventsWithInfos) {\n\t\t\tfor (let userAlarmInfo of userAlarmInfos) {\n\t\t\t\tthis.scheduleUserAlarmInfo(event, userAlarmInfo, scheduler)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync loadAlarms(alarmInfos: Array<IdTuple>, user: User): Promise<Array<UserAlarmInfo>> {\n\t\tconst { alarmInfoList } = user\n\n\t\tif (alarmInfoList == null) {\n\t\t\treturn []\n\t\t}\n\n\t\tconst ids = alarmInfos.filter((alarmInfoId) => isSameId(listIdPart(alarmInfoId), alarmInfoList.alarms))\n\n\t\tif (ids.length === 0) {\n\t\t\treturn []\n\t\t}\n\n\t\treturn this.entityClient.loadMultiple(UserAlarmInfoTypeRef, listIdPart(ids[0]), ids.map(elementIdPart))\n\t}\n\n\tasync deleteCalendar(calendar: CalendarInfo): Promise<void> {\n\t\tawait this.calendarFacade.deleteCalendar(calendar.groupRoot._id)\n\t}\n\n\tprivate async entityEventsReceived(updates: ReadonlyArray<EntityUpdateData>): Promise<void> {\n\t\tfor (const entityEventData of updates) {\n\t\t\tif (isUpdateForTypeRef(UserAlarmInfoTypeRef, entityEventData)) {\n\t\t\t\tif (entityEventData.operation === OperationType.CREATE) {\n\t\t\t\t\t// Updates for UserAlarmInfo and CalendarEvent come in a\n\t\t\t\t\t// separate batches and there's a race between loading of the\n\t\t\t\t\t// UserAlarmInfo and creation of the event.\n\t\t\t\t\t// We try to load UserAlarmInfo. Then we wait until the\n\t\t\t\t\t// CalendarEvent is there (which might already be true)\n\t\t\t\t\t// and load it.\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst userAlarmInfo = await this.entityClient.load(UserAlarmInfoTypeRef, [entityEventData.instanceListId, entityEventData.instanceId])\n\n\t\t\t\t\t\tconst { listId, elementId } = userAlarmInfo.alarmInfo.calendarRef\n\t\t\t\t\t\tconst deferredEvent = getFromMap(this.pendingAlarmRequests, elementId, defer)\n\t\t\t\t\t\t// Don't wait for the deferred event promise because it can lead to a deadlock.\n\t\t\t\t\t\t// Since issue #2264 we process event batches sequentially and the\n\t\t\t\t\t\t// deferred event can never be resolved until the calendar event update is received.\n\t\t\t\t\t\tdeferredEvent.promise = deferredEvent.promise.then(async () => {\n\t\t\t\t\t\t\tconst calendarEvent = await this.entityClient.load(CalendarEventTypeRef, [listId, elementId])\n\t\t\t\t\t\t\tconst scheduler = await this.alarmScheduler()\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tthis.scheduleUserAlarmInfo(calendarEvent, userAlarmInfo, scheduler)\n\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\tif (e instanceof NotFoundError) {\n\t\t\t\t\t\t\t\t\tconsole.log(TAG, \"event not found\", [listId, elementId])\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tthrow e\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\treturn\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (e instanceof NotFoundError) {\n\t\t\t\t\t\t\tconsole.log(TAG, e, \"Event or alarm were not found: \", entityEventData, e)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow e\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (entityEventData.operation === OperationType.DELETE) {\n\t\t\t\t\treturn await this.cancelUserAlarmInfo(entityEventData.instanceId)\n\t\t\t\t}\n\t\t\t} else if (\n\t\t\t\tisUpdateForTypeRef(CalendarEventTypeRef, entityEventData) &&\n\t\t\t\t(entityEventData.operation === OperationType.CREATE || entityEventData.operation === OperationType.UPDATE)\n\t\t\t) {\n\t\t\t\tconst deferredEvent = getFromMap(this.pendingAlarmRequests, entityEventData.instanceId, defer)\n\t\t\t\tdeferredEvent.resolve(undefined)\n\t\t\t\tawait deferredEvent.promise\n\t\t\t} else if (isUpdateForTypeRef(CalendarEventUpdateTypeRef, entityEventData) && entityEventData.operation === OperationType.CREATE) {\n\t\t\t\ttry {\n\t\t\t\t\tconst invite = await this.entityClient.load(CalendarEventUpdateTypeRef, [entityEventData.instanceListId, entityEventData.instanceId])\n\t\t\t\t\tawait this.handleCalendarEventUpdate(invite)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof NotFoundError) {\n\t\t\t\t\t\tconsole.log(TAG, \"invite not found\", [entityEventData.instanceListId, entityEventData.instanceId], e)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate localAlarmsEnabled(): boolean {\n\t\treturn !isApp() && !isDesktop() && this.logins.isInternalUserLoggedIn() && !this.logins.isEnabled(FeatureType.DisableCalendar)\n\t}\n\n\tprivate scheduleUserAlarmInfo(event: CalendarEvent, userAlarmInfo: UserAlarmInfo, scheduler: AlarmScheduler): void {\n\t\tthis.userAlarmToAlarmInfo.set(getElementId(userAlarmInfo), userAlarmInfo.alarmInfo.alarmIdentifier)\n\n\t\tscheduler.scheduleAlarm(event, userAlarmInfo.alarmInfo, event.repeatRule, (title, body) => {\n\t\t\tthis.notifications.showNotification(\n\t\t\t\ttitle,\n\t\t\t\t{\n\t\t\t\t\tbody,\n\t\t\t\t},\n\t\t\t\t() => m.route.set(\"/calendar\"),\n\t\t\t)\n\t\t})\n\t}\n\n\tprivate async cancelUserAlarmInfo(userAlarmInfoId: Id): Promise<any> {\n\t\tconst identifier = this.userAlarmToAlarmInfo.get(userAlarmInfoId)\n\n\t\tif (identifier) {\n\t\t\tconst alarmScheduler = await this.alarmScheduler()\n\t\t\talarmScheduler.cancelAlarm(identifier)\n\t\t}\n\t}\n}\n\n// allDay event consists of full UTC days. It always starts at 00:00:00.00 of its start day in UTC and ends at\n// 0 of the next day in UTC. Full day event time is relative to the local timezone. So startTime and endTime of\n// allDay event just points us to the correct date.\n// e.g. there's an allDay event in Europe/Berlin at 2nd of may. We encode it as:\n// {startTime: new Date(Date.UTC(2019, 04, 2, 0, 0, 0, 0)), {endTime: new Date(Date.UTC(2019, 04, 3, 0, 0, 0, 0))}}\n// We check the condition with time == 0 and take a UTC date (which is [2-3) so full day on the 2nd of May). We\nfunction repeatRulesEqual(repeatRule: CalendarRepeatRule | null, repeatRule2: CalendarRepeatRule | null): boolean {\n\treturn (\n\t\t(repeatRule == null && repeatRule2 == null) ||\n\t\t(repeatRule != null &&\n\t\t\trepeatRule2 != null &&\n\t\t\trepeatRule.endType === repeatRule2.endType &&\n\t\t\trepeatRule.endValue === repeatRule2.endValue &&\n\t\t\trepeatRule.frequency === repeatRule2.frequency &&\n\t\t\trepeatRule.interval === repeatRule2.interval &&\n\t\t\trepeatRule.timeZone === repeatRule2.timeZone &&\n\t\t\tisSameExclusions(repeatRule.excludedDates, repeatRule2.excludedDates))\n\t)\n}\n\n/**\n * compare two lists of dateWrappers\n * @param dates sorted list of dateWrappers from earliest to latest\n * @param dates2 sorted list of dateWrappers from earliest to latest\n */\nfunction isSameExclusions(dates: ReadonlyArray<DateWrapper>, dates2: ReadonlyArray<DateWrapper>): boolean {\n\tif (dates.length !== dates2.length) return false\n\tfor (let i = 0; i < dates.length; i++) {\n\t\tconst { date: a } = dates[i]\n\t\tconst { date: b } = dates2[i]\n\t\tif (a.getTime() !== b.getTime()) return false\n\t}\n\treturn true\n}\n","import stream from \"mithril/stream\"\nimport type { ProgressMonitorId } from \"../common/utils/ProgressMonitor\"\nimport { ProgressMonitor } from \"../common/utils/ProgressMonitor\"\n\nexport type ExposedProgressTracker = Pick<ProgressTracker, \"registerMonitor\" | \"workDoneForMonitor\">\n\n/**\n * The progress tracker controls the progress bar located in Header.js\n * You can register progress monitors with it and then make workDone calls on them\n * and then the total progress will be shown at the top of the window\n */\nexport class ProgressTracker {\n\t// Will stream a number between 0 and 1\n\tonProgressUpdate: stream<number>\n\tprivate monitors: Map<ProgressMonitorId, ProgressMonitor>\n\tprivate idCounter: ProgressMonitorId\n\n\tconstructor() {\n\t\t// initially, there is no work so we are done by default.\n\t\tthis.onProgressUpdate = stream(1)\n\t\tthis.monitors = new Map()\n\t\tthis.idCounter = 0\n\t}\n\n\t/**\n\t * Register a monitor with the tracker, so that it's progress can be displayed\n\t * Returns an ID as a handle, useful for making calls from the worker\n\t *\n\t * Make sure that monitor completes so it can be unregistered.\n\t * @param work - total work to do\n\t */\n\tregisterMonitorSync(work: number): ProgressMonitorId {\n\t\tconst id = this.idCounter++\n\t\tconst monitor = new ProgressMonitor(work, (percentage) => this.onProgress(id, percentage))\n\n\t\tthis.monitors.set(id, monitor)\n\n\t\treturn id\n\t}\n\n\t/** async wrapper for remote */\n\tasync registerMonitor(work: number): Promise<ProgressMonitorId> {\n\t\treturn this.registerMonitorSync(work)\n\t}\n\n\tasync workDoneForMonitor(id: ProgressMonitorId, amount: number): Promise<void> {\n\t\tthis.getMonitor(id)?.workDone(amount)\n\t}\n\n\tgetMonitor(id: ProgressMonitorId): ProgressMonitor | null {\n\t\treturn this.monitors.get(id) ?? null\n\t}\n\n\tprivate onProgress(id: ProgressMonitorId, percentage: number) {\n\t\t// notify\n\t\tthis.onProgressUpdate(this.completedAmount())\n\t\t// we might be done with this one\n\t\tif (percentage >= 100) this.monitors.delete(id)\n\t}\n\n\t/**\n\t * Total work that will be done from all monitors\n\t */\n\ttotalWork(): number {\n\t\tlet total = 0\n\n\t\tfor (const monitor of this.monitors.values()) {\n\t\t\ttotal += monitor.totalWork\n\t\t}\n\n\t\treturn total\n\t}\n\n\t/**\n\t * Current absolute amount of completed work from all monitors\n\t */\n\tcompletedWork(): number {\n\t\tlet total = 0\n\n\t\tfor (const monitor of this.monitors.values()) {\n\t\t\ttotal += monitor.workCompleted\n\t\t}\n\n\t\treturn total\n\t}\n\n\t/**\n\t * Completed percentage of completed work as a number between 0 and 1\n\t */\n\tcompletedAmount(): number {\n\t\tconst totalWork = this.totalWork()\n\t\tconst completedWork = this.completedWork()\n\t\t// no work to do means you have done all the work\n\t\treturn totalWork !== 0 ? Math.min(1, completedWork / totalWork) : 1\n\t}\n}\n","import type { Dialog } from \"../../gui/base/Dialog\"\nimport type { SendMailModel } from \"../editor/SendMailModel\"\nimport { lastThrow, remove } from \"@tutao/tutanota-utils\"\nimport type { Mail } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { isSameId } from \"../../api/common/utils/EntityUtils\"\nimport Stream from \"mithril/stream\"\n\nexport const enum SaveStatusEnum {\n\tSaving = 0,\n\tSaved = 1,\n\tNotSaved = 2,\n}\n\nexport const enum SaveErrorReason {\n\tUnknown,\n\tConnectionLost,\n}\n\nexport type SaveStatus =\n\t| {\n\t\t\tstatus: SaveStatusEnum.Saving\n\t  }\n\t| {\n\t\t\tstatus: SaveStatusEnum.Saved\n\t  }\n\t| {\n\t\t\tstatus: SaveStatusEnum.NotSaved\n\t\t\treason: SaveErrorReason\n\t  }\n\nexport type MinimizedEditor = {\n\tdialog: Dialog\n\tsendMailModel: SendMailModel\n\t// we pass sendMailModel for easier access to contents of mail,\n\tdispose: () => void\n\t// disposes dialog and templatePopup eventListeners when minimized mail is removed\n\tsaveStatus: Stream<SaveStatus>\n\tcloseOverlayFunction: () => Promise<void>\n}\n\n/**\n * handles minimized Editors\n */\nexport class MinimizedMailEditorViewModel {\n\t_minimizedEditors: Array<MinimizedEditor>\n\n\tconstructor() {\n\t\tthis._minimizedEditors = []\n\t}\n\n\tminimizeMailEditor(\n\t\tdialog: Dialog,\n\t\tsendMailModel: SendMailModel,\n\t\tdispose: () => void,\n\t\tsaveStatus: Stream<SaveStatus>,\n\t\tcloseOverlayFunction: () => Promise<void>,\n\t): MinimizedEditor {\n\t\tdialog.close()\n\n\t\t// disallow creation of duplicate minimized mails\n\t\tif (!this._minimizedEditors.find((editor) => editor.dialog === dialog)) {\n\t\t\tthis._minimizedEditors.push({\n\t\t\t\tsendMailModel: sendMailModel,\n\t\t\t\tdialog: dialog,\n\t\t\t\tdispose: dispose,\n\t\t\t\tsaveStatus,\n\t\t\t\tcloseOverlayFunction,\n\t\t\t})\n\t\t}\n\n\t\treturn lastThrow(this._minimizedEditors)\n\t}\n\n\t// fully removes and reopens clicked mail\n\treopenMinimizedEditor(editor: MinimizedEditor): void {\n\t\teditor.closeOverlayFunction()\n\t\teditor.dialog.show()\n\t\tremove(this._minimizedEditors, editor)\n\t}\n\n\t// fully removes clicked mail\n\tremoveMinimizedEditor(editor: MinimizedEditor): void {\n\t\teditor.closeOverlayFunction()\n\t\teditor.dispose()\n\t\tremove(this._minimizedEditors, editor)\n\t}\n\n\tgetMinimizedEditors(): Array<MinimizedEditor> {\n\t\treturn this._minimizedEditors\n\t}\n\n\tgetEditorForDraft(mail: Mail): MinimizedEditor | null {\n\t\treturn (\n\t\t\tthis.getMinimizedEditors().find((e) => {\n\t\t\t\tconst draft = e.sendMailModel.getDraft()\n\t\t\t\treturn draft ? isSameId(draft._id, mail._id) : null\n\t\t\t}) ?? null\n\t\t)\n\t}\n}\n","import type { CredentialEncryptionMode } from \"./CredentialEncryptionMode\"\nimport type { Base64, Base64Url } from \"@tutao/tutanota-utils\"\nimport { assertNotNull } from \"@tutao/tutanota-utils\"\nimport type { Credentials } from \"./Credentials\"\nimport { DatabaseKeyFactory } from \"./DatabaseKeyFactory\"\nimport { CredentialsKeyMigrator } from \"./CredentialsKeyMigrator.js\"\nimport { InterWindowEventFacadeSendDispatcher } from \"../../native/common/generatedipc/InterWindowEventFacadeSendDispatcher.js\"\nimport { SqlCipherFacade } from \"../../native/common/generatedipc/SqlCipherFacade.js\"\n\n/**\n * Type for persistent credentials, that contain the full credentials data.\n */\nexport type PersistentCredentials = {\n\tcredentialInfo: CredentialsInfo\n\taccessToken: Base64\n\tdatabaseKey: Base64 | null\n\tencryptedPassword: Base64Url\n}\n\n/**\n * Credentials type that does not contain the actual credentials, but only meta information.\n */\nexport type CredentialsInfo = {\n\tlogin: string\n\tuserId: Id\n\ttype: \"internal\" | \"external\"\n}\n\n/**\n * Interface for encrypting credentials. If and how credentials are encrypted with an additional layer is platform-dependent and will\n * be decided by the platform-specific implementation of this interface.\n */\nexport interface CredentialsEncryption {\n\t/**\n\t * Encrypts {@param credentials} using the encryption mode set for the device.\n\t * @param credentials\n\t */\n\tencrypt(credentials: CredentialsAndDatabaseKey): Promise<PersistentCredentials>\n\n\t/**\n\t * Decrypts {@param encryptedCredentials} using the encryption mode set for the device.\n\t * @param encryptedCredentials\n\t */\n\tdecrypt(encryptedCredentials: PersistentCredentials): Promise<CredentialsAndDatabaseKey>\n\n\t/**\n\t * Returns all credentials encryption modes that are supported by the device.\n\t */\n\tgetSupportedEncryptionModes(): Promise<ReadonlyArray<CredentialEncryptionMode>>\n}\n\n/**\n * Interface for storing credentials on a device.\n */\nexport interface CredentialsStorage {\n\t/**\n\t * Stores {@param persistentCredentials}. If another set of credentials exists for the same userId, it will be overwritten.\n\t * @param persistentCredentials\n\t */\n\tstore(persistentCredentials: PersistentCredentials): void\n\n\t/**\n\t * Loads the credentials for {@param userId}.\n\t * @param userId\n\t */\n\tloadByUserId(userId: Id): PersistentCredentials | null\n\n\t/**\n\t * Loads all credentials stored on the device.\n\t */\n\tloadAll(): Array<PersistentCredentials>\n\n\t/**\n\t * Deletes any stored credentials for {@param userId}.\n\t * @param userId\n\t */\n\tdeleteByUserId(userId: Id): void\n\n\t/**\n\t * Returns the credentials encryption mode, i.e. how the intermediate key used for encrypting credentials is encrypted on the device.\n\t */\n\tgetCredentialEncryptionMode(): CredentialEncryptionMode | null\n\n\t/**\n\t * Sets the credentials encryption mode, i.e. how the intermediate key used for encrypting credentials is encrypted on the device.\n\t * @param encryptionMode\n\t */\n\tsetCredentialEncryptionMode(encryptionMode: CredentialEncryptionMode | null): void\n\n\t/**\n\t * Returns the (encrypted) key used for encrypting the access token and the database key\n\t * This is encrypted using a key in the device's keystore\n\t */\n\tgetCredentialsEncryptionKey(): Uint8Array | null\n\n\t/**\n\t * Sets the (encrypted) key used for encrypting credentials.\n\t * @param credentialsEncryptionKey\n\t */\n\tsetCredentialsEncryptionKey(credentialsEncryptionKey: Uint8Array | null): void\n}\n\nexport type CredentialsAndDatabaseKey = {\n\tcredentials: Credentials\n\tdatabaseKey?: Uint8Array | null\n}\n\n/**\n * Main entry point to interact with credentials, i.e. storing and retrieving credentials from/to persistence.\n */\nexport class CredentialsProvider {\n\tconstructor(\n\t\tprivate readonly credentialsEncryption: CredentialsEncryption,\n\t\tprivate readonly storage: CredentialsStorage,\n\t\tprivate readonly keyMigrator: CredentialsKeyMigrator,\n\t\tprivate readonly databaseKeyFactory: DatabaseKeyFactory,\n\t\tprivate readonly sqliteCipherFacade: SqlCipherFacade | null,\n\t\tprivate readonly interWindowEventSender: InterWindowEventFacadeSendDispatcher | null,\n\t) {}\n\n\t/**\n\t * Stores credentials. If credentials already exist for login, they will be overwritten.\n\t * Also creates a database key\n\t */\n\tasync store(credentialsAndKey: CredentialsAndDatabaseKey): Promise<void> {\n\t\tconst encryptedCredentials = await this.credentialsEncryption.encrypt(credentialsAndKey)\n\t\tthis.storage.store(encryptedCredentials)\n\t}\n\n\tasync getCredentialsInfoByUserId(userId: Id): Promise<CredentialsInfo | null> {\n\t\tconst persistentCredentials = this.storage.loadByUserId(userId)\n\n\t\treturn persistentCredentials?.credentialInfo ?? null\n\t}\n\n\t/**\n\t * Returns the full credentials for the userId passed in.\n\t * @param userId\n\t */\n\tasync getCredentialsByUserId(userId: Id): Promise<CredentialsAndDatabaseKey | null> {\n\t\tconst persistentCredentials = this.storage.loadByUserId(userId)\n\n\t\tif (persistentCredentials == null) {\n\t\t\treturn null\n\t\t}\n\n\t\tconst decrypted = await this.credentialsEncryption.decrypt(persistentCredentials)\n\n\t\tif (decrypted.databaseKey == null) {\n\t\t\t// When offline mode is first released, there will be users who have saved credentials but no database key.\n\t\t\t// In the future, we will never save credentials without it, but we need to create one here\n\n\t\t\tdecrypted.databaseKey = await this.databaseKeyFactory.generateKey()\n\n\t\t\tif (decrypted.databaseKey != null) {\n\t\t\t\tconst reEncrypted = await this.credentialsEncryption.encrypt(decrypted)\n\t\t\t\tthis.storage.store(reEncrypted)\n\t\t\t}\n\t\t}\n\n\t\treturn decrypted\n\t}\n\n\t/**\n\t * Returns the stored credentials infos of all internal users, i.e. users that have a \"real\" tutanota account and not the ones that\n\t * have a secure external mailbox.\n\t */\n\tasync getInternalCredentialsInfos(): Promise<ReadonlyArray<CredentialsInfo>> {\n\t\tconst allCredentials = this.storage.loadAll().map((persistentCredentials) => persistentCredentials.credentialInfo)\n\t\treturn allCredentials.filter((credential) => {\n\t\t\treturn credential.type === \"internal\"\n\t\t})\n\t}\n\n\t/**\n\t * Deletes stored credentials with specified userId.\n\t * No-op if credentials are not there.\n\t * @param opts.deleteOfflineDb whether to delete offline database. Will delete by default.\n\t */\n\tasync deleteByUserId(userId: Id, opts: { deleteOfflineDb: boolean } = { deleteOfflineDb: true }): Promise<void> {\n\t\tawait this.interWindowEventSender?.localUserDataInvalidated(userId)\n\t\tif (opts.deleteOfflineDb) {\n\t\t\tawait this.sqliteCipherFacade?.deleteDb(userId)\n\t\t}\n\t\tthis.storage.deleteByUserId(userId)\n\t}\n\n\t/**\n\t * Sets the credentials encryption mode, i.e. how the intermediate key used for encrypting credentials is protected.\n\t * @param encryptionMode\n\t * @throws KeyPermanentlyInvalidatedError\n\t * @throws CredentialAuthenticationError\n\t */\n\tasync setCredentialsEncryptionMode(encryptionMode: CredentialEncryptionMode): Promise<void> {\n\t\tif (encryptionMode === this.getCredentialsEncryptionMode()) {\n\t\t\treturn\n\t\t}\n\n\t\tconst oldKeyEncrypted = this.storage.getCredentialsEncryptionKey()\n\n\t\tif (oldKeyEncrypted) {\n\t\t\t// if we have a key we must have a method\n\t\t\tconst oldEncryptionMode = assertNotNull(this.storage.getCredentialEncryptionMode())\n\t\t\tconst newlyEncryptedKey = await this.keyMigrator.migrateCredentialsKey(oldKeyEncrypted, oldEncryptionMode, encryptionMode)\n\n\t\t\tthis.storage.setCredentialsEncryptionKey(newlyEncryptedKey)\n\t\t}\n\n\t\tthis.storage.setCredentialEncryptionMode(encryptionMode)\n\t}\n\n\t/**\n\t * Returns the credentials encryption mode, i.e. how the intermediate key used for encrypting credentials is protected.\n\t */\n\tgetCredentialsEncryptionMode(): CredentialEncryptionMode | null {\n\t\treturn this.storage.getCredentialEncryptionMode()\n\t}\n\n\t/**\n\t * Returns all credentials encryption modes that are supported by the device.\n\t */\n\tasync getSupportedEncryptionModes(): Promise<ReadonlyArray<CredentialEncryptionMode>> {\n\t\treturn await this.credentialsEncryption.getSupportedEncryptionModes()\n\t}\n\n\t/**\n\t * Removes all stored credentials as well as any settings associated with credentials encryption.\n\t */\n\tasync clearCredentials(reason: Error | string): Promise<void> {\n\t\tconsole.warn(\"clearing all stored credentials:\", reason)\n\t\tconst storedCredentials = this.storage.loadAll()\n\n\t\tfor (let storedCredential of storedCredentials) {\n\t\t\tawait this.deleteByUserId(storedCredential.credentialInfo.userId)\n\t\t}\n\n\t\tthis.storage.setCredentialsEncryptionKey(null)\n\n\t\tthis.storage.setCredentialEncryptionMode(null)\n\t}\n}\n","import type { CredentialsStorage } from \"./CredentialsProvider.js\"\nimport type { DeviceEncryptionFacade } from \"../../api/worker/facades/DeviceEncryptionFacade\"\nimport { base64ToUint8Array, uint8ArrayToBase64 } from \"@tutao/tutanota-utils\"\nimport type { CredentialEncryptionMode } from \"./CredentialEncryptionMode\"\nimport { NativeCredentialsFacade } from \"../../native/common/generatedipc/NativeCredentialsFacade.js\"\n\n/**\n * Interface for obtaining the key that is used to encrypt credentials. Any access to that key should always be done using this interface\n * rather than directly accessing device storage.\n */\nexport class CredentialsKeyProvider {\n\tconstructor(\n\t\tprivate readonly nativeCredentials: NativeCredentialsFacade,\n\t\tprivate readonly credentialsStorage: CredentialsStorage,\n\t\tprivate readonly deviceEncryptionFacade: DeviceEncryptionFacade,\n\t) {}\n\n\t/**\n\t * Return the key that is used for encrypting credentials on the device. If no key exists on the device, a new key will be created\n\t * and also stored in the device's credentials storage.\n\t */\n\tasync getCredentialsKey(): Promise<Uint8Array> {\n\t\tconst encryptedCredentialsKey = this.credentialsStorage.getCredentialsEncryptionKey()\n\n\t\tif (encryptedCredentialsKey) {\n\t\t\tconst credentialsKey = await this.nativeCredentials.decryptUsingKeychain(encryptedCredentialsKey, this.getEncryptionMode())\n\t\t\treturn credentialsKey\n\t\t} else {\n\t\t\tconst credentialsKey = await this.deviceEncryptionFacade.generateKey()\n\t\t\tconst encryptedCredentialsKey = await this.nativeCredentials.encryptUsingKeychain(credentialsKey, this.getEncryptionMode())\n\n\t\t\tthis.credentialsStorage.setCredentialsEncryptionKey(encryptedCredentialsKey)\n\n\t\t\treturn credentialsKey\n\t\t}\n\t}\n\n\tprivate getEncryptionMode(): CredentialEncryptionMode {\n\t\tconst encryptionMode = this.credentialsStorage.getCredentialEncryptionMode()\n\n\t\tif (!encryptionMode) {\n\t\t\tthrow new Error(\"Encryption mode not set\")\n\t\t}\n\n\t\treturn encryptionMode\n\t}\n}\n","import type { CredentialsEncryption, PersistentCredentials } from \"./CredentialsProvider.js\"\nimport { CredentialsAndDatabaseKey } from \"./CredentialsProvider.js\"\nimport type { DeviceEncryptionFacade } from \"../../api/worker/facades/DeviceEncryptionFacade\"\nimport { base64ToUint8Array, stringToUtf8Uint8Array, uint8ArrayToBase64, utf8Uint8ArrayToString } from \"@tutao/tutanota-utils\"\nimport type { CredentialEncryptionMode } from \"./CredentialEncryptionMode\"\nimport { CryptoError } from \"../../api/common/error/CryptoError.js\"\nimport { KeyPermanentlyInvalidatedError } from \"../../api/common/error/KeyPermanentlyInvalidatedError.js\"\nimport { CredentialsKeyProvider } from \"./CredentialsKeyProvider.js\"\nimport { NativeCredentialsFacade } from \"../../native/common/generatedipc/NativeCredentialsFacade.js\"\n\n/**\n * Credentials encryption implementation that uses the native (platform-specific) keychain implementation. It uses an intermediate key to\n * encrypt the credentials stored on the device. The intermediate key in turn is encrypted using the native keychain.\n */\nexport class NativeCredentialsEncryption implements CredentialsEncryption {\n\tconstructor(\n\t\tprivate readonly credentialsKeyProvider: CredentialsKeyProvider,\n\t\tprivate readonly deviceEncryptionFacade: DeviceEncryptionFacade,\n\t\tprivate readonly nativeCredentials: NativeCredentialsFacade,\n\t) {}\n\n\tasync encrypt({ credentials, databaseKey }: CredentialsAndDatabaseKey): Promise<PersistentCredentials> {\n\t\tconst { encryptedPassword } = credentials\n\n\t\tif (encryptedPassword == null) {\n\t\t\tthrow new Error(\"Trying to encrypt non-persistent credentials\")\n\t\t}\n\n\t\tconst credentialsKey = await this.credentialsKeyProvider.getCredentialsKey()\n\n\t\tconst base64accessToken = stringToUtf8Uint8Array(credentials.accessToken)\n\t\tconst encryptedAccessToken = await this.deviceEncryptionFacade.encrypt(credentialsKey, base64accessToken)\n\t\tconst encryptedAccessTokenBase64 = uint8ArrayToBase64(encryptedAccessToken)\n\n\t\tlet encryptedDatabaseKeyBase64: string | null = null\n\t\tif (databaseKey) {\n\t\t\tconst encryptedDatabaseKey = await this.deviceEncryptionFacade.encrypt(credentialsKey, databaseKey)\n\t\t\tencryptedDatabaseKeyBase64 = uint8ArrayToBase64(encryptedDatabaseKey)\n\t\t}\n\n\t\treturn {\n\t\t\tcredentialInfo: {\n\t\t\t\tlogin: credentials.login,\n\t\t\t\tuserId: credentials.userId,\n\t\t\t\ttype: credentials.type,\n\t\t\t},\n\t\t\tencryptedPassword,\n\t\t\taccessToken: encryptedAccessTokenBase64,\n\t\t\tdatabaseKey: encryptedDatabaseKeyBase64,\n\t\t}\n\t}\n\n\tasync decrypt(encryptedCredentials: PersistentCredentials): Promise<CredentialsAndDatabaseKey> {\n\t\tconst credentialsKey = await this.credentialsKeyProvider.getCredentialsKey()\n\n\t\ttry {\n\t\t\tconst accessToken = utf8Uint8ArrayToString(\n\t\t\t\tawait this.deviceEncryptionFacade.decrypt(credentialsKey, base64ToUint8Array(encryptedCredentials.accessToken)),\n\t\t\t)\n\n\t\t\tconst databaseKey = encryptedCredentials.databaseKey\n\t\t\t\t? await this.deviceEncryptionFacade.decrypt(credentialsKey, base64ToUint8Array(encryptedCredentials.databaseKey))\n\t\t\t\t: null\n\n\t\t\treturn {\n\t\t\t\tcredentials: {\n\t\t\t\t\tlogin: encryptedCredentials.credentialInfo.login,\n\t\t\t\t\tuserId: encryptedCredentials.credentialInfo.userId,\n\t\t\t\t\ttype: encryptedCredentials.credentialInfo.type,\n\t\t\t\t\tencryptedPassword: encryptedCredentials.encryptedPassword,\n\t\t\t\t\taccessToken,\n\t\t\t\t},\n\t\t\t\tdatabaseKey,\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tif (e instanceof CryptoError) {\n\t\t\t\t// If the key could not be decrypted it means that something went very wrong. We will probably not be able to do anything about it so just\n\t\t\t\t// delete everything.\n\t\t\t\tthrow new KeyPermanentlyInvalidatedError(`Could not decrypt credentials: ${e.stack ?? e.message}`)\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t}\n\t}\n\n\tasync getSupportedEncryptionModes(): Promise<ReadonlyArray<CredentialEncryptionMode>> {\n\t\treturn this.nativeCredentials.getSupportedEncryptionModes()\n\t}\n}\n","import type { CredentialEncryptionMode } from \"./CredentialEncryptionMode\"\nimport { NativeCredentialsFacade } from \"../../native/common/generatedipc/NativeCredentialsFacade.js\"\n\n/**\n * Interface for credentials key migration. Migration becomes necessary when the encryption mode for a device is changed,\n * which requires re-encrypting the intermediate key used for credential encryption.\n */\nexport interface CredentialsKeyMigrator {\n\t/**\n\t * Migrates a credentials key, i.e. decrypts it using {@param oldMode} and re-encrypts it using {@param newMode}.\n\t * @param oldKeyEncrypted Key to be migrated (encrypted using {@param oldMode}.\n\t * @param oldMode Encryption mode that has been used to encrypt {@param oldKeyEncrypted}.\n\t * @param newMode Mode with which the decrypted {@param oldKeyEncrypted} will be re-encrypted.\n\t * @returns Migrated (re-encrypted) credentials key.\n\t */\n\tmigrateCredentialsKey(oldKeyEncrypted: Uint8Array, oldMode: CredentialEncryptionMode, newMode: CredentialEncryptionMode): Promise<Uint8Array>\n}\n\n/**\n * Platform-independent implementation for of ICredentialsKeyMigrator.\n */\nexport class DefaultCredentialsKeyMigrator implements CredentialsKeyMigrator {\n\tconstructor(private readonly nativeCredentials: NativeCredentialsFacade) {}\n\n\tasync migrateCredentialsKey(oldKeyEncrypted: Uint8Array, oldMode: CredentialEncryptionMode, newMode: CredentialEncryptionMode): Promise<Uint8Array> {\n\t\tconst oldKeyDecryptedKeyBase64 = await this.nativeCredentials.decryptUsingKeychain(oldKeyEncrypted, oldMode)\n\t\treturn await this.nativeCredentials.encryptUsingKeychain(oldKeyDecryptedKeyBase64, newMode)\n\t}\n}\n\n/**\n * Stub to be used on platforms for which we have not implemented credentials encryption with an intermediate key yet.\n * It will throw an error when called, since no key migration should ever be triggered on platforms that don't support the feature.\n */\nexport class StubCredentialsKeyMigrator implements CredentialsKeyMigrator {\n\tmigrateCredentialsKey(oldKeyEncrypted: Uint8Array, oldMode: CredentialEncryptionMode, newMode: CredentialEncryptionMode): Promise<Uint8Array> {\n\t\tthrow new Error(\"Should not be used\")\n\t}\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport { scaleToVisualPasswordStrength } from \"../misc/passwords/PasswordUtils\"\nimport { theme } from \"./theme.js\"\n\nexport interface CompletenessIndicatorAttrs {\n\tpercentageCompleted: number\n\twidth?: string\n}\n\nexport class CompletenessIndicator implements Component<CompletenessIndicatorAttrs> {\n\tview({ attrs }: Vnode<CompletenessIndicatorAttrs>): Children {\n\t\treturn m(\n\t\t\t\"\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\tborder: `1px solid ${theme.content_button}`,\n\t\t\t\t\twidth: attrs.width ?? \"100px\",\n\t\t\t\t\theight: \"10px\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tm(\"\", {\n\t\t\t\tstyle: {\n\t\t\t\t\t\"background-color\": theme.content_button,\n\t\t\t\t\twidth: scaleToVisualPasswordStrength(attrs.percentageCompleted) + \"%\",\n\t\t\t\t\theight: \"100%\",\n\t\t\t\t},\n\t\t\t}),\n\t\t)\n\t}\n}\n","import m from \"mithril\"\nimport { assertMainOrNode, isAdminClient } from \"../../api/common/Env\"\nimport { Dialog, DialogType } from \"../base/Dialog\"\nimport { DefaultAnimationTime } from \"../animation/Animations\"\nimport type { TranslationKey } from \"../../misc/LanguageViewModel\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport { progressIcon } from \"../base/Icon\"\nimport { CompletenessIndicator } from \"../CompletenessIndicator.js\"\nimport Stream from \"mithril/stream\"\nimport { TabIndex } from \"../../api/common/TutanotaConstants\"\nimport type { lazy } from \"@tutao/tutanota-utils\"\nimport { delay } from \"@tutao/tutanota-utils\"\n\nassertMainOrNode()\n\nexport async function showProgressDialog<T>(\n\tmessageIdOrMessageFunction: TranslationKey | lazy<string>,\n\taction: Promise<T>,\n\tprogressStream?: Stream<number>,\n): Promise<T> {\n\tif (progressStream != null) {\n\t\tprogressStream.map(() => {\n\t\t\tm.redraw()\n\t\t})\n\t}\n\n\tconst progressDialog = new Dialog(DialogType.Progress, {\n\t\tview: () =>\n\t\t\tm(\n\t\t\t\t\".hide-outline\",\n\t\t\t\t{\n\t\t\t\t\t// We make this element focusable so that the screen reader announces the dialog\n\t\t\t\t\ttabindex: TabIndex.Default,\n\n\t\t\t\t\toncreate(vnode) {\n\t\t\t\t\t\t// We need to delay so that the eelement is attached to the parent\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t;(vnode.dom as HTMLElement).focus()\n\t\t\t\t\t\t}, 10)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t[\n\t\t\t\t\tm(\".flex-center\", progressStream ? m(CompletenessIndicator, { percentageCompleted: progressStream() }) : progressIcon()),\n\t\t\t\t\tm(\"p#dialog-title\", lang.getMaybeLazy(messageIdOrMessageFunction)),\n\t\t\t\t],\n\t\t\t),\n\t}).setCloseHandler(() => {\n\t\t// do not close progress on onClose event\n\t})\n\tprogressDialog.show()\n\tlet start = new Date().getTime()\n\tlet minDialogVisibilityMillis = isAdminClient() ? 0 : 1000\n\ttry {\n\t\treturn await action\n\t} finally {\n\t\tconst diff = Date.now() - start\n\t\tawait delay(Math.max(minDialogVisibilityMillis - diff, 0))\n\t\tprogressDialog.close()\n\t\tawait delay(DefaultAnimationTime)\n\t}\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport type { TranslationKey } from \"../LanguageViewModel\"\nimport { lang } from \"../LanguageViewModel\"\nimport { Button, ButtonAttrs, ButtonType } from \"../../gui/base/Button.js\"\nimport { Icon, progressIcon } from \"../../gui/base/Icon\"\nimport { Icons, SecondFactorImage } from \"../../gui/base/icons/Icons\"\nimport { theme } from \"../../gui/theme\"\nimport type { Thunk } from \"@tutao/tutanota-utils\"\nimport { Autocomplete, TextField } from \"../../gui/base/TextField.js\"\n\ntype WebauthnState = { state: \"init\" } | { state: \"progress\" } | { state: \"error\"; error: TranslationKey }\n\ntype WebauthnAnotherDomainParams = {\n\tcanLogin: false\n\totherLoginDomain: string\n}\ntype WebauthnLoginParams = {\n\tcanLogin: true\n\tstate: WebauthnState\n\tdoWebauthn: Thunk\n}\ntype WebauthnParams = WebauthnLoginParams | WebauthnAnotherDomainParams\ntype OtpParams = {\n\tcodeFieldValue: string\n\tinProgress: boolean\n\tonValueChanged: (arg0: string) => unknown\n}\nexport type SecondFactorViewAttrs = {\n\totp: OtpParams | null\n\twebauthn: WebauthnParams | null\n\tonRecover: Thunk | null\n}\n\n/** Displays options for second factor authentication. */\nexport class SecondFactorAuthView implements Component<SecondFactorViewAttrs> {\n\tview(vnode: Vnode<SecondFactorViewAttrs>): Children {\n\t\tconst { attrs } = vnode\n\t\treturn m(\".flex.col\", [\n\t\t\tm(\"p.center\", [lang.get(attrs.webauthn?.canLogin || attrs.otp ? \"secondFactorPending_msg\" : \"secondFactorPendingOtherClientOnly_msg\")]),\n\t\t\tthis.renderWebauthn(vnode.attrs),\n\t\t\tthis._renderOtp(vnode.attrs),\n\t\t\tthis._renderRecover(vnode.attrs),\n\t\t])\n\t}\n\n\t_renderOtp(attrs: SecondFactorViewAttrs): Children {\n\t\tconst { otp } = attrs\n\n\t\tif (!otp) {\n\t\t\treturn null\n\t\t}\n\n\t\treturn m(\n\t\t\t\".left.mb\",\n\t\t\tm(TextField, {\n\t\t\t\tlabel: \"totpCode_label\",\n\t\t\t\tvalue: otp.codeFieldValue,\n\t\t\t\tautocompleteAs: Autocomplete.oneTimeCode,\n\t\t\t\toninput: (value) => otp.onValueChanged(value.trim()),\n\t\t\t\tinjectionsRight: () => (otp.inProgress ? m(\".mr-s\", progressIcon()) : null),\n\t\t\t}),\n\t\t)\n\t}\n\n\trenderWebauthn(attrs: SecondFactorViewAttrs): Children {\n\t\tconst { webauthn } = attrs\n\n\t\tif (!webauthn) {\n\t\t\treturn null\n\t\t}\n\n\t\tif (webauthn.canLogin) {\n\t\t\treturn this.renderWebauthnLogin(webauthn)\n\t\t} else {\n\t\t\treturn this._renderOtherDomainLogin(webauthn)\n\t\t}\n\t}\n\n\trenderWebauthnLogin(webauthn: WebauthnLoginParams): Children {\n\t\tlet items\n\t\tconst { state } = webauthn\n\n\t\tconst doWebauthnButtonAttrs: ButtonAttrs = {\n\t\t\tlabel: \"useSecurityKey_action\",\n\t\t\tclick: () => webauthn.doWebauthn(),\n\t\t\ttype: ButtonType.Login,\n\t\t}\n\n\t\tswitch (state.state) {\n\t\t\tcase \"init\":\n\t\t\t\titems = [m(\".align-self-center\", m(Button, doWebauthnButtonAttrs))]\n\t\t\t\tbreak\n\n\t\t\tcase \"progress\":\n\t\t\t\titems = [m(\".flex.justify-center\", [m(\".mr-s\", progressIcon()), m(\"\", lang.get(\"waitingForU2f_msg\"))])]\n\t\t\t\tbreak\n\n\t\t\tcase \"error\":\n\t\t\t\titems = [\n\t\t\t\t\tm(\".flex.col.items-center\", [\n\t\t\t\t\t\tm(\".flex.items-center\", [\n\t\t\t\t\t\t\tm(\n\t\t\t\t\t\t\t\t\".mr-s\",\n\t\t\t\t\t\t\t\tm(Icon, {\n\t\t\t\t\t\t\t\t\ticon: Icons.Cancel,\n\t\t\t\t\t\t\t\t\tlarge: true,\n\t\t\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\t\t\tfill: theme.content_accent,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tm(\"\", lang.get(state.error)),\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tm(Button, doWebauthnButtonAttrs),\n\t\t\t\t\t]),\n\t\t\t\t]\n\t\t\t\tbreak\n\n\t\t\tdefault:\n\t\t\t\tthrow new Error()\n\t\t}\n\n\t\treturn [m(\".flex-center\", m(\"img\", { src: SecondFactorImage })), m(\".mt.flex.col\", items)]\n\t}\n\n\t_renderOtherDomainLogin(attrs: WebauthnAnotherDomainParams): Children {\n\t\tconst href = `https://${attrs.otherLoginDomain}`\n\t\treturn m(\n\t\t\t\"a\",\n\t\t\t{\n\t\t\t\thref,\n\t\t\t},\n\t\t\tlang.get(\"differentSecurityKeyDomain_msg\", {\n\t\t\t\t\"{domain}\": href,\n\t\t\t}),\n\t\t)\n\t}\n\n\t_renderRecover(attrs: SecondFactorViewAttrs): Children {\n\t\tconst { onRecover } = attrs\n\n\t\tif (onRecover == null) {\n\t\t\treturn null\n\t\t}\n\n\t\treturn m(\".small.text-center.pt\", [\n\t\t\tm(\n\t\t\t\t`a[href=#]`,\n\t\t\t\t{\n\t\t\t\t\tonclick: (e: MouseEvent) => {\n\t\t\t\t\t\tonRecover()\n\t\t\t\t\t\te.preventDefault()\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tlang.get(\"recoverAccountAccess_action\"),\n\t\t\t),\n\t\t])\n\t}\n}\n","export function appIdToLoginDomain(appId: string): string {\n\t// If it's legacy U2F key, get domain from before the path part. Otherwise it's just a domain.\n\tconst domain = appId.endsWith(\".json\") ? appId.split(\"/\")[2] : appId\n\treturn domain === \"tutanota.com\" ? \"mail.tutanota.com\" : domain\n}\n","import { SecondFactorType } from \"../../api/common/TutanotaConstants.js\"\nimport type { Thunk } from \"@tutao/tutanota-utils\"\nimport { assertNotNull, getFirstOrThrow } from \"@tutao/tutanota-utils\"\nimport type { TranslationKey } from \"../LanguageViewModel.js\"\nimport type { Challenge } from \"../../api/entities/sys/TypeRefs.js\"\nimport { createSecondFactorAuthData } from \"../../api/entities/sys/TypeRefs.js\"\nimport { AccessBlockedError, BadRequestError, NotAuthenticatedError } from \"../../api/common/error/RestError.js\"\nimport { Dialog } from \"../../gui/base/Dialog.js\"\nimport m from \"mithril\"\nimport { SecondFactorAuthView } from \"./SecondFactorAuthView.js\"\nimport { WebauthnClient } from \"./webauthn/WebauthnClient.js\"\nimport type { LoginFacade } from \"../../api/worker/facades/LoginFacade.js\"\nimport { CancelledError } from \"../../api/common/error/CancelledError.js\"\nimport { WebauthnError } from \"../../api/common/error/WebauthnError.js\"\nimport { appIdToLoginDomain } from \"./SecondFactorUtils.js\"\n\ntype AuthData = {\n\treadonly sessionId: IdTuple\n\treadonly challenges: ReadonlyArray<Challenge>\n\treadonly mailAddress: string | null\n}\ntype WebauthnState = { state: \"init\" } | { state: \"progress\" } | { state: \"error\"; error: TranslationKey }\n\ntype OtpState = {\n\tcode: string\n\tinProgress: boolean\n}\n\n/**\n * Dialog which allows user to use second factor authentication and allows to reset second factor.\n * It will show that the login can be approved form another session and depending on what is supported it\n * might display one or more of:\n *  - WebAuthentication\n *  - TOTP\n *  - login from another domain message\n *  - lost access button\n * */\nexport class SecondFactorAuthDialog {\n\tprivate waitingForSecondFactorDialog: Dialog | null = null\n\tprivate webauthnState: WebauthnState = { state: \"init\" }\n\tprivate otpState: OtpState = { code: \"\", inProgress: false }\n\n\t/** @private */\n\tprivate constructor(\n\t\tprivate readonly webauthnClient: WebauthnClient,\n\t\tprivate readonly loginFacade: LoginFacade,\n\t\tprivate readonly authData: AuthData,\n\t\tprivate readonly onClose: Thunk,\n\t) {}\n\n\t/**\n\t * @param webauthnClient\n\t * @param loginFacade\n\t * @param authData\n\t * @param onClose will be called when the dialog is closed (one way or another).\n\t */\n\tstatic show(webauthnClient: WebauthnClient, loginFacade: LoginFacade, authData: AuthData, onClose: Thunk): SecondFactorAuthDialog {\n\t\tconst dialog = new SecondFactorAuthDialog(webauthnClient, loginFacade, authData, onClose)\n\n\t\tdialog.show()\n\n\t\treturn dialog\n\t}\n\n\tclose() {\n\t\tif (this.waitingForSecondFactorDialog?.visible) {\n\t\t\tthis.waitingForSecondFactorDialog?.close()\n\t\t}\n\n\t\tthis.webauthnClient.abortCurrentOperation()\n\t\tthis.waitingForSecondFactorDialog = null\n\n\t\tthis.onClose()\n\t}\n\n\tprivate async show() {\n\t\tconst u2fChallenge = this.authData.challenges.find(\n\t\t\t(challenge) => challenge.type === SecondFactorType.u2f || challenge.type === SecondFactorType.webauthn,\n\t\t)\n\n\t\tconst otpChallenge = this.authData.challenges.find((challenge) => challenge.type === SecondFactorType.totp)\n\t\tconst u2fSupported = await this.webauthnClient.isSupported()\n\n\t\tconsole.log(\"webauthn supported: \", u2fSupported)\n\n\t\tlet canLoginWithU2f: boolean\n\t\tlet otherLoginDomain: string | null\n\t\tif (u2fChallenge?.u2f != null && u2fSupported) {\n\t\t\tconst { canAttempt, cannotAttempt } = await this.webauthnClient.canAttemptChallenge(u2fChallenge.u2f)\n\t\t\tcanLoginWithU2f = canAttempt.length !== 0\n\t\t\totherLoginDomain = cannotAttempt.length > 0 ? appIdToLoginDomain(getFirstOrThrow(cannotAttempt).appId) : null\n\t\t} else {\n\t\t\tcanLoginWithU2f = false\n\t\t\totherLoginDomain = null\n\t\t}\n\n\t\tconst { mailAddress } = this.authData\n\t\tthis.waitingForSecondFactorDialog = Dialog.showActionDialog({\n\t\t\ttitle: \"\",\n\t\t\tallowOkWithReturn: true,\n\t\t\tchild: {\n\t\t\t\tview: () => {\n\t\t\t\t\treturn m(SecondFactorAuthView, {\n\t\t\t\t\t\twebauthn: canLoginWithU2f\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tcanLogin: true,\n\t\t\t\t\t\t\t\t\tstate: this.webauthnState,\n\t\t\t\t\t\t\t\t\tdoWebauthn: () => this.doWebauthn(assertNotNull(u2fChallenge)),\n\t\t\t\t\t\t\t  }\n\t\t\t\t\t\t\t: otherLoginDomain\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tcanLogin: false,\n\t\t\t\t\t\t\t\t\totherLoginDomain,\n\t\t\t\t\t\t\t  }\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\totp: otpChallenge\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tcodeFieldValue: this.otpState.code,\n\t\t\t\t\t\t\t\t\tinProgress: this.otpState.inProgress,\n\t\t\t\t\t\t\t\t\tonValueChanged: (newValue) => (this.otpState.code = newValue),\n\t\t\t\t\t\t\t  }\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\tonRecover: mailAddress ? () => this.recoverLogin(mailAddress) : null,\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t},\n\t\t\tokAction: otpChallenge ? () => this.onConfirmOtp() : null,\n\t\t\tcancelAction: () => this.cancel(),\n\t\t})\n\t}\n\n\tasync onConfirmOtp() {\n\t\tthis.otpState.inProgress = true\n\t\tconst authData = createSecondFactorAuthData({\n\t\t\ttype: SecondFactorType.totp,\n\t\t\tsession: this.authData.sessionId,\n\t\t\totpCode: this.otpState.code.replace(/ /g, \"\"),\n\t\t})\n\n\t\ttry {\n\t\t\tawait this.loginFacade.authenticateWithSecondFactor(authData)\n\t\t\tthis.waitingForSecondFactorDialog?.close()\n\t\t} catch (e) {\n\t\t\tif (e instanceof NotAuthenticatedError) {\n\t\t\t\tDialog.message(\"loginFailed_msg\")\n\t\t\t} else if (e instanceof BadRequestError) {\n\t\t\t\tDialog.message(\"loginFailed_msg\")\n\t\t\t} else if (e in AccessBlockedError) {\n\t\t\t\tDialog.message(\"loginFailedOften_msg\")\n\t\t\t\tthis.close()\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.otpState.inProgress = false\n\t\t}\n\t}\n\n\tprivate async cancel(): Promise<void> {\n\t\tthis.webauthnClient.abortCurrentOperation()\n\t\tawait this.loginFacade.cancelCreateSession(this.authData.sessionId)\n\t\tthis.close()\n\t}\n\n\tprivate async doWebauthn(u2fChallenge: Challenge) {\n\t\tthis.webauthnState = {\n\t\t\tstate: \"progress\",\n\t\t}\n\t\tconst sessionId = this.authData.sessionId\n\t\tconst challenge = assertNotNull(u2fChallenge.u2f)\n\n\t\ttry {\n\t\t\tconst webauthnResponseData = await this.webauthnClient.authenticate(challenge)\n\t\t\tconst authData = createSecondFactorAuthData({\n\t\t\t\ttype: SecondFactorType.webauthn,\n\t\t\t\tsession: sessionId,\n\t\t\t\twebauthn: webauthnResponseData,\n\t\t\t})\n\t\t\tawait this.loginFacade.authenticateWithSecondFactor(authData)\n\t\t} catch (e) {\n\t\t\tif (e instanceof CancelledError) {\n\t\t\t\tthis.webauthnState = {\n\t\t\t\t\tstate: \"init\",\n\t\t\t\t}\n\t\t\t} else if (e instanceof AccessBlockedError && this.waitingForSecondFactorDialog?.visible) {\n\t\t\t\tDialog.message(\"loginFailedOften_msg\")\n\t\t\t\tthis.close()\n\t\t\t} else if (e instanceof WebauthnError) {\n\t\t\t\tconsole.log(\"Error during webAuthn: \", e)\n\t\t\t\tthis.webauthnState = {\n\t\t\t\t\tstate: \"error\",\n\t\t\t\t\terror: \"couldNotAuthU2f_msg\",\n\t\t\t\t}\n\t\t\t} else if (e instanceof NotAuthenticatedError) {\n\t\t\t\tthis.webauthnState = {\n\t\t\t\t\tstate: \"init\",\n\t\t\t\t}\n\t\t\t\tDialog.message(\"loginFailed_msg\")\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t} finally {\n\t\t\tm.redraw()\n\t\t}\n\t}\n\n\tprivate async recoverLogin(mailAddress: string) {\n\t\tthis.cancel()\n\t\tconst dialog = await import(\"../../login/recover/RecoverLoginDialog\")\n\t\tdialog.show(mailAddress, \"secondFactor\")\n\t}\n}\n","import m from \"mithril\"\nimport type { Challenge, Session } from \"../../api/entities/sys/TypeRefs.js\"\nimport { createSecondFactorAuthData, SessionTypeRef } from \"../../api/entities/sys/TypeRefs.js\"\nimport { Dialog } from \"../../gui/base/Dialog\"\nimport { OperationType, SessionState } from \"../../api/common/TutanotaConstants\"\nimport { lang } from \"../LanguageViewModel\"\nimport { neverNull } from \"@tutao/tutanota-utils\"\nimport { NotFoundError } from \"../../api/common/error/RestError\"\nimport type { EntityUpdateData, EventController } from \"../../api/main/EventController\"\nimport { isUpdateForTypeRef } from \"../../api/main/EventController\"\nimport { isSameId } from \"../../api/common/utils/EntityUtils\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport type { EntityClient } from \"../../api/common/EntityClient\"\nimport { WebauthnClient } from \"./webauthn/WebauthnClient\"\nimport { SecondFactorAuthDialog } from \"./SecondFactorAuthDialog\"\nimport type { LoginFacade } from \"../../api/worker/facades/LoginFacade\"\n\nassertMainOrNode()\n\n/**\n * Handles showing and hiding of the following dialogs:\n * 1. Waiting for second factor approval (either token or by other client) during login\n * 2. Ask for approving the login on another client (setupAcceptOtherClientLoginListener() must have been called initially).\n *      If the dialog is visible and another client tries to login at the same time, that second login is ignored.\n */\nexport class SecondFactorHandler {\n\treadonly _eventController: EventController\n\treadonly _entityClient: EntityClient\n\treadonly _webauthnClient: WebauthnClient\n\treadonly _loginFacade: LoginFacade\n\t_otherLoginSessionId: IdTuple | null\n\t_otherLoginDialog: Dialog | null\n\t_otherLoginListenerInitialized: boolean\n\t_waitingForSecondFactorDialog: SecondFactorAuthDialog | null\n\n\tconstructor(eventController: EventController, entityClient: EntityClient, webauthnClient: WebauthnClient, loginFacade: LoginFacade) {\n\t\tthis._eventController = eventController\n\t\tthis._entityClient = entityClient\n\t\tthis._webauthnClient = webauthnClient\n\t\tthis._loginFacade = loginFacade\n\t\tthis._otherLoginSessionId = null\n\t\tthis._otherLoginDialog = null\n\t\tthis._otherLoginListenerInitialized = false\n\t\tthis._waitingForSecondFactorDialog = null\n\t}\n\n\tsetupAcceptOtherClientLoginListener() {\n\t\tif (this._otherLoginListenerInitialized) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._otherLoginListenerInitialized = true\n\t\tthis._eventController.addEntityListener((updates) => this._entityEventsReceived(updates))\n\t}\n\n\tasync _entityEventsReceived(updates: ReadonlyArray<EntityUpdateData>) {\n\t\tfor (const update of updates) {\n\t\t\tconst sessionId: IdTuple = [neverNull(update.instanceListId), update.instanceId]\n\n\t\t\tif (isUpdateForTypeRef(SessionTypeRef, update)) {\n\t\t\t\tif (update.operation === OperationType.CREATE) {\n\t\t\t\t\tlet session\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsession = await this._entityClient.load(SessionTypeRef, sessionId)\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (e instanceof NotFoundError) {\n\t\t\t\t\t\t\tconsole.log(\"Failed to load session\", e)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow e\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif (session.state === SessionState.SESSION_STATE_PENDING) {\n\t\t\t\t\t\tif (this._otherLoginDialog != null) {\n\t\t\t\t\t\t\tthis._otherLoginDialog.close()\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis._otherLoginSessionId = session._id\n\n\t\t\t\t\t\tthis._showConfirmLoginDialog(session)\n\t\t\t\t\t}\n\t\t\t\t} else if (update.operation === OperationType.UPDATE && this._otherLoginSessionId && isSameId(this._otherLoginSessionId, sessionId)) {\n\t\t\t\t\tlet session\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsession = await this._entityClient.load(SessionTypeRef, sessionId)\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (e instanceof NotFoundError) {\n\t\t\t\t\t\t\tconsole.log(\"Failed to load session\", e)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow e\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif (\n\t\t\t\t\t\tsession.state !== SessionState.SESSION_STATE_PENDING &&\n\t\t\t\t\t\tthis._otherLoginDialog &&\n\t\t\t\t\t\tisSameId(neverNull(this._otherLoginSessionId), sessionId)\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis._otherLoginDialog.close()\n\n\t\t\t\t\t\tthis._otherLoginSessionId = null\n\t\t\t\t\t\tthis._otherLoginDialog = null\n\t\t\t\t\t}\n\t\t\t\t} else if (update.operation === OperationType.DELETE && this._otherLoginSessionId && isSameId(this._otherLoginSessionId, sessionId)) {\n\t\t\t\t\tif (this._otherLoginDialog) {\n\t\t\t\t\t\tthis._otherLoginDialog.close()\n\n\t\t\t\t\t\tthis._otherLoginSessionId = null\n\t\t\t\t\t\tthis._otherLoginDialog = null\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t_showConfirmLoginDialog(session: Session) {\n\t\tlet text: string\n\n\t\tif (session.loginIpAddress) {\n\t\t\ttext = lang.get(\"secondFactorConfirmLogin_msg\", {\n\t\t\t\t\"{clientIdentifier}\": session.clientIdentifier,\n\t\t\t\t\"{ipAddress}\": session.loginIpAddress,\n\t\t\t})\n\t\t} else {\n\t\t\ttext = lang.get(\"secondFactorConfirmLoginNoIp_msg\", {\n\t\t\t\t\"{clientIdentifier}\": session.clientIdentifier,\n\t\t\t})\n\t\t}\n\n\t\tthis._otherLoginDialog = Dialog.showActionDialog({\n\t\t\ttitle: lang.get(\"secondFactorConfirmLogin_label\"),\n\t\t\tchild: {\n\t\t\t\tview: () => m(\".text-break.pt\", text),\n\t\t\t},\n\t\t\tokAction: async () => {\n\t\t\t\tawait this._loginFacade.authenticateWithSecondFactor(\n\t\t\t\t\tcreateSecondFactorAuthData({\n\t\t\t\t\t\tsession: session._id,\n\t\t\t\t\t\ttype: null, // Marker for confirming another session\n\t\t\t\t\t}),\n\t\t\t\t)\n\n\t\t\t\tif (this._otherLoginDialog) {\n\t\t\t\t\tthis._otherLoginDialog.close()\n\n\t\t\t\t\tthis._otherLoginSessionId = null\n\t\t\t\t\tthis._otherLoginDialog = null\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t\t// close the dialog manually after 1 min because the session is not updated if the other client is closed\n\t\tlet sessionId = session._id\n\t\tsetTimeout(() => {\n\t\t\tif (this._otherLoginDialog && isSameId(neverNull(this._otherLoginSessionId), sessionId)) {\n\t\t\t\tthis._otherLoginDialog.close()\n\n\t\t\t\tthis._otherLoginSessionId = null\n\t\t\t\tthis._otherLoginDialog = null\n\t\t\t}\n\t\t}, 60 * 1000)\n\t}\n\n\tcloseWaitingForSecondFactorDialog() {\n\t\tthis._waitingForSecondFactorDialog?.close()\n\t\tthis._waitingForSecondFactorDialog = null\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tasync showSecondFactorAuthenticationDialog(sessionId: IdTuple, challenges: ReadonlyArray<Challenge>, mailAddress: string | null) {\n\t\tif (this._waitingForSecondFactorDialog) {\n\t\t\treturn\n\t\t}\n\n\t\tthis._waitingForSecondFactorDialog = SecondFactorAuthDialog.show(\n\t\t\tthis._webauthnClient,\n\t\t\tthis._loginFacade,\n\t\t\t{\n\t\t\t\tsessionId,\n\t\t\t\tchallenges,\n\t\t\t\tmailAddress,\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tthis._waitingForSecondFactorDialog = null\n\t\t\t},\n\t\t)\n\t}\n}\n","export const WEBAUTHN_RP_ID = \"tutanota.com\"\nexport const U2f_APPID_SUFFIX = \"/u2f-appid.json\"\nexport const U2F_APPID = \"https://tutanota.com/u2f-appid.json\"\n","/** @file Types from Credential Management API. */\n\n/** see https://www.iana.org/assignments/cose/cose.xhtml#algorithms */\nexport enum COSEAlgorithmIdentifier {\n\tES256 = -7,\n\tES384 = -35,\n\tES512 = -36,\n\tEdDSA = -8,\n}\n","import { COSEAlgorithmIdentifier } from \"./WebauthnTypes.js\"\nimport { ProgrammingError } from \"../../../api/common/error/ProgrammingError.js\"\nimport { getApiOrigin, isApp } from \"../../../api/common/Env.js\"\nimport { U2F_APPID, U2f_APPID_SUFFIX, WEBAUTHN_RP_ID } from \"./WebAuthn.js\"\nimport { WebAuthnFacade } from \"../../../native/common/generatedipc/WebAuthnFacade.js\"\nimport { stringToUtf8Uint8Array } from \"@tutao/tutanota-utils\"\nimport { CancelledError } from \"../../../api/common/error/CancelledError.js\"\nimport { WebauthnError } from \"../../../api/common/error/WebauthnError.js\"\nimport { WebAuthnRegistrationChallenge } from \"../../../native/common/generatedipc/WebAuthnRegistrationChallenge.js\"\nimport { WebAuthnRegistrationResult } from \"../../../native/common/generatedipc/WebAuthnRegistrationResult.js\"\nimport { WebAuthnSignChallenge } from \"../../../native/common/generatedipc/WebAuthnSignChallenge.js\"\nimport { WebAuthnSignResult } from \"../../../native/common/generatedipc/WebAuthnSignResult.js\"\n\nconst WEBAUTHN_TIMEOUT_MS = 60000\n\n/** An actual webauthn implementation in browser. */\nexport class BrowserWebauthn implements WebAuthnFacade {\n\t/**\n\t * Relying Party Identifier\n\t * see https://www.w3.org/TR/webauthn-2/#public-key-credential-source-rpid\n\t */\n\tprivate readonly rpId: string\n\t/** Backward-compatible identifier for the legacy U2F API */\n\tprivate readonly appId: string\n\tprivate currentOperationSignal: AbortController | null = null\n\n\tconstructor(private readonly api: CredentialsContainer, hostname: string) {\n\t\tthis.rpId = this.rpIdFromHostname(hostname)\n\t\tthis.appId = this.appidFromHostname(hostname)\n\t}\n\n\tasync canAttemptChallengeForRpId(rpId: string): Promise<boolean> {\n\t\treturn rpId === this.rpId\n\t}\n\n\tasync canAttemptChallengeForU2FAppId(appId: string): Promise<boolean> {\n\t\treturn this.appId === appId\n\t}\n\n\t/**\n\t * test whether hardware key second factors are supported for this client\n\t */\n\tasync isSupported(): Promise<boolean> {\n\t\treturn (\n\t\t\t!isApp() &&\n\t\t\tthis.api != null &&\n\t\t\t// @ts-ignore see polyfill.js\n\t\t\t// We just stub BigInt in order to import cborg without issues but we can't actually use it\n\t\t\t!BigInt.polyfilled\n\t\t)\n\t}\n\n\tasync register({ challenge, userId, name, displayName }: WebAuthnRegistrationChallenge): Promise<WebAuthnRegistrationResult> {\n\t\tconst publicKeyCredentialCreationOptions: PublicKeyCredentialCreationOptions = {\n\t\t\tchallenge,\n\t\t\trp: {\n\t\t\t\tname: \"Tutanota\",\n\t\t\t\tid: this.rpId,\n\t\t\t},\n\t\t\tuser: {\n\t\t\t\tid: stringToUtf8Uint8Array(userId),\n\t\t\t\tname,\n\t\t\t\tdisplayName,\n\t\t\t},\n\t\t\tpubKeyCredParams: [\n\t\t\t\t{\n\t\t\t\t\talg: COSEAlgorithmIdentifier.ES256,\n\t\t\t\t\ttype: \"public-key\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tauthenticatorSelection: {\n\t\t\t\tauthenticatorAttachment: \"cross-platform\",\n\t\t\t\tuserVerification: \"discouraged\",\n\t\t\t},\n\t\t\ttimeout: WEBAUTHN_TIMEOUT_MS,\n\t\t\tattestation: \"none\",\n\t\t}\n\t\tthis.currentOperationSignal = new AbortController()\n\t\tconst credential = (await this.api.create({\n\t\t\tpublicKey: publicKeyCredentialCreationOptions,\n\t\t\tsignal: this.currentOperationSignal.signal,\n\t\t})) as PublicKeyCredential\n\n\t\treturn {\n\t\t\trpId: this.rpId,\n\t\t\trawId: new Uint8Array(credential.rawId),\n\t\t\tattestationObject: new Uint8Array((credential.response as AuthenticatorAttestationResponse).attestationObject),\n\t\t}\n\t}\n\n\tasync sign({ challenge, keys }: WebAuthnSignChallenge): Promise<WebAuthnSignResult> {\n\t\tconst allowCredentials: PublicKeyCredentialDescriptor[] = keys.map((key) => {\n\t\t\treturn {\n\t\t\t\tid: key.id,\n\t\t\t\ttype: \"public-key\",\n\t\t\t}\n\t\t})\n\t\tconst publicKeyCredentialRequestOptions: PublicKeyCredentialRequestOptions = {\n\t\t\tchallenge: challenge,\n\t\t\trpId: this.rpId,\n\t\t\tallowCredentials,\n\t\t\textensions: {\n\t\t\t\tappid: this.appId,\n\t\t\t},\n\t\t\tuserVerification: \"discouraged\",\n\t\t\ttimeout: WEBAUTHN_TIMEOUT_MS,\n\t\t}\n\t\tlet assertion\n\n\t\tthis.currentOperationSignal = new AbortController()\n\t\ttry {\n\t\t\tassertion = await this.api.get({\n\t\t\t\tpublicKey: publicKeyCredentialRequestOptions,\n\t\t\t\tsignal: this.currentOperationSignal.signal,\n\t\t\t})\n\t\t} catch (e) {\n\t\t\tif (e.name === \"AbortError\") {\n\t\t\t\tthrow new CancelledError(e)\n\t\t\t} else {\n\t\t\t\tthrow new WebauthnError(e)\n\t\t\t}\n\t\t}\n\n\t\tconst publicKeyCredential = assertion as PublicKeyCredential | null\n\n\t\tif (publicKeyCredential == null) {\n\t\t\tthrow new ProgrammingError(\"Webauthn credential could not be unambiguously resolved\")\n\t\t}\n\t\tconst assertionResponse = publicKeyCredential.response as AuthenticatorAssertionResponse\n\t\treturn {\n\t\t\trawId: new Uint8Array(publicKeyCredential.rawId),\n\t\t\tauthenticatorData: new Uint8Array(assertionResponse.authenticatorData),\n\t\t\tsignature: new Uint8Array(assertionResponse.signature),\n\t\t\tclientDataJSON: new Uint8Array(assertionResponse.clientDataJSON),\n\t\t}\n\t}\n\n\tasync abortCurrentOperation(): Promise<void> {\n\t\tthis.currentOperationSignal?.abort()\n\t\tthis.currentOperationSignal = null\n\t}\n\n\tprivate rpIdFromHostname(hostname: string): string {\n\t\tif (hostname.endsWith(WEBAUTHN_RP_ID)) {\n\t\t\treturn WEBAUTHN_RP_ID\n\t\t} else {\n\t\t\treturn hostname\n\t\t}\n\t}\n\n\tprivate appidFromHostname(hostname: string): string {\n\t\tif (hostname.endsWith(WEBAUTHN_RP_ID)) {\n\t\t\treturn U2F_APPID\n\t\t} else {\n\t\t\treturn getApiOrigin() + U2f_APPID_SUFFIX\n\t\t}\n\t}\n}\n","/** One part of the test. Has multiple metrics that are sent together. */\nexport class Stage {\n    constructor(number, test, minPings, maxPings) {\n        this.number = number;\n        this.test = test;\n        this.minPings = minPings;\n        this.maxPings = maxPings;\n        this.collectedMetrics = new Map();\n        this.metricConfigs = new Map();\n    }\n    /**\n     * Attempts to complete the stage and returns true if a ping has been sent successfully.\n     */\n    async complete() {\n        return await this.test.completeStage(this);\n    }\n    setMetric(metric) {\n        this.collectedMetrics.set(metric.name, metric);\n    }\n    setMetricConfig(metricConfig) {\n        this.metricConfigs.set(metricConfig.name, metricConfig);\n    }\n}\nexport class ObsoleteStage extends Stage {\n    async complete() {\n        return true;\n    }\n    setMetric(metric) {\n        // no op\n    }\n}\n","import { ObsoleteStage } from \"./Stage.js\";\nconst NO_PARTICIPATION_VARIANT = 0;\nconst ASSIGNMENT_STAGE = -1;\n/** Holds all variants and can render current variant. Combines a test's config and the user's assignment. */\nexport class UsageTest {\n    constructor(testId, testName, variant, active) {\n        this.testId = testId;\n        this.testName = testName;\n        this.variant = variant;\n        this.active = active;\n        this.stages = new Map();\n        this.lastCompletedStage = 0;\n        // storage for data that is aggregated across stages and sent at some point\n        this.meta = {};\n        /**\n         * Enabling this makes it possible to restart a test even if the last stage has not been sent.\n         */\n        this.allowEarlyRestarts = false;\n        this.sentPings = 0;\n        this.started = false;\n        // Enables recording the time that has passed since the last ping (and attaching it as a metric 'secondsPassed')\n        this.recordTime = false;\n    }\n    /**\n    Tries to restart the test (by sending stage 0) regardless of the allowEarlyRestarts setting\n     */\n    forceRestart() {\n        return this.completeStage(this.getStage(0), true);\n    }\n    isStarted() {\n        return this.started;\n    }\n    getStage(stageNum) {\n        const stage = this.stages.get(stageNum);\n        if (!stage) {\n            console.log(`Stage ${stageNum} is not registered, meaning that test '${this.testName}' is likely misconfigured`);\n            return new ObsoleteStage(0, this, 0, 0);\n        }\n        return stage;\n    }\n    addStage(stage) {\n        if (this.stages.get(stage.number)) {\n            throw new Error(`Stage ${stage.number} is already registered`);\n        }\n        this.stages.set(stage.number, stage);\n        return stage;\n    }\n    renderVariant(variants) {\n        return variants[this.variant]();\n    }\n    /**\n     * Completes a range of stages in the case that we want to make sure that previous stages are/have been sent.\n     *\n     * Useful when reaching a stage necessitates (and implies) that all previous stages have been sent successfully.\n     */\n    async completeRange(start, end) {\n        for (let i = start; i <= end; i++) {\n            await this.getStage(i).complete();\n        }\n    }\n    /**\n     * Should not be used directly. Use stage.complete() instead.\n     */\n    async completeStage(stage, forceRestart = false) {\n        if (!this.pingAdapter) {\n            throw new Error(\"no ping adapter has been registered\");\n        }\n        else if (this.variant === NO_PARTICIPATION_VARIANT || !this.active) {\n            return false;\n        }\n        else if (this.sentPings >= stage.maxPings && this.lastCompletedStage === stage.number && (stage.number !== 0 || !this.allowEarlyRestarts)) {\n            console.log(`Not sending ping for stage (${stage.number}) of test '${this.testId}' because maxPings=${stage.maxPings} has been reached`);\n            return false;\n        }\n        else if (!forceRestart && !this.allowEarlyRestarts && this.isStarted() && stage.number === 0 && this.lastCompletedStage !== this.stages.size - 1) {\n            // we were not configured to restart and got a complete() for the first stage and have not finished the test yet\n            // -> this would be a restart in the middle of the test\n            console.log(`Cannot restart test '${this.testName}' because allowEarlyRestarts=false and the final stage has not been reached`);\n            return false;\n        }\n        else if (stage.number < this.lastCompletedStage && stage.number !== 0) {\n            console.log(`Cannot send ping for stage (${stage.number}) of test '${this.testId}' because stage ${this.lastCompletedStage} has already been sent`);\n            return false;\n        }\n        for (let i = this.lastCompletedStage + 1; i < stage.number; i++) {\n            let currentStage = this.stages.get(i);\n            if (!!currentStage && currentStage.minPings != 0) {\n                console.log(`Not sending ping for stage (${stage.number}) in wrong order of test '${this.testId}' because stage ${currentStage.number} is not finished`);\n                return false;\n            }\n        }\n        console.log(`Test '${this.testName}': Completing stage ${stage.number}, variant ${this.variant}`);\n        this.sentPings = stage.number === this.lastCompletedStage ? this.sentPings + 1 : 1;\n        this.lastCompletedStage = stage.number;\n        if (this.recordTime) {\n            const currentDate = new Date();\n            if (stage.number > 0) {\n                const secondsPassed = this.lastPingDate ? (currentDate.getTime() - this.lastPingDate.getTime()) / 1000 : 0;\n                stage.setMetric({\n                    name: \"secondsPassed\",\n                    value: secondsPassed.toString(),\n                });\n            }\n            this.lastPingDate = currentDate;\n        }\n        await this.pingAdapter.sendPing(this, stage);\n        this.started = true;\n        return true;\n    }\n}\nexport class ObsoleteUsageTest extends UsageTest {\n    constructor(testId, testName, variant) {\n        super(testId, testName, variant, false);\n        this.obsoleteStage = new ObsoleteStage(0, this, 1, 1);\n    }\n    getStage(stageNum) {\n        return this.obsoleteStage;\n    }\n    addStage(stage) {\n        return this.obsoleteStage;\n    }\n    renderVariant(variants) {\n        return variants[0]();\n    }\n    async completeStage(stage) {\n        return true;\n    }\n}\n","import { ObsoleteUsageTest } from \"./UsageTest.js\";\n/** Centralized place which holds all the {@link UsageTest}s. */\nexport class UsageTestController {\n    constructor(pingAdapter) {\n        this.pingAdapter = pingAdapter;\n        this.tests = new Map();\n        this.obsoleteUsageTest = new ObsoleteUsageTest(\"obsolete\", \"obsolete\", 0);\n    }\n    addTest(test) {\n        test.pingAdapter = this.pingAdapter;\n        this.tests.set(test.testId, test);\n    }\n    addTests(tests) {\n        for (let test of tests) {\n            this.addTest(test);\n        }\n    }\n    setTests(tests) {\n        this.tests.clear();\n        this.addTests(tests);\n    }\n    /**\n     * Searches a test first by its ID and then, if no match is found, by its name.\n     * If no test matches by name either, then we assume that the test is finished and the server no longer sends assignments for it.\n     * In that case, we want to render the no-participation variant, so a sham test instance needs to be returned.\n     *\n     * @param testIdOrName The test's ID or its name\n     */\n    getTest(testIdOrName) {\n        let result = this.tests.get(testIdOrName);\n        if (result) {\n            return result;\n        }\n        for (let test of this.tests.values()) {\n            if (test.testName === testIdOrName) {\n                return test;\n            }\n        }\n        console.log(`Test '${testIdOrName}' not found, using obsolete...`);\n        return this.obsoleteUsageTest;\n    }\n    /**\n     * some components are used in multiple places, but only want to do a test in one of them.\n     * use this to get a test that always renders variant 0 and doesn't send pings.\n     */\n    getObsoleteTest() {\n        return this.obsoleteUsageTest;\n    }\n}\n","import {\n\tcreateUsageTestAssignmentIn,\n\tcreateUsageTestMetricData,\n\tcreateUsageTestParticipationIn,\n\tUsageTestAssignment,\n\tUsageTestAssignmentOut,\n\tUsageTestAssignmentTypeRef,\n} from \"../api/entities/usage/TypeRefs.js\"\nimport { PingAdapter, Stage, UsageTest, UsageTestController } from \"@tutao/tutanota-usagetests\"\nimport { assertNotNull, filterInt, lazy, neverNull } from \"@tutao/tutanota-utils\"\nimport { BadRequestError, NotFoundError, PreconditionFailedError } from \"../api/common/error/RestError\"\nimport { UsageTestMetricType } from \"../api/common/TutanotaConstants\"\nimport { SuspensionError } from \"../api/common/error/SuspensionError\"\nimport { SuspensionBehavior } from \"../api/worker/rest/RestClient\"\nimport { DateProvider } from \"../api/common/DateProvider.js\"\nimport { IServiceExecutor } from \"../api/common/ServiceRequest\"\nimport { UsageTestAssignmentService, UsageTestParticipationService } from \"../api/entities/usage/Services.js\"\nimport { resolveTypeReference } from \"../api/common/EntityFunctions\"\nimport { lang, TranslationKey } from \"./LanguageViewModel\"\nimport stream from \"mithril/stream\"\nimport { Dialog, DialogType } from \"../gui/base/Dialog\"\nimport { DropDownSelector, SelectorItem } from \"../gui/base/DropDownSelector\"\nimport m, { Children } from \"mithril\"\nimport { isOfflineError } from \"../api/common/utils/ErrorCheckUtils.js\"\nimport { LoginController } from \"../api/main/LoginController.js\"\nimport { CustomerProperties, CustomerPropertiesTypeRef, CustomerTypeRef } from \"../api/entities/sys/TypeRefs.js\"\nimport { EntityClient } from \"../api/common/EntityClient.js\"\nimport { EntityUpdateData, EventController, isUpdateForTypeRef } from \"../api/main/EventController.js\"\nimport { createUserSettingsGroupRoot, UserSettingsGroupRootTypeRef } from \"../api/entities/tutanota/TypeRefs.js\"\n\nconst PRESELECTED_LIKERT_VALUE = null\n\ntype ExperienceSamplingOptions = {\n\ttitle?: lazy<string> | string\n\texplanationText?: TranslationKey | lazy<string>\n\tperMetric: {\n\t\t[key: string]: {\n\t\t\tquestion: TranslationKey | lazy<string>\n\t\t\tanswerOptions: Array<string>\n\t\t}\n\t}\n}\n\nexport async function showExperienceSamplingDialog(stage: Stage, experienceSamplingOptions: ExperienceSamplingOptions): Promise<void> {\n\tconst likertMetrics = Array.from(stage.metricConfigs.values()).filter(\n\t\t(metricConfig) => (metricConfig.type as UsageTestMetricType) === UsageTestMetricType.Likert,\n\t)\n\tconst selectedValues = new Map(likertMetrics.map((likertMetric) => [likertMetric.name, stream(PRESELECTED_LIKERT_VALUE)]))\n\n\tDialog.showActionDialog({\n\t\ttype: DialogType.EditMedium,\n\t\tokAction: (dialog: Dialog) => {\n\t\t\tfor (let [metricName, selectedValue] of selectedValues) {\n\t\t\t\tconst selection = selectedValue()\n\n\t\t\t\tif (selection === null) {\n\t\t\t\t\t// User did not select an answer\n\t\t\t\t\treturn Dialog.message(\"experienceSamplingSelectAnswer_msg\")\n\t\t\t\t}\n\n\t\t\t\tstage.setMetric({\n\t\t\t\t\tname: metricName,\n\t\t\t\t\tvalue: selection,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tstage.complete().then(() => dialog.close())\n\t\t\treturn Dialog.message(\"experienceSamplingThankYou_msg\")\n\t\t},\n\t\ttitle: experienceSamplingOptions.title ?? lang.get(\"experienceSamplingHeader_label\"),\n\t\tchild: () => {\n\t\t\tconst children: Array<Children> = []\n\n\t\t\tif (experienceSamplingOptions.explanationText) {\n\t\t\t\tconst explanationTextLines = lang.getMaybeLazy(experienceSamplingOptions.explanationText).split(\"\\n\")\n\n\t\t\t\tchildren.push(\n\t\t\t\t\tm(\"#dialog-message.text-break.text-prewrap.selectable.scroll\", [explanationTextLines.map((line) => m(\".text-break.selectable\", line))]),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tfor (let likertMetricConfig of likertMetrics) {\n\t\t\t\tconst metricOptions = experienceSamplingOptions[\"perMetric\"][likertMetricConfig.name]\n\n\t\t\t\tconst answerOptionItems: Array<SelectorItem<string>> = metricOptions.answerOptions.map((answerOption, index) => {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: answerOption,\n\t\t\t\t\t\tvalue: (index + 1).toString(),\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tchildren.push(m(\"p.text-prewrap.scroll\", lang.getMaybeLazy(metricOptions.question)))\n\n\t\t\t\tchildren.push(\n\t\t\t\t\tm(DropDownSelector, {\n\t\t\t\t\t\tlabel: \"experienceSamplingAnswer_label\",\n\t\t\t\t\t\titems: answerOptionItems,\n\t\t\t\t\t\tselectedValue: selectedValues.get(likertMetricConfig.name)!,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\treturn children\n\t\t},\n\t})\n}\n\nexport interface PersistedAssignmentData {\n\tupdatedAt: number\n\tassignments: UsageTestAssignment[]\n\tusageModelVersion: number\n}\n\nexport interface UsageTestStorage {\n\tgetTestDeviceId(): Promise<string | null>\n\n\tstoreTestDeviceId(testDeviceId: string): Promise<void>\n\n\tgetAssignments(): Promise<PersistedAssignmentData | null>\n\n\tstoreAssignments(persistedAssignmentData: PersistedAssignmentData): Promise<void>\n}\n\nexport class EphemeralUsageTestStorage implements UsageTestStorage {\n\tprivate assignments: PersistedAssignmentData | null = null\n\tprivate testDeviceId: string | null = null\n\n\tgetAssignments(): Promise<PersistedAssignmentData | null> {\n\t\treturn Promise.resolve(this.assignments)\n\t}\n\n\tgetTestDeviceId(): Promise<string | null> {\n\t\treturn Promise.resolve(this.testDeviceId)\n\t}\n\n\tstoreAssignments(persistedAssignmentData: PersistedAssignmentData): Promise<void> {\n\t\tthis.assignments = persistedAssignmentData\n\t\treturn Promise.resolve()\n\t}\n\n\tstoreTestDeviceId(testDeviceId: string): Promise<void> {\n\t\tthis.testDeviceId = testDeviceId\n\t\treturn Promise.resolve()\n\t}\n}\n\nexport const ASSIGNMENT_UPDATE_INTERVAL_MS = 1000 * 60 * 60 // 1h\n\nexport const enum StorageBehavior {\n\t/* Store usage test assignments in the \"persistent\" storage. Currently, this is the client's instance of DeviceConfig, which uses the browser's local storage.\n\tUse if the user is logged in and has opted in to sending usage data. */\n\tPersist,\n\t/* Store usage test assignments in the \"ephemeral\" storage. Currently, this is an instance of EphemeralUsageTestStorage.\n\tUse if the user is not logged in. */\n\tEphemeral,\n}\n\nexport class UsageTestModel implements PingAdapter {\n\tprivate storageBehavior = StorageBehavior.Ephemeral\n\tprivate customerProperties?: CustomerProperties\n\tprivate lastOptInDecision: boolean | null = null\n\tprivate lastPing = Promise.resolve()\n\n\tconstructor(\n\t\tprivate readonly storages: { [key in StorageBehavior]: UsageTestStorage },\n\t\tprivate readonly dateProvider: DateProvider,\n\t\tprivate readonly serviceExecutor: IServiceExecutor,\n\t\tprivate readonly entityClient: EntityClient,\n\t\tprivate readonly loginController: LoginController,\n\t\tprivate readonly eventController: EventController,\n\t\tprivate readonly usageTestController: () => UsageTestController,\n\t) {\n\t\teventController.addEntityListener((updates: ReadonlyArray<EntityUpdateData>) => {\n\t\t\treturn this.entityEventsReceived(updates)\n\t\t})\n\t}\n\n\tasync entityEventsReceived(updates: ReadonlyArray<EntityUpdateData>) {\n\t\tfor (const update of updates) {\n\t\t\tif (isUpdateForTypeRef(CustomerPropertiesTypeRef, update)) {\n\t\t\t\tawait this.updateCustomerProperties()\n\t\t\t} else if (isUpdateForTypeRef(UserSettingsGroupRootTypeRef, update)) {\n\t\t\t\tconst updatedOptInDecision = this.loginController.getUserController().userSettingsGroupRoot.usageDataOptedIn\n\n\t\t\t\tif (this.lastOptInDecision === updatedOptInDecision) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Opt-in decision has changed, load tests\n\t\t\t\tconst tests = await this.loadActiveUsageTests()\n\t\t\t\tthis.usageTestController().setTests(tests)\n\t\t\t\tthis.lastOptInDecision = updatedOptInDecision\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * only for usage from the console. may have unintended consequences when used too early or too late.\n\t * @param test the name of the test to change the variant on\n\t * @param variant the number of the variant to use from here on\n\t */\n\tprivate setVariant(test: string, variant: number) {\n\t\tthis.usageTestController().getTest(test).variant = variant\n\t}\n\n\tprivate async updateCustomerProperties() {\n\t\tconst customer = await this.entityClient.load(CustomerTypeRef, neverNull(this.loginController.getUserController().user.customer))\n\t\tthis.customerProperties = await this.entityClient.load(CustomerPropertiesTypeRef, neverNull(customer.properties))\n\t}\n\n\t/**\n\t * Needs to be called after construction, ideally after login, so that the logged-in user's CustomerProperties are loaded.\n\t */\n\tasync init() {\n\t\tawait this.updateCustomerProperties()\n\t}\n\n\tsetStorageBehavior(storageBehavior: StorageBehavior) {\n\t\tthis.storageBehavior = storageBehavior\n\t}\n\n\tprivate storage() {\n\t\treturn this.storages[this.storageBehavior]\n\t}\n\n\t/**\n\t * Returns true if the customer has opted out.\n\t * Defaults to true if init() has not been called.\n\t */\n\tisCustomerOptedOut(): boolean {\n\t\treturn this.customerProperties?.usageDataOptedOut ?? true\n\t}\n\n\t/**\n\t * Returns true if the opt-in dialog indicator should be shown, depending on the user's and the customer's decisions.\n\t * Defaults to false if init() has not been called.\n\t */\n\tshowOptInIndicator(): boolean {\n\t\tif (!this.loginController.isUserLoggedIn() || this.isCustomerOptedOut()) {\n\t\t\t// shortcut if customer has opted out (or is not logged in)\n\t\t\treturn false\n\t\t}\n\n\t\treturn this.loginController.getUserController().userSettingsGroupRoot.usageDataOptedIn === null\n\t}\n\n\t/**\n\t * Sets the user's usage data opt-in decision. True means they opt in.\n\t *\n\t * Immediately refetches the user's active usage tests if they opted in.\n\t */\n\tpublic async setOptInDecision(decision: boolean) {\n\t\tconst userSettingsGroupRoot = createUserSettingsGroupRoot(this.loginController.getUserController().userSettingsGroupRoot)\n\t\tuserSettingsGroupRoot.usageDataOptedIn = decision\n\n\t\tawait this.entityClient.update(userSettingsGroupRoot)\n\t\tthis.lastOptInDecision = decision\n\n\t\tif (decision) {\n\t\t\tconst tests = await this.doLoadActiveUsageTests()\n\t\t\tthis.usageTestController().setTests(tests)\n\t\t}\n\t}\n\n\tprivate getOptInDecision(): boolean {\n\t\tif (!this.loginController.isUserLoggedIn()) {\n\t\t\treturn false\n\t\t}\n\n\t\tconst userOptIn = this.loginController.getUserController().userSettingsGroupRoot.usageDataOptedIn\n\n\t\tif (!userOptIn) {\n\t\t\t// shortcut if userOptIn not set or equal to false\n\t\t\treturn false\n\t\t}\n\n\t\t// customer opt-out overrides the user setting\n\t\treturn !assertNotNull(this.customerProperties).usageDataOptedOut\n\t}\n\n\t/**\n\t * If the storageBehavior is set to StorageBehavior.Persist, then init() must have been called before calling this method.\n\t */\n\tasync loadActiveUsageTests() {\n\t\tif (this.storageBehavior === StorageBehavior.Persist && !this.getOptInDecision()) {\n\t\t\treturn []\n\t\t}\n\n\t\treturn await this.doLoadActiveUsageTests()\n\t}\n\n\tprivate async doLoadActiveUsageTests() {\n\t\tconst persistedData = await this.storage().getAssignments()\n\t\tconst modelVersion = await this.modelVersion()\n\n\t\tif (persistedData == null || persistedData.usageModelVersion !== modelVersion || Date.now() - persistedData.updatedAt > ASSIGNMENT_UPDATE_INTERVAL_MS) {\n\t\t\treturn this.assignmentsToTests(await this.loadAssignments())\n\t\t} else {\n\t\t\treturn this.assignmentsToTests(persistedData.assignments)\n\t\t}\n\t}\n\n\tprivate async modelVersion(): Promise<number> {\n\t\tconst model = await resolveTypeReference(UsageTestAssignmentTypeRef)\n\t\treturn filterInt(model.version)\n\t}\n\n\tprivate async loadAssignments(): Promise<UsageTestAssignment[]> {\n\t\tconst testDeviceId = await this.storage().getTestDeviceId()\n\t\tconst data = createUsageTestAssignmentIn({\n\t\t\ttestDeviceId: testDeviceId,\n\t\t})\n\n\t\ttry {\n\t\t\tconst response: UsageTestAssignmentOut = testDeviceId\n\t\t\t\t? await this.serviceExecutor.put(UsageTestAssignmentService, data, {\n\t\t\t\t\t\tsuspensionBehavior: SuspensionBehavior.Throw,\n\t\t\t\t  })\n\t\t\t\t: await this.serviceExecutor.post(UsageTestAssignmentService, data, {\n\t\t\t\t\t\tsuspensionBehavior: SuspensionBehavior.Throw,\n\t\t\t\t  })\n\t\t\tawait this.storage().storeTestDeviceId(response.testDeviceId)\n\t\t\tawait this.storage().storeAssignments({\n\t\t\t\tassignments: response.assignments,\n\t\t\t\tupdatedAt: this.dateProvider.now(),\n\t\t\t\tusageModelVersion: await this.modelVersion(),\n\t\t\t})\n\n\t\t\treturn response.assignments\n\t\t} catch (e) {\n\t\t\tif (e instanceof SuspensionError) {\n\t\t\t\tconsole.log(\"rate-limit for new assignments reached, disabling tests\")\n\t\t\t\treturn []\n\t\t\t} else if (isOfflineError(e)) {\n\t\t\t\tconsole.log(\"offline, disabling tests\")\n\t\t\t\treturn []\n\t\t\t}\n\n\t\t\tthrow e\n\t\t}\n\t}\n\n\tprivate assignmentsToTests(assignments: UsageTestAssignment[]): UsageTest[] {\n\t\treturn assignments.map((usageTestAssignment) => {\n\t\t\tconst test = new UsageTest(usageTestAssignment.testId, usageTestAssignment.name, Number(usageTestAssignment.variant), usageTestAssignment.sendPings)\n\n\t\t\tfor (const [index, stageConfig] of usageTestAssignment.stages.entries()) {\n\t\t\t\tconst stage = new Stage(index, test, Number(stageConfig.minPings), Number(stageConfig.maxPings))\n\t\t\t\tstageConfig.metrics.forEach((metricConfig) => {\n\t\t\t\t\tconst configValues = new Map<string, string>()\n\n\t\t\t\t\tmetricConfig.configValues.forEach((metricConfigValue) => {\n\t\t\t\t\t\tconfigValues.set(metricConfigValue.key, metricConfigValue.value)\n\t\t\t\t\t})\n\n\t\t\t\t\tstage.setMetricConfig({\n\t\t\t\t\t\tname: metricConfig.name,\n\t\t\t\t\t\ttype: metricConfig.type,\n\t\t\t\t\t\tconfigValues,\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\ttest.addStage(stage)\n\t\t\t}\n\n\t\t\treturn test\n\t\t})\n\t}\n\n\tasync sendPing(test: UsageTest, stage: Stage): Promise<void> {\n\t\tthis.lastPing = this.lastPing.then(\n\t\t\t() => this.doSendPing(stage, test),\n\t\t\t() => this.doSendPing(stage, test),\n\t\t)\n\t\treturn this.lastPing\n\t}\n\n\tprivate async doSendPing(stage: Stage, test: UsageTest) {\n\t\t// Immediately stop sending pings if the user has opted out.\n\t\t// Only applicable if the user opts out and then does not re-log.\n\t\tif (this.storageBehavior === StorageBehavior.Persist && !this.getOptInDecision()) {\n\t\t\treturn\n\t\t}\n\n\t\tconst testDeviceId = await this.storage().getTestDeviceId()\n\t\tif (testDeviceId == null) {\n\t\t\tconsole.warn(\"No device id set before sending pings\")\n\t\t\treturn\n\t\t}\n\n\t\tconst metrics = Array.from(stage.collectedMetrics).map(([key, { name, value }]) =>\n\t\t\tcreateUsageTestMetricData({\n\t\t\t\tname: name,\n\t\t\t\tvalue: value,\n\t\t\t}),\n\t\t)\n\n\t\tconst data = createUsageTestParticipationIn({\n\t\t\ttestId: test.testId,\n\t\t\tmetrics,\n\t\t\tstage: stage.number.toString(),\n\t\t\ttestDeviceId: testDeviceId,\n\t\t})\n\n\t\ttry {\n\t\t\tawait this.serviceExecutor.post(UsageTestParticipationService, data, {\n\t\t\t\tsuspensionBehavior: SuspensionBehavior.Throw,\n\t\t\t})\n\t\t} catch (e) {\n\t\t\tif (e instanceof SuspensionError) {\n\t\t\t\ttest.active = false\n\t\t\t\tconsole.log(\"rate-limit for pings reached\")\n\t\t\t} else if (e instanceof PreconditionFailedError) {\n\t\t\t\tif (e.data === \"invalid_state\") {\n\t\t\t\t\ttest.active = false\n\t\t\t\t\tconsole.log(`Tried to send ping for paused test ${test.testName}`, e)\n\t\t\t\t} else if (e.data === \"invalid_restart\") {\n\t\t\t\t\ttest.active = false\n\t\t\t\t\tconsole.log(`Tried to restart test '${test.testName}' in ParticipationMode.Once that device has already participated in`, e)\n\t\t\t\t} else if (e.data === \"invalid_stage\") {\n\t\t\t\t\tconsole.log(`Tried to send ping for wrong stage ${stage.number} of test '${test.testName}'`, e)\n\t\t\t\t} else if (e.data === \"invalid_stage_skip\") {\n\t\t\t\t\tconsole.log(`Tried to skip a required stage before stage ${stage.number} of test '${test.testName}'`, e)\n\t\t\t\t} else if (e.data === \"invalid_stage_repetition\") {\n\t\t\t\t\tconsole.log(`Tried to repeat stage ${stage.number} of test '${test.testName}' too many times`, e)\n\t\t\t\t} else {\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t} else if (e instanceof NotFoundError) {\n\t\t\t\t// Cached assignments are likely out of date if we run into a NotFoundError here.\n\t\t\t\t// We should not attempt to re-send pings, as the relevant test has likely been deleted.\n\t\t\t\t// Hence, we just remove the cached assignment and disable the test.\n\t\t\t\ttest.active = false\n\t\t\t\tconsole.log(`Tried to send ping. Removing test '${test.testName}' from storage`, e)\n\n\t\t\t\tconst storedAssignments = await this.storage().getAssignments()\n\t\t\t\tif (storedAssignments) {\n\t\t\t\t\tawait this.storage().storeAssignments({\n\t\t\t\t\t\tupdatedAt: storedAssignments.updatedAt,\n\t\t\t\t\t\tusageModelVersion: storedAssignments.usageModelVersion,\n\t\t\t\t\t\tassignments: storedAssignments.assignments.filter((assignment) => assignment.testId !== test.testId),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if (e instanceof BadRequestError) {\n\t\t\t\ttest.active = false\n\t\t\t\tconsole.log(`Tried to send ping. Setting test '${test.testName}' inactive because it is misconfigured`, e)\n\t\t\t} else if (isOfflineError(e)) {\n\t\t\t\tconsole.log(\"Tried to send ping, but we are offline\", e)\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t}\n\t}\n}\n","import { SecondFactorHandler } from \"../../misc/2fa/SecondFactorHandler.js\"\nimport { defer, DeferredObject } from \"@tutao/tutanota-utils\"\nimport { Challenge } from \"../entities/sys/TypeRefs.js\"\nimport { LoginListener } from \"../worker/facades/LoginFacade.js\"\n\nexport const enum LoginFailReason {\n\tSessionExpired,\n\tError,\n}\n\n/** Listener for the login events from the worker side. */\nexport class PageContextLoginListener implements LoginListener {\n\tprivate loginPromise: DeferredObject<void> = defer()\n\tprivate fullLoginFailed: boolean = false\n\n\tconstructor(private readonly secondFactorHandler: SecondFactorHandler) {}\n\n\t/** e.g. after temp logout */\n\treset() {\n\t\tthis.loginPromise = defer()\n\t\tthis.fullLoginFailed = false\n\t}\n\n\twaitForFullLogin(): Promise<void> {\n\t\treturn this.loginPromise.promise\n\t}\n\n\t/**\n\t * Partial login reached: cached entities and user are available.\n\t */\n\tonPartialLoginSuccess(): Promise<void> {\n\t\treturn Promise.resolve()\n\t}\n\n\t/**\n\t * Full login reached: any network requests can be made\n\t */\n\tasync onFullLoginSuccess(): Promise<void> {\n\t\tthis.fullLoginFailed = false\n\t\tthis.loginPromise.resolve()\n\t}\n\n\t/**\n\t * call when the login fails for invalid session or other reasons\n\t */\n\tasync onLoginFailure(reason: LoginFailReason): Promise<void> {\n\t\tthis.fullLoginFailed = true\n\t\tif (reason === LoginFailReason.SessionExpired) {\n\t\t\tconst { reloginForExpiredSession } = await import(\"../../misc/ErrorHandlerImpl.js\")\n\t\t\tawait reloginForExpiredSession()\n\t\t}\n\t}\n\n\t/**\n\t * call when retrying full login\n\t */\n\tonRetryLogin(): void {\n\t\tthis.fullLoginFailed = false\n\t}\n\n\t/**\n\t * Shows a dialog with possibility to use second factor and with a message that the login can be approved from another client.\n\t */\n\tonSecondFactorChallenge(sessionId: IdTuple, challenges: ReadonlyArray<Challenge>, mailAddress: string | null): Promise<void> {\n\t\treturn this.secondFactorHandler.showSecondFactorAuthenticationDialog(sessionId, challenges, mailAddress)\n\t}\n\n\t/**\n\t * true if the last full login attempt failed\n\t * may revert to false when retrying.\n\t */\n\tgetFullLoginFailed(): boolean {\n\t\treturn this.fullLoginFailed\n\t}\n}\n","import { DataFile } from \"../api/common/DataFile\"\nimport { assertMainOrNode } from \"../api/common/Env\"\nimport { File as TutanotaFile } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { ProgressObserver, FileController, openDataFileInBrowser, zipDataFiles } from \"./FileController.js\"\nimport { sortableTimestamp } from \"@tutao/tutanota-utils\"\nimport { BlobFacade } from \"../api/worker/facades/lazy/BlobFacade.js\"\nimport { FileFacade } from \"../api/worker/facades/lazy/FileFacade.js\"\nimport { assertOnlyDataFiles, FileReference } from \"../api/common/utils/FileUtils.js\"\nimport { ProgrammingError } from \"../api/common/error/ProgrammingError.js\"\n\nassertMainOrNode()\n\nexport class FileControllerBrowser extends FileController {\n\tconstructor(blobFacade: BlobFacade, fileFacade: FileFacade, guiDownload: ProgressObserver) {\n\t\tsuper(blobFacade, fileFacade, guiDownload)\n\t}\n\n\tasync saveDataFile(file: DataFile): Promise<void> {\n\t\treturn openDataFileInBrowser(file)\n\t}\n\n\tasync downloadAndDecrypt(file: TutanotaFile): Promise<DataFile | FileReference> {\n\t\treturn this.getAsDataFile(file)\n\t}\n\n\tasync writeDownloadedFiles(downloadedFiles: Array<FileReference | DataFile>): Promise<void> {\n\t\tif (downloadedFiles.length < 1) {\n\t\t\treturn\n\t\t}\n\t\tassertOnlyDataFiles(downloadedFiles)\n\t\tconst fileToSave = downloadedFiles.length > 1 ? await zipDataFiles(downloadedFiles, `${sortableTimestamp()}-attachments.zip`) : downloadedFiles[0]\n\t\treturn await openDataFileInBrowser(fileToSave)\n\t}\n\n\tasync cleanUp(downloadedFiles: DataFile[]): Promise<void> {\n\t\t// there is nothing to do since nothing gets saved until the browser puts it into the final location\n\t}\n\n\tprotected async openDownloadedFiles(downloadedFiles: Array<FileReference | DataFile>): Promise<void> {\n\t\t// opening and downloading a file is the same thing in browser environment\n\t\treturn await this.writeDownloadedFiles(downloadedFiles)\n\t}\n}\n","import { Dialog } from \"../gui/base/Dialog\"\nimport { DataFile } from \"../api/common/DataFile\"\nimport { assertMainOrNode, isAndroidApp, isApp, isDesktop, isElectronClient, isIOSApp, isTest } from \"../api/common/Env\"\nimport { assert, assertNotNull, promiseMap, sortableTimestamp } from \"@tutao/tutanota-utils\"\nimport { File as TutanotaFile } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { assertOnlyFileReferences, FileReference } from \"../api/common/utils/FileUtils\"\nimport { CancelledError } from \"../api/common/error/CancelledError\"\nimport type { NativeFileApp } from \"../native/common/FileApp\"\nimport { ArchiveDataType } from \"../api/common/TutanotaConstants\"\nimport { BlobFacade } from \"../api/worker/facades/lazy/BlobFacade.js\"\nimport { FileFacade } from \"../api/worker/facades/lazy/FileFacade.js\"\nimport { createReferencingInstance, FileController, isLegacyFile, ProgressObserver, zipDataFiles } from \"./FileController.js\"\nimport { ProgrammingError } from \"../api/common/error/ProgrammingError.js\"\n\nassertMainOrNode()\n\n/**\n * coordinates downloads when we have access to native functionality\n */\nexport class FileControllerNative extends FileController {\n\tconstructor(blobFacade: BlobFacade, fileFacade: FileFacade, guiDownload: ProgressObserver, private readonly fileApp: NativeFileApp) {\n\t\tassert(isElectronClient() || isApp() || isTest(), \"Don't make native file controller when not in native\")\n\t\tsuper(blobFacade, fileFacade, guiDownload)\n\t}\n\n\tprotected async cleanUp(files: Array<FileReference | DataFile>) {\n\t\tassertOnlyFileReferences(files)\n\t\tif (files.length > 0) {\n\t\t\tfor (const file of files) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.fileApp.deleteFile(file.location)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.log(\"failed to delete file\", file.location, e)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Does not delete temporary file in app.\n\t */\n\tasync saveDataFile(file: DataFile): Promise<void> {\n\t\t// For apps \"opening\" DataFile currently means saving and opening it.\n\t\ttry {\n\t\t\tconst fileReference = await this.fileApp.writeDataFile(file)\n\t\t\tif (isAndroidApp() || isDesktop()) {\n\t\t\t\tawait this.fileApp.putFileIntoDownloadsFolder(fileReference.location)\n\t\t\t\treturn\n\t\t\t} else if (isIOSApp()) {\n\t\t\t\treturn this.fileApp.open(fileReference)\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tif (e instanceof CancelledError) {\n\t\t\t\t// no-op. User cancelled file dialog\n\t\t\t\tconsole.log(\"saveDataFile cancelled\")\n\t\t\t} else {\n\t\t\t\tconsole.warn(\"openDataFile failed\", e)\n\t\t\t\tawait Dialog.message(\"canNotOpenFileOnDevice_msg\")\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Public for testing */\n\tasync downloadAndDecrypt(tutanotaFile: TutanotaFile): Promise<FileReference> {\n\t\tif (isLegacyFile(tutanotaFile)) {\n\t\t\treturn await this.fileFacade.downloadFileContentNative(tutanotaFile)\n\t\t} else {\n\t\t\treturn await this.blobFacade.downloadAndDecryptNative(\n\t\t\t\tArchiveDataType.Attachments,\n\t\t\t\tcreateReferencingInstance(tutanotaFile),\n\t\t\t\ttutanotaFile.name,\n\t\t\t\tassertNotNull(tutanotaFile.mimeType, \"tried to call blobfacade.downloadAndDecryptNative with null mimeType\"),\n\t\t\t)\n\t\t}\n\t}\n\n\tasync writeDownloadedFiles(downloadedFiles: FileReference[]): Promise<void> {\n\t\tif (isIOSApp()) {\n\t\t\tawait this.processDownloadedFilesIOS(downloadedFiles)\n\t\t} else if (isDesktop()) {\n\t\t\tawait this.processDownloadedFilesDesktop(downloadedFiles)\n\t\t} else if (isAndroidApp()) {\n\t\t\tawait promiseMap(downloadedFiles, (file) => this.fileApp.putFileIntoDownloadsFolder(file.location))\n\t\t} else {\n\t\t\tthrow new ProgrammingError(\"in filecontroller native but not in ios, android or desktop? - tried to write\")\n\t\t}\n\t}\n\n\tasync openDownloadedFiles(downloadedFiles: FileReference[]): Promise<void> {\n\t\tif (isIOSApp()) {\n\t\t\tawait this.processDownloadedFilesIOS(downloadedFiles)\n\t\t} else if (isDesktop() || isAndroidApp()) {\n\t\t\tawait this.openFiles(downloadedFiles)\n\t\t} else {\n\t\t\tthrow new ProgrammingError(\"in filecontroller native but not in ios, android or desktop? - tried to open\")\n\t\t}\n\t}\n\n\t/**\n\t * for downloading multiple files on desktop. multiple files are bundled in a zip file, single files\n\t *\n\t * we could use the same strategy as on android, but\n\t * if the user doesn't have a default dl path selected on desktop,\n\t * the client will ask for a location for each file separately, so we zip them for now.\n\t */\n\tprivate async processDownloadedFilesDesktop(downloadedFiles: FileReference[]): Promise<void> {\n\t\tif (downloadedFiles.length < 1) {\n\t\t\treturn\n\t\t}\n\t\tconsole.log(\"downloaded files in processing\", downloadedFiles)\n\t\tconst dataFiles = (await promiseMap(downloadedFiles, (f) => this.fileApp.readDataFile(f.location))).filter(Boolean)\n\t\tconst fileInTemp =\n\t\t\tdataFiles.length === 1\n\t\t\t\t? downloadedFiles[0]\n\t\t\t\t: await this.fileApp.writeDataFile(await zipDataFiles(dataFiles as Array<DataFile>, `${sortableTimestamp()}-attachments.zip`))\n\t\tawait this.fileApp.putFileIntoDownloadsFolder(fileInTemp.location).finally(async () => {\n\t\t\ttry {\n\t\t\t\tawait this.fileApp.deleteFile(fileInTemp.location)\n\t\t\t} catch (e) {\n\t\t\t\tconsole.log(\"failed to delete file\", fileInTemp.location, e)\n\t\t\t}\n\t\t})\n\t}\n\n\t// on iOS, we don't actually show downloadAll and open the attachment immediately\n\t// the user is presented with an option to save the file to their file system by the OS\n\tprivate async processDownloadedFilesIOS(downloadedFiles: FileReference[]): Promise<void> {\n\t\tawait promiseMap(downloadedFiles, async (file) => {\n\t\t\ttry {\n\t\t\t\tawait this.fileApp.open(file)\n\t\t\t} finally {\n\t\t\t\tawait this.fileApp.deleteFile(file.location).catch((e: any) => console.log(\"failed to delete file\", file.location, e))\n\t\t\t}\n\t\t})\n\t}\n\n\tprivate async openFiles(downloadedFiles: FileReference[]): Promise<void[]> {\n\t\treturn promiseMap(downloadedFiles, async (file) => {\n\t\t\ttry {\n\t\t\t\tawait this.fileApp.open(file)\n\t\t\t} finally {\n\t\t\t\t// on desktop, we don't get to know when the other app is done with the file, so we leave cleanup to the OS\n\t\t\t\tif (isApp()) await this.fileApp.deleteFile(file.location).catch((e: any) => console.log(\"failed to delete file\", file.location, e))\n\t\t\t}\n\t\t})\n\t}\n}\n","import { createNewsIn, NewsId, NewsOut } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { IServiceExecutor } from \"../../api/common/ServiceRequest.js\"\nimport { NewsService } from \"../../api/entities/tutanota/Services.js\"\nimport { NotFoundError } from \"../../api/common/error/RestError.js\"\nimport { NewsListItem } from \"./NewsListItem.js\"\nimport { isIOSApp } from \"../../api/common/Env.js\"\n\n/**\n * Interface for storing information about displayed news items on the device.\n */\nexport interface NewsItemStorage {\n\tacknowledgeNewsItemForDevice(newsId: Id): void\n\n\thasAcknowledgedNewsItemForDevice(newsId: Id): boolean\n}\n\n/**\n * Makes calls to the NewsService in order to load the user's unacknowledged NewsItems and stores them.\n */\nexport class NewsModel {\n\tliveNewsIds: NewsId[] = []\n\tliveNewsListItems: Record<string, NewsListItem> = {}\n\n\tconstructor(\n\t\tprivate readonly serviceExecutor: IServiceExecutor,\n\t\tprivate readonly storage: NewsItemStorage,\n\t\tprivate readonly newsListItemFactory: (name: string) => Promise<NewsListItem | null>,\n\t) {}\n\n\t/**\n\t * Loads the user's unacknowledged NewsItems.\n\t */\n\tasync loadNewsIds(): Promise<NewsId[]> {\n\t\tconst response: NewsOut = await this.serviceExecutor.get(NewsService, null)\n\n\t\tthis.liveNewsIds = []\n\t\tthis.liveNewsListItems = {}\n\n\t\tfor (const newsItemId of response.newsItemIds) {\n\t\t\tconst newsItemName = newsItemId.newsItemName\n\t\t\tconst newsListItem = await this.newsListItemFactory(newsItemName)\n\n\t\t\tif (!!newsListItem && (await newsListItem.isShown(newsItemId))) {\n\t\t\t\t// we can't display those news items unless we allow apple payments\n\t\t\t\tconst unsupportedIosNewsItem = isIOSApp() && [\"newPlans\", \"newPlansOfferEnding\"].includes(newsItemId.newsItemName)\n\t\t\t\tif (!unsupportedIosNewsItem) {\n\t\t\t\t\tthis.liveNewsIds.push(newsItemId)\n\t\t\t\t\tthis.liveNewsListItems[newsItemName] = newsListItem\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.liveNewsIds\n\t}\n\n\t/**\n\t * Acknowledges the NewsItem with the given ID.\n\t */\n\tasync acknowledgeNews(newsItemId: Id): Promise<boolean> {\n\t\tconst data = createNewsIn({ newsItemId })\n\n\t\ttry {\n\t\t\tawait this.serviceExecutor.post(NewsService, data)\n\t\t\treturn true\n\t\t} catch (e) {\n\t\t\tif (e instanceof NotFoundError) {\n\t\t\t\t// NewsItem not found, likely deleted on the server\n\t\t\t\tconsole.log(`Could not acknowledge newsItem with ID '${newsItemId}'`)\n\t\t\t\treturn false\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t} finally {\n\t\t\tawait this.loadNewsIds()\n\t\t}\n\t}\n\n\tacknowledgeNewsForDevice(newsItemId: Id) {\n\t\treturn this.storage.acknowledgeNewsItemForDevice(newsItemId)\n\t}\n\n\thasAcknowledgedNewsForDevice(newsItemId: Id): boolean {\n\t\treturn this.storage.hasAcknowledgedNewsItemForDevice(newsItemId)\n\t}\n}\n","import { WsConnectionState } from \"../api/main/WorkerClient.js\"\nimport stream from \"mithril/stream\"\nimport { identity } from \"@tutao/tutanota-utils\"\nimport { CloseEventBusOption } from \"../api/common/TutanotaConstants.js\"\nimport { ExposedEventBus } from \"../api/worker/WorkerImpl.js\"\nimport { WebsocketLeaderStatus } from \"../api/entities/sys/TypeRefs.js\"\n\nexport interface WebsocketConnectivityListener {\n\tupdateWebSocketState(wsConnectionState: WsConnectionState): Promise<void>\n\tonLeaderStatusChanged(leaderStatus: WebsocketLeaderStatus): Promise<void>\n}\n\n/** A web page thread view on websocket/event bus. */\nexport class WebsocketConnectivityModel implements WebsocketConnectivityListener {\n\tprivate readonly wsState = stream<WsConnectionState>(WsConnectionState.terminated)\n\tprivate leaderStatus: boolean = false\n\n\tconstructor(private readonly eventBus: ExposedEventBus) {}\n\n\tasync updateWebSocketState(wsConnectionState: WsConnectionState): Promise<void> {\n\t\tthis.wsState(wsConnectionState)\n\t}\n\n\tasync onLeaderStatusChanged(leaderStatus: WebsocketLeaderStatus): Promise<void> {\n\t\tthis.leaderStatus = leaderStatus.leaderStatus\n\t}\n\n\tisLeader(): boolean {\n\t\treturn this.leaderStatus\n\t}\n\n\twsConnection(): stream<WsConnectionState> {\n\t\t// .map() to make a defensive copy\n\t\treturn this.wsState.map(identity)\n\t}\n\n\ttryReconnect(closeIfOpen: boolean, enableAutomaticState: boolean, delay: number | null = null): Promise<void> {\n\t\treturn this.eventBus.tryReconnect(closeIfOpen, enableAutomaticState, delay)\n\t}\n\n\tclose(option: CloseEventBusOption) {\n\t\treturn this.eventBus.close(option)\n\t}\n}\n","import stream from \"mithril/stream\"\nimport Stream from \"mithril/stream\"\n\nexport type OperationId = number\n\nexport type ExposedOperationProgressTracker = Pick<OperationProgressTracker, \"onProgress\">\n\n/**\n * This is a multiplexer for tracking individual remote async operations.\n * Unlike {@link ProgressTracker} does not accumulate the progress and doesn't compute the percentage from units of work.\n *\n * progress is tracked with numbers between 0 and 100\n */\nexport class OperationProgressTracker {\n\tprivate readonly progressPerOp: Map<OperationId, Stream<number>> = new Map()\n\tprivate operationId = 0\n\n\t/**\n\t * Prepares a new operation and gives a handle for it which contains:\n\t *   - id for sending updates\n\t *   - progress, a stream to observe\n\t *   - done, a handle to stop tracking the operation progress\n\t */\n\tstartNewOperation(): { id: OperationId; progress: Stream<number>; done: () => unknown } {\n\t\tconst id = this.operationId++\n\t\tconst progress = stream<number>(0)\n\t\tthis.progressPerOp.set(id, progress)\n\t\treturn { id, progress, done: () => this.progressPerOp.delete(id) }\n\t}\n\n\t/** Updates the progress for {@param operation} with {@param progressValue}. */\n\tasync onProgress(operation: OperationId, progressValue: number): Promise<void> {\n\t\tthis.progressPerOp.get(operation)?.(progressValue)\n\t}\n}\n","import m from \"mithril\"\nimport { show as showNotificationOverlay } from \"./base/NotificationOverlay\"\nimport { lang, TranslationKey } from \"../misc/LanguageViewModel\"\nimport { assertMainOrNode } from \"../api/common/Env\"\nimport { SearchModel } from \"../search/model/SearchModel.js\"\nimport { SearchIndexStateInfo } from \"../api/worker/search/SearchTypes.js\"\n\nassertMainOrNode()\n\nexport interface InfoMessage {\n\ttranslationKey: TranslationKey\n\targs: Record<string, any>\n}\n\nexport class InfoMessageHandler {\n\tconstructor(private readonly searchModel: SearchModel) {}\n\n\tasync onInfoMessage(message: InfoMessage): Promise<void> {\n\t\tshowNotificationOverlay(\n\t\t\t{\n\t\t\t\tview: () => m(\"\", lang.get(message.translationKey, message.args)),\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: \"close_alt\",\n\t\t\t},\n\t\t\t[],\n\t\t)\n\t}\n\n\tasync onSearchIndexStateUpdate(state: SearchIndexStateInfo): Promise<void> {\n\t\tthis.searchModel.indexState(state)\n\t}\n}\n","import { throttleRoute } from \"../misc/RouteChange.js\"\nimport m from \"mithril\"\nimport { debounceStart } from \"@tutao/tutanota-utils\"\n\n/** URL-related functions */\nexport interface Router {\n\tgetFullPath(): string\n\t/** will do parameter substitution like mithril route */\n\trouteTo(path: string, params: Record<string, any>): void\n}\n\nexport class ThrottledRouter implements Router {\n\tprivate readonly throttledRoute = debounceStart(32, throttleRoute())\n\n\tgetFullPath(): string {\n\t\treturn m.route.get()\n\t}\n\n\trouteTo(path: string, params: Record<string, any>) {\n\t\tthis.throttledRoute(path, params)\n\t}\n}\n\n/** router that is scoped to a specific prefix and will ignore the path changes outside of it */\nexport class ScopedRouter<Scope extends string> implements Router {\n\tconstructor(private readonly router: Router, readonly scope: Scope) {}\n\n\tgetFullPath(): string {\n\t\treturn this.router.getFullPath()\n\t}\n\n\trouteTo(path: string, params: Record<string, any>) {\n\t\tif (this.router.getFullPath().startsWith(this.scope)) {\n\t\t\tthis.router.routeTo(path, params)\n\t\t}\n\t}\n}\n","import type { WorkerClient } from \"./WorkerClient\"\nimport { bootstrapWorker } from \"./WorkerClient\"\nimport { EventController } from \"./EventController\"\nimport { EntropyCollector } from \"./EntropyCollector\"\nimport { SearchModel } from \"../../search/model/SearchModel\"\nimport { MailboxDetail, MailModel } from \"../../mail/model/MailModel\"\nimport { assertMainOrNode, getWebRoot, isAndroidApp, isApp, isBrowser, isDesktop, isElectronClient, isIOSApp, isOfflineStorageAvailable } from \"../common/Env\"\nimport { notifications } from \"../../gui/Notifications\"\nimport { LoginController } from \"./LoginController\"\nimport type { ContactModel } from \"../../contacts/model/ContactModel\"\nimport { EntityClient } from \"../common/EntityClient\"\nimport { CalendarModel } from \"../../calendar/model/CalendarModel\"\nimport type { DeferredObject, lazy, lazyAsync } from \"@tutao/tutanota-utils\"\nimport { defer, lazyMemoized, noOp } from \"@tutao/tutanota-utils\"\nimport { ProgressTracker } from \"./ProgressTracker\"\nimport { MinimizedMailEditorViewModel } from \"../../mail/model/MinimizedMailEditorViewModel\"\nimport { SchedulerImpl } from \"../common/utils/Scheduler.js\"\nimport type { CredentialsProvider } from \"../../misc/credentials/CredentialsProvider.js\"\nimport { createCredentialsProvider } from \"../../misc/credentials/CredentialsProviderFactory\"\nimport type { LoginFacade } from \"../worker/facades/LoginFacade\"\nimport type { CustomerFacade } from \"../worker/facades/lazy/CustomerFacade.js\"\nimport type { GiftCardFacade } from \"../worker/facades/lazy/GiftCardFacade.js\"\nimport type { ConfigurationDatabase } from \"../worker/facades/lazy/ConfigurationDatabase.js\"\nimport type { CalendarFacade } from \"../worker/facades/lazy/CalendarFacade.js\"\nimport type { MailFacade } from \"../worker/facades/lazy/MailFacade.js\"\nimport type { ShareFacade } from \"../worker/facades/lazy/ShareFacade.js\"\nimport type { CounterFacade } from \"../worker/facades/lazy/CounterFacade.js\"\nimport type { Indexer } from \"../worker/search/Indexer\"\nimport type { SearchFacade } from \"../worker/search/SearchFacade\"\nimport type { BookingFacade } from \"../worker/facades/lazy/BookingFacade.js\"\nimport type { MailAddressFacade } from \"../worker/facades/lazy/MailAddressFacade.js\"\nimport type { FileFacade } from \"../worker/facades/lazy/FileFacade.js\"\nimport type { ContactFormFacade } from \"../worker/facades/lazy/ContactFormFacade.js\"\nimport type { DeviceEncryptionFacade } from \"../worker/facades/DeviceEncryptionFacade\"\nimport { FileController, guiDownload } from \"../../file/FileController\"\nimport type { NativeFileApp } from \"../../native/common/FileApp\"\nimport type { NativePushServiceApp } from \"../../native/main/NativePushServiceApp\"\nimport type { NativeInterfaceMain } from \"../../native/main/NativeInterfaceMain\"\nimport type { NativeInterfaces } from \"../../native/main/NativeInterfaceFactory.js\"\nimport { ProgrammingError } from \"../common/error/ProgrammingError\"\nimport { SecondFactorHandler } from \"../../misc/2fa/SecondFactorHandler\"\nimport { WebauthnClient } from \"../../misc/2fa/webauthn/WebauthnClient\"\nimport type { UserManagementFacade } from \"../worker/facades/lazy/UserManagementFacade.js\"\nimport type { GroupManagementFacade } from \"../worker/facades/lazy/GroupManagementFacade.js\"\nimport { WorkerRandomizer } from \"../worker/WorkerImpl\"\nimport { exposeRemote } from \"../common/WorkerProxy\"\nimport { ExposedNativeInterface } from \"../../native/common/NativeInterface\"\nimport { BrowserWebauthn } from \"../../misc/2fa/webauthn/BrowserWebauthn.js\"\nimport { UsageTestController } from \"@tutao/tutanota-usagetests\"\nimport { EphemeralUsageTestStorage, StorageBehavior, UsageTestModel } from \"../../misc/UsageTestModel\"\nimport { deviceConfig } from \"../../misc/DeviceConfig\"\nimport { IServiceExecutor } from \"../common/ServiceRequest.js\"\nimport type { BlobFacade } from \"../worker/facades/lazy/BlobFacade.js\"\nimport { CryptoFacade } from \"../worker/crypto/CryptoFacade\"\nimport { RecipientsModel } from \"./RecipientsModel\"\nimport { ExposedCacheStorage } from \"../worker/rest/DefaultEntityRestCache.js\"\nimport { PageContextLoginListener } from \"./PageContextLoginListener.js\"\nimport { SearchTextInAppFacade } from \"../../native/common/generatedipc/SearchTextInAppFacade.js\"\nimport { SettingsFacade } from \"../../native/common/generatedipc/SettingsFacade.js\"\nimport { MobileSystemFacade } from \"../../native/common/generatedipc/MobileSystemFacade.js\"\nimport { CommonSystemFacade } from \"../../native/common/generatedipc/CommonSystemFacade.js\"\nimport { DesktopSystemFacade } from \"../../native/common/generatedipc/DesktopSystemFacade.js\"\nimport { ThemeFacade } from \"../../native/common/generatedipc/ThemeFacade.js\"\nimport { FileControllerBrowser } from \"../../file/FileControllerBrowser.js\"\nimport { FileControllerNative } from \"../../file/FileControllerNative.js\"\nimport { windowFacade } from \"../../misc/WindowFacade.js\"\nimport { InterWindowEventFacadeSendDispatcher } from \"../../native/common/generatedipc/InterWindowEventFacadeSendDispatcher.js\"\nimport { SqlCipherFacade } from \"../../native/common/generatedipc/SqlCipherFacade.js\"\nimport { NewsModel } from \"../../misc/news/NewsModel.js\"\nimport type { OwnMailAddressNameChanger } from \"../../settings/mailaddress/OwnMailAddressNameChanger.js\"\nimport type { MailAddressNameChanger, MailAddressTableModel } from \"../../settings/mailaddress/MailAddressTableModel.js\"\nimport type { AnotherUserMailAddressNameChanger } from \"../../settings/mailaddress/AnotherUserMailAddressNameChanger.js\"\nimport type { GroupInfo } from \"../entities/sys/TypeRefs.js\"\nimport type { SendMailModel } from \"../../mail/editor/SendMailModel.js\"\nimport type { CalendarEvent, Mail, MailboxProperties } from \"../entities/tutanota/TypeRefs.js\"\nimport type { CreateMailViewerOptions } from \"../../mail/view/MailViewer.js\"\nimport type { RecipientsSearchModel } from \"../../misc/RecipientsSearchModel.js\"\nimport type { MailViewerViewModel } from \"../../mail/view/MailViewerViewModel.js\"\nimport { NoZoneDateProvider } from \"../common/utils/NoZoneDateProvider.js\"\nimport { WebsocketConnectivityModel } from \"../../misc/WebsocketConnectivityModel.js\"\nimport { DrawerMenuAttrs } from \"../../gui/nav/DrawerMenu.js\"\nimport { EntropyFacade } from \"../worker/facades/EntropyFacade.js\"\nimport { OperationProgressTracker } from \"./OperationProgressTracker.js\"\nimport { WorkerFacade } from \"../worker/facades/WorkerFacade.js\"\nimport { InfoMessageHandler } from \"../../gui/InfoMessageHandler.js\"\nimport { OfflineIndicatorViewModel } from \"../../gui/base/OfflineIndicatorViewModel.js\"\nimport { AppHeaderAttrs, Header } from \"../../gui/Header.js\"\nimport { CalendarViewModel } from \"../../calendar/view/CalendarViewModel.js\"\nimport { ReceivedGroupInvitationsModel } from \"../../sharing/model/ReceivedGroupInvitationsModel.js\"\nimport { Const, GroupType } from \"../common/TutanotaConstants.js\"\nimport type { ExternalLoginViewModel } from \"../../login/ExternalLoginView.js\"\nimport type { ConversationViewModel, ConversationViewModelFactory } from \"../../mail/view/ConversationViewModel.js\"\nimport { AlarmScheduler } from \"../../calendar/date/AlarmScheduler.js\"\nimport { CalendarEventModel } from \"../../calendar/date/eventeditor/CalendarEventModel.js\"\nimport { showProgressDialog } from \"../../gui/dialogs/ProgressDialog.js\"\nimport { SearchViewModel } from \"../../search/view/SearchViewModel.js\"\nimport { SearchRouter } from \"../../search/view/SearchRouter.js\"\nimport { MailOpenedListener } from \"../../mail/view/MailViewModel.js\"\nimport { InboxRuleHandler } from \"../../mail/model/InboxRuleHandler.js\"\nimport { Router, ScopedRouter, ThrottledRouter } from \"../../gui/ScopedRouter.js\"\n\nassertMainOrNode()\n\nclass MainLocator {\n\teventController!: EventController\n\tsearch!: SearchModel\n\tmailModel!: MailModel\n\tcalendarModel!: CalendarModel\n\tminimizedMailModel!: MinimizedMailEditorViewModel\n\tcontactModel!: ContactModel\n\tentityClient!: EntityClient\n\tprogressTracker!: ProgressTracker\n\tcredentialsProvider!: CredentialsProvider\n\tworker!: WorkerClient\n\tfileController!: FileController\n\tsecondFactorHandler!: SecondFactorHandler\n\twebAuthn!: WebauthnClient\n\tloginFacade!: LoginFacade\n\tlogins!: LoginController\n\theader!: Header\n\tcustomerFacade!: CustomerFacade\n\tgiftCardFacade!: GiftCardFacade\n\tgroupManagementFacade!: GroupManagementFacade\n\tconfigFacade!: ConfigurationDatabase\n\tcalendarFacade!: CalendarFacade\n\tmailFacade!: MailFacade\n\tshareFacade!: ShareFacade\n\tcounterFacade!: CounterFacade\n\tindexerFacade!: Indexer\n\tsearchFacade!: SearchFacade\n\tbookingFacade!: BookingFacade\n\tmailAddressFacade!: MailAddressFacade\n\tfileFacade!: FileFacade\n\tblobFacade!: BlobFacade\n\tuserManagementFacade!: UserManagementFacade\n\tcontactFormFacade!: ContactFormFacade\n\tdeviceEncryptionFacade!: DeviceEncryptionFacade\n\tusageTestController!: UsageTestController\n\tusageTestModel!: UsageTestModel\n\tnewsModel!: NewsModel\n\tserviceExecutor!: IServiceExecutor\n\tcryptoFacade!: CryptoFacade\n\tsearchTextFacade!: SearchTextInAppFacade\n\tdesktopSettingsFacade!: SettingsFacade\n\tdesktopSystemFacade!: DesktopSystemFacade\n\tinterWindowEventSender!: InterWindowEventFacadeSendDispatcher\n\tcacheStorage!: ExposedCacheStorage\n\tworkerFacade!: WorkerFacade\n\tloginListener!: PageContextLoginListener\n\trandom!: WorkerRandomizer\n\tsqlCipherFacade!: SqlCipherFacade\n\tconnectivityModel!: WebsocketConnectivityModel\n\toperationProgressTracker!: OperationProgressTracker\n\tinfoMessageHandler!: InfoMessageHandler\n\tConst!: Record<string, any>\n\n\tprivate nativeInterfaces: NativeInterfaces | null = null\n\tprivate exposedNativeInterfaces: ExposedNativeInterface | null = null\n\tprivate entropyFacade!: EntropyFacade\n\n\tasync recipientsModel(): Promise<RecipientsModel> {\n\t\tconst { RecipientsModel } = await import(\"./RecipientsModel.js\")\n\t\treturn new RecipientsModel(this.contactModel, this.logins, this.mailFacade, this.entityClient)\n\t}\n\n\tasync noZoneDateProvider(): Promise<NoZoneDateProvider> {\n\t\treturn new NoZoneDateProvider()\n\t}\n\n\tasync sendMailModel(mailboxDetails: MailboxDetail, mailboxProperties: MailboxProperties): Promise<SendMailModel> {\n\t\tconst factory = await this.sendMailModelSyncFactory(mailboxDetails, mailboxProperties)\n\t\treturn factory()\n\t}\n\n\tprivate readonly redraw: lazyAsync<() => unknown> = lazyMemoized(async () => {\n\t\tconst m = await import(\"mithril\")\n\t\treturn m.redraw\n\t})\n\n\treadonly offlineIndicatorViewModel = lazyMemoized(async () => {\n\t\treturn new OfflineIndicatorViewModel(\n\t\t\tthis.cacheStorage,\n\t\t\tthis.loginListener,\n\t\t\tthis.connectivityModel,\n\t\t\tthis.logins,\n\t\t\tthis.progressTracker,\n\t\t\tawait this.redraw(),\n\t\t)\n\t})\n\n\tasync appHeaderAttrs(): Promise<AppHeaderAttrs> {\n\t\treturn {\n\t\t\tofflineIndicatorModel: await this.offlineIndicatorViewModel(),\n\t\t\tnewsModel: this.newsModel,\n\t\t}\n\t}\n\n\treadonly mailViewModel = lazyMemoized(async () => {\n\t\tconst { MailViewModel } = await import(\"../../mail/view/MailViewModel.js\")\n\t\tconst conversationViewModelFactory = await this.conversationViewModelFactory()\n\t\tconst router = new ScopedRouter(this.throttledRouter(), \"/mail\")\n\t\treturn new MailViewModel(\n\t\t\tthis.mailModel,\n\t\t\tthis.entityClient,\n\t\t\tthis.eventController,\n\t\t\tthis.connectivityModel,\n\t\t\tthis.cacheStorage,\n\t\t\tconversationViewModelFactory,\n\t\t\tthis.mailOpenedListener,\n\t\t\tdeviceConfig,\n\t\t\tthis.inboxRuleHanlder(),\n\t\t\trouter,\n\t\t\tawait this.redraw(),\n\t\t)\n\t})\n\n\tinboxRuleHanlder(): InboxRuleHandler {\n\t\treturn new InboxRuleHandler(this.mailFacade, this.entityClient, this.logins)\n\t}\n\n\tasync searchViewModelFactory(): Promise<() => SearchViewModel> {\n\t\tconst { SearchViewModel } = await import(\"../../search/view/SearchViewModel.js\")\n\t\tconst conversationViewModelFactory = await this.conversationViewModelFactory()\n\t\tconst redraw = await this.redraw()\n\t\tconst searchRouter = await this.scopedSearchRouter()\n\t\treturn () => {\n\t\t\treturn new SearchViewModel(\n\t\t\t\tsearchRouter,\n\t\t\t\tthis.search,\n\t\t\t\tthis.searchFacade,\n\t\t\t\tthis.mailModel,\n\t\t\t\tthis.logins,\n\t\t\t\tthis.indexerFacade,\n\t\t\t\tthis.entityClient,\n\t\t\t\tthis.eventController,\n\t\t\t\tthis.mailOpenedListener,\n\t\t\t\tconversationViewModelFactory,\n\t\t\t\tredraw,\n\t\t\t)\n\t\t}\n\t}\n\n\treadonly throttledRouter: lazy<Router> = lazyMemoized(() => new ThrottledRouter())\n\n\treadonly scopedSearchRouter: lazyAsync<SearchRouter> = lazyMemoized(async () => {\n\t\tconst { SearchRouter } = await import(\"../../search/view/SearchRouter.js\")\n\t\treturn new SearchRouter(new ScopedRouter(this.throttledRouter(), \"/search\"))\n\t})\n\n\treadonly unscopedSearchRouter: lazyAsync<SearchRouter> = lazyMemoized(async () => {\n\t\tconst { SearchRouter } = await import(\"../../search/view/SearchRouter.js\")\n\t\treturn new SearchRouter(this.throttledRouter())\n\t})\n\n\treadonly mailOpenedListener: MailOpenedListener = {\n\t\tonEmailOpened: isDesktop()\n\t\t\t? (mail) => {\n\t\t\t\t\tthis.desktopSystemFacade.sendSocketMessage(mail.sender.address)\n\t\t\t  }\n\t\t\t: noOp,\n\t}\n\n\treadonly contactViewModel = lazyMemoized(async () => {\n\t\tconst { ContactViewModel } = await import(\"../../contacts/view/ContactViewModel.js\")\n\t\tconst router = new ScopedRouter(this.throttledRouter(), \"/contact\")\n\t\treturn new ContactViewModel(this.contactModel, this.entityClient, this.eventController, router, await this.redraw())\n\t})\n\n\tasync calendarViewModel(): Promise<CalendarViewModel> {\n\t\tconst { ReceivedGroupInvitationsModel } = await import(\"../../sharing/model/ReceivedGroupInvitationsModel.js\")\n\t\tconst { CalendarViewModel } = await import(\"../../calendar/view/CalendarViewModel.js\")\n\t\tconst calendarInvitations = new ReceivedGroupInvitationsModel(GroupType.Calendar, this.eventController, this.entityClient, this.logins)\n\t\tcalendarInvitations.init()\n\t\treturn new CalendarViewModel(\n\t\t\tthis.logins,\n\t\t\tasync (event: CalendarEvent) => {\n\t\t\t\tconst mailboxDetail = await this.mailModel.getUserMailboxDetails()\n\t\t\t\tconst mailboxProperties = await this.mailModel.getMailboxProperties(mailboxDetail.mailboxGroupRoot)\n\t\t\t\treturn await this.calendarEventModel(event, mailboxDetail, mailboxProperties, null)\n\t\t\t},\n\t\t\tthis.calendarModel,\n\t\t\tthis.entityClient,\n\t\t\tthis.eventController,\n\t\t\tthis.progressTracker,\n\t\t\tdeviceConfig,\n\t\t\tcalendarInvitations,\n\t\t)\n\t}\n\n\t/** This ugly bit exists because CalendarEventWhoModel wants a sync factory. */\n\tprivate async sendMailModelSyncFactory(mailboxDetails: MailboxDetail, mailboxProperties: MailboxProperties): Promise<() => SendMailModel> {\n\t\tconst { SendMailModel } = await import(\"../../mail/editor/SendMailModel\")\n\t\tconst recipientsModel = await this.recipientsModel()\n\t\tconst dateProvider = await this.noZoneDateProvider()\n\t\treturn () =>\n\t\t\tnew SendMailModel(\n\t\t\t\tthis.mailFacade,\n\t\t\t\tthis.entityClient,\n\t\t\t\tthis.logins,\n\t\t\t\tthis.mailModel,\n\t\t\t\tthis.contactModel,\n\t\t\t\tthis.eventController,\n\t\t\t\tmailboxDetails,\n\t\t\t\trecipientsModel,\n\t\t\t\tdateProvider,\n\t\t\t\tmailboxProperties,\n\t\t\t)\n\t}\n\n\tasync calendarEventModel(\n\t\tevent: Partial<CalendarEvent>,\n\t\tmailboxDetail: MailboxDetail,\n\t\tmailboxProperties: MailboxProperties,\n\t\tresponseTo: Mail | null,\n\t): Promise<CalendarEventModel> {\n\t\tconst [{ makeCalendarEventModel }, { getTimeZone }, { calendarUpdateDistributor }] = await Promise.all([\n\t\t\timport(\"../../calendar/date/eventeditor/CalendarEventModel.js\"),\n\t\t\timport(\"../../calendar/date/CalendarUtils.js\"),\n\t\t\timport(\"../../calendar/date/CalendarUpdateDistributor.js\"),\n\t\t])\n\t\tconst sendMailModelFactory = await this.sendMailModelSyncFactory(mailboxDetail, mailboxProperties)\n\t\tconst showProgress = <T>(p: Promise<T>) => showProgressDialog(\"pleaseWait_msg\", p)\n\n\t\treturn await makeCalendarEventModel(\n\t\t\tevent,\n\t\t\tawait this.recipientsModel(),\n\t\t\tthis.calendarModel,\n\t\t\tthis.logins,\n\t\t\tmailboxDetail,\n\t\t\tmailboxProperties,\n\t\t\tsendMailModelFactory,\n\t\t\tcalendarUpdateDistributor,\n\t\t\tthis.entityClient,\n\t\t\tresponseTo,\n\t\t\tgetTimeZone(),\n\t\t\tshowProgress,\n\t\t)\n\t}\n\n\tasync recipientsSearchModel(): Promise<RecipientsSearchModel> {\n\t\tconst { RecipientsSearchModel } = await import(\"../../misc/RecipientsSearchModel.js\")\n\t\treturn new RecipientsSearchModel(await this.recipientsModel(), this.contactModel, isApp() ? this.systemFacade : null)\n\t}\n\n\treadonly conversationViewModelFactory: lazyAsync<ConversationViewModelFactory> = async () => {\n\t\tconst { ConversationViewModel } = await import(\"../../mail/view/ConversationViewModel.js\")\n\t\tconst factory = await this.mailViewerViewModelFactory()\n\t\tconst m = await import(\"mithril\")\n\t\treturn (options: CreateMailViewerOptions) => {\n\t\t\treturn new ConversationViewModel(\n\t\t\t\toptions,\n\t\t\t\t(options) => factory(options),\n\t\t\t\tthis.entityClient,\n\t\t\t\tthis.eventController,\n\t\t\t\tdeviceConfig,\n\t\t\t\tthis.mailModel,\n\t\t\t\tm.redraw,\n\t\t\t)\n\t\t}\n\t}\n\n\tasync conversationViewModel(options: CreateMailViewerOptions): Promise<ConversationViewModel> {\n\t\tconst factory = await this.conversationViewModelFactory()\n\t\treturn factory(options)\n\t}\n\n\tasync mailViewerViewModelFactory(): Promise<(options: CreateMailViewerOptions) => MailViewerViewModel> {\n\t\tconst { MailViewerViewModel } = await import(\"../../mail/view/MailViewerViewModel.js\")\n\t\treturn ({ mail, showFolder }) =>\n\t\t\tnew MailViewerViewModel(\n\t\t\t\tmail,\n\t\t\t\tshowFolder,\n\t\t\t\tthis.entityClient,\n\t\t\t\tthis.mailModel,\n\t\t\t\tthis.contactModel,\n\t\t\t\tthis.configFacade,\n\t\t\t\tthis.fileFacade,\n\t\t\t\tthis.fileController,\n\t\t\t\tthis.logins,\n\t\t\t\tasync (mailboxDetails) => {\n\t\t\t\t\tconst mailboxProperties = await this.mailModel.getMailboxProperties(mailboxDetails.mailboxGroupRoot)\n\t\t\t\t\treturn this.sendMailModel(mailboxDetails, mailboxProperties)\n\t\t\t\t},\n\t\t\t\tthis.eventController,\n\t\t\t\tthis.workerFacade,\n\t\t\t\tthis.search,\n\t\t\t)\n\t}\n\n\tasync externalLoginViewModelFactory(): Promise<() => ExternalLoginViewModel> {\n\t\tconst { ExternalLoginViewModel } = await import(\"../../login/ExternalLoginView.js\")\n\t\treturn () => new ExternalLoginViewModel(this.credentialsProvider)\n\t}\n\n\tget native(): NativeInterfaceMain {\n\t\treturn this.getNativeInterface(\"native\")\n\t}\n\n\tget fileApp(): NativeFileApp {\n\t\treturn this.getNativeInterface(\"fileApp\")\n\t}\n\n\tget pushService(): NativePushServiceApp {\n\t\treturn this.getNativeInterface(\"pushService\")\n\t}\n\n\tget commonSystemFacade(): CommonSystemFacade {\n\t\treturn this.getNativeInterface(\"commonSystemFacade\")\n\t}\n\n\tget themeFacade(): ThemeFacade {\n\t\treturn this.getNativeInterface(\"themeFacade\")\n\t}\n\n\tget systemFacade(): MobileSystemFacade {\n\t\treturn this.getNativeInterface(\"mobileSystemFacade\")\n\t}\n\n\tasync mailAddressTableModelForOwnMailbox(): Promise<MailAddressTableModel> {\n\t\tconst { MailAddressTableModel } = await import(\"../../settings/mailaddress/MailAddressTableModel.js\")\n\t\tconst nameChanger = await this.ownMailAddressNameChanger()\n\t\treturn new MailAddressTableModel(\n\t\t\tthis.entityClient,\n\t\t\tthis.serviceExecutor,\n\t\t\tthis.mailAddressFacade,\n\t\t\tthis.logins,\n\t\t\tthis.eventController,\n\t\t\tthis.logins.getUserController().userGroupInfo,\n\t\t\tnameChanger,\n\t\t)\n\t}\n\n\tasync mailAddressTableModelForAdmin(mailGroupId: Id, userId: Id, userGroupInfo: GroupInfo): Promise<MailAddressTableModel> {\n\t\tconst { MailAddressTableModel } = await import(\"../../settings/mailaddress/MailAddressTableModel.js\")\n\t\tconst nameChanger = await this.adminNameChanger(mailGroupId, userId)\n\t\treturn new MailAddressTableModel(\n\t\t\tthis.entityClient,\n\t\t\tthis.serviceExecutor,\n\t\t\tthis.mailAddressFacade,\n\t\t\tthis.logins,\n\t\t\tthis.eventController,\n\t\t\tuserGroupInfo,\n\t\t\tnameChanger,\n\t\t)\n\t}\n\n\tasync ownMailAddressNameChanger(): Promise<MailAddressNameChanger> {\n\t\tconst { OwnMailAddressNameChanger } = await import(\"../../settings/mailaddress/OwnMailAddressNameChanger.js\")\n\t\treturn new OwnMailAddressNameChanger(this.mailModel, this.entityClient)\n\t}\n\n\tasync adminNameChanger(mailGroupId: Id, userId: Id): Promise<MailAddressNameChanger> {\n\t\tconst { AnotherUserMailAddressNameChanger } = await import(\"../../settings/mailaddress/AnotherUserMailAddressNameChanger.js\")\n\t\treturn new AnotherUserMailAddressNameChanger(this.mailAddressFacade, mailGroupId, userId)\n\t}\n\n\tasync drawerAttrsFactory(): Promise<() => DrawerMenuAttrs> {\n\t\treturn () => ({\n\t\t\tlogins: this.logins,\n\t\t\tnewsModel: this.newsModel,\n\t\t\tdesktopSystemFacade: this.desktopSystemFacade,\n\t\t})\n\t}\n\n\tprivate getExposedNativeInterface(): ExposedNativeInterface {\n\t\tif (isBrowser()) {\n\t\t\tthrow new ProgrammingError(\"Tried to access native interfaces in browser\")\n\t\t}\n\n\t\tif (this.exposedNativeInterfaces == null) {\n\t\t\tthis.exposedNativeInterfaces = exposeRemote<ExposedNativeInterface>((msg) => this.native.invokeNative(msg.requestType, msg.args))\n\t\t}\n\n\t\treturn this.exposedNativeInterfaces\n\t}\n\n\tprivate getNativeInterface<T extends keyof NativeInterfaces>(name: T): NativeInterfaces[T] {\n\t\tif (!this.nativeInterfaces) {\n\t\t\tthrow new ProgrammingError(`Tried to use ${name} in web`)\n\t\t}\n\n\t\treturn this.nativeInterfaces[name]\n\t}\n\n\tprivate readonly _workerDeferred: DeferredObject<WorkerClient>\n\tprivate _entropyCollector!: EntropyCollector\n\tprivate _deferredInitialized: DeferredObject<void> = defer()\n\n\tget initialized(): Promise<void> {\n\t\treturn this._deferredInitialized.promise\n\t}\n\n\tconstructor() {\n\t\tthis._workerDeferred = defer()\n\t}\n\n\tasync init(): Promise<void> {\n\t\t// Split init in two separate parts: creating modules and causing side effects.\n\t\t// We would like to do both on normal init but on HMR we just want to replace modules without a new worker. If we create a new\n\t\t// worker we end up losing state on the worker side (including our session).\n\t\tthis.worker = bootstrapWorker(this)\n\t\tawait this._createInstances()\n\t\tthis._entropyCollector = new EntropyCollector(this.entropyFacade, await this.scheduler(), window)\n\n\t\tthis._entropyCollector.start()\n\n\t\tthis._deferredInitialized.resolve()\n\t}\n\n\tasync _createInstances() {\n\t\tconst {\n\t\t\tloginFacade,\n\t\t\tcustomerFacade,\n\t\t\tgiftCardFacade,\n\t\t\tgroupManagementFacade,\n\t\t\tconfigFacade,\n\t\t\tcalendarFacade,\n\t\t\tmailFacade,\n\t\t\tshareFacade,\n\t\t\tcounterFacade,\n\t\t\tindexerFacade,\n\t\t\tsearchFacade,\n\t\t\tbookingFacade,\n\t\t\tmailAddressFacade,\n\t\t\tfileFacade,\n\t\t\tblobFacade,\n\t\t\tuserManagementFacade,\n\t\t\tcontactFormFacade,\n\t\t\tdeviceEncryptionFacade,\n\t\t\trestInterface,\n\t\t\tserviceExecutor,\n\t\t\tcryptoFacade,\n\t\t\tcacheStorage,\n\t\t\trandom,\n\t\t\teventBus,\n\t\t\tentropyFacade,\n\t\t\tworkerFacade,\n\t\t} = this.worker.getWorkerInterface()\n\t\tthis.loginFacade = loginFacade\n\t\tthis.customerFacade = customerFacade\n\t\tthis.giftCardFacade = giftCardFacade\n\t\tthis.groupManagementFacade = groupManagementFacade\n\t\tthis.configFacade = configFacade\n\t\tthis.calendarFacade = calendarFacade\n\t\tthis.mailFacade = mailFacade\n\t\tthis.shareFacade = shareFacade\n\t\tthis.counterFacade = counterFacade\n\t\tthis.indexerFacade = indexerFacade\n\t\tthis.searchFacade = searchFacade\n\t\tthis.bookingFacade = bookingFacade\n\t\tthis.mailAddressFacade = mailAddressFacade\n\t\tthis.fileFacade = fileFacade\n\t\tthis.blobFacade = blobFacade\n\t\tthis.userManagementFacade = userManagementFacade\n\t\tthis.contactFormFacade = contactFormFacade\n\t\tthis.deviceEncryptionFacade = deviceEncryptionFacade\n\t\tthis.serviceExecutor = serviceExecutor\n\t\tthis.logins = new LoginController()\n\t\t// Should be called elsewhere later e.g. in mainLocator\n\t\tthis.logins.init()\n\t\tthis.eventController = new EventController(locator.logins)\n\t\tthis.progressTracker = new ProgressTracker()\n\t\tthis.search = new SearchModel(this.searchFacade)\n\t\tthis.entityClient = new EntityClient(restInterface)\n\t\tthis.cryptoFacade = cryptoFacade\n\t\tthis.cacheStorage = cacheStorage\n\t\tthis.entropyFacade = entropyFacade\n\t\tthis.workerFacade = workerFacade\n\t\tthis.connectivityModel = new WebsocketConnectivityModel(eventBus)\n\t\tthis.mailModel = new MailModel(\n\t\t\tnotifications,\n\t\t\tthis.eventController,\n\t\t\tthis.connectivityModel,\n\t\t\tthis.mailFacade,\n\t\t\tthis.entityClient,\n\t\t\tthis.logins,\n\t\t\tthis.inboxRuleHanlder(),\n\t\t)\n\t\tthis.operationProgressTracker = new OperationProgressTracker()\n\t\tthis.infoMessageHandler = new InfoMessageHandler(this.search)\n\t\tthis.Const = Const\n\t\tif (!isBrowser()) {\n\t\t\tconst { WebDesktopFacade } = await import(\"../../native/main/WebDesktopFacade\")\n\t\t\tconst { WebMobileFacade } = await import(\"../../native/main/WebMobileFacade.js\")\n\t\t\tconst { WebCommonNativeFacade } = await import(\"../../native/main/WebCommonNativeFacade.js\")\n\t\t\tconst { WebInterWindowEventFacade } = await import(\"../../native/main/WebInterWindowEventFacade.js\")\n\t\t\tconst { WebAuthnFacadeSendDispatcher } = await import(\"../../native/common/generatedipc/WebAuthnFacadeSendDispatcher.js\")\n\t\t\tconst { createNativeInterfaces, createDesktopInterfaces } = await import(\"../../native/main/NativeInterfaceFactory.js\")\n\t\t\tthis.nativeInterfaces = createNativeInterfaces(\n\t\t\t\tnew WebMobileFacade(this.connectivityModel, this.mailModel),\n\t\t\t\tnew WebDesktopFacade(),\n\t\t\t\tnew WebInterWindowEventFacade(this.logins, windowFacade),\n\t\t\t\tnew WebCommonNativeFacade(),\n\t\t\t\tcryptoFacade,\n\t\t\t\tcalendarFacade,\n\t\t\t\tthis.entityClient,\n\t\t\t\tthis.logins,\n\t\t\t)\n\n\t\t\tif (isElectronClient()) {\n\t\t\t\tconst desktopInterfaces = createDesktopInterfaces(this.native)\n\t\t\t\tthis.searchTextFacade = desktopInterfaces.searchTextFacade\n\t\t\t\tthis.interWindowEventSender = desktopInterfaces.interWindowEventSender\n\t\t\t\tthis.webAuthn = new WebauthnClient(new WebAuthnFacadeSendDispatcher(this.native), getWebRoot())\n\t\t\t\tif (isDesktop()) {\n\t\t\t\t\tthis.desktopSettingsFacade = desktopInterfaces.desktopSettingsFacade\n\t\t\t\t\tthis.desktopSystemFacade = desktopInterfaces.desktopSystemFacade\n\t\t\t\t}\n\t\t\t} else if (isAndroidApp() || isIOSApp()) {\n\t\t\t\tthis.webAuthn = new WebauthnClient(new WebAuthnFacadeSendDispatcher(this.native), getWebRoot())\n\t\t\t}\n\t\t\tif (isOfflineStorageAvailable()) {\n\t\t\t\tthis.sqlCipherFacade = this.nativeInterfaces.sqlCipherFacade\n\t\t\t}\n\t\t}\n\n\t\tif (this.webAuthn == null) {\n\t\t\tthis.webAuthn = new WebauthnClient(new BrowserWebauthn(navigator.credentials, window.location.hostname), getWebRoot())\n\t\t}\n\t\tthis.secondFactorHandler = new SecondFactorHandler(this.eventController, this.entityClient, this.webAuthn, this.loginFacade)\n\t\tthis.loginListener = new PageContextLoginListener(this.secondFactorHandler)\n\t\tthis.credentialsProvider = await createCredentialsProvider(\n\t\t\tdeviceEncryptionFacade,\n\t\t\tthis.nativeInterfaces?.native ?? null,\n\t\t\tthis.nativeInterfaces?.sqlCipherFacade ?? null,\n\t\t\tisDesktop() ? this.interWindowEventSender : null,\n\t\t)\n\t\tthis.random = random\n\n\t\tthis.usageTestModel = new UsageTestModel(\n\t\t\t{\n\t\t\t\t[StorageBehavior.Persist]: deviceConfig,\n\t\t\t\t[StorageBehavior.Ephemeral]: new EphemeralUsageTestStorage(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tnow(): number {\n\t\t\t\t\treturn Date.now()\n\t\t\t\t},\n\t\t\t\ttimeZone(): string {\n\t\t\t\t\tthrow new Error(\"Not implemented by this provider\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tthis.serviceExecutor,\n\t\t\tthis.entityClient,\n\t\t\tthis.logins,\n\t\t\tthis.eventController,\n\t\t\t() => this.usageTestController,\n\t\t)\n\n\t\tthis.newsModel = new NewsModel(this.serviceExecutor, deviceConfig, async (name: string) => {\n\t\t\tswitch (name) {\n\t\t\t\tcase \"usageOptIn\":\n\t\t\t\t\tconst { UsageOptInNews } = await import(\"../../misc/news/items/UsageOptInNews.js\")\n\t\t\t\t\treturn new UsageOptInNews(this.newsModel, this.usageTestModel)\n\t\t\t\tcase \"recoveryCode\":\n\t\t\t\t\tconst { RecoveryCodeNews } = await import(\"../../misc/news/items/RecoveryCodeNews.js\")\n\t\t\t\t\treturn new RecoveryCodeNews(this.newsModel, this.logins.getUserController(), this.userManagementFacade)\n\t\t\t\tcase \"pinBiometrics\":\n\t\t\t\t\tconst { PinBiometricsNews } = await import(\"../../misc/news/items/PinBiometricsNews.js\")\n\t\t\t\t\treturn new PinBiometricsNews(this.newsModel, this.credentialsProvider, this.logins.getUserController().userId)\n\t\t\t\tcase \"referralLink\":\n\t\t\t\t\tconst { ReferralLinkNews } = await import(\"../../misc/news/items/ReferralLinkNews.js\")\n\t\t\t\t\tconst dateProvider = await this.noZoneDateProvider()\n\t\t\t\t\treturn new ReferralLinkNews(this.newsModel, dateProvider, this.logins.getUserController())\n\t\t\t\tcase \"newPlans\":\n\t\t\t\t\tconst { NewPlansNews } = await import(\"../../misc/news/items/NewPlansNews.js\")\n\t\t\t\t\treturn new NewPlansNews(this.newsModel, this.logins.getUserController())\n\t\t\t\tcase \"newPlansOfferEnding\":\n\t\t\t\t\tconst { NewPlansOfferEndingNews } = await import(\"../../misc/news/items/NewPlansOfferEndingNews.js\")\n\t\t\t\t\treturn new NewPlansOfferEndingNews(this.newsModel, this.logins.getUserController())\n\t\t\t\tdefault:\n\t\t\t\t\tconsole.log(`No implementation for news named '${name}'`)\n\t\t\t\t\treturn null\n\t\t\t}\n\t\t})\n\n\t\tthis.fileController =\n\t\t\tthis.nativeInterfaces == null\n\t\t\t\t? new FileControllerBrowser(blobFacade, fileFacade, guiDownload)\n\t\t\t\t: new FileControllerNative(blobFacade, fileFacade, guiDownload, this.nativeInterfaces.fileApp)\n\n\t\tthis.calendarModel = new CalendarModel(\n\t\t\tnotifications,\n\t\t\tthis.alarmScheduler,\n\t\t\tthis.eventController,\n\t\t\tthis.serviceExecutor,\n\t\t\tthis.logins,\n\t\t\tthis.progressTracker,\n\t\t\tthis.entityClient,\n\t\t\tthis.mailModel,\n\t\t\tthis.calendarFacade,\n\t\t\tthis.fileController,\n\t\t)\n\t\tconst { ContactModelImpl } = await import(\"../../contacts/model/ContactModel\")\n\t\tthis.contactModel = new ContactModelImpl(this.searchFacade, this.entityClient, this.logins)\n\t\tthis.minimizedMailModel = new MinimizedMailEditorViewModel()\n\t\tthis.usageTestController = new UsageTestController(this.usageTestModel)\n\t}\n\n\tprivate alarmScheduler: () => Promise<AlarmScheduler> = lazyMemoized(async () => {\n\t\tconst { AlarmSchedulerImpl } = await import(\"../../calendar/date/AlarmScheduler\")\n\t\tconst { DefaultDateProvider } = await import(\"../../calendar/date/CalendarUtils\")\n\t\tconst dateProvider = new DefaultDateProvider()\n\t\treturn new AlarmSchedulerImpl(dateProvider, await this.scheduler())\n\t})\n\n\tprivate async scheduler(): Promise<SchedulerImpl> {\n\t\tconst dateProvider = await this.noZoneDateProvider()\n\t\treturn new SchedulerImpl(dateProvider, window, window)\n\t}\n}\n\nexport type IMainLocator = Readonly<MainLocator>\n\nexport const locator: IMainLocator = new MainLocator()\n\nif (typeof window !== \"undefined\") {\n\twindow.tutao.locator = locator\n}\n","import { lang } from \"../../misc/LanguageViewModel\"\nimport m, { Component, Vnode } from \"mithril\"\nimport stream from \"mithril/stream\"\nimport { Dialog } from \"../base/Dialog\"\nimport { Keys } from \"../../api/common/TutanotaConstants\"\nimport { TextField } from \"../base/TextField.js\"\nimport type { Shortcut } from \"../../misc/KeyManager\"\nimport { ButtonType } from \"../base/Button.js\"\nimport { DialogHeaderBarAttrs } from \"../base/DialogHeaderBar\"\n\nfunction makeShortcutName(shortcut: Shortcut): string {\n\treturn (\n\t\t(shortcut.meta ? Keys.META.name + \" + \" : \"\") +\n\t\t(shortcut.ctrl ? Keys.CTRL.name + \" + \" : \"\") +\n\t\t(shortcut.shift ? Keys.SHIFT.name + \" + \" : \"\") +\n\t\t(shortcut.alt ? Keys.ALT.name + \" + \" : \"\") +\n\t\tshortcut.key.name\n\t)\n}\n\n/**\n * return a promise that resolves when the dialog is closed\n */\nexport function showShortcutDialog(shortcuts: Array<Shortcut>): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tlet dialog: Dialog\n\n\t\tconst close = () => {\n\t\t\tdialog.close()\n\t\t\tresolve()\n\t\t}\n\n\t\tconst headerAttrs: DialogHeaderBarAttrs = {\n\t\t\tleft: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"close_alt\",\n\t\t\t\t\tclick: close,\n\t\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t\t},\n\t\t\t],\n\t\t\tmiddle: () => lang.get(\"keyboardShortcuts_title\"),\n\t\t}\n\t\tdialog = Dialog.editDialog(headerAttrs, ShortcutDialog, {\n\t\t\tshortcuts,\n\t\t})\n\t\t\t.addShortcut({\n\t\t\t\tkey: Keys.ESC,\n\t\t\t\texec: close,\n\t\t\t\thelp: \"close_alt\",\n\t\t\t})\n\t\t\t.show()\n\t})\n}\n\ntype ShortcutDialogAttrs = {\n\tshortcuts: Array<Shortcut>\n}\n\n/**\n * The Dialog that shows the currently active Keyboard shortcuts when you press F1\n *\n *\n */\n\nclass ShortcutDialog implements Component<ShortcutDialogAttrs> {\n\tview(vnode: Vnode<ShortcutDialogAttrs>) {\n\t\tconst { shortcuts } = vnode.attrs\n\t\tconst textFieldAttrs = shortcuts\n\t\t\t.filter((shortcut) => shortcut.enabled == null || shortcut.enabled())\n\t\t\t.map((shortcut) => ({\n\t\t\t\tlabel: () => makeShortcutName(shortcut),\n\t\t\t\tvalue: lang.get(shortcut.help),\n\t\t\t\tdisabled: true,\n\t\t\t}))\n\t\treturn m(\n\t\t\t\"div.pb\",\n\t\t\ttextFieldAttrs.map((t) => m(TextField, t)),\n\t\t)\n\t}\n}\n","import { assertNotNull, downcast, isEmpty, neverNull } from \"@tutao/tutanota-utils\"\nimport { Dialog } from \"../gui/base/Dialog\"\nimport type { TranslationKey, TranslationText } from \"./LanguageViewModel\"\nimport { isIOSApp } from \"../api/common/Env\"\nimport type { clickHandler } from \"../gui/base/GuiUtils\"\nimport { locator } from \"../api/main/MainLocator\"\nimport type { UserController } from \"../api/main/UserController.js\"\nimport { BookingTypeRef } from \"../api/entities/sys/TypeRefs.js\"\nimport { GENERATED_MAX_ID } from \"../api/common/utils/EntityUtils.js\"\nimport { AvailablePlanType, Const, NewBusinessPlans, NewPaidPlans, NewPersonalPlans, PlanType } from \"../api/common/TutanotaConstants.js\"\nimport { showSwitchDialog } from \"../subscription/SwitchSubscriptionDialog.js\"\nimport { ProgrammingError } from \"../api/common/error/ProgrammingError.js\"\nimport { getAvailableMatchingPlans } from \"../subscription/SubscriptionUtils.js\"\n\n/**\n * Opens a dialog which states that the function is not available in the Free subscription and provides an option to upgrade.\n */\nexport async function showNotAvailableForFreeDialog(acceptedPlans: AvailablePlanType[] = NewPaidPlans) {\n\tif (isIOSApp()) {\n\t\tawait Dialog.message(\"notAvailableInApp_msg\")\n\t} else {\n\t\tconst wizard = await import(\"../subscription/UpgradeSubscriptionWizard\")\n\t\tconst customerInfo = await locator.logins.getUserController().loadCustomerInfo()\n\n\t\tconst businessPlanRequired =\n\t\t\tacceptedPlans.filter((plan) => NewBusinessPlans.includes(plan)).length === acceptedPlans.length &&\n\t\t\tNewPersonalPlans.includes(downcast(customerInfo.plan))\n\t\tconst msg = businessPlanRequired ? \"pricing.notSupportedByPersonalPlan_msg\" : \"newPaidPlanRequired_msg\"\n\n\t\tawait wizard.showUpgradeWizard(locator.logins, acceptedPlans, msg)\n\t}\n}\n\nexport function createNotAvailableForFreeClickHandler(acceptedPlans: AvailablePlanType[], click: clickHandler, available: () => boolean): clickHandler {\n\treturn (e, dom) => {\n\t\tif (!available()) {\n\t\t\tshowNotAvailableForFreeDialog(acceptedPlans)\n\t\t} else {\n\t\t\tclick(e, dom)\n\t\t}\n\t}\n}\n\n/**\n * Returns whether a paid subscriptino is active and shows one of the showNotAvailableForFreeDialog or subscription cancelled dialogs if needed.\n */\nexport async function checkPaidSubscription(): Promise<boolean> {\n\tif (locator.logins.getUserController().isFreeAccount()) {\n\t\tshowNotAvailableForFreeDialog()\n\t\treturn false\n\t}\n\n\tconst customer = await locator.logins.getUserController().loadCustomer()\n\tif (customer.canceledPremiumAccount) {\n\t\treturn Dialog.message(\"subscriptionCancelledMessage_msg\").then(() => false)\n\t} else {\n\t\treturn true\n\t}\n}\n\nexport async function showMoreStorageNeededOrderDialog(messageIdOrMessageFunction: TranslationKey): Promise<PlanType | void> {\n\tconst userController = locator.logins.getUserController()\n\tif (!userController.isGlobalAdmin()) {\n\t\treturn Dialog.message(\"insufficientStorageWarning_msg\")\n\t}\n\tconst confirmed = await Dialog.confirm(messageIdOrMessageFunction, \"upgrade_action\")\n\tif (confirmed) {\n\t\tif (userController.isFreeAccount()) {\n\t\t\tconst wizard = await import(\"../subscription/UpgradeSubscriptionWizard\")\n\t\t\treturn wizard.showUpgradeWizard(locator.logins)\n\t\t} else {\n\t\t\tconst usedStorage = Number(await locator.userManagementFacade.readUsedUserStorage(userController.user))\n\t\t\tconst { getAvailableMatchingPlans } = await import(\"../subscription/SubscriptionUtils.js\")\n\t\t\tconst plansWithMoreStorage = await getAvailableMatchingPlans(\n\t\t\t\tlocator.serviceExecutor,\n\t\t\t\t(config) => Number(config.storageGb) * Const.MEMORY_GB_FACTOR > usedStorage,\n\t\t\t)\n\t\t\tif (isEmpty(plansWithMoreStorage)) {\n\t\t\t\tawait Dialog.message(userController.isGlobalAdmin() ? \"insufficientStorageAdmin_msg\" : \"insufficientStorageUser_msg\")\n\t\t\t} else {\n\t\t\t\tawait showPlanUpgradeRequiredDialog(plansWithMoreStorage)\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * @returns true if the needed plan has been ordered\n */\nexport async function showPlanUpgradeRequiredDialog(acceptedPlans: AvailablePlanType[], reason?: TranslationText): Promise<boolean> {\n\tif (isEmpty(acceptedPlans)) {\n\t\tthrow new ProgrammingError(\"no plans specified\")\n\t}\n\tconst userController = locator.logins.getUserController()\n\tif (userController.isFreeAccount()) {\n\t\tshowNotAvailableForFreeDialog(acceptedPlans)\n\t\treturn false\n\t} else {\n\t\tif (reason == null) {\n\t\t\t// show generic reason if not supplied\n\t\t\tlet customerInfo = await userController.loadCustomerInfo()\n\t\t\tconst businessPlanRequired =\n\t\t\t\tacceptedPlans.filter((plan) => NewBusinessPlans.includes(plan)).length === acceptedPlans.length &&\n\t\t\t\t!NewBusinessPlans.includes(downcast(customerInfo.plan))\n\t\t\treason = businessPlanRequired ? \"pricing.notSupportedByPersonalPlan_msg\" : \"newPaidPlanRequired_msg\"\n\t\t}\n\t\tawait showSwitchPlanDialog(userController, acceptedPlans, reason)\n\t\treturn acceptedPlans.includes(downcast<AvailablePlanType>(await userController.getPlanType()))\n\t}\n}\n\nexport async function showUpgradeWizardOrSwitchSubscriptionDialog(userController: UserController): Promise<PlanType> {\n\tif (userController.isFreeAccount()) {\n\t\tconst { showUpgradeWizard } = await import(\"../subscription/UpgradeSubscriptionWizard\")\n\t\treturn showUpgradeWizard(locator.logins)\n\t} else {\n\t\treturn showSwitchPlanDialog(userController, NewPaidPlans)\n\t}\n}\n\nasync function showSwitchPlanDialog(userController: UserController, acceptedPlans: AvailablePlanType[], reason?: TranslationText): Promise<PlanType> {\n\tlet customerInfo = await userController.loadCustomerInfo()\n\tconst bookings = await locator.entityClient.loadRange(BookingTypeRef, neverNull(customerInfo.bookings).items, GENERATED_MAX_ID, 1, true)\n\tconst { showSwitchDialog } = await import(\"../subscription/SwitchSubscriptionDialog\")\n\treturn showSwitchDialog(\n\t\tawait userController.loadCustomer(),\n\t\tcustomerInfo,\n\t\tawait userController.loadAccountingInfo(),\n\t\tassertNotNull(bookings[0]),\n\t\tacceptedPlans,\n\t\treason ?? null,\n\t)\n}\n","import { client } from \"./ClientDetector\"\n\nfunction fallbackCopyToClipboard(text: string): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst textArea = document.createElement(\"textarea\")\n\t\ttextArea.value = text\n\t\twindow.document.body.appendChild(textArea)\n\t\ttextArea.focus()\n\t\ttextArea.select()\n\n\t\ttry {\n\t\t\tdocument.execCommand(\"copy\")\n\t\t} catch (err) {\n\t\t\treject(\"fallback copy failed\")\n\t\t}\n\n\t\twindow.document.body.removeChild(textArea)\n\t\tconsole.log(\"fallback copy successful\")\n\t\tresolve()\n\t})\n}\n\nfunction iosCopyToClipboard(text: string) {\n\tconst el = document.createElement(\"textarea\")\n\tel.value = text\n\tel.contentEditable = \"true\"\n\tel.readOnly = true\n\twindow.document.body.appendChild(el)\n\tconst range = document.createRange()\n\trange.selectNodeContents(el)\n\tconst s = window.getSelection()\n\ts?.removeAllRanges()\n\ts?.addRange(range)\n\tel.setSelectionRange(0, 999999) // A big number, to cover anything that could be inside the element.\n\n\twindow.document.execCommand(\"copy\")\n\twindow.document.body.removeChild(el)\n}\n\nexport async function copyToClipboard(text: string): Promise<void> {\n\ttry {\n\t\tawait navigator.clipboard.writeText(text)\n\t} catch {\n\t\tconsole.log(\"copy failed, trying fallback\")\n\n\t\tif (client.isIos()) {\n\t\t\treturn iosCopyToClipboard(text)\n\t\t} else {\n\t\t\treturn fallbackCopyToClipboard(text)\n\t\t}\n\t}\n}\n","import stream from \"mithril/stream\"\nimport { TextField, TextFieldType } from \"../gui/base/TextField.js\"\nimport { lang } from \"./LanguageViewModel\"\nimport { Dialog, DialogType } from \"../gui/base/Dialog\"\nimport * as notificationOverlay from \"../gui/base/NotificationOverlay\"\nimport m from \"mithril\"\nimport { Checkbox } from \"../gui/base/Checkbox.js\"\nimport { Button, ButtonType } from \"../gui/base/Button.js\"\nimport { ExpanderButton, ExpanderPanel } from \"../gui/base/Expander\"\nimport { downcast, ErrorInfo, errorToString, neverNull, typedKeys, uint8ArrayToString } from \"@tutao/tutanota-utils\"\nimport { locator } from \"../api/main/MainLocator\"\nimport { AccountType, ConversationType, Keys, MailMethod } from \"../api/common/TutanotaConstants\"\nimport { copyToClipboard } from \"./ClipboardUtils\"\nimport { px } from \"../gui/size\"\nimport { isApp, isDesktop, Mode } from \"../api/common/Env\"\nimport { RecipientType } from \"../api/common/recipients/Recipient.js\"\nimport { Attachment } from \"../mail/editor/SendMailModel.js\"\nimport { createLogFile } from \"../api/common/Logger.js\"\nimport { DataFile } from \"../api/common/DataFile.js\"\n\ntype FeedbackContent = {\n\tmessage: string\n\tsubject: string\n\tlogs: Array<Attachment>\n}\n\nexport async function promptForFeedbackAndSend(e: ErrorInfo): Promise<{ ignored: boolean }> {\n\tconst loggedIn = locator.logins.isUserLoggedIn()\n\tlet ignoreChecked = false\n\tlet sendLogs = true\n\tconst logs = await getLogAttachments()\n\n\treturn new Promise((resolve) => {\n\t\tconst preparedContent = prepareFeedbackContent(e, loggedIn)\n\t\tconst detailsExpanded = stream(false)\n\t\tlet userMessage = \"\"\n\t\tlet errorOkAction = (dialog: Dialog) => {\n\t\t\tpreparedContent.message = userMessage + \"\\n\" + preparedContent.message\n\t\t\tresolve(preparedContent)\n\t\t\tdialog.close()\n\t\t}\n\n\t\tnotificationOverlay.show(\n\t\t\t{\n\t\t\t\tview: () =>\n\t\t\t\t\tm(\"\", [\n\t\t\t\t\t\t\"An error occurred\",\n\t\t\t\t\t\tm(Checkbox, {\n\t\t\t\t\t\t\tlabel: () => \"Ignore the error for this session\",\n\t\t\t\t\t\t\tchecked: ignoreChecked,\n\t\t\t\t\t\t\tonChecked: (checked) => (ignoreChecked = checked),\n\t\t\t\t\t\t}),\n\t\t\t\t\t]),\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: \"close_alt\",\n\t\t\t\tclick: () => resolve(null),\n\t\t\t},\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\tlabel: () => \"Send report\",\n\t\t\t\t\tclick: () => showReportDialog(),\n\t\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t\t},\n\t\t\t],\n\t\t)\n\n\t\tfunction showReportDialog() {\n\t\t\tDialog.showActionDialog({\n\t\t\t\tokActionTextId: \"send_action\",\n\t\t\t\ttitle: lang.get(\"sendErrorReport_action\"),\n\t\t\t\ttype: DialogType.EditMedium,\n\t\t\t\tchild: {\n\t\t\t\t\tview: () => {\n\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\tm(TextField, {\n\t\t\t\t\t\t\t\tlabel: \"yourMessage_label\",\n\t\t\t\t\t\t\t\thelpLabel: () => lang.get(\"feedbackOnErrorInfo_msg\"),\n\t\t\t\t\t\t\t\tvalue: userMessage,\n\t\t\t\t\t\t\t\ttype: TextFieldType.Area,\n\t\t\t\t\t\t\t\toninput: (value) => (userMessage = value),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tm(Checkbox, {\n\t\t\t\t\t\t\t\tlabel: () => lang.get(\"sendLogs_action\"),\n\t\t\t\t\t\t\t\thelpLabel: () => lang.get(\"sendLogsInfo_msg\"),\n\t\t\t\t\t\t\t\tchecked: sendLogs,\n\t\t\t\t\t\t\t\tonChecked: (checked) => (sendLogs = checked),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tm(\n\t\t\t\t\t\t\t\t\".flex.flex-column.space-around.items-center\",\n\t\t\t\t\t\t\t\tlogs.map((l) =>\n\t\t\t\t\t\t\t\t\tm(Button, {\n\t\t\t\t\t\t\t\t\t\tlabel: () => l.name,\n\t\t\t\t\t\t\t\t\t\ttype: ButtonType.Bubble,\n\t\t\t\t\t\t\t\t\t\tclick: () => showLogDialog(l.name, uint8ArrayToString(\"utf-8\", (l as DataFile).data)),\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tm(\n\t\t\t\t\t\t\t\t\".flex-end\",\n\t\t\t\t\t\t\t\tm(\n\t\t\t\t\t\t\t\t\t\".right\",\n\t\t\t\t\t\t\t\t\tm(ExpanderButton, {\n\t\t\t\t\t\t\t\t\t\tlabel: \"details_label\",\n\t\t\t\t\t\t\t\t\t\texpanded: detailsExpanded(),\n\t\t\t\t\t\t\t\t\t\tonExpandedChange: detailsExpanded,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tm(\n\t\t\t\t\t\t\t\tExpanderPanel,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\texpanded: detailsExpanded(),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tm(\".selectable\", [\n\t\t\t\t\t\t\t\t\tm(\".selectable\", preparedContent.subject),\n\t\t\t\t\t\t\t\t\tpreparedContent.message.split(\"\\n\").map((l) => (l.trim() === \"\" ? m(\".pb\", \"\") : m(\"\", l))),\n\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tokAction: errorOkAction,\n\t\t\t\tcancelAction: () => resolve(null),\n\t\t\t})\n\t\t}\n\t}).then((content: FeedbackContent) => {\n\t\tconst ret = { ignored: ignoreChecked }\n\t\tif (!content) return ret\n\t\tif (sendLogs) content.logs = logs\n\t\telse content.logs = []\n\t\tsendFeedbackMail(content)\n\t\treturn ret\n\t})\n}\n\n/**\n * show the contents of a log file in a large dialog.\n * @param heading the title of the dialog\n * @param text the text to display\n */\nasync function showLogDialog(heading: string, text: string) {\n\tlet logDialog: Dialog\n\tconst closeLogDialog = () => logDialog?.close()\n\n\tlogDialog = Dialog.largeDialog(\n\t\t{\n\t\t\tright: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"ok_action\",\n\t\t\t\t\tclick: closeLogDialog,\n\t\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t\t},\n\t\t\t],\n\t\t\tmiddle: () => heading,\n\t\t},\n\t\t{\n\t\t\tview: () => m(\".white-space-pre.pt.pb.selectable\", text),\n\t\t},\n\t)\n\t\t.addShortcut({\n\t\t\tkey: Keys.ESC,\n\t\t\texec: closeLogDialog,\n\t\t\thelp: \"close_alt\",\n\t\t})\n\t\t.setCloseHandler(closeLogDialog)\n\t\t.show()\n}\n\nexport async function showErrorDialogNotLoggedIn(e: ErrorInfo): Promise<void> {\n\tconst content = prepareFeedbackContent(e, false)\n\tconst expanded = stream(false)\n\tconst message = content.subject + \"\\n\\n\" + content.message\n\n\tconst info = () => [\n\t\tm(\n\t\t\t\".flex.col.items-end.plr\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\tmarginTop: \"-16px\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t[\n\t\t\t\tm(\n\t\t\t\t\t\"div.mr-negative-xs\",\n\t\t\t\t\tm(ExpanderButton, {\n\t\t\t\t\t\texpanded: expanded(),\n\t\t\t\t\t\tonExpandedChange: expanded,\n\t\t\t\t\t\tlabel: \"showMore_action\",\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t],\n\t\t),\n\t\tm(\n\t\t\tExpanderPanel,\n\t\t\t{\n\t\t\t\texpanded: expanded(),\n\t\t\t},\n\t\t\t[\n\t\t\t\tm(\n\t\t\t\t\t\".flex-end.plr\",\n\t\t\t\t\tm(Button, {\n\t\t\t\t\t\tlabel: \"copy_action\",\n\t\t\t\t\t\tclick: () => copyToClipboard(message),\n\t\t\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tm(\n\t\t\t\t\t\".plr.selectable.pb.scroll.text-pre\",\n\t\t\t\t\t{\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\theight: px(200),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tmessage,\n\t\t\t\t),\n\t\t\t],\n\t\t),\n\t]\n\n\treturn Dialog.message(\"unknownError_msg\", info)\n}\n\nexport async function sendFeedbackMail(content: FeedbackContent): Promise<void> {\n\tconst name = \"\"\n\tconst mailAddress = \"reports@tutao.de\"\n\t// We want to treat what we have as text, not as HTML so we escape it. This is an easy way to do it.\n\tconst escapedBody = new Option(content.message).innerHTML\n\tconst logins = locator.logins\n\tconst draft = await locator.mailFacade.createDraft({\n\t\tsubject: content.subject,\n\t\tbodyText: escapedBody.split(\"\\n\").join(\"<br>\"),\n\t\tsenderMailAddress: neverNull(logins.getUserController().userGroupInfo.mailAddress),\n\t\tsenderName: \"\",\n\t\ttoRecipients: [\n\t\t\t{\n\t\t\t\tname,\n\t\t\t\taddress: mailAddress,\n\t\t\t},\n\t\t],\n\t\tccRecipients: [],\n\t\tbccRecipients: [],\n\t\tconversationType: ConversationType.NEW,\n\t\tpreviousMessageId: null,\n\t\tattachments: content.logs,\n\t\tconfidential: true,\n\t\treplyTos: [],\n\t\tmethod: MailMethod.NONE,\n\t})\n\tawait locator.mailFacade.sendDraft(\n\t\tdraft,\n\t\t[\n\t\t\t{\n\t\t\t\tname,\n\t\t\t\taddress: mailAddress,\n\t\t\t\ttype: RecipientType.INTERNAL,\n\t\t\t\tcontact: null,\n\t\t\t},\n\t\t],\n\t\t\"de\",\n\t)\n}\n\nfunction prepareFeedbackContent(error: ErrorInfo, loggedIn: boolean): FeedbackContent {\n\tconst timestamp = new Date()\n\tlet { message, client, type } = clientInfoString(timestamp, loggedIn)\n\n\tif (error) {\n\t\tmessage += errorToString(error)\n\t}\n\n\tconst subject = `Feedback v${env.versionNumber} - ${error && error.name ? error.name : \"?\"} - ${type} - ${client}`\n\treturn {\n\t\tmessage,\n\t\tsubject,\n\t\tlogs: [],\n\t}\n}\n\nexport function clientInfoString(\n\ttimestamp: Date,\n\tloggedIn: boolean,\n): {\n\tmessage: string\n\tclient: string\n\ttype: string\n} {\n\tconst type = loggedIn\n\t\t? neverNull(typedKeys(AccountType).find((typeName) => AccountType[typeName] === locator.logins.getUserController().user.accountType))\n\t\t: \"UNKNOWN\"\n\n\tconst client = (() => {\n\t\tswitch (env.mode) {\n\t\t\tcase Mode.Browser:\n\t\t\tcase Mode.Test:\n\t\t\t\treturn env.mode\n\t\t\tdefault:\n\t\t\t\treturn env.platformId ?? \"\"\n\t\t}\n\t})()\n\n\tlet message = `\\n\\n Client: ${client}`\n\tmessage += `\\n Type: ${type}`\n\tmessage += `\\n Tutanota version: ${env.versionNumber}`\n\tmessage += `\\n Timestamp (UTC): ${timestamp.toUTCString()}`\n\tmessage += `\\n User agent:\\n${navigator.userAgent}` + \"\\n\"\n\treturn {\n\t\tmessage,\n\t\tclient,\n\t\ttype,\n\t}\n}\n\nexport async function getLogAttachments(timestamp?: Date): Promise<Array<Attachment>> {\n\tconst logs: Array<Attachment> = []\n\tconst global = downcast<Window>(window)\n\n\tif (global.logger) {\n\t\tconst mainEntries = global.logger.getEntries()\n\t\tconst mainLogFile = createLogFile(mainEntries.join(\"\\n\"), \"main\", timestamp?.getTime())\n\t\tlogs.push(mainLogFile)\n\t\tconst workerLogEntries = await locator.workerFacade.getLog()\n\t\tconst workerLogFile = await createLogFile(workerLogEntries.join(\"\\n\"), \"worker\", timestamp?.getTime())\n\t\tlogs.push(workerLogFile)\n\t}\n\n\tif (isDesktop() || isApp()) {\n\t\tconst nativeLog = await locator.commonSystemFacade.getLog()\n\t\tconst nativeLogFile = createLogFile(nativeLog, isDesktop() ? \"desktop\" : \"device\", timestamp?.getTime())\n\t\tlogs.push(nativeLogFile)\n\t}\n\n\treturn logs\n}\n","import {\n\tAccessBlockedError,\n\tAccessDeactivatedError,\n\tAccessExpiredError,\n\tConnectionError,\n\tInsufficientStorageError,\n\tInvalidSoftwareVersionError,\n\tNotAuthenticatedError,\n\tRequestTimeoutError,\n\tServiceUnavailableError,\n\tSessionExpiredError,\n} from \"../api/common/error/RestError\"\nimport { Dialog } from \"../gui/base/Dialog\"\nimport { lang } from \"./LanguageViewModel\"\nimport { assertMainOrNode, isDesktop, isOfflineStorageAvailable } from \"../api/common/Env\"\nimport { assertNotNull, noOp } from \"@tutao/tutanota-utils\"\nimport { OutOfSyncError } from \"../api/common/error/OutOfSyncError\"\nimport { showProgressDialog } from \"../gui/dialogs/ProgressDialog\"\nimport { IndexingNotSupportedError } from \"../api/common/error/IndexingNotSupportedError\"\nimport { windowFacade } from \"./WindowFacade\"\nimport { locator } from \"../api/main/MainLocator\"\nimport { QuotaExceededError } from \"../api/common/error/QuotaExceededError\"\nimport { UserError } from \"../api/main/UserError\"\nimport { showMoreStorageNeededOrderDialog } from \"./SubscriptionDialogs\"\nimport { showSnackBar } from \"../gui/base/SnackBar\"\nimport { Credentials } from \"./credentials/Credentials\"\nimport { promptForFeedbackAndSend, showErrorDialogNotLoggedIn } from \"./ErrorReporter\"\nimport { CancelledError } from \"../api/common/error/CancelledError\"\nimport { getLoginErrorMessage } from \"./LoginUtils\"\nimport { isOfflineError } from \"../api/common/utils/ErrorCheckUtils.js\"\nimport { SessionType } from \"../api/common/SessionType.js\"\nimport { OfflineDbClosedError } from \"../api/common/error/OfflineDbClosedError.js\"\nimport { UserTypeRef } from \"../api/entities/sys/TypeRefs.js\"\n\nassertMainOrNode()\n\nlet unknownErrorDialogActive = false\nlet notConnectedDialogActive = false\nlet invalidSoftwareVersionActive = false\nlet loginDialogActive = false\nlet isLoggingOut = false\nlet serviceUnavailableDialogActive = false\nlet requestTimeoutDialogActive = false\nlet shownQuotaError = false\nlet showingImportError = false\nconst ignoredMessages = [\"webkitExitFullScreen\", \"googletag\", \"avast_submit\"]\n\nexport async function handleUncaughtErrorImpl(e: Error) {\n\tconst { logins, interWindowEventSender, sqlCipherFacade, search } = locator\n\n\tif (isLoggingOut) {\n\t\t// ignore all errors while logging out\n\t\treturn\n\t}\n\n\t// This is from the s.js and it shouldn't change. Unfortunately it is a plain Error.\n\tif (\n\t\te.message.includes(\"(SystemJS https://github.com/systemjs/systemjs/blob/master/docs/errors.md#\") ||\n\t\te.message.includes(\"(SystemJS https://git.io/JvFET#3)\") // points to the above url\n\t) {\n\t\thandleImportError()\n\t\treturn\n\t}\n\n\tif (e instanceof UserError) {\n\t\treturn showUserError(e)\n\t}\n\n\tif (isOfflineError(e)) {\n\t\tshowOfflineMessage()\n\t} else if (e instanceof InvalidSoftwareVersionError) {\n\t\tif (!invalidSoftwareVersionActive) {\n\t\t\tinvalidSoftwareVersionActive = true\n\t\t\tDialog.message(\"outdatedClient_msg\").then(() => (invalidSoftwareVersionActive = false))\n\t\t}\n\t} else if (\n\t\te instanceof NotAuthenticatedError ||\n\t\te instanceof AccessBlockedError ||\n\t\te instanceof AccessDeactivatedError ||\n\t\te instanceof AccessExpiredError\n\t) {\n\t\t// If the session is closed (e.g. password is changed) we log user out forcefully so we reload the page\n\t\tif (logins.isUserLoggedIn()) {\n\t\t\tlogoutIfNoPasswordPrompt()\n\t\t}\n\t} else if (e instanceof SessionExpiredError) {\n\t\treloginForExpiredSession()\n\t} else if (e instanceof OutOfSyncError) {\n\t\tconst isOffline = isOfflineStorageAvailable() && logins.isUserLoggedIn() && logins.getUserController().sessionType === SessionType.Persistent\n\n\t\tawait Dialog.message(\"outOfSync_label\", lang.get(isOffline ? \"dataExpiredOfflineDb_msg\" : \"dataExpired_msg\"))\n\n\t\tconst { userId } = logins.getUserController()\n\t\tif (isDesktop()) {\n\t\t\tawait interWindowEventSender?.localUserDataInvalidated(userId)\n\t\t\tawait sqlCipherFacade?.deleteDb(userId)\n\t\t}\n\t\tawait logins.logout(false)\n\t\tawait windowFacade.reload({ noAutoLogin: true })\n\t} else if (e instanceof InsufficientStorageError) {\n\t\tif (logins.getUserController().isGlobalAdmin()) {\n\t\t\tshowMoreStorageNeededOrderDialog(\"insufficientStorageAdmin_msg\")\n\t\t} else {\n\t\t\tconst errorMessage = () => lang.get(\"insufficientStorageUser_msg\") + \" \" + lang.get(\"contactAdmin_msg\")\n\n\t\t\tDialog.message(errorMessage)\n\t\t}\n\t} else if (e instanceof ServiceUnavailableError) {\n\t\tif (!serviceUnavailableDialogActive) {\n\t\t\tserviceUnavailableDialogActive = true\n\t\t\tDialog.message(\"serviceUnavailable_msg\").then(() => {\n\t\t\t\tserviceUnavailableDialogActive = false\n\t\t\t})\n\t\t}\n\t} else if (e instanceof RequestTimeoutError) {\n\t\tif (!requestTimeoutDialogActive) {\n\t\t\trequestTimeoutDialogActive = true\n\t\t\tDialog.message(\"requestTimeout_msg\").then(() => {\n\t\t\t\trequestTimeoutDialogActive = false\n\t\t\t})\n\t\t}\n\t} else if (e instanceof IndexingNotSupportedError) {\n\t\tconsole.log(\"Indexing not supported\", e)\n\t\tsearch.indexingSupported = false\n\t} else if (e instanceof QuotaExceededError) {\n\t\tif (!shownQuotaError) {\n\t\t\tshownQuotaError = true\n\t\t\tDialog.message(\"storageQuotaExceeded_msg\")\n\t\t}\n\t} else if (e instanceof OfflineDbClosedError) {\n\t\tif (!loginDialogActive) {\n\t\t\tthrow e\n\t\t}\n\t} else if (ignoredError(e)) {\n\t\t// ignore, this is not our code\n\t} else {\n\t\tif (!unknownErrorDialogActive) {\n\t\t\tunknownErrorDialogActive = true\n\n\t\t\t// only logged in users can report errors because we send mail for that.\n\t\t\tif (logins.isUserLoggedIn()) {\n\t\t\t\tconst { ignored } = await promptForFeedbackAndSend(e)\n\t\t\t\tunknownErrorDialogActive = false\n\t\t\t\tif (ignored) {\n\t\t\t\t\tignoredMessages.push(e.message)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconsole.log(\"Unknown error\", e)\n\t\t\t\tshowErrorDialogNotLoggedIn(e).then(() => (unknownErrorDialogActive = false))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction showOfflineMessage() {\n\tif (!notConnectedDialogActive) {\n\t\tnotConnectedDialogActive = true\n\t\tshowSnackBar({\n\t\t\tmessage: \"serverNotReachable_msg\",\n\t\t\tbutton: {\n\t\t\t\tlabel: \"ok_action\",\n\t\t\t\tclick: () => {},\n\t\t\t},\n\t\t\tonClose: () => {\n\t\t\t\tnotConnectedDialogActive = false\n\t\t\t},\n\t\t})\n\t}\n}\n\nfunction logoutIfNoPasswordPrompt() {\n\tif (!loginDialogActive) {\n\t\twindowFacade.reload({})\n\t}\n}\n\nexport async function reloginForExpiredSession() {\n\tif (loginDialogActive) {\n\t\treturn\n\t}\n\tconst { logins, loginFacade, secondFactorHandler, credentialsProvider, sqlCipherFacade, cacheStorage } = locator\n\t// Make sure that partial login part is complete before we will try to make a new session.\n\t// Otherwise we run into a race condition where login failure arrives before we initialize userController.\n\tawait logins.waitForPartialLogin()\n\tconsole.log(\"RELOGIN\", logins.isUserLoggedIn())\n\tconst oldSessionType = logins.getUserController().sessionType\n\tconst userId = logins.getUserController().user._id\n\tconst mailAddress = assertNotNull(logins.getUserController().userGroupInfo.mailAddress, \"could not get mailAddress from userGroupInfo\")\n\t// Fetch old credentials to preserve database key if it's there\n\tconst oldCredentials = await credentialsProvider.getCredentialsByUserId(userId)\n\t// we're deleting the outdated user here because before resetSession() the cache is still open and can be modified.\n\tawait cacheStorage?.deleteIfExists(UserTypeRef, null, userId)\n\tconst sessionReset = loginFacade.resetSession()\n\tloginDialogActive = true\n\n\tconst dialog = Dialog.showRequestPasswordDialog({\n\t\taction: async (pw) => {\n\t\t\tawait sessionReset\n\t\t\tlet credentials: Credentials\n\t\t\tlet databaseKey: Uint8Array | null\n\t\t\ttry {\n\t\t\t\tconst newSessionData = await logins.createSession(mailAddress, pw, oldSessionType, oldCredentials?.databaseKey)\n\t\t\t\tcredentials = newSessionData.credentials\n\t\t\t\tdatabaseKey = newSessionData.databaseKey\n\t\t\t} catch (e) {\n\t\t\t\tif (\n\t\t\t\t\te instanceof CancelledError ||\n\t\t\t\t\te instanceof AccessBlockedError ||\n\t\t\t\t\te instanceof NotAuthenticatedError ||\n\t\t\t\t\te instanceof AccessDeactivatedError ||\n\t\t\t\t\te instanceof ConnectionError\n\t\t\t\t) {\n\t\t\t\t\tconst { getLoginErrorMessage } = await import(\"../misc/LoginUtils.js\")\n\t\t\t\t\treturn lang.getMaybeLazy(getLoginErrorMessage(e, false))\n\t\t\t\t} else {\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\t// Once login succeeds we need to manually close the dialog\n\t\t\t\tsecondFactorHandler.closeWaitingForSecondFactorDialog()\n\t\t\t}\n\t\t\tawait credentialsProvider.deleteByUserId(userId, { deleteOfflineDb: false })\n\t\t\tif (oldSessionType === SessionType.Persistent) {\n\t\t\t\tawait credentialsProvider.store({ credentials, databaseKey })\n\t\t\t}\n\t\t\tloginDialogActive = false\n\t\t\tdialog.close()\n\t\t\treturn \"\"\n\t\t},\n\t\tcancel: {\n\t\t\ttextId: \"logout_label\",\n\t\t\taction() {\n\t\t\t\twindowFacade.reload({})\n\t\t\t},\n\t\t},\n\t})\n}\n\nfunction ignoredError(e: Error): boolean {\n\treturn e.message != null && ignoredMessages.some((s) => e.message.includes(s))\n}\n\n/**\n * Trying to handle errors during logout can cause unhandled error loops, so we just want to ignore them\n */\nexport function disableErrorHandlingDuringLogout() {\n\tisLoggingOut = true\n\tshowProgressDialog(\"loggingOut_msg\", new Promise(noOp))\n}\n\nfunction handleImportError() {\n\tif (showingImportError) {\n\t\treturn\n\t}\n\n\tshowingImportError = true\n\tconst message =\n\t\t\"There was an error while loading part of the app. It might be that you are offline, running an outdated version, or your browser is blocking the request.\"\n\tDialog.choice(\n\t\t() => message,\n\t\t[\n\t\t\t{\n\t\t\t\ttext: \"close_alt\",\n\t\t\t\tvalue: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\ttext: \"reloadPage_action\",\n\t\t\t\tvalue: true,\n\t\t\t},\n\t\t],\n\t).then((reload) => {\n\t\tshowingImportError = false\n\n\t\tif (reload) {\n\t\t\twindowFacade.reload({})\n\t\t}\n\t})\n}\n\nif (typeof window !== \"undefined\") {\n\t// @ts-ignore\n\twindow.tutao.testError = () => handleUncaughtErrorImpl(new Error(\"test error!\"))\n}\n\nexport function showUserError(error: UserError): Promise<void> {\n\treturn Dialog.message(() => error.message)\n}\n","import { styles } from \"./styles\"\nimport { px, size } from \"./size\"\nimport { client } from \"../misc/ClientDetector\"\nimport { lang } from \"../misc/LanguageViewModel\"\nimport { noselect, position_absolute, positionValue } from \"./mixins\"\nimport { assertMainOrNode, isAdminClient, isApp, isElectronClient } from \"../api/common/Env\"\nimport { getElevatedBackground, getNavigationMenuBg, theme } from \"./theme\"\nimport { BrowserType } from \"../misc/ClientConstants\"\nimport { stateBgActive, stateBgFocus, stateBgHover, stateBgLike } from \"./builtinThemes.js\"\nimport { FontIcons } from \"./base/icons/FontIcons.js\"\nimport { DefaultAnimationTime } from \"./animation/Animations.js\"\n\nassertMainOrNode()\n\nexport function getFonts(): string {\n\t// see https://bitsofco.de/the-new-system-font-stack/\n\tconst fonts: Array<string> = [\n\t\t\"-apple-system\",\n\t\t\"system-ui\",\n\t\t\"BlinkMacSystemFont\",\n\t\t\"Segoe UI\",\n\t\t\"Roboto\",\n\t\t\"Helvetica Neue\",\n\t\t\"Helvetica\",\n\t\t\"Arial\",\n\t\t\"sans-serif\",\n\t]\n\t// workaround for incorrect Japanese font see https://github.com/tutao/tutanota/issues/1909\n\tif (env.platformId === \"win32\" && lang.code === \"ja\") fonts.push(\"SimHei\", \"黑体\")\n\tfonts.push(\"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\")\n\treturn fonts.join(\", \")\n}\n\nconst boxShadow = `0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)`\nconst searchBarShadow = \"0px 2px 4px rgb(0, 0, 0, 0.12)\"\n\nconst scrollbarWidthHeight = \"18px\"\nstyles.registerStyle(\"main\", () => {\n\treturn {\n\t\t\"#link-tt\": isElectronClient()\n\t\t\t? {\n\t\t\t\t\t\"pointer-events\": \"none\",\n\t\t\t\t\t\"font-size\": px(size.font_size_small),\n\t\t\t\t\t\"padding-left\": px(size.hpad_small),\n\t\t\t\t\t\"padding-right\": px(size.hpad_small),\n\t\t\t\t\t\"padding-top\": px(size.vpad_xs),\n\t\t\t\t\tposition: \"fixed\",\n\t\t\t\t\tbottom: px(size.vpad_xs),\n\t\t\t\t\tleft: px(size.vpad_xs),\n\t\t\t\t\t\"text-align\": \"center\",\n\t\t\t\t\tcolor: theme.content_bg,\n\t\t\t\t\t\"text-decoration\": \"none\",\n\t\t\t\t\t\"background-color\": theme.content_fg,\n\t\t\t\t\tborder: \"1px solid \" + theme.content_bg,\n\t\t\t\t\topacity: 0,\n\t\t\t\t\ttransition: \"opacity .1s linear\",\n\t\t\t\t\t\"font-family\": \"monospace\",\n\t\t\t  }\n\t\t\t: {},\n\t\t\"#link-tt.reveal\": isElectronClient()\n\t\t\t? {\n\t\t\t\t\topacity: 1,\n\t\t\t\t\ttransition: \"opacity .1s linear\",\n\t\t\t\t\t\"z-index\": 100,\n\t\t\t  }\n\t\t\t: {},\n\t\t\"*:not(input):not(textarea)\": isAdminClient()\n\t\t\t? {}\n\t\t\t: {\n\t\t\t\t\t\"user-select\": \"none\",\n\n\t\t\t\t\t/* disable selection/Copy for UI elements*/\n\t\t\t\t\t\"-ms-user-select\": \"none\",\n\t\t\t\t\t\"-webkit-user-select\": \"none\",\n\t\t\t\t\t\"-moz-user-select\": \"none\",\n\t\t\t\t\t\"-webkit-touch-callout\": \"none\",\n\n\t\t\t\t\t/* disable the IOS popup when long-press on a link */\n\t\t\t\t\t\"-webkit-tap-highlight-color\": \"rgba(0, 0, 0, 0)\",\n\t\t\t  },\n\t\t\"*:not(input):not(textarea):not([draggable='true'])\": {\n\t\t\t\"-webkit-user-drag\": \"none\",\n\t\t},\n\t\t// Disable outline for mouse and touch navigation\n\t\t\":where(.mouse-nav) *, :where(.touch-nav) *\": {\n\t\t\toutline: \"none\",\n\t\t},\n\t\t\".selectable\": {\n\t\t\t\"user-select\": \"text !important\",\n\t\t\t\"-ms-user-select\": \"text !important\",\n\t\t\t\"-webkit-user-select\": \"text !important\",\n\t\t\t\"-moz-user-select\": \"text !important\",\n\t\t\t\"-webkit-touch-callout\": \"default !important\",\n\t\t},\n\t\t\".selectable *\": {\n\t\t\t\"user-select\": \"text !important\",\n\t\t\t\"-ms-user-select\": \"text !important\",\n\t\t\t\"-webkit-user-select\": \"text !important\",\n\t\t\t\"-moz-user-select\": \"text !important\",\n\t\t\t\"-webkit-touch-callout\": \"default !important\",\n\t\t},\n\t\t\"@font-face\": {\n\t\t\t\"font-family\": \"'Ionicons'\",\n\t\t\tsrc: `url('${window.tutao.appState.prefixWithoutFile}/images/font.ttf') format('truetype')`,\n\t\t\t\"font-weight\": \"normal\",\n\t\t\t\"font-style\": \"normal\",\n\t\t},\n\t\t// Allow long-click contextual actions for iOS\n\t\t\".touch-callout *\": {\n\t\t\t\"-webkit-touch-callout\": \"default !important\",\n\t\t},\n\n\t\t/*\n\t Box Sizing\n\t */\n\t\t[`html, body, div, article, section, main, footer, header, form, fieldset, legend,\n            pre, code, p, a, h1, h2, h3, h4, h5, h6, ul, ol, li, dl, dt, dd, textarea,\n            input[type=\"email\"], input[type=\"number\"], input[type=\"password\"],\n            input[type=\"tel\"], input[type=\"text\"], input[type=\"url\"], .border-box`]: {\n\t\t\t\"box-sizing\": \"border-box\",\n\t\t},\n\t\ta: {\n\t\t\tcolor: \"inherit\",\n\t\t},\n\t\t\":root\": {\n\t\t\t// We need it because we can't get env() value from JS directly\n\t\t\t\"--safe-area-inset-bottom\": \"env(safe-area-inset-bottom)\",\n\t\t\t\"--safe-area-inset-top\": \"env(safe-area-inset-top)\",\n\t\t\t\"--safe-area-inset-right\": \"env(safe-area-inset-right)\",\n\t\t\t\"--safe-area-inset-left\": \"env(safe-area-inset-left)\",\n\t\t},\n\t\t\"html, body\": {\n\t\t\theight: \"100%\",\n\t\t\tmargin: 0,\n\t\t\twidth: \"100%\",\n\t\t},\n\t\thtml: {\n\t\t\t\"-webkit-font-smoothing\": \"subpixel-antialiased\",\n\t\t},\n\t\t// define font-smoothing for css animation in safari\n\t\tbody: {\n\t\t\tposition: \"fixed\",\n\t\t\t// Fix body for iOS & Safari\n\t\t\t// It is inlined to \"transparent\" in HTML so we have to overwrite it.\n\t\t\t\"background-color\": `${theme.content_bg} !important`,\n\t\t},\n\t\t\"button, textarea\": {\n\t\t\tpadding: 0,\n\t\t\t\"text-align\": \"left\",\n\t\t},\n\t\tbutton: {\n\t\t\tbackground: \"transparent\", // removes default browser style for buttons\n\t\t},\n\t\t\"button:disabled\": {\n\t\t\tcursor: \"default\",\n\t\t},\n\t\t\"body, button\": {\n\t\t\t// Yes we have to tell buttons separately because browser button styles override general body ones\n\t\t\toverflow: \"hidden\",\n\t\t\t// see: https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ and github\n\t\t\t\"font-family\": getFonts(),\n\t\t\t\"font-size\": px(size.font_size_base),\n\t\t\t\"line-height\": size.line_height,\n\t\t\tcolor: theme.content_fg,\n\t\t\t\"-webkit-text-size-adjust\": \"none\", // fix for safari browser\n\t\t},\n\t\t\"small, .small\": {\n\t\t\t\"font-size\": px(size.font_size_small),\n\t\t},\n\t\t\".smaller\": {\n\t\t\t\"font-size\": px(size.font_size_smaller),\n\t\t},\n\t\t\".b\": {\n\t\t\t\"font-weight\": \"bold\",\n\t\t},\n\t\t\".font-weight-600\": {\n\t\t\t\"font-weight\": \"600\",\n\t\t},\n\t\t\".i\": {\n\t\t\t\"font-style\": \"italic\",\n\t\t},\n\t\t\".click\": {\n\t\t\tcursor: \"pointer\",\n\t\t\t\"-webkit-tap-highlight-color\": \"rgba(255, 255, 255, 0)\",\n\t\t},\n\t\t\".click-disabled\": {\n\t\t\tcursor: \"default\",\n\t\t},\n\t\t\".text\": {\n\t\t\tcursor: \"text\",\n\t\t},\n\t\t\".overflow-hidden\": {\n\t\t\toverflow: \"hidden\",\n\t\t},\n\t\t\".overflow-x-hidden\": {\n\t\t\t\"overflow-x\": \"hidden\",\n\t\t},\n\t\t\".overflow-y-hidden\": {\n\t\t\t\"overflow-y\": \"hidden\",\n\t\t},\n\t\t\".overflow-y-visible\": {\n\t\t\t\"overflow-y\": \"visible !important\",\n\t\t},\n\t\t\".overflow-y-scroll\": {\n\t\t\t\"overflow-y\": \"scroll\",\n\t\t\t\"webkit-overflow-scrolling\": \"touch\",\n\t\t},\n\t\t\".overflow-visible\": {\n\t\t\toverflow: \"visible\",\n\t\t},\n\t\t\"h1, h2, h3, h4, h5, h6\": {\n\t\t\tmargin: 0,\n\t\t\t\"font-weight\": \"normal\",\n\t\t},\n\t\t\"h1, .h1\": {\n\t\t\t\"font-size\": px(size.font_size_base * 2),\n\t\t},\n\t\t\"h2, .h2\": {\n\t\t\t\"font-size\": px(size.font_size_base * 1.8),\n\t\t},\n\t\t\"h3, .h3\": {\n\t\t\t\"font-size\": px(size.font_size_base * 1.6),\n\t\t},\n\t\t\"h4, .h4\": {\n\t\t\t\"font-size\": px(size.font_size_base * 1.4),\n\t\t},\n\t\t\"h5, .h5\": {\n\t\t\t\"font-size\": px(size.font_size_base * 1.2),\n\t\t},\n\t\t\"h6, .h6\": {\n\t\t\t\"font-size\": px(size.font_size_base * 1.1),\n\t\t},\n\t\t\"input, button, select, textarea\": {\n\t\t\t\"font-family\": \"inherit\",\n\t\t\t\"font-size\": \"inherit\",\n\t\t\t\"line-height\": \"inherit\",\n\t\t},\n\t\t\".hr\": {\n\t\t\tmargin: 0,\n\t\t\tborder: \"none\",\n\t\t\theight: \"1px\",\n\t\t\t\"background-color\": theme.list_border,\n\t\t},\n\t\t\".border\": {\n\t\t\tborder: `1px solid ${theme.content_border}`,\n\t\t},\n\t\t\".border-top\": {\n\t\t\t\"border-top\": `1px solid ${theme.content_border}`,\n\t\t},\n\t\t\"#mail-body.break-pre pre\": {\n\t\t\t\"white-space\": \"pre-wrap\",\n\t\t\t\"word-break\": \"normal\",\n\t\t\t\"overflow-wrap\": \"anywhere\",\n\t\t},\n\t\t\".white-space-pre\": {\n\t\t\t\"white-space\": \"pre\",\n\t\t},\n\t\t\".min-content\": {\n\t\t\twidth: \"min-content\",\n\t\t\theight: \"min-content\",\n\t\t},\n\t\t// margins\n\t\t\".m-0\": {\n\t\t\tmargin: 0,\n\t\t},\n\t\t\".mt\": {\n\t\t\t\"margin-top\": px(size.vpad),\n\t\t},\n\t\t\".mt-xs\": {\n\t\t\t\"margin-top\": px(size.vpad_xs),\n\t\t},\n\t\t\".mt-xxs\": {\n\t\t\t\"margin-top\": px(2),\n\t\t},\n\t\t\".mt-s\": {\n\t\t\t\"margin-top\": px(size.vpad_small),\n\t\t},\n\t\t\".mt-m\": {\n\t\t\t\"margin-top\": px(size.hpad),\n\t\t},\n\t\t\".mt-l\": {\n\t\t\t\"margin-top\": px(size.vpad_large),\n\t\t},\n\t\t\".mt-xl\": {\n\t\t\t\"margin-top\": px(size.vpad_xl),\n\t\t},\n\t\t\".mt-form\": {\n\t\t\t\"margin-top\": px(size.hpad_medium),\n\t\t},\n\t\t\".mb-0\": {\n\t\t\t\"margin-bottom\": 0,\n\t\t},\n\t\t\".mb\": {\n\t\t\t\"margin-bottom\": px(size.vpad),\n\t\t},\n\t\t\".mb-s\": {\n\t\t\t\"margin-bottom\": px(size.vpad_small),\n\t\t},\n\t\t\".mb-xs\": {\n\t\t\t\"margin-bottom\": px(size.vpad_xs),\n\t\t},\n\t\t\".mb-l\": {\n\t\t\t\"margin-bottom\": px(size.vpad_large),\n\t\t},\n\t\t\".mb-xl\": {\n\t\t\t\"margin-bottom\": px(size.vpad_xl),\n\t\t},\n\t\t\".mlr\": {\n\t\t\t\"margin-left\": px(size.hpad),\n\t\t\t\"margin-right\": px(size.hpad),\n\t\t},\n\t\t\".mlr-button\": {\n\t\t\t\"margin-left\": px(size.hpad_button),\n\t\t\t\"margin-right\": px(size.hpad_button),\n\t\t},\n\t\t\".mlr-l\": {\n\t\t\t\"margin-left\": px(size.hpad_large),\n\t\t\t\"margin-right\": px(size.hpad_large),\n\t\t},\n\t\t\".mr-s\": {\n\t\t\t\"margin-right\": px(size.vpad_small),\n\t\t},\n\t\t\".mr-xs\": {\n\t\t\t\"margin-right\": px(size.vpad_xs),\n\t\t},\n\t\t\".ml-s\": {\n\t\t\t\"margin-left\": px(size.vpad_small),\n\t\t},\n\t\t\".ml-m\": {\n\t\t\t\"margin-left\": px(size.hpad_medium),\n\t\t},\n\t\t\".ml-l\": {\n\t\t\t\"margin-left\": px(size.hpad_large),\n\t\t},\n\t\t\".mr-m\": {\n\t\t\t\"margin-right\": px(size.hpad_medium),\n\t\t},\n\t\t\".mr-l\": {\n\t\t\t\"margin-right\": px(size.hpad_large),\n\t\t},\n\t\t\".mlr-s\": {\n\t\t\t\"margin-left\": px(size.hpad_small),\n\t\t\t\"margin-right\": px(size.hpad_small),\n\t\t},\n\t\t\".mtb-0\": {\n\t\t\t\"margin-top\": px(0),\n\t\t\t\"margin-bottom\": px(0),\n\t\t},\n\t\t\".mr\": {\n\t\t\t\"margin-right\": px(size.hpad),\n\t\t},\n\t\t\".ml\": {\n\t\t\t\"margin-left\": px(size.hpad),\n\t\t},\n\t\t// paddings\n\t\t\".p0\": {\n\t\t\tpadding: \"0\",\n\t\t},\n\t\t\".pt\": {\n\t\t\t\"padding-top\": px(size.vpad),\n\t\t},\n\t\t\".pt-0\": {\n\t\t\t\"padding-top\": 0,\n\t\t},\n\t\t\".pt-s\": {\n\t\t\t\"padding-top\": px(size.vpad_small),\n\t\t},\n\t\t\".pt-l\": {\n\t\t\t\"padding-top\": px(size.vpad_large),\n\t\t},\n\t\t\".pt-m\": {\n\t\t\t\"padding-top\": px(size.hpad),\n\t\t},\n\t\t\".pt-ml\": {\n\t\t\t\"padding-top\": px(size.vpad_ml),\n\t\t},\n\t\t\".pt-xl\": {\n\t\t\t\"padding-top\": px(size.vpad_xl),\n\t\t},\n\t\t\".pt-xs\": {\n\t\t\t\"padding-top\": px(size.vpad_xs),\n\t\t},\n\t\t\".pb-0\": {\n\t\t\t\"padding-bottom\": 0,\n\t\t},\n\t\t\".pb\": {\n\t\t\t\"padding-bottom\": px(size.vpad),\n\t\t},\n\t\t\".pb-2\": {\n\t\t\t\"padding-bottom\": \"2px\",\n\t\t},\n\t\t// for dropdown toggles\n\t\t\".pb-s\": {\n\t\t\t\"padding-bottom\": px(size.vpad_small),\n\t\t},\n\t\t\".pb-xs\": {\n\t\t\t\"padding-bottom\": px(size.vpad_xs),\n\t\t},\n\t\t\".pb-l\": {\n\t\t\t\"padding-bottom\": px(size.vpad_large),\n\t\t},\n\t\t\".pb-xl\": {\n\t\t\t\"padding-bottom\": px(size.vpad_xl),\n\t\t},\n\t\t\".pb-m\": {\n\t\t\t\"padding-bottom\": px(size.hpad),\n\t\t},\n\t\t\".pb-ml\": {\n\t\t\t\"padding-bottom\": px(size.vpad_ml),\n\t\t},\n\t\t\".pb-floating\": {\n\t\t\t\"padding-bottom\": px(size.button_floating_size + size.hpad_large),\n\t\t},\n\t\t// allow scrolling across the floating button\n\t\t\".plr\": {\n\t\t\t\"padding-left\": px(size.hpad),\n\t\t\t\"padding-right\": px(size.hpad),\n\t\t},\n\t\t\".pl\": {\n\t\t\t\"padding-left\": px(size.hpad),\n\t\t},\n\t\t\".pl-s\": {\n\t\t\t\"padding-left\": px(size.hpad_small),\n\t\t},\n\t\t\".pl-m\": {\n\t\t\t\"padding-left\": px(size.hpad),\n\t\t},\n\t\t\".pl-xs\": {\n\t\t\t\"padding-left\": px(size.vpad_xs),\n\t\t},\n\t\t\".pr\": {\n\t\t\t\"padding-right\": px(size.hpad),\n\t\t},\n\t\t\".pr-s\": {\n\t\t\t\"padding-right\": px(size.hpad_small),\n\t\t},\n\t\t\".pr-m\": {\n\t\t\t\"padding-right\": px(size.vpad),\n\t\t},\n\t\t\".plr-s\": {\n\t\t\t\"padding-left\": px(size.hpad_small),\n\t\t\t\"padding-right\": px(size.hpad_small),\n\t\t},\n\t\t\".plr-m\": {\n\t\t\t\"padding-left\": px(size.hpad),\n\t\t\t\"padding-right\": px(size.hpad),\n\t\t},\n\t\t// p-l will be overwritten in media query mobile\n\t\t\".plr-l\": {\n\t\t\t\"padding-left\": px(size.hpad_large),\n\t\t\t\"padding-right\": px(size.hpad_large),\n\t\t},\n\t\t\".plr-2l\": {\n\t\t\t\"padding-left\": px(size.hpad_large * 2),\n\t\t\t\"padding-right\": px(size.hpad_large * 2),\n\t\t},\n\t\t\".pl-l\": {\n\t\t\t\"padding-left\": px(size.hpad_large),\n\t\t},\n\t\t\".pr-l\": {\n\t\t\t\"padding-right\": px(size.hpad_large),\n\t\t},\n\t\t\".plr-button\": {\n\t\t\t\"padding-left\": px(size.hpad_button),\n\t\t\t\"padding-right\": px(size.hpad_button),\n\t\t},\n\t\t\".plr-button-double\": {\n\t\t\t\"padding-left\": px(size.hpad_button * 2),\n\t\t\t\"padding-right\": px(size.hpad_button * 2),\n\t\t},\n\t\t\".plr-nav-button\": {\n\t\t\t\"padding-left\": px(size.hpad_nav_button),\n\t\t\t\"padding-right\": px(size.hpad_nav_button),\n\t\t},\n\t\t\".pl-button\": {\n\t\t\t\"padding-left\": px(size.hpad_button),\n\t\t},\n\t\t\".mr-button\": {\n\t\t\t\"margin-right\": px(size.hpad_button),\n\t\t},\n\t\t\".mt-negative-hpad-button\": {\n\t\t\t\"margin-top\": px(-size.hpad_button),\n\t\t},\n\t\t\".mt-negative-s\": {\n\t\t\t\"margin-top\": px(-size.vpad_small),\n\t\t},\n\t\t\".mt-negative-m\": {\n\t\t\t\"margin-top\": px(-size.vpad),\n\t\t},\n\t\t\".mt-negative-l\": {\n\t\t\t\"margin-top\": px(-size.hpad_large),\n\t\t},\n\t\t\".mr-negative-s\": {\n\t\t\t\"margin-right\": px(-size.hpad_button),\n\t\t},\n\t\t\".ml-negative-s\": {\n\t\t\t\"margin-left\": px(-size.hpad_button),\n\t\t},\n\t\t// negative margin to handle the default padding of a button\n\t\t\".ml-negative-l\": {\n\t\t\t\"margin-left\": px(-size.hpad_large),\n\t\t},\n\t\t\".ml-negative-xs\": {\n\t\t\t\"margin-left\": px(-3),\n\t\t},\n\t\t\".ml-negative-bubble\": {\n\t\t\t\"margin-left\": px(-7),\n\t\t},\n\t\t\".mr-negative-m\": {\n\t\t\t\"margin-right\": px(-(size.hpad_button + size.hpad_nav_button)),\n\t\t},\n\t\t// negative margin to handle the padding of a nav button\n\t\t\".fixed-bottom-right\": {\n\t\t\tposition: \"fixed\",\n\t\t\tbottom: px(size.hpad),\n\t\t\tright: px(size.hpad_large),\n\t\t},\n\t\t\".mr-negative-xs\": {\n\t\t\t\"margin-right\": px(-3),\n\t\t},\n\t\t// common setting\n\t\t\".text-ellipsis\": {\n\t\t\toverflow: \"hidden\",\n\t\t\t\"text-overflow\": \"ellipsis\",\n\t\t\t\"min-width\": 0,\n\t\t\t\"white-space\": \"nowrap\",\n\t\t},\n\t\t\".min-width-0\": {\n\t\t\t\"min-width\": 0,\n\t\t},\n\t\t// used to enable text ellipsis in flex child elements see https://css-tricks.com/flexbox-truncated-text/\n\t\t\".text-break\": {\n\t\t\toverflow: \"hidden\",\n\t\t\t\"word-break\": \"normal\",\n\t\t\t\"overflow-wrap\": \"anywhere\",\n\t\t},\n\t\t\".break-word\": {\n\t\t\t\"word-break\": \"normal\",\n\t\t\t\"overflow-wrap\": \"break-word\",\n\t\t},\n\t\t\".break-all\": {\n\t\t\t\"word-break\": \"break-all\",\n\t\t},\n\t\t\".break-word-links a\": {\n\t\t\t\"word-wrap\": \"break-word\",\n\t\t},\n\t\t\".text-prewrap\": {\n\t\t\t\"white-space\": \"pre-wrap\",\n\t\t},\n\t\t\".text-preline\": {\n\t\t\t\"white-space\": \"pre-line\",\n\t\t},\n\t\t\".text-pre\": {\n\t\t\t\"white-space\": \"pre\",\n\t\t},\n\t\t\".line-break-anywhere\": {\n\t\t\t\"line-break\": \"anywhere\",\n\t\t},\n\t\t\".z1\": {\n\t\t\t\"z-index\": \"1\",\n\t\t},\n\t\t\".z2\": {\n\t\t\t\"z-index\": \"2\",\n\t\t},\n\t\t\".z3\": {\n\t\t\t\"z-index\": \"3\",\n\t\t},\n\t\t\".z4\": {\n\t\t\t\"z-index\": \"4\",\n\t\t},\n\t\t\".noselect\": noselect,\n\t\t\".no-wrap\": {\n\t\t\t\"white-space\": \"nowrap\",\n\t\t},\n\t\t\".height-100p\": {\n\t\t\theight: \"100%\",\n\t\t},\n\t\t\".view-columns\": {\n\t\t\toverflow: \"hidden\",\n\t\t},\n\t\t\".view-column\": {\n\t\t\t\"will-change\": \"transform\",\n\t\t},\n\t\t\".will-change-alpha\": {\n\t\t\t\"will-change\": \"alpha\",\n\t\t},\n\t\t// borders\n\t\t\".border-bottom\": {\n\t\t\t\"border-bottom\": `1px solid ${theme.content_border}`,\n\t\t},\n\t\t\".border-left\": {\n\t\t\t\"border-left\": `1px solid ${theme.content_border}`,\n\t\t},\n\t\t// colors\n\t\t\".bg-transparent\": {\n\t\t\t\"background-color\": \"transparent\",\n\t\t},\n\t\t\".bg-white\": {\n\t\t\t\"background-color\": \"white\",\n\t\t},\n\t\t\".content-black\": {\n\t\t\tcolor: \"black\",\n\t\t},\n\t\t\".content-fg\": {\n\t\t\tcolor: theme.content_fg,\n\t\t},\n\t\t\".content-accent-fg\": {\n\t\t\tcolor: theme.content_accent,\n\t\t},\n\t\t\".svg-content-fg path\": {\n\t\t\tfill: theme.content_fg,\n\t\t},\n\t\t\".content-bg\": {\n\t\t\t\"background-color\": theme.content_bg,\n\t\t},\n\t\t\".nav-bg\": {\n\t\t\t\"background-color\": theme.navigation_bg,\n\t\t},\n\t\t\".content-hover:hover\": {\n\t\t\tcolor: theme.content_accent,\n\t\t},\n\t\t\".no-hover\": {\n\t\t\t\"pointer-events\": \"none\",\n\t\t},\n\t\t\".content-message-bg\": {\n\t\t\t\"background-color\": theme.content_message_bg,\n\t\t},\n\t\t\".elevated-bg\": {\n\t\t\t\"background-color\": getElevatedBackground(),\n\t\t},\n\t\t\".list-bg\": {\n\t\t\t\"background-color\": theme.list_bg,\n\t\t},\n\t\t\".list-accent-fg\": {\n\t\t\tcolor: theme.list_accent_fg,\n\t\t},\n\t\t\".svg-list-accent-fg path\": {\n\t\t\tfill: theme.list_accent_fg,\n\t\t},\n\t\t\".bg-accent-fg\": {\n\t\t\t\"background-color\": theme.list_accent_fg,\n\t\t},\n\t\t\".list-border-bottom\": {\n\t\t\t\"border-bottom\": `1px solid ${theme.list_border}`,\n\t\t},\n\t\t\".accent-bg\": {\n\t\t\t\"background-color\": theme.content_accent,\n\t\t\tcolor: theme.content_button_icon_selected,\n\t\t},\n\t\t\".accent-fg\": {\n\t\t\tcolor: theme.content_button_icon,\n\t\t},\n\t\t\".accent-fg path\": {\n\t\t\tfill: theme.content_button_icon,\n\t\t},\n\t\t\".red\": {\n\t\t\t\"background-color\": \"#840010\",\n\t\t},\n\t\t\".swipe-spacer\": {\n\t\t\tcolor: \"#ffffff\",\n\t\t},\n\t\t\".swipe-spacer path\": {\n\t\t\tfill: \"#ffffff\",\n\t\t},\n\t\t\".blue\": {\n\t\t\t\"background-color\": \"#2196F3\",\n\t\t},\n\t\t\".underline\": {\n\t\t\t\"text-decoration\": \"underline\",\n\t\t},\n\t\t\".hover-ul:hover\": {\n\t\t\t\"text-decoration\": isApp() ? \"none\" : \"underline\",\n\t\t},\n\t\t// positioning1\n\t\t\".fill-absolute\": {\n\t\t\tposition: \"absolute\",\n\t\t\ttop: 0,\n\t\t\tbottom: 0,\n\t\t\tleft: 0,\n\t\t\tright: 0,\n\t\t},\n\t\t\".abs\": {\n\t\t\tposition: \"absolute\",\n\t\t},\n\t\t\".fixed\": {\n\t\t\tposition: \"fixed\",\n\t\t},\n\t\t\".rel\": {\n\t\t\tposition: \"relative\",\n\t\t},\n\t\t\".max-width-s\": {\n\t\t\t\"max-width\": px(360),\n\t\t},\n\t\t\".max-width-m\": {\n\t\t\t\"max-width\": px(450),\n\t\t},\n\t\t\".max-width-l\": {\n\t\t\t\"max-width\": px(800),\n\t\t},\n\t\t\".max-width-200\": {\n\t\t\t\"max-width\": px(200),\n\t\t},\n\t\t\".scroll\": {\n\t\t\t\"overflow-y\": client.overflowAuto,\n\t\t\t\"-webkit-overflow-scrolling\": \"touch\",\n\t\t},\n\t\t\".scroll-no-overlay\": {\n\t\t\t\"overflow-y\": \"auto\",\n\t\t\t\"-webkit-overflow-scrolling\": \"touch\",\n\t\t},\n\t\t\".scroll-x\": {\n\t\t\t\"overflow-x\": \"auto\",\n\t\t\t\"-webkit-overflow-scrolling\": \"touch\",\n\t\t},\n\t\t\"*\": {\n\t\t\t\"scrollbar-color\": `${theme.content_button} transparent`,\n\t\t\t\"scrollbar-width\": \"thin\",\n\t\t},\n\t\t\"::-webkit-scrollbar\": !client.isMobileDevice()\n\t\t\t? {\n\t\t\t\t\tbackground: \"transparent\",\n\t\t\t\t\twidth: scrollbarWidthHeight, // width of vertical scrollbar\n\t\t\t\t\theight: scrollbarWidthHeight, // width of horizontal scrollbar\n\t\t\t  }\n\t\t\t: {},\n\t\t\"::-webkit-scrollbar-thumb\": !client.isMobileDevice()\n\t\t\t? {\n\t\t\t\t\tbackground: theme.content_button,\n\t\t\t\t\t// reduce the background\n\t\t\t\t\t\"border-left\": \"15px solid transparent\",\n\t\t\t\t\t\"background-clip\": \"padding-box\",\n\t\t\t  }\n\t\t\t: {},\n\t\t\"*::-webkit-scrollbar-thumb:hover\": {\n\t\t\t\"border-left\": \"8px solid transparent\",\n\t\t},\n\t\t// scrollbar will be disabled for mobile devices, even with .scroll applied,\n\t\t// apply this class if you need it to show\n\t\t\".visible-scrollbar::-webkit-scrollbar\": {\n\t\t\tbackground: \"transparent\",\n\t\t\twidth: \"6px\",\n\t\t},\n\t\t\".visible-scrollbar::-webkit-scrollbar-thumb\": {\n\t\t\tbackground: theme.content_button,\n\t\t\t\"border-radius\": \"3px\",\n\t\t},\n\t\t// we are trying to handle 3 cases:\n\t\t// gecko/FF: supports scrollbar-gutter but not custom scrollbars\n\t\t// blink/Chrome: supports scrollbar-gutter and custom scrollbars\n\t\t// webkit/Safari: supports custom scrollbars but not scrollbar-gutter\n\t\t// so for scrolling containers we just force the scrollbars with `overflow: scroll` and for non-scrolling ones we fall back to padding\n\t\t\".scrollbar-gutter-stable-or-fallback\": {\n\t\t\t\"scrollbar-gutter\": \"stable\",\n\t\t},\n\t\t\"@supports not (scrollbar-gutter: stable)\": {\n\t\t\t\".scrollbar-gutter-stable-or-fallback\": {\n\t\t\t\t\"padding-right\": scrollbarWidthHeight,\n\t\t\t},\n\t\t},\n\t\t//TODO: migrate to .text-center\n\t\t\".center\": {\n\t\t\t\"text-align\": \"center\",\n\t\t},\n\t\t\".dropdown-info\": {\n\t\t\t\"padding-bottom\": \"5px\",\n\t\t\t\"padding-left\": \"16px\",\n\t\t\t\"padding-right\": \"16px\",\n\t\t},\n\t\t\".dropdown-info + .dropdown-button\": {\n\t\t\t\"border-top\": `1px solid ${theme.content_border}`,\n\t\t},\n\t\t\".dropdown-info + .dropdown-info\": {\n\t\t\t\"padding-top\": \"0\",\n\t\t},\n\t\t\".text-center\": {\n\t\t\t\"text-align\": \"center\",\n\t\t},\n\t\t\".right\": {\n\t\t\t\"text-align\": \"right\",\n\t\t},\n\t\t\".left\": {\n\t\t\t\"text-align\": \"left\",\n\t\t},\n\t\t\".start\": {\n\t\t\t\"text-align\": \"start\",\n\t\t},\n\t\t\".statusTextColor\": {\n\t\t\tcolor: theme.content_accent,\n\t\t},\n\t\t\".button-height\": {\n\t\t\theight: px(size.button_height),\n\t\t},\n\t\t\".button-min-height\": {\n\t\t\t\"min-height\": px(size.button_height),\n\t\t},\n\t\t\".button-width-fixed\": {\n\t\t\twidth: px(size.button_height),\n\t\t},\n\t\t\".large-button-height\": {\n\t\t\theight: px(size.button_floating_size),\n\t\t},\n\t\t\".large-button-width\": {\n\t\t\twidth: px(size.button_floating_size),\n\t\t},\n\t\t// Stretch editor a little bit more than parent so that the content is visible\n\t\t\".full-height\": {\n\t\t\t\"min-height\": client.isIos() ? \"101%\" : \"100%\",\n\t\t},\n\t\t\".full-width\": {\n\t\t\twidth: \"100%\",\n\t\t},\n\t\t\".half-width\": {\n\t\t\twidth: \"50%\",\n\t\t},\n\t\t\".block\": {\n\t\t\tdisplay: \"block\",\n\t\t},\n\t\t\".inline-block\": {\n\t\t\tdisplay: \"inline-block\",\n\t\t},\n\t\t\".no-text-decoration\": {\n\t\t\t\"text-decoration\": \"none\",\n\t\t},\n\t\t\".strike\": {\n\t\t\t\"text-decoration\": \"line-through\",\n\t\t},\n\t\t\".text-align-vertical\": {\n\t\t\t\"vertical-align\": \"text-top\",\n\t\t},\n\t\t// flex box\n\t\t\".flex-space-around\": {\n\t\t\tdisplay: \"flex\",\n\t\t\t\"justify-content\": \"space-around\",\n\t\t},\n\t\t\".flex-space-between\": {\n\t\t\tdisplay: \"flex\",\n\t\t\t\"justify-content\": \"space-between\",\n\t\t},\n\t\t\".flex-fixed\": {\n\t\t\tflex: \"0 0 auto\",\n\t\t},\n\t\t\".flex-center\": {\n\t\t\tdisplay: \"flex\",\n\t\t\t\"justify-content\": \"center\",\n\t\t},\n\t\t\".flex-end\": {\n\t\t\tdisplay: \"flex\",\n\t\t\t\"justify-content\": \"flex-end\",\n\t\t},\n\t\t\".flex-start\": {\n\t\t\tdisplay: \"flex\",\n\t\t\t\"justify-content\": \"flex-start\",\n\t\t},\n\t\t\".flex-v-center\": {\n\t\t\tdisplay: \"flex\",\n\t\t\t\"flex-direction\": \"column\",\n\t\t\t\"justify-content\": \"center\",\n\t\t},\n\t\t\".flex-direction-change\": {\n\t\t\tdisplay: \"flex\",\n\t\t\t\"justify-content\": \"center\",\n\t\t},\n\t\t\".flex-column\": {\n\t\t\t\"flex-direction\": \"column\",\n\t\t},\n\t\t//TODO migrate to .col\n\t\t\".col\": {\n\t\t\t\"flex-direction\": \"column\",\n\t\t},\n\t\t\".row\": {\n\t\t\t\"flex-direction\": \"row\",\n\t\t},\n\t\t\".flex-column-reverse\": {\n\t\t\t\"flex-direction\": \"column-reverse\",\n\t\t},\n\t\t//TODO: migrate to col-reverse\n\t\t\".col-reverse\": {\n\t\t\t\"flex-direction\": \"column-reverse\",\n\t\t},\n\t\t\".column-gap\": {\n\t\t\t\"column-gap\": px(size.hpad),\n\t\t},\n\t\t\".flex\": {\n\t\t\tdisplay: \"flex\",\n\t\t},\n\t\t\".flex-grow\": {\n\t\t\tflex: \"1\",\n\t\t},\n\t\t\".flex-hide\": {\n\t\t\tflex: \"0\",\n\t\t},\n\t\t\".flex-third\": {\n\t\t\tflex: \"1 0 0\",\n\t\t\t\"min-width\": \"100px\",\n\t\t},\n\t\t// splits a flex layout into three same width columns\n\t\t\".flex-third-middle\": {\n\t\t\tflex: \"2 1 0\",\n\t\t},\n\t\t// take up more space for the middle column\n\t\t\".flex-half\": {\n\t\t\tflex: \"0 0 50%\",\n\t\t},\n\t\t// splits a flex layout into two same width columns\n\t\t\".flex-grow-shrink-half\": {\n\t\t\tflex: \"1 1 50%\",\n\t\t},\n\t\t\".flex-nogrow-shrink-half\": {\n\t\t\tflex: \"0 1 50%\",\n\t\t},\n\t\t\".flex-grow-shrink-auto\": {\n\t\t\tflex: \"1 1 auto\",\n\t\t},\n\t\t// allow element to grow and shrink using the elements width as default size.\n\t\t\".flex-grow-shrink-150\": {\n\t\t\tflex: \"1 1 150px\",\n\t\t},\n\t\t\".flex-no-shrink\": {\n\t\t\tflex: \"1 0 0\",\n\t\t},\n\t\t\".flex-no-grow-no-shrink-auto\": {\n\t\t\tflex: \"0 0 auto\",\n\t\t},\n\t\t\".flex-no-grow\": {\n\t\t\tflex: \"0\",\n\t\t},\n\t\t\".no-shrink\": {\n\t\t\t\"flex-shrink\": \"0\",\n\t\t},\n\t\t\".flex-no-grow-shrink-auto\": {\n\t\t\tflex: \"0 1 auto\",\n\t\t},\n\t\t\".flex-wrap\": {\n\t\t\t\"flex-wrap\": \"wrap\",\n\t\t},\n\t\t// TODO: migrate to .wrap\n\t\t\".wrap\": {\n\t\t\t\"flex-wrap\": \"wrap\",\n\t\t},\n\t\t// elements may move into the next line\n\t\t\".items-center\": {\n\t\t\t\"align-items\": \"center\",\n\t\t},\n\t\t//TODO: migrate to .center-vertically\n\t\t\".center-vertically\": {\n\t\t\t\"align-items\": \"center\",\n\t\t},\n\t\t\".items-end\": {\n\t\t\t\"align-items\": \"flex-end\",\n\t\t},\n\t\t\".items-start\": {\n\t\t\t\"align-items\": \"flex-start\",\n\t\t},\n\t\t\".items-base\": {\n\t\t\t\"align-items\": \"baseline\",\n\t\t},\n\t\t\".items-stretch\": {\n\t\t\t\"align-items\": \"stretch\",\n\t\t},\n\t\t\".align-self-start\": {\n\t\t\t\"align-self\": \"start\",\n\t\t},\n\t\t\".align-self-center\": {\n\t\t\t\"align-self\": \"center\",\n\t\t},\n\t\t\".align-self-end\": {\n\t\t\t\"align-self\": \"flex-end\",\n\t\t},\n\t\t\".align-self-stretch\": {\n\t\t\t\"align-self\": \"stretch\",\n\t\t},\n\t\t\".justify-center\": {\n\t\t\t\"justify-content\": \"center\",\n\t\t},\n\t\t//TODO: migrate to justify-horizontally\n\t\t\".center-horizontally\": {\n\t\t\t\"justify-content\": \"center\",\n\t\t},\n\t\t\".justify-between\": {\n\t\t\t\"justify-content\": \"space-between\",\n\t\t},\n\t\t\".justify-end\": {\n\t\t\t\"justify-content\": \"flex-end\",\n\t\t},\n\t\t\".justify-start\": {\n\t\t\t\"justify-content\": \"flex-start\",\n\t\t},\n\t\t\".justify-right\": {\n\t\t\t\"justify-content\": \"right\",\n\t\t},\n\t\t\".child-grow > *\": {\n\t\t\tflex: \"1 1 auto\",\n\t\t},\n\t\t\".last-child-fixed > *:last-child\": {\n\t\t\tflex: \"1 0 100px\",\n\t\t},\n\t\t\".limit-width\": {\n\t\t\t\"max-width\": \"100%\",\n\t\t},\n\t\t\".flex-transition\": {\n\t\t\ttransition: \"flex 200ms linear\",\n\t\t},\n\t\t\".border-radius\": {\n\t\t\t\"border-radius\": px(size.border_radius),\n\t\t},\n\t\t\".border-radius-top\": {\n\t\t\t\"border-top-left-radius\": px(size.border_radius),\n\t\t\t\"border-top-right-radius\": px(size.border_radius),\n\t\t},\n\t\t\".border-radius-top-left-big\": {\n\t\t\t\"border-top-left-radius\": px(size.border_radius_big),\n\t\t},\n\t\t\".border-radius-bottom\": {\n\t\t\t\"border-bottom-left-radius\": px(size.border_radius),\n\t\t\t\"border-bottom-right-radius\": px(size.border_radius),\n\t\t},\n\t\t\".border-radius-small\": {\n\t\t\t\"border-radius\": px(size.border_radius_small),\n\t\t},\n\t\t\".border-radius-big\": {\n\t\t\t\"border-radius\": px(size.border_radius_big),\n\t\t},\n\t\t\".editor-border\": {\n\t\t\tborder: `1px solid ${theme.content_border}`,\n\t\t\t\"padding-top\": px(size.vpad_small),\n\t\t\t\"padding-bottom\": px(size.vpad_small),\n\t\t\t\"padding-left\": px(size.hpad),\n\t\t\t\"padding-right\": px(size.hpad),\n\t\t},\n\t\t\".editor-border-active\": {\n\t\t\tborder: `2px solid ${theme.content_accent}`,\n\t\t\t\"padding-top\": px(size.vpad_small - 1),\n\t\t\t\"padding-bottom\": px(size.vpad_small - 1),\n\t\t\t\"padding-left\": px(size.hpad - 1),\n\t\t\t\"padding-right\": px(size.hpad - 1),\n\t\t},\n\t\t\".editor-no-top-border\": {\n\t\t\t\"border-top-color\": \"transparent\",\n\t\t},\n\t\t// icon\n\t\t\".icon\": {\n\t\t\theight: px(size.icon_size_medium),\n\t\t\twidth: px(size.icon_size_medium),\n\t\t},\n\t\t\".icon > svg\": {\n\t\t\theight: px(size.icon_size_medium),\n\t\t\twidth: px(size.icon_size_medium),\n\t\t},\n\t\t// a bit cursed solution to make the visible icon not too huge relative to the tiny \"close\" icon that we have but also to keep the size consistent\n\t\t// with icon-large so that the text field doesn't jump around\n\t\t\".icon-progress-search\": {\n\t\t\theight: px(size.icon_size_large),\n\t\t\twidth: px(size.icon_size_large),\n\t\t},\n\t\t\".icon-progress-search > svg\": {\n\t\t\theight: px(20),\n\t\t\twidth: px(20),\n\t\t},\n\t\t\".search-bar\": {\n\t\t\ttransition: \"all 200ms\",\n\t\t\t\"background-color\": stateBgLike,\n\t\t},\n\t\t\".search-bar:hover\": {\n\t\t\t\"background-color\": stateBgHover,\n\t\t},\n\t\t\".search-bar[focused=true]\": {\n\t\t\t\"background-color\": theme.content_bg,\n\t\t\t\"box-shadow\": searchBarShadow,\n\t\t},\n\t\t\".icon-progress-tiny\": {\n\t\t\theight: px(15),\n\t\t\twidth: px(15),\n\t\t},\n\t\t\".icon-progress-tiny > svg\": {\n\t\t\theight: px(15),\n\t\t\twidth: px(15),\n\t\t},\n\t\t\".icon-small\": {\n\t\t\theight: px(size.font_size_small),\n\t\t\twidth: px(size.font_size_small),\n\t\t},\n\t\t\".icon-small > svg\": {\n\t\t\theight: px(size.font_size_small),\n\t\t\twidth: px(size.font_size_small),\n\t\t},\n\t\t\".icon-large\": {\n\t\t\theight: px(size.icon_size_large),\n\t\t\twidth: px(size.icon_size_large),\n\t\t},\n\t\t\".icon-large > svg\": {\n\t\t\theight: px(size.icon_size_large),\n\t\t\twidth: px(size.icon_size_large),\n\t\t},\n\t\t\".icon-xl\": {\n\t\t\theight: px(size.icon_size_xl),\n\t\t\twidth: px(size.icon_size_xl),\n\t\t},\n\t\t\".icon-xl > svg\": {\n\t\t\theight: px(size.icon_size_xl),\n\t\t\twidth: px(size.icon_size_xl),\n\t\t},\n\t\t\".icon-message-box\": {\n\t\t\theight: px(size.icon_message_box),\n\t\t\twidth: px(size.icon_message_box),\n\t\t},\n\t\t\".icon-message-box > svg\": {\n\t\t\theight: px(size.icon_message_box),\n\t\t\twidth: px(size.icon_message_box),\n\t\t},\n\t\t\".icon-progress > svg\": {\n\t\t\t\"animation-name\": \"rotate-icon\",\n\t\t\t\"animation-duration\": \"2s\",\n\t\t\t\"animation-iteration-count\": \"infinite\",\n\t\t\t\"animation-timing-function\": \"calculatePosition\",\n\t\t\t\"transform-origin\": \"50% 50%\",\n\t\t\tdisplay: \"inline-block\",\n\t\t},\n\t\t\".icon-button\": {\n\t\t\t\"border-radius\": \"25%\",\n\t\t\twidth: px(size.button_height),\n\t\t\theight: px(size.button_height),\n\t\t\t\"max-width\": px(size.button_height),\n\t\t\t\"max-height\": px(size.button_height),\n\t\t},\n\t\t\".center-h\": {\n\t\t\tmargin: \"0 auto\",\n\t\t},\n\t\t\".toggle-button\": {\n\t\t\t\"border-radius\": \"25%\",\n\t\t\twidth: px(size.button_height),\n\t\t\theight: px(size.button_height),\n\t\t\t\"max-width\": px(size.button_height),\n\t\t\t\"max-height\": px(size.button_height),\n\t\t},\n\t\t\".compact\": {\n\t\t\twidth: `${size.button_height_compact}px !important`,\n\t\t\theight: `${size.button_height_compact}px !important`,\n\t\t},\n\t\t// state-bg is a simulation of a \"state layer\" from Material but without an additional layer\n\t\t// We don't exactly follow transparency for it because we combine transparency with light grey color which works well on both light and dark themes\n\t\t\".state-bg\": {\n\t\t\tbackground: \"transparent\",\n\t\t\ttransition: \"background 0.6s\",\n\t\t\t// undoing our default button styling\n\t\t\topacity: \"1 !important\",\n\t\t},\n\t\t// Only enable hover for mouse and keyboard navigation (not touch) because\n\t\t// :hover will bet stuck after the touch on mobile.\n\t\t// Use :where() to not count towards specificity, otherwise this is more specific\n\t\t// than :active (which is unconditional\n\t\t\":where(.mouse-nav) .state-bg:hover, :where(.keyboard-nav) .state-bg:hover\": {\n\t\t\tbackground: stateBgHover,\n\t\t\t\"transition-duration\": \".3s\",\n\t\t},\n\t\t\":where(.keyboard-nav) .state-bg:focus\": {\n\t\t\tbackground: stateBgFocus,\n\t\t\t\"transition-duration\": \".3s\",\n\t\t\t// disable default focus indicator because we have our own for this element\n\t\t\toutline: \"none\",\n\t\t},\n\t\t\".state-bg:active, .state-bg[toggled=true]\": {\n\t\t\tbackground: stateBgActive,\n\t\t\t\"transition-duration\": \".3s\",\n\t\t},\n\t\t\".translucent\": {\n\t\t\topacity: \"0.4\",\n\t\t},\n\t\t\".opaque\": {\n\t\t\topacity: \"1\",\n\t\t},\n\t\t\"@keyframes rotate-icon\": {\n\t\t\t\"0%\": {\n\t\t\t\ttransform: \"rotate(0deg)\",\n\t\t\t},\n\t\t\t\"100%\": {\n\t\t\t\ttransform: \"rotate(360deg)\",\n\t\t\t},\n\t\t},\n\t\t// custom styling for views\n\t\t// the main view\n\t\t\".main-view\": {\n\t\t\tposition: \"absolute\",\n\t\t\ttop: 0,\n\t\t\tright: px(0),\n\t\t\tbottom: px(0),\n\t\t\tleft: px(0),\n\t\t\t\"overflow-x\": \"hidden\",\n\t\t},\n\t\t\".mlr-safe-inset\": {\n\t\t\t\"margin-right\": \"env(safe-area-inset-right)\",\n\t\t\t\"margin-left\": \"env(safe-area-inset-left)\",\n\t\t},\n\t\t\".plr-safe-inset\": {\n\t\t\t\"padding-right\": \"env(safe-area-inset-right)\",\n\t\t\t\"padding-left\": \"env(safe-area-inset-left)\",\n\t\t},\n\t\t\".mt-safe-inset\": {\n\t\t\t\"margin-top\": \"env(safe-area-inset-top)\",\n\t\t},\n\t\t// header\n\t\t\".header-nav\": {\n\t\t\theight: px(size.navbar_height),\n\t\t\t\"background-color\": theme.navigation_bg,\n\t\t\t\"z-index\": 2,\n\t\t},\n\t\t\"bottom-nav, .bottom-nav\": {\n\t\t\t\"border-top\": `1px solid ${theme.navigation_border}`,\n\t\t\theight: positionValue(size.bottom_nav_bar),\n\t\t\tbackground: theme.header_bg,\n\t\t\t\"margin-bottom\": \"env(safe-area-inset-bottom)\",\n\t\t\t\"z-index\": 2,\n\t\t},\n\t\t\".notification-overlay-content\": {\n\t\t\t\"margin-left\": px(size.vpad),\n\t\t\t\"margin-right\": px(size.vpad),\n\t\t\t\"padding-top\": px(size.vpad),\n\t\t},\n\t\t\".logo-circle\": {\n\t\t\twidth: px(size.button_icon_bg_size),\n\t\t\theight: px(size.button_icon_bg_size),\n\t\t\t\"border-radius\": \"50%\",\n\t\t\toverflow: \"hidden\",\n\t\t},\n\t\t\".dot\": {\n\t\t\twidth: px(size.dot_size),\n\t\t\theight: px(size.dot_size),\n\t\t\t\"border-radius\": \"50%\",\n\t\t\toverflow: \"hidden\",\n\t\t\t\"margin-top\": px(6),\n\t\t},\n\t\t\".news-button\": {\n\t\t\tposition: \"relative\",\n\t\t},\n\t\t\".logo-text\": {\n\t\t\theight: px(size.header_logo_height),\n\t\t\twidth: px(128),\n\t\t},\n\t\t\".logo-height\": {\n\t\t\theight: px(size.header_logo_height),\n\t\t},\n\t\t\".logo-height > svg, .logo-height > img\": {\n\t\t\theight: px(size.header_logo_height),\n\t\t},\n\t\t\".custom-logo\": {\n\t\t\twidth: px(200),\n\t\t\t\"background-repeat\": \"no-repeat\",\n\t\t\t\"background-size\": \"auto 100%\",\n\t\t},\n\t\t\".nav-bar-spacer\": {\n\t\t\twidth: \"0px\",\n\t\t\theight: \"22px\",\n\t\t\t\"margin-left\": \"2px\",\n\t\t\t\"border-color\": theme.navigation_border,\n\t\t\t\"border-width\": \"1px\",\n\t\t\t\"border-style\": \"solid\",\n\t\t},\n\t\t// dialogs\n\t\t\".dialog\": {\n\t\t\t\"min-width\": px(200),\n\t\t},\n\t\t\".dialog-width-l\": {\n\t\t\t\"max-width\": px(800),\n\t\t},\n\t\t\".dialog-width-m\": {\n\t\t\t\"max-width\": px(500),\n\t\t},\n\t\t\".dialog-width-s\": {\n\t\t\t\"max-width\": px(400),\n\t\t},\n\t\t\".dialog-width-alert\": {\n\t\t\t\"max-width\": px(350),\n\t\t},\n\t\t\".dialog-header\": {\n\t\t\t\"border-bottom\": `1px solid ${theme.content_border}`,\n\t\t\theight: px(size.button_height + 1),\n\t\t},\n\t\t\".dialog-header-line-height\": {\n\t\t\t\"line-height\": px(size.button_height),\n\t\t},\n\t\t\".dialog-progress\": {\n\t\t\t\"text-align\": \"center\",\n\t\t\tpadding: px(size.hpad_large),\n\t\t\twidth: `calc(100% - ${2 * size.hpad}px)`,\n\t\t},\n\t\t\".faq-items img\": {\n\t\t\t\"max-width\": \"100%\",\n\t\t\theight: \"auto\",\n\t\t},\n\t\t\".dialog-container\": position_absolute(size.button_height + 1, 0, 0, 0),\n\t\t\".dialog-contentButtonsBottom\": {\n\t\t\tpadding: `0 ${px(size.hpad_large)} ${px(size.vpad)} ${px(size.hpad_large)}`,\n\t\t},\n\t\t\".dialog-img\": {\n\t\t\twidth: px(150),\n\t\t\theight: \"auto\",\n\t\t},\n\t\t\".dialog-buttons\": {\n\t\t\t\"border-top\": `1px solid ${theme.content_border}`,\n\t\t},\n\t\t\".dialog-buttons > button\": {\n\t\t\tflex: \"1\",\n\t\t},\n\t\t\".dialog-buttons > button:not(:first-child)\": {\n\t\t\t\"border-left\": `1px solid ${theme.content_border}`,\n\t\t\t\"margin-left\": \"0\",\n\t\t},\n\t\t\".dialog-max-height\": {\n\t\t\t\"max-height\": \"calc(100vh - 100px)\",\n\t\t},\n\t\t// mail folder view column\n\t\t\" .folder-column\": {\n\t\t\theight: \"100%\",\n\t\t\t\"padding-top\": \"env(safe-area-inset-top)\",\n\t\t},\n\t\t\".list-border-right\": {\n\t\t\t\"border-right\": `1px solid ${theme.list_border}`,\n\t\t},\n\t\t\".folders\": {\n\t\t\t\"margin-bottom\": px(12),\n\t\t},\n\t\t\".folder-row\": {\n\t\t\t\"align-items\": \"center\",\n\t\t\tposition: \"relative\",\n\t\t},\n\t\t\".template-list-row\": {\n\t\t\t\"border-left\": px(size.border_selection) + \" solid transparent\",\n\t\t\t\"align-items\": \"center\",\n\t\t\tposition: \"relative\",\n\t\t},\n\t\t\".counter-badge\": {\n\t\t\t\"padding-left\": px(4),\n\t\t\t\"padding-right\": px(4),\n\t\t\t\"border-radius\": px(8),\n\t\t\t\"line-height\": px(16),\n\t\t\t\"font-size\": px(size.font_size_small),\n\t\t\t\"font-weight\": \"bold\",\n\t\t\t\"min-width\": px(16),\n\t\t\t\"min-height\": px(16),\n\t\t\t\"text-align\": \"center\",\n\t\t},\n\t\t\".row-selected\": {\n\t\t\t\"border-color\": `${theme.list_accent_fg} !important`,\n\t\t\tcolor: `${theme.list_accent_fg}`,\n\t\t},\n\t\t\".hoverable-list-item:hover\": {\n\t\t\t\"border-color\": `${theme.list_accent_fg} !important`,\n\t\t\tcolor: `${theme.list_accent_fg}`,\n\t\t},\n\t\t\".expander\": {\n\t\t\theight: px(size.button_height),\n\t\t\t\"min-width\": px(size.button_height),\n\t\t},\n\t\t// mail view editor\n\t\t\".mail-viewer-firstLine\": {\n\t\t\t\"pading-top\": px(10),\n\t\t},\n\t\t\".hide-outline\": {\n\t\t\toutline: \"none\",\n\t\t},\n\t\t\".nofocus:focus\": {\n\t\t\toutline: \"none\",\n\t\t},\n\t\t\".input\": {\n\t\t\toutline: \"none\",\n\t\t},\n\t\t\"blockquote.tutanota_quote, blockquote[type=cite]\": {\n\t\t\t\"border-left\": `1px solid ${theme.content_accent}`,\n\t\t\t\"padding-left\": px(size.hpad),\n\t\t\t\"margin-left\": px(0),\n\t\t\t\"margin-right\": px(0),\n\t\t},\n\t\t\".tutanota-placeholder\": {\n\t\t\t\"max-width\": \"100px !important\",\n\t\t\t\"max-height\": \"100px !important\",\n\t\t},\n\t\t\".MsoNormal\": {\n\t\t\tmargin: 0,\n\t\t},\n\t\t// list\n\t\t\".list\": {\n\t\t\toverflow: \"hidden\",\n\t\t\t\"list-style\": \"none\",\n\t\t\tmargin: 0,\n\t\t\tpadding: 0,\n\t\t},\n\t\t\".list-row\": {\n\t\t\tposition: \"absolute\",\n\t\t\tleft: 0,\n\t\t\tright: 0,\n\t\t\theight: px(size.list_row_height),\n\t\t},\n\t\t\".odd-row\": {\n\t\t\t\"background-color\": theme.list_bg,\n\t\t},\n\t\t\".list-loading\": {\n\t\t\tbottom: 0,\n\t\t},\n\t\t// mail list\n\t\t\".teamLabel\": {\n\t\t\tcolor: theme.list_alternate_bg,\n\t\t\t\"background-color\": theme.list_accent_fg,\n\t\t},\n\t\t\".ion\": {\n\t\t\tdisplay: \"inline-block\",\n\t\t\t\"font-family\": \"'Ionicons'\",\n\t\t\tspeak: \"none\",\n\t\t\t\"font-style\": \"normal\",\n\t\t\t\"font-weight\": \"normal\",\n\t\t\t\"font-variant\": \"normal\",\n\t\t\t\"text-transform\": \"none\",\n\t\t\t\"text-rendering\": \"auto\",\n\t\t\t\"line-height\": \"1\",\n\t\t\t\"-webkit-font-smoothing\": \"antialiased\",\n\t\t\t\"-moz-osx-font-smoothing\": \"grayscale\",\n\t\t},\n\t\t\".badge-line-height\": {\n\t\t\t\"line-height\": px(18),\n\t\t},\n\t\t\".list-font-icons\": {\n\t\t\t\"letter-spacing\": \"8px\",\n\t\t\t\"text-align\": \"right\",\n\t\t\t\"margin-right\": \"-8px\",\n\t\t},\n\t\t\".monospace\": {\n\t\t\t\"font-family\": '\"Lucida Console\", Monaco, monospace',\n\t\t},\n\t\t\".hidden\": {\n\t\t\tvisibility: \"hidden\",\n\t\t},\n\t\t// action bar\n\t\t\".action-bar\": {\n\t\t\twidth: \"initial\",\n\t\t\t\"margin-left\": \"auto\",\n\t\t},\n\t\t\".ml-between-s > :not(:first-child)\": {\n\t\t\t\"margin-left\": px(size.hpad_small),\n\t\t},\n\t\t\".mt-between-s > :not(:first-child)\": {\n\t\t\t\"margin-top\": px(size.hpad_small),\n\t\t},\n\t\t\".mt-between-m > :not(:first-child)\": {\n\t\t\t\"margin-top\": px(size.hpad),\n\t\t},\n\t\t// dropdown\n\t\t\".dropdown-panel\": {\n\t\t\tposition: \"absolute\",\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\toverflow: \"hidden\", // while the dropdown is slided open we do not want to show the scrollbars. overflow-y is later overwritten to show scrollbars if necessary\n\t\t},\n\t\t\".dropdown-content:first-child\": {\n\t\t\t\"padding-top\": px(size.vpad_small),\n\t\t},\n\t\t\".dropdown-content:last-child\": {\n\t\t\t\"padding-bottom\": px(size.vpad_small),\n\t\t},\n\t\t\".dropdown-content > *\": {\n\t\t\twidth: \"100%\",\n\t\t},\n\t\t\".dropdown-shadow\": {\n\t\t\t\"box-shadow\": boxShadow,\n\t\t},\n\t\t\".minimized-shadow\": {\n\t\t\t// shadow params: 1.offset-x 2.offset-y 3.blur 4.spread 5.color\n\t\t\t\"box-shadow\": `0px 0px 4px 2px ${theme.header_box_shadow_bg}`, // similar to header bar shadow\n\t\t},\n\t\t//dropdown filter bar\n\t\t\".dropdown-bar\": {\n\t\t\t\"border-style\": \"solid\",\n\t\t\t\"border-width\": \"0px 0px 1px 0px\",\n\t\t\t\"border-color\": theme.content_border,\n\t\t\t\"padding-bottom\": \"1px\",\n\t\t\t\"z-index\": 1,\n\t\t\t\"border-radius\": `${size.border_radius}px ${size.border_radius}px 0 0`,\n\t\t\tcolor: theme.content_fg,\n\t\t},\n\t\t\".dropdown-bar:focus\": {\n\t\t\t\"border-style\": \"solid\",\n\t\t\t\"border-width\": \"0px 0px 2px 0px\",\n\t\t\t\"border-color\": `${theme.content_accent}`,\n\t\t\t\"padding-bottom\": \"0px\",\n\t\t},\n\t\t\".dropdown-button\": {\n\t\t\theight: px(size.button_height),\n\t\t\t\"padding-left\": px(size.vpad),\n\t\t\t\"padding-right\": px(size.vpad),\n\t\t},\n\t\t\"button, .nav-button\": {\n\t\t\tborder: 0,\n\t\t\tcursor: \"pointer\",\n\t\t\toverflow: \"hidden\",\n\t\t\t\"white-space\": \"nowrap\",\n\t\t\tmargin: 0,\n\t\t\t// for safari\n\t\t\t\"flex-shrink\": 0,\n\t\t\t\"-webkit-tap-highlight-color\": \"rgba(255, 255, 255, 0)\",\n\t\t},\n\t\t\".nav-button:hover\": !isApp()\n\t\t\t? {\n\t\t\t\t\t// \"text-decoration\": \"underline\",\n\t\t\t\t\t// opacity: 0.7,\n\t\t\t  }\n\t\t\t: {},\n\t\t\".nav-button:focus\": client.isDesktopDevice()\n\t\t\t? {\n\t\t\t\t\t// \"text-decoration\": \"underline\",\n\t\t\t\t\t// opacity: 0.7,\n\t\t\t  }\n\t\t\t: {},\n\t\t\"button:focus, button:hover\": client.isDesktopDevice()\n\t\t\t? {\n\t\t\t\t\topacity: 0.7,\n\t\t\t  }\n\t\t\t: {},\n\t\t\".button-icon\": {\n\t\t\twidth: px(size.button_icon_bg_size),\n\t\t\theight: px(size.button_icon_bg_size),\n\t\t\t\"border-radius\": px(size.button_icon_bg_size),\n\t\t\t\"min-width\": px(size.button_icon_bg_size),\n\t\t},\n\t\t\".login\": {\n\t\t\twidth: \"100%\",\n\t\t\t\"border-radius\": px(size.border_radius),\n\t\t},\n\t\t\".button-content\": {\n\t\t\theight: px(size.button_height),\n\t\t\t\"min-width\": px(size.button_height),\n\t\t},\n\t\t\".primary\": {\n\t\t\tcolor: theme.content_accent,\n\t\t\t\"font-weight\": \"bold\",\n\t\t},\n\t\t\".secondary\": {\n\t\t\tcolor: theme.content_accent,\n\t\t},\n\t\t\".textBubble\": {\n\t\t\tcolor: theme.content_accent,\n\t\t\t\"padding-top\": px(size.text_bubble_tpad),\n\t\t},\n\t\t\".bubble\": {\n\t\t\t\"border-radius\": px(size.border_radius),\n\t\t\theight: px(size.button_height_bubble),\n\t\t\t\"background-color\": theme.button_bubble_bg,\n\t\t\tcolor: theme.button_bubble_fg,\n\t\t},\n\t\t\".keyword-bubble\": {\n\t\t\t\"max-width\": \"300px\",\n\t\t\t\"border-radius\": px(size.border_radius),\n\t\t\t\"margin-bottom\": px(size.vpad_small / 2),\n\t\t\t\"margin-right\": px(size.vpad_small / 2),\n\t\t\t\"background-color\": theme.button_bubble_bg,\n\t\t\tpadding: `${px(size.vpad_small / 2)} ${px(size.vpad_small)} ${px(size.vpad_small / 2)} ${px(size.vpad_small)}`,\n\t\t},\n\t\t\".keyword-bubble-no-padding\": {\n\t\t\t\"max-width\": \"300px\",\n\t\t\t\"border-radius\": px(size.border_radius),\n\t\t\tmargin: px(size.vpad_small / 2),\n\t\t\t\"background-color\": theme.button_bubble_bg,\n\t\t},\n\t\t\".bubble-color\": {\n\t\t\t\"background-color\": theme.button_bubble_bg,\n\t\t\tcolor: theme.button_bubble_fg,\n\t\t},\n\t\tmark: {\n\t\t\t// 'background-color': theme.content_button,\n\t\t\t// 'color': theme.content_button_icon,\n\t\t\t\"background-color\": theme.content_accent,\n\t\t\tcolor: theme.content_button_icon_selected,\n\t\t},\n\t\t\".segmentControl\": {\n\t\t\t// same border as for bubble buttons\n\t\t\t\"border-top\": `${px((size.button_height - size.button_height_bubble) / 2)} solid transparent`,\n\t\t\t\"border-bottom\": `${px((size.button_height - size.button_height_bubble) / 2)} solid transparent`,\n\t\t},\n\t\t\".segmentControl-border\": {\n\t\t\tborder: `1px solid ${theme.content_border}`,\n\t\t\t\"padding-top\": px(1),\n\t\t\t\"padding-bottom\": px(1),\n\t\t\t\"padding-left\": px(1),\n\t\t\t\"padding-right\": px(1),\n\t\t},\n\t\t\".segmentControl-border-active\": {\n\t\t\tborder: `2px solid ${theme.content_accent}`,\n\t\t\t\"padding-top\": px(0),\n\t\t\t\"padding-bottom\": px(0),\n\t\t\t\"padding-left\": px(0),\n\t\t\t\"padding-right\": px(0),\n\t\t},\n\t\t\".segmentControlItem\": {\n\t\t\tcursor: \"pointer\",\n\t\t\tbackground: \"transparent\",\n\t\t},\n\t\t\".segmentControlItem:last-child\": {\n\t\t\t\"border-bottom-right-radius\": px(size.border_radius_small),\n\t\t\t\"border-top-right-radius\": px(size.border_radius_small),\n\t\t},\n\t\t\".segmentControlItem:first-child\": {\n\t\t\t\"border-bottom-left-radius\": px(size.border_radius_small),\n\t\t\t\"border-top-left-radius\": px(size.border_radius_small),\n\t\t},\n\t\t// contact\n\t\t\".wrapping-row\": {\n\t\t\tdisplay: \"flex\",\n\t\t\t\"flex-flow\": \"row wrap\",\n\t\t\t\"margin-right\": px(-size.hpad_large),\n\t\t},\n\t\t\".wrapping-row > *\": {\n\t\t\tflex: \"1 0 40%\",\n\t\t\t\"margin-right\": px(size.hpad_large),\n\t\t\t\"min-width\": px(200), // makes sure the row is wrapped with too large content\n\t\t},\n\t\t\".non-wrapping-row\": {\n\t\t\tdisplay: \"flex\",\n\t\t\t\"flex-flow\": \"row\",\n\t\t\t\"margin-right\": px(-size.hpad_large),\n\t\t},\n\t\t\".non-wrapping-row > *\": {\n\t\t\tflex: \"1 0 40%\",\n\t\t\t\"margin-right\": px(size.hpad_large),\n\t\t},\n\t\t// text input field\n\t\t\".inputWrapper\": {\n\t\t\tflex: \"1 1 auto\",\n\t\t\tbackground: \"transparent\",\n\t\t\toverflow: \"hidden\",\n\t\t},\n\t\t// textarea\n\t\t\".input, .input-area\": {\n\t\t\tdisplay: \"block\",\n\t\t\tresize: \"none\",\n\t\t\tborder: 0,\n\t\t\tpadding: 0,\n\t\t\tmargin: 0,\n\t\t\t// for safari browser\n\t\t\tbackground: \"transparent\",\n\t\t\twidth: \"100%\",\n\t\t\toverflow: \"hidden\",\n\t\t\tcolor: theme.content_fg,\n\t\t},\n\t\t\".input-no-clear::-ms-clear\": {\n\t\t\t// remove the clear (x) button from edge input fields\n\t\t\tdisplay: \"none\",\n\t\t},\n\t\t\".resize-none\": {\n\t\t\tresize: \"none\",\n\t\t},\n\t\t// table\n\t\t\".table\": {\n\t\t\t\"border-collapse\": \"collapse\",\n\t\t\t\"table-layout\": \"fixed\",\n\t\t\twidth: \"100%\",\n\t\t},\n\t\t\".table-header-border tr:first-child\": {\n\t\t\t\"border-bottom\": `1px solid ${theme.content_border}`,\n\t\t},\n\t\t\".table td\": {\n\t\t\t\"vertical-align\": \"middle\",\n\t\t},\n\t\ttd: {\n\t\t\tpadding: 0,\n\t\t},\n\t\t\".column-width-small\": {\n\t\t\twidth: px(size.column_width_s_desktop),\n\t\t},\n\t\t\".column-width-largest\": {},\n\t\t\".buyOptionBox\": {\n\t\t\tposition: \"relative\",\n\t\t\tdisplay: \"inline-block\",\n\t\t\tborder: `1px solid ${theme.content_border}`,\n\t\t\twidth: \"100%\",\n\t\t\tpadding: px(10),\n\t\t},\n\t\t\".buyOptionBox.active\": {\n\t\t\tborder: `1px solid ${theme.content_accent}`,\n\t\t},\n\t\t\".buyOptionBox.highlighted\": {\n\t\t\tborder: `2px solid ${theme.content_accent}`,\n\t\t\tpadding: px(9),\n\t\t},\n\t\t\".info-badge\": {\n\t\t\t\"border-radius\": px(8),\n\t\t\t\"line-height\": px(16),\n\t\t\t\"font-size\": px(12),\n\t\t\t\"font-weight\": \"bold\",\n\t\t\twidth: px(16),\n\t\t\theight: px(16),\n\t\t\t\"text-align\": \"center\",\n\t\t\tcolor: \"white\",\n\t\t\tbackground: theme.content_button,\n\t\t},\n\t\t\".tooltip\": {\n\t\t\tposition: \"relative\",\n\t\t\tdisplay: \"inline-block\",\n\t\t},\n\t\t\".tooltip .tooltiptext\": {\n\t\t\tvisibility: \"hidden\",\n\t\t\t\"background-color\": theme.content_button,\n\t\t\tcolor: theme.content_bg,\n\t\t\t\"text-align\": \"center\",\n\t\t\tpadding: \"5px 5px\",\n\t\t\t\"border-radius\": px(6),\n\t\t\tposition: \"absolute\",\n\t\t\t\"z-index\": 1,\n\t\t\ttop: \"150%\",\n\t\t\tleft: \"50%\",\n\t\t},\n\t\t/* we're selecting every element that's after a summary tag and is inside an opened details tag */\n\t\t\"details[open] summary ~ *\": {\n\t\t\tanimation: \"expand .2s ease-in-out\",\n\t\t},\n\t\t\".expand\": {\n\t\t\tanimation: \"expand .2s ease-in-out\",\n\t\t},\n\t\t\"@keyframes expand\": {\n\t\t\t\"0%\": {\n\t\t\t\topacity: 0,\n\t\t\t\t\"margin-top\": \"-10px\",\n\t\t\t\theight: \"0%\",\n\t\t\t},\n\t\t\t\"100%\": {\n\t\t\t\topacity: 1,\n\t\t\t\t\"margin-top\": px(0),\n\t\t\t\theight: \"100%\",\n\t\t\t},\n\t\t},\n\t\t\".info-badge:active\": {\n\t\t\tbackground: theme.content_bg,\n\t\t\tcolor: theme.content_button,\n\t\t},\n\t\t\".tooltip:hover .tooltiptext, .tooltip[expanded=true] .tooltiptext\": {\n\t\t\tvisibility: \"visible\",\n\t\t},\n\t\t\".ribbon-horizontal\": {\n\t\t\tposition: \"absolute\",\n\t\t\t\"margin-bottom\": \"80px\",\n\t\t\tbackground: theme.content_accent,\n\t\t\ttop: \"50px\",\n\t\t\tleft: \"-6px\",\n\t\t\tright: \"-6px\",\n\t\t\tcolor: theme.content_bg,\n\t\t},\n\t\t\".ribbon-horizontal:after\": {\n\t\t\tcontent: '\"\"',\n\t\t\tposition: \"absolute\",\n\t\t\theight: 0,\n\t\t\twidth: 0,\n\t\t\t\"border-left\": `6px solid ${theme.content_accent}`,\n\t\t\t\"border-bottom\": \"6px solid transparent\",\n\t\t\tbottom: \"-6px\",\n\t\t\tright: 0,\n\t\t},\n\t\t\".ribbon-horizontal:before\": {\n\t\t\tcontent: '\"\"',\n\t\t\tposition: \"absolute\",\n\t\t\theight: 0,\n\t\t\twidth: 0,\n\t\t\t\"border-right\": `6px solid ${theme.content_accent}`,\n\t\t\t\"border-bottom\": \"6px solid transparent\",\n\t\t\tbottom: \"-6px\",\n\t\t\tleft: 0,\n\t\t},\n\t\t// calendar\n\t\t\".flex-end-on-child .button-content\": {\n\t\t\t\"align-items\": \"flex-end !important\",\n\t\t},\n\t\t\".calendar-checkbox\": {\n\t\t\theight: px(22),\n\t\t\twidth: px(22),\n\t\t\t\"border-width\": \"1.5px\",\n\t\t\t\"border-style\": \"solid\",\n\t\t\t\"border-radius\": \"2px\",\n\t\t},\n\t\t\".checkbox\": {\n\t\t\tappearance: \"none\",\n\t\t\t// reset browser style\n\t\t\tmargin: \"0\",\n\t\t\tdisplay: \"block\",\n\t\t\twidth: px(size.checkbox_size),\n\t\t\theight: px(size.checkbox_size),\n\t\t\tborder: `2px solid ${theme.content_button}`,\n\t\t\t\"border-radius\": \"3px\",\n\t\t\tposition: \"relative\",\n\t\t\ttransition: `border ${DefaultAnimationTime}ms cubic-bezier(.4,.0,.23,1)`,\n\t\t\topacity: \"0.8\",\n\t\t},\n\t\t\".checkbox:hover\": {\n\t\t\topacity: \"1\",\n\t\t},\n\t\t\".checkbox:checked\": {\n\t\t\tborder: `7px solid ${theme.content_accent}`,\n\t\t\topacity: \"1\",\n\t\t},\n\t\t\".checkbox:checked:after\": {\n\t\t\tdisplay: \"inline-flex\",\n\t\t},\n\t\t\".checkbox:after\": {\n\t\t\t\"font-family\": \"'Ionicons'\",\n\t\t\tcontent: `'${FontIcons.Checkbox}'`,\n\t\t\tposition: \"absolute\",\n\t\t\tdisplay: \"none\",\n\t\t\t\"font-size\": \"12px\",\n\t\t\t// related to border width\n\t\t\ttop: \"-6px\",\n\t\t\tleft: \"-6px\",\n\t\t\tright: 0,\n\t\t\tbottom: 0,\n\t\t\t\"line-height\": \"12px\",\n\t\t\tcolor: theme.content_bg,\n\t\t\t\"align-items\": \"center\",\n\t\t\twidth: \"12px\",\n\t\t\theight: \"12px\",\n\t\t},\n\t\t\".checkbox:before\": {\n\t\t\tcontent: \"''\",\n\t\t\tposition: \"absolute\",\n\t\t\twidth: \"30px\",\n\t\t\theight: \"30px\",\n\t\t\t// position relative to the inner size of checkbox (inside the border)\n\t\t\ttop: \"-10px\",\n\t\t\tleft: \"-10px\",\n\t\t\t\"border-radius\": px(size.border_radius),\n\t\t\t// position is relate to padding and we animate padding so to keep the checkbox in place we also animate position so it looks like it doesn't move\n\t\t\ttransition: `all ${DefaultAnimationTime}ms cubic-bezier(.4,.0,.23,1)`,\n\t\t},\n\t\t\".checkbox:checked:before\": {\n\t\t\t// position relative to the inner size of the checkbox (inside the border) and selected checkbox has border 50%\n\t\t\ttop: \"-15px\",\n\t\t\tleft: \"-15px\",\n\t\t},\n\t\t\".checkbox:hover:before\": {\n\t\t\tbackground: stateBgHover,\n\t\t},\n\t\t\".checkbox:active:before\": {\n\t\t\tbackground: stateBgActive,\n\t\t},\n\t\t\".list-checkbox\": {\n\t\t\topacity: \"0.4\",\n\t\t},\n\t\t\".calendar-alternate-background\": {\n\t\t\tbackground: `${theme.list_alternate_bg} !important`,\n\t\t},\n\t\t\".calendar-day:hover\": {\n\t\t\tbackground: theme.list_alternate_bg,\n\t\t},\n\t\t\".calendar-day:hover .calendar-day-header-button\": {\n\t\t\topacity: 1,\n\t\t},\n\t\t\".calendar-day-header-button\": {\n\t\t\topacity: 0,\n\t\t},\n\t\t\".calendar-hour\": {\n\t\t\t\"border-bottom\": `1px solid ${theme.content_border}`,\n\t\t\theight: px(size.calendar_hour_height),\n\t\t\tflex: \"1 0 auto\",\n\t\t},\n\t\t\".calendar-hour:hover\": {\n\t\t\tbackground: theme.list_alternate_bg,\n\t\t},\n\t\t\".calendar-column-border\": {\n\t\t\t\"border-right\": `1px solid ${theme.content_border}`,\n\t\t},\n\t\t\".calendar-column-border:nth-child(7)\": {\n\t\t\t\"border-right\": \"none\",\n\t\t},\n\t\t\".calendar-hour-margin\": {\n\t\t\t\"margin-left\": px(size.calendar_hour_width),\n\t\t},\n\t\t\".calendar-day\": {\n\t\t\t\"border-top\": `1px solid ${theme.content_border}`,\n\t\t\ttransition: \"background 0.4s\",\n\t\t\tbackground: theme.list_bg,\n\t\t},\n\t\t\".cursor-pointer\": {\n\t\t\tcursor: \"pointer\",\n\t\t},\n\t\t\".calendar-day-indicator\": {\n\t\t\t// overriden for mobile\n\t\t\theight: \"22px\",\n\t\t\t\"line-height\": \"24px\",\n\t\t\t\"text-align\": \"center\",\n\t\t\t\"font-size\": \"14px\",\n\t\t},\n\t\t\".calendar-day .calendar-day-indicator:hover\": {\n\t\t\tbackground: theme.list_message_bg,\n\t\t\topacity: 0.7,\n\t\t},\n\t\t\".calendar-day-number\": {\n\t\t\tmargin: \"3px auto\",\n\t\t\twidth: \"22px\",\n\t\t},\n\t\t\".calendar-event\": {\n\t\t\t\"border-radius\": px(4),\n\t\t\tborder: ` ${size.calendar_event_border}px solid ${theme.content_bg}`,\n\t\t\t\"padding-left\": \"4px\",\n\t\t\t\"font-weight\": \"600\",\n\t\t\t\"box-sizing\": \"content-box\",\n\t\t},\n\t\t\".fade-in\": {\n\t\t\topacity: 1,\n\t\t\t\"animation-name\": \"fadeInOpacity\",\n\t\t\t\"animation-iteration-count\": 1,\n\t\t\t\"animation-timing-function\": \"ease-in\",\n\t\t\t\"animation-duration\": \"200ms\",\n\t\t},\n\t\t\"@keyframes fadeInOpacity\": {\n\t\t\t\"0%\": {\n\t\t\t\topacity: 0,\n\t\t\t},\n\t\t\t\"100%\": {\n\t\t\t\topacity: 1,\n\t\t\t},\n\t\t},\n\t\t\".calendar-bubble-more-padding-day .calendar-event\": {\n\t\t\tborder: `1px solid ${theme.list_bg}`,\n\t\t},\n\t\t\".darker-hover:hover\": {\n\t\t\tfilter: \"brightness(95%)\",\n\t\t},\n\t\t\".darkest-hover:hover\": {\n\t\t\tfilter: \"brightness(70%)\",\n\t\t},\n\t\t\".event-continues-left\": {\n\t\t\t\"border-top-left-radius\": 0,\n\t\t\t\"border-bottom-left-radius\": 0,\n\t\t\t\"border-left\": \"none\",\n\t\t},\n\t\t\".event-continues-right\": {\n\t\t\t\"margin-right\": 0,\n\t\t\t\"border-right\": \"none\",\n\t\t\t\"border-top-right-radius\": 0,\n\t\t\t\"border-bottom-right-radius\": 0,\n\t\t},\n\t\t\".event-continues-right-arrow\": {\n\t\t\twidth: 0,\n\t\t\theight: 0,\n\t\t\t\"border-top\": \"9px solid transparent\",\n\t\t\t\"border-bottom\": \"9px solid transparent\",\n\t\t\t\"border-left\": \"6px solid green\",\n\t\t\t\"margin-top\": px(1),\n\t\t\t\"margin-bottom\": px(1),\n\t\t},\n\t\t\".time-field\": {\n\t\t\twidth: \"80px\",\n\t\t},\n\t\t\".calendar-agenda-time-column\": {\n\t\t\twidth: px(80),\n\t\t},\n\t\t\".calendar-agenda-time-column > *\": {\n\t\t\theight: px(44),\n\t\t},\n\t\t\".calendar-agenda-row\": {\n\t\t\t\"min-height\": \"44px\",\n\t\t\tflex: \"1 0 auto\",\n\t\t},\n\t\t\".calendar-switch-button\": {\n\t\t\twidth: \"40px\",\n\t\t\t\"text-align\": \"center\",\n\t\t},\n\t\t\".calendar-long-events-header\": {\n\t\t\toverflow: \"hidden\",\n\t\t\t\"background-color\": theme.content_bg,\n\t\t\t\"border-bottom\": `1px solid ${theme.content_border}`,\n\t\t},\n\t\t\".calendar-month-week-number\": {\n\t\t\t\"font-size\": \"12px\",\n\t\t\topacity: \"0.8\",\n\t\t\ttop: \"8px\",\n\t\t\tleft: \"6px\",\n\t\t},\n\t\t\".color-picker\": {\n\t\t\theight: px(30),\n\t\t\twidth: px(100),\n\t\t},\n\t\t\".calendar-invite-field\": {\n\t\t\t\"min-width\": \"80px\",\n\t\t},\n\t\t\".block-list\": {\n\t\t\t\"list-style\": \"none\",\n\t\t\tpadding: 0,\n\t\t},\n\t\t\".block-list li\": {\n\t\t\tdisplay: \"block\",\n\t\t},\n\t\t\".sticky\": {\n\t\t\tposition: \"sticky\",\n\t\t},\n\t\t\".text-fade\": {\n\t\t\tcolor: theme.content_button,\n\t\t},\n\t\t// media query for small devices where elements should be arranged in one column\n\t\t// also adaptions for table column widths\n\t\t\"@media (max-width: 400px)\": {\n\t\t\t// currently used for the reminder dialog\n\t\t\t\".flex-direction-change\": {\n\t\t\t\tdisplay: \"flex\",\n\t\t\t\t\"flex-direction\": \"column-reverse\",\n\t\t\t\t\"justify-content\": \"center\",\n\t\t\t},\n\t\t\t\".column-width-small\": {\n\t\t\t\twidth: px(size.column_width_s_mobile),\n\t\t\t},\n\t\t},\n\t\t\"@keyframes move-stripes\": {\n\t\t\t\"0%\": {\n\t\t\t\t\"background-position\": \"0 0\",\n\t\t\t},\n\t\t\t\"100%\": {\n\t\t\t\t\"background-position\": \"15px 0\",\n\t\t\t},\n\t\t},\n\t\t\".indefinite-progress\": {\n\t\t\t\"background-image\": `repeating-linear-gradient(\n  -45deg,\n  ${theme.content_accent},\n  ${theme.content_accent} 5px,\n  ${theme.content_bg} 5px,\n  ${theme.content_bg} 10px\n);`,\n\t\t\t// WebKit based browsers initially implemented old specification, we cannot specify unprefixed value\n\t\t\t// for them\n\t\t\t[(client.browser === BrowserType.SAFARI ? \"-webkit-background-size\" : \"background-size\") as string]: px(15),\n\t\t\twidth: \"100%\",\n\t\t\theight: px(3),\n\t\t\tanimation: \"move-stripes 2s linear infinite\",\n\t\t},\n\t\t\".transition-margin\": {\n\t\t\ttransition: `margin-bottom 200ms ease-in-out`,\n\t\t},\n\t\t\".circle\": {\n\t\t\t\"border-radius\": \"50%\",\n\t\t},\n\t\t\".clickable\": {\n\t\t\tcursor: \"pointer\",\n\t\t},\n\t\t\".switch-month-button svg\": {\n\t\t\tfill: theme.navigation_button,\n\t\t},\n\t\t\"drawer-menu\": {\n\t\t\twidth: px(size.drawer_menu_width),\n\t\t\tbackground: getNavigationMenuBg(),\n\t\t},\n\t\t\".menu-shadow\": {\n\t\t\t\"box-shadow\": \"0 4px 5px 2px rgba(0,0,0,0.14), 0 4px 5px 2px rgba(0,0,0,0.14), 0 4px 5px 2px rgba(0,0,0,0.14)\",\n\t\t},\n\t\t\".big-input input\": {\n\t\t\t\"font-size\": px(size.font_size_base * 1.4),\n\t\t\t\"line-height\": `${px(size.font_size_base * 1.4 + 2)} !important`,\n\t\t},\n\t\t[`@media (max-width: ${size.desktop_layout_width - 1}px)`]: {\n\t\t\t\".main-view\": {\n\t\t\t\ttop: 0,\n\t\t\t\tbottom: 0,\n\t\t\t},\n\t\t\t\".fixed-bottom-right\": {\n\t\t\t\tbottom: px(size.hpad_large_mobile + size.bottom_nav_bar),\n\t\t\t\tright: px(size.hpad_large_mobile),\n\t\t\t},\n\t\t\t\".custom-logo\": {\n\t\t\t\twidth: px(40),\n\t\t\t},\n\t\t\t\".notification-overlay-content\": {\n\t\t\t\t\"padding-top\": px(size.vpad_small),\n\t\t\t},\n\t\t\t\".calendar-day-indicator\": {\n\t\t\t\theight: \"20px\",\n\t\t\t\t\"line-height\": \"20px\",\n\t\t\t\t\"text-align\": \"center\",\n\t\t\t\t\"font-size\": \"14px\",\n\t\t\t},\n\t\t\t\".calendar-day-number\": {\n\t\t\t\tmargin: \"2px auto\",\n\t\t\t\twidth: \"20px\",\n\t\t\t},\n\t\t\t\".calendar-hour-margin\": {\n\t\t\t\t\"margin-left\": px(size.calendar_hour_width_mobile),\n\t\t\t},\n\t\t\t\".calendar-month-week-number\": {\n\t\t\t\t\"font-size\": \"10px\",\n\t\t\t\topacity: \"0.8\",\n\t\t\t\ttop: \"3px\",\n\t\t\t\tleft: \"3px\",\n\t\t\t},\n\t\t},\n\t\t\".cursor-grabbing *\": {\n\t\t\tcursor: \"grabbing !important\",\n\t\t},\n\t\t// This is applied to elements that should indicate they will be draggable when some key is pressed.\n\t\t// Ideally we would use cursor: grab here, but it doesn't seem to be supported in electron\n\t\t\".drag-mod-key *\": {\n\t\t\tcursor: \"copy !important\",\n\t\t},\n\t\t//We us this class to hide contents that should just be visible for printing\n\t\t\".noscreen\": {\n\t\t\tdisplay: \"none\",\n\t\t},\n\t\t\"@media print\": {\n\t\t\t\".color-adjust-exact\": {\n\t\t\t\t\"color-adjust\": \"exact\",\n\t\t\t\t\"-webkit-print-color-adjust\": \"exact\",\n\t\t\t},\n\t\t\t\".noprint\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\t\".noscreen\": {\n\t\t\t\tdisplay: \"initial\",\n\t\t\t},\n\t\t\t\".print\": {\n\t\t\t\tcolor: \"black\",\n\t\t\t\t\"background-color\": \"white\",\n\t\t\t\tdisplay: \"block\",\n\t\t\t},\n\t\t\t\"html, body\": {\n\t\t\t\tposition: \"initial\",\n\t\t\t\toverflow: \"visible !important\",\n\t\t\t},\n\t\t\t// overwrite position \"fixed\" otherwise only one page will be printed.\n\t\t\t\".header-nav\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\t\".main-view\": {\n\t\t\t\ttop: 0,\n\t\t\t\tposition: \"static !important\",\n\t\t\t},\n\t\t\t\".dropdown-panel\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\t\".fill-absolute\": {\n\t\t\t\tposition: \"static !important\",\n\t\t\t\tdisplay: \"initial\",\n\t\t\t},\n\t\t\t\".view-columns\": {\n\t\t\t\twidth: \"100% !important\",\n\t\t\t\ttransform: \"initial !important\",\n\t\t\t\tdisplay: \"initial\",\n\t\t\t\tposition: \"initial\",\n\t\t\t},\n\t\t\t\".view-column:nth-child(1), .view-column:nth-child(2)\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\t\".view-column\": {\n\t\t\t\twidth: \"100% !important\",\n\t\t\t},\n\t\t\t\"#mail-viewer\": {\n\t\t\t\toverflow: \"visible\",\n\t\t\t\tdisplay: \"block\",\n\t\t\t},\n\t\t\t\"#mail-body\": {\n\t\t\t\toverflow: \"visible\",\n\t\t\t},\n\t\t\t\"#login-view\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\t\".dialog-header\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\t\".dialog-container\": {\n\t\t\t\toverflow: \"visible\",\n\t\t\t\tposition: \"static !important\",\n\t\t\t},\n\t\t\t\"#wizard-paging\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\t\"button:not(.print)\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\t\".bottom-nav\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\t\".mobile .view-column:nth-child(2)\": {\n\t\t\t\tdisplay: \"initial\",\n\t\t\t},\n\t\t\t\".folder-column\": {\n\t\t\t\tdisplay: \"none\",\n\t\t\t},\n\t\t\tpre: {\n\t\t\t\t\"word-break\": \"normal\",\n\t\t\t\t\"overflow-wrap\": \"anywhere\",\n\t\t\t\t\"white-space\": \"break-spaces\",\n\t\t\t},\n\t\t},\n\t\t// detect webkit autofills; see TextField and https://medium.com/@brunn/detecting-autofilled-fields-in-javascript-aed598d25da7\n\t\t\"@keyframes onAutoFillStart\": {\n\t\t\tfrom: {\n\t\t\t\t/**/\n\t\t\t},\n\t\t\tto: {\n\t\t\t\t/**/\n\t\t\t},\n\t\t},\n\t\t\"@keyframes onAutoFillCancel\": {\n\t\t\tfrom: {\n\t\t\t\t/**/\n\t\t\t},\n\t\t\tto: {\n\t\t\t\t/**/\n\t\t\t},\n\t\t},\n\t\t// use the animations as hooks for JS to capture 'animationstart' events\n\t\t\"input:-webkit-autofill\": {\n\t\t\t\"animation-name\": \"onAutoFillStart\",\n\t\t},\n\t\t\"input:not(:-webkit-autofill)\": {\n\t\t\t\"animation-name\": \"onAutoFillCancel\",\n\t\t},\n\t\t// for compatibility with Outlook 2010/2013 emails. have a negative indentation (18.0pt) on each list element and additionally this class\n\t\t// we strip all global style definitions, so the list elements are only indented to the left if we do not allow the MsoListParagraph classes\n\t\t// they are whitelisted in HtmlSanitizer.js\n\t\t\".MsoListParagraph, .MsoListParagraphCxSpFirst, .MsoListParagraphCxSpMiddle, .MsoListParagraphCxSpLast\": {\n\t\t\t\"margin-left\": \"36.0pt\",\n\t\t},\n\t\t\"span.vertical-text\": {\n\t\t\ttransform: \"rotate(180deg)\",\n\t\t\t\"writing-mode\": \"vertical-rl\",\n\t\t},\n\t\t\"ul.usage-test-opt-in-bullets\": {\n\t\t\tmargin: \"0 auto\",\n\t\t\t\"list-style\": \"disc\",\n\t\t\t\"text-align\": \"left\",\n\t\t},\n\t\t\".bonus-month\": {\n\t\t\tbackground: theme.content_accent,\n\t\t\tcolor: theme.content_bg,\n\t\t\twidth: px(100),\n\t\t\t\"min-width\": px(100),\n\t\t\theight: px(100),\n\t\t\t\"min-height\": px(100),\n\t\t\t\"border-radius\": px(100),\n\t\t},\n\t}\n})\n","import type { LoginController } from \"../api/main/LoginController\"\nimport { Dialog } from \"../gui/base/Dialog\"\nimport { generatedIdToTimestamp } from \"../api/common/utils/EntityUtils\"\nimport type { TranslationText } from \"./LanguageViewModel\"\nimport { lang } from \"./LanguageViewModel\"\nimport { getApiOrigin } from \"../api/common/Env\"\nimport {\n\tAccessBlockedError,\n\tAccessDeactivatedError,\n\tAccessExpiredError,\n\tBadRequestError,\n\tConnectionError,\n\tNotAuthenticatedError,\n\tNotAuthorizedError,\n\tNotFoundError,\n\tTooManyRequestsError,\n} from \"../api/common/error/RestError\"\nimport { CancelledError } from \"../api/common/error/CancelledError\"\nimport { ApprovalStatus, getCustomerApprovalStatus } from \"../api/common/TutanotaConstants\"\nimport type { ResetAction } from \"../login/recover/RecoverLoginDialog\"\nimport { showProgressDialog } from \"../gui/dialogs/ProgressDialog\"\nimport { UserError } from \"../api/main/UserError\"\nimport { noOp, ofClass } from \"@tutao/tutanota-utils\"\nimport { showUserError } from \"./ErrorHandlerImpl\"\nimport type { SubscriptionParameters } from \"../subscription/UpgradeSubscriptionWizard\"\nimport { locator } from \"../api/main/MainLocator\"\nimport { CredentialAuthenticationError } from \"../api/common/error/CredentialAuthenticationError\"\nimport type { Params } from \"mithril\"\nimport { LoginState } from \"../login/LoginViewModel.js\"\n\n/**\n * Shows warnings if the invoices are not paid or the registration is not approved yet.\n * @param includeInvoiceNotPaidForAdmin If true, also shows a warning for an admin if the invoice is not paid (use at login), if false does not show this warning (use when sending an email).\n * @param defaultStatus This status is used if the actual status on the customer is \"0\"\n * @returns True if the user may still send emails, false otherwise.\n */\nexport function checkApprovalStatus(logins: LoginController, includeInvoiceNotPaidForAdmin: boolean, defaultStatus?: ApprovalStatus): Promise<boolean> {\n\tif (!logins.getUserController().isInternalUser()) {\n\t\t// external users are not authorized to load the customer\n\t\treturn Promise.resolve(true)\n\t}\n\n\treturn logins\n\t\t.getUserController()\n\t\t.loadCustomer()\n\t\t.then((customer) => {\n\t\t\tconst approvalStatus = getCustomerApprovalStatus(customer)\n\t\t\tconst status = approvalStatus === ApprovalStatus.REGISTRATION_APPROVED && defaultStatus != null ? defaultStatus : approvalStatus\n\t\t\tif (\n\t\t\t\tstatus === ApprovalStatus.REGISTRATION_APPROVAL_NEEDED ||\n\t\t\t\tstatus === ApprovalStatus.DELAYED ||\n\t\t\t\tstatus === ApprovalStatus.REGISTRATION_APPROVAL_NEEDED_AND_INITIALLY_ACCESSED\n\t\t\t) {\n\t\t\t\treturn Dialog.message(\"waitingForApproval_msg\").then(() => false)\n\t\t\t} else if (status === ApprovalStatus.DELAYED_AND_INITIALLY_ACCESSED) {\n\t\t\t\tif (new Date().getTime() - generatedIdToTimestamp(customer._id) > 2 * 24 * 60 * 60 * 1000) {\n\t\t\t\t\treturn Dialog.message(\"requestApproval_msg\").then(() => true)\n\t\t\t\t} else {\n\t\t\t\t\treturn Dialog.message(\"waitingForApproval_msg\").then(() => false)\n\t\t\t\t}\n\t\t\t} else if (status === ApprovalStatus.INVOICE_NOT_PAID) {\n\t\t\t\tif (logins.getUserController().isGlobalAdmin()) {\n\t\t\t\t\tif (includeInvoiceNotPaidForAdmin) {\n\t\t\t\t\t\treturn Dialog.message(() => {\n\t\t\t\t\t\t\treturn lang.get(\"invoiceNotPaid_msg\", {\n\t\t\t\t\t\t\t\t\"{1}\": getApiOrigin(),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.then(() => {\n\t\t\t\t\t\t\t\t// TODO: navigate to payment site in settings\n\t\t\t\t\t\t\t\t//m.route.set(\"/settings\")\n\t\t\t\t\t\t\t\t//tutao.locator.settingsViewModel.show(tutao.tutanota.ctrl.SettingsViewModel.DISPLAY_ADMIN_PAYMENT);\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.then(() => true)\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst errorMessage = () => lang.get(\"invoiceNotPaidUser_msg\") + \" \" + lang.get(\"contactAdmin_msg\")\n\n\t\t\t\t\treturn Dialog.message(errorMessage).then(() => false)\n\t\t\t\t}\n\t\t\t} else if (status === ApprovalStatus.SPAM_SENDER) {\n\t\t\t\tDialog.message(\"loginAbuseDetected_msg\") // do not logout to avoid that we try to reload with mail editor open\n\n\t\t\t\treturn false\n\t\t\t} else if (status === ApprovalStatus.PAID_SUBSCRIPTION_NEEDED) {\n\t\t\t\tconst message = lang.get(\"upgradeNeeded_msg\")\n\t\t\t\treturn Dialog.reminder(lang.get(\"upgradeReminderTitle_msg\"), message).then((confirmed) => {\n\t\t\t\t\tif (confirmed) {\n\t\t\t\t\t\timport(\"../subscription/UpgradeSubscriptionWizard\").then((m) => m.showUpgradeWizard(logins))\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\treturn true\n\t\t\t}\n\t\t})\n}\n\nexport function getLoginErrorMessage(error: Error, isExternalLogin: boolean): TranslationText {\n\tswitch (error.constructor) {\n\t\tcase BadRequestError:\n\t\tcase NotAuthenticatedError:\n\t\tcase AccessDeactivatedError:\n\t\t\treturn \"loginFailed_msg\"\n\n\t\tcase AccessBlockedError:\n\t\t\treturn \"loginFailedOften_msg\"\n\n\t\tcase AccessExpiredError:\n\t\t\treturn isExternalLogin ? \"expiredLink_msg\" : \"inactiveAccount_msg\"\n\n\t\tcase TooManyRequestsError:\n\t\t\treturn \"tooManyAttempts_msg\"\n\n\t\tcase CancelledError:\n\t\t\treturn \"emptyString_msg\"\n\n\t\tcase CredentialAuthenticationError:\n\t\t\treturn () =>\n\t\t\t\tlang.get(\"couldNotUnlockCredentials_msg\", {\n\t\t\t\t\t\"{reason}\": error.message,\n\t\t\t\t})\n\n\t\tcase ConnectionError:\n\t\t\treturn \"connectionLostLong_msg\"\n\n\t\tdefault:\n\t\t\treturn \"emptyString_msg\"\n\t}\n}\n\n/**\n * Handle expected login errors\n * Any unexpected errors will be rethrown\n */\nexport function handleExpectedLoginError<E extends Error>(error: E, handler: (error: E) => void) {\n\tif (\n\t\terror instanceof BadRequestError ||\n\t\terror instanceof NotAuthenticatedError ||\n\t\terror instanceof AccessExpiredError ||\n\t\terror instanceof AccessBlockedError ||\n\t\terror instanceof AccessDeactivatedError ||\n\t\terror instanceof TooManyRequestsError ||\n\t\terror instanceof CancelledError ||\n\t\terror instanceof CredentialAuthenticationError ||\n\t\terror instanceof ConnectionError\n\t) {\n\t\thandler(error)\n\t} else {\n\t\tthrow error\n\t}\n}\n\nexport function getLoginErrorStateAndMessage(error: Error): { errorMessage: TranslationText; state: LoginState } {\n\tlet errorMessage = getLoginErrorMessage(error, false)\n\tlet state\n\tif (error instanceof BadRequestError || error instanceof NotAuthenticatedError) {\n\t\tstate = LoginState.InvalidCredentials\n\t} else if (error instanceof AccessExpiredError) {\n\t\tstate = LoginState.AccessExpired\n\t} else {\n\t\tstate = LoginState.UnknownError\n\t}\n\thandleExpectedLoginError(error, noOp)\n\treturn {\n\t\terrorMessage,\n\t\tstate,\n\t}\n}\n\nexport async function showSignupDialog(urlParams: Params) {\n\tconst subscriptionParams = getSubscriptionParameters(urlParams)\n\tconst registrationDataId = getRegistrationDataIdFromParams(urlParams)\n\tconst referralCode = getReferralCodeFromParams(urlParams)\n\tawait showProgressDialog(\n\t\t\"loading_msg\",\n\t\tlocator.worker.initialized.then(async () => {\n\t\t\tconst { loadSignupWizard } = await import(\"../subscription/UpgradeSubscriptionWizard\")\n\t\t\tawait loadSignupWizard(subscriptionParams, registrationDataId, referralCode)\n\t\t}),\n\t).catch(\n\t\tofClass(UserError, async (e) => {\n\t\t\tconst m = await import(\"mithril\")\n\t\t\tawait showUserError(e)\n\t\t\t// redirects if there were invalid parameters, e.g. for referral codes and campaignIds\n\t\t\tm.route.set(\"/signup\")\n\t\t}),\n\t)\n}\n\nfunction getSubscriptionParameters(hashParams: Params): SubscriptionParameters | null {\n\tif (typeof hashParams.subscription === \"string\" && typeof hashParams.type === \"string\" && typeof hashParams.interval === \"string\") {\n\t\tconst { subscription, type, interval } = hashParams\n\t\treturn {\n\t\t\tsubscription,\n\t\t\ttype,\n\t\t\tinterval,\n\t\t}\n\t} else {\n\t\treturn null\n\t}\n}\n\nexport function getReferralCodeFromParams(urlParams: Params): string | null {\n\tif (typeof urlParams.ref === \"string\") {\n\t\treturn urlParams.ref\n\t}\n\treturn null\n}\n\nexport function getRegistrationDataIdFromParams(hashParams: Params): string | null {\n\tif (typeof hashParams.token === \"string\") {\n\t\treturn hashParams.token\n\t}\n\treturn null\n}\n\nasync function loadRedeemGiftCardWizard(urlHash: string): Promise<Dialog> {\n\tconst wizard = await import(\"../subscription/giftcards/RedeemGiftCardWizard\")\n\treturn wizard.loadRedeemGiftCardWizard(urlHash)\n}\n\nexport async function showGiftCardDialog(urlHash: string) {\n\tshowProgressDialog(\"loading_msg\", loadRedeemGiftCardWizard(urlHash))\n\t\t.then((dialog) => dialog.show())\n\t\t.catch((e) => {\n\t\t\tif (e instanceof NotAuthorizedError || e instanceof NotFoundError) {\n\t\t\t\tthrow new UserError(\"invalidGiftCard_msg\")\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t})\n\t\t.catch(ofClass(UserError, showUserError))\n}\n\nexport async function showRecoverDialog(mailAddress: string, resetAction: ResetAction) {\n\tconst dialog = await import(\"../login/recover/RecoverLoginDialog\")\n\tdialog.show(mailAddress, resetAction)\n}\n","import { keyManager } from \"./KeyManager.js\"\nimport { FeatureType, Keys } from \"../api/common/TutanotaConstants.js\"\nimport { locator } from \"../api/main/MainLocator.js\"\nimport m from \"mithril\"\nimport { CALENDAR_PREFIX, CONTACTS_PREFIX, LogoutUrl, MAIL_PREFIX, SETTINGS_PREFIX } from \"./RouteChange.js\"\n\nexport function setupNavShortcuts() {\n\tkeyManager.registerShortcuts([\n\t\t{\n\t\t\tkey: Keys.M,\n\t\t\tenabled: () => locator.logins.isUserLoggedIn(),\n\t\t\texec: () => m.route.set(MAIL_PREFIX),\n\t\t\thelp: \"mailView_action\",\n\t\t},\n\t\t{\n\t\t\tkey: Keys.C,\n\t\t\tenabled: () => locator.logins.isInternalUserLoggedIn() && !locator.logins.isEnabled(FeatureType.DisableContacts),\n\t\t\texec: () => m.route.set(CONTACTS_PREFIX),\n\t\t\thelp: \"contactView_action\",\n\t\t},\n\t\t{\n\t\t\tkey: Keys.O,\n\t\t\tenabled: () => locator.logins.isInternalUserLoggedIn(),\n\t\t\texec: () => m.route.set(CALENDAR_PREFIX),\n\t\t\thelp: \"calendarView_action\",\n\t\t},\n\t\t{\n\t\t\tkey: Keys.S,\n\t\t\tenabled: () => locator.logins.isInternalUserLoggedIn(),\n\t\t\texec: () => m.route.set(SETTINGS_PREFIX),\n\t\t\thelp: \"settingsView_action\",\n\t\t},\n\t\t{\n\t\t\tkey: Keys.L,\n\t\t\tshift: true,\n\t\t\tctrl: true,\n\t\t\tenabled: () => locator.logins.isUserLoggedIn(),\n\t\t\texec: (key) => m.route.set(LogoutUrl),\n\t\t\thelp: \"logout_label\",\n\t\t},\n\t])\n}\n","import { Vnode } from \"mithril\"\nimport { TopLevelAttrs } from \"../TopLevelView.js\"\n\n/**\n * Base (utility) class for top-level components. Will handle URL updates for you automatically and will only call {@link onNewUrl} when necessary.\n */\nexport abstract class BaseTopLevelView {\n\tprivate lastPath: string = \"\"\n\n\toninit({ attrs }: Vnode<TopLevelAttrs>) {\n\t\tthis.lastPath = attrs.requestedPath\n\t\tthis.onNewUrl(attrs.args, attrs.requestedPath)\n\t}\n\n\tonbeforeupdate({ attrs }: Vnode<TopLevelAttrs>) {\n\t\t// onupdate() is called on every re-render but we don't want to call onNewUrl all the time\n\t\tif (this.lastPath !== attrs.requestedPath) {\n\t\t\tthis.lastPath = attrs.requestedPath\n\t\t\tthis.onNewUrl(attrs.args, attrs.requestedPath)\n\t\t}\n\t}\n\n\tprotected abstract onNewUrl(args: Record<string, any>, requestedPath: string): void\n}\n","import { pureComponent } from \"./base/PureComponent.js\"\nimport { styles } from \"./styles.js\"\nimport m from \"mithril\"\nimport { DesktopBaseHeader } from \"./base/DesktopBaseHeader.js\"\nimport { AriaLandmarks, landmarkAttrs } from \"./AriaUtils.js\"\nimport { theme } from \"./theme.js\"\n\n/** Small header-like view for the login screens. */\nexport const LoginScreenHeader = pureComponent(() =>\n\tstyles.isDesktopLayout()\n\t\t? m(DesktopBaseHeader)\n\t\t: m(\n\t\t\t\t\".mt-l.flex.justify-center.mb\",\n\t\t\t\tm(\n\t\t\t\t\t\".logo.logo-height.mt-safe-inset\",\n\t\t\t\t\t{\n\t\t\t\t\t\t...landmarkAttrs(AriaLandmarks.Banner, \"Tutanota logo\"),\n\t\t\t\t\t},\n\t\t\t\t\tm.trust(theme.logo),\n\t\t\t\t),\n\t\t  ),\n)\n","import m, { Children, Component } from \"mithril\"\nimport { ColumnType, ViewColumn } from \"../base/ViewColumn.js\"\nimport type { windowSizeListener } from \"../../misc/WindowFacade.js\"\nimport { windowFacade } from \"../../misc/WindowFacade.js\"\nimport { size } from \"../size.js\"\nimport { alpha, AlphaEnum, animations, transform, TransformEnum } from \"../animation/Animations.js\"\nimport { ease } from \"../animation/Easing.js\"\nimport { theme } from \"../theme.js\"\nimport { neverNull } from \"@tutao/tutanota-utils\"\nimport { styles } from \"../styles.js\"\nimport { AriaLandmarks } from \"../AriaUtils.js\"\nimport { LayerType } from \"../../RootView.js\"\nimport { assertMainOrNode } from \"../../api/common/Env.js\"\n\nassertMainOrNode()\nexport type GestureInfo = {\n\tx: number\n\ty: number\n\ttime: number\n\tidentifier: number\n}\nexport const gestureInfoFromTouch = (touch: Touch): GestureInfo => ({\n\tx: touch.pageX,\n\ty: touch.pageY,\n\ttime: performance.now(),\n\tidentifier: touch.identifier,\n})\n\ninterface ViewSliderAttrs {\n\theader: Children\n\tbottomNav: Children\n}\n\n/**\n * Represents a view with multiple view columns. Depending on the screen width and the view columns configurations,\n * the actual widths and positions of the view columns is calculated. This allows a consistent layout for any browser\n * resolution on any type of device.\n */\nexport class ViewSlider implements Component<ViewSliderAttrs> {\n\tcolumns: ViewColumn[]\n\tprivate _mainColumn: ViewColumn\n\tfocusedColumn: ViewColumn\n\tprivate _visibleBackgroundColumns: ViewColumn[]\n\tprivate _domSlidingPart!: HTMLElement\n\tview: Component<ViewSliderAttrs>[\"view\"]\n\tprivate _busy: Promise<unknown>\n\tprivate _parentName: string\n\tprivate _isModalBackgroundVisible: boolean\n\tprivate readonly resizeListener: windowSizeListener = () => this._updateVisibleBackgroundColumns()\n\tprivate readonly handleHistoryEvent = () => {\n\t\tconst prev = this.getPreviousColumn()\n\t\tif (prev != null && prev.columnType !== ColumnType.Foreground) {\n\t\t\tthis.focusPreviousColumn()\n\t\t\treturn false\n\t\t} else if (this.isForegroundColumnFocused()) {\n\t\t\tthis.focusNextColumn()\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\n\t/** Creates the event listeners as soon as this component is loaded (invoked by mithril)*/\n\toncreate: () => void = () => {\n\t\tthis._updateVisibleBackgroundColumns()\n\n\t\twindowFacade.addResizeListener(this.resizeListener)\n\t\twindowFacade.addHistoryEventListener(this.handleHistoryEvent)\n\t}\n\n\t/** Removes the registered event listeners as soon as this component is unloaded (invoked by mithril)*/\n\tonremove: () => void = () => {\n\t\twindowFacade.removeResizeListener(this.resizeListener)\n\t\twindowFacade.removeHistoryEventListener(this.handleHistoryEvent)\n\t}\n\t_getSideColDom: () => HTMLElement | null = () => this.columns[0]._domColumn\n\n\tconstructor(viewColumns: ViewColumn[], parentName: string) {\n\t\tthis.columns = viewColumns\n\t\tthis._mainColumn = neverNull(viewColumns.find((column) => column.columnType === ColumnType.Background)) // the first background column is the main column\n\n\t\tthis.focusedColumn = this._mainColumn\n\t\tthis._visibleBackgroundColumns = []\n\n\t\tthis._updateVisibleBackgroundColumns()\n\n\t\tthis._busy = Promise.resolve()\n\t\tthis._parentName = parentName\n\t\tthis._isModalBackgroundVisible = false\n\t\tthis.columns.forEach((column) => column.setRole(this._getColumnRole(column)))\n\n\t\tthis.view = ({ attrs }): Children => {\n\t\t\tconst mainSliderColumns = this._getColumnsForMainSlider()\n\n\t\t\tconst allBackgroundColumnsAreVisible = this._visibleBackgroundColumns.length === mainSliderColumns.length\n\t\t\treturn m(\n\t\t\t\t\".fill-absolute.flex.col\",\n\t\t\t\t{\n\t\t\t\t\toncreate: (vnode) => {\n\t\t\t\t\t\tthis._attachTouchHandler(vnode.dom as HTMLElement)\n\t\t\t\t\t},\n\t\t\t\t\tonremove: () => {\n\t\t\t\t\t\tif (this.columns[0].columnType === ColumnType.Foreground && this.columns[0].isInForeground) {\n\t\t\t\t\t\t\tthis.columns[0].isInForeground = false\n\t\t\t\t\t\t\tthis._isModalBackgroundVisible = false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t[\n\t\t\t\t\tstyles.isUsingBottomNavigation() ? null : attrs.header,\n\t\t\t\t\tm(\n\t\t\t\t\t\t\".view-columns.flex-grow.rel\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\toncreate: (vnode) => {\n\t\t\t\t\t\t\t\tthis._domSlidingPart = vnode.dom as HTMLElement\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\twidth: this.getWidth() + \"px\",\n\t\t\t\t\t\t\t\ttransform: \"translateX(\" + this.getOffset(this._visibleBackgroundColumns[0]) + \"px)\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmainSliderColumns.map((column, index) =>\n\t\t\t\t\t\t\tm(column, {\n\t\t\t\t\t\t\t\t// Only apply right border if 1. all background columns are visible. 2. It's not the last column.\n\t\t\t\t\t\t\t\t// Perhaps the condition should be \"there's another visible column after this one\" but it works like this too\n\t\t\t\t\t\t\t\trightBorder: allBackgroundColumnsAreVisible && index !== mainSliderColumns.length - 1,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tstyles.isUsingBottomNavigation() ? attrs.bottomNav : null,\n\t\t\t\t\tthis._getColumnsForOverlay().map((c) => m(c, {})),\n\t\t\t\t\tthis._createModalBackground(),\n\t\t\t\t],\n\t\t\t)\n\t\t}\n\t}\n\n\t_getColumnRole(column: ViewColumn): AriaLandmarks | null {\n\t\t// role  for foreground column is handled inside FolderColumnView\n\t\tif (column.columnType === ColumnType.Foreground) {\n\t\t\treturn null\n\t\t}\n\n\t\treturn this._mainColumn === column ? AriaLandmarks.Main : AriaLandmarks.Region\n\t}\n\n\tgetMainColumn(): ViewColumn {\n\t\treturn this._mainColumn\n\t}\n\n\t_getColumnsForMainSlider(): Array<ViewColumn> {\n\t\treturn this.columns.filter((c) => c.columnType === ColumnType.Background || c.visible)\n\t}\n\n\t_getColumnsForOverlay(): Array<ViewColumn> {\n\t\treturn this.columns.filter((c) => c.columnType === ColumnType.Foreground && !c.visible)\n\t}\n\n\t_createModalBackground(): Children {\n\t\tif (this._isModalBackgroundVisible) {\n\t\t\treturn [\n\t\t\t\tm(\".fill-absolute.will-change-alpha\", {\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tzIndex: LayerType.ForegroundMenu,\n\t\t\t\t\t},\n\t\t\t\t\toncreate: (vnode) => {\n\t\t\t\t\t\tthis._busy.then(() => animations.add(vnode.dom as HTMLElement, alpha(AlphaEnum.BackgroundColor, theme.modal_bg, 0, 0.5)))\n\t\t\t\t\t},\n\t\t\t\t\tonbeforeremove: (vnode) => {\n\t\t\t\t\t\treturn this._busy.then(() => animations.add(vnode.dom as HTMLElement, alpha(AlphaEnum.BackgroundColor, theme.modal_bg, 0.5, 0)))\n\t\t\t\t\t},\n\t\t\t\t\tonclick: () => {\n\t\t\t\t\t\tthis.focus(this._visibleBackgroundColumns[0])\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t]\n\t\t} else {\n\t\t\treturn []\n\t\t}\n\t}\n\n\t_updateVisibleBackgroundColumns() {\n\t\tthis.focusedColumn = this.focusedColumn || this._mainColumn\n\t\tlet visibleColumns: ViewColumn[] = [this.focusedColumn.columnType === ColumnType.Background ? this.focusedColumn : this._mainColumn]\n\t\tlet remainingSpace = window.innerWidth - visibleColumns[0].minWidth\n\t\tlet nextVisibleColumn = this.getNextVisibleColumn(visibleColumns, this.columns)\n\n\t\twhile (nextVisibleColumn && remainingSpace >= nextVisibleColumn.minWidth) {\n\t\t\tvisibleColumns.push(nextVisibleColumn)\n\t\t\tremainingSpace -= nextVisibleColumn.minWidth\n\t\t\tnextVisibleColumn = this.getNextVisibleColumn(visibleColumns, this.columns)\n\t\t}\n\n\t\t// visible columns must be sort by the initial column order\n\t\tvisibleColumns.sort((a, b) => this.columns.indexOf(a) - this.columns.indexOf(b))\n\n\t\tthis._distributeRemainingSpace(visibleColumns, remainingSpace)\n\n\t\tthis._setWidthForHiddenColumns(visibleColumns)\n\n\t\tthis.columns.forEach((column) => (column.visible = visibleColumns.includes(column)))\n\t\tthis.updateOffsets()\n\t\tthis._visibleBackgroundColumns = visibleColumns\n\n\t\tif (this.allColumnsVisible()) {\n\t\t\tthis.focusedColumn.isInForeground = false\n\t\t\tthis._isModalBackgroundVisible = false\n\n\t\t\tif (this.columns[0]._domColumn) {\n\t\t\t\tthis.columns[0]._domColumn.style.transform = \"\"\n\t\t\t}\n\t\t}\n\n\t\twindow.requestAnimationFrame(() => m.redraw())\n\t}\n\n\tgetVisibleBackgroundColumns(): ViewColumn[] {\n\t\treturn this._visibleBackgroundColumns.slice()\n\t}\n\n\tisUsingOverlayColumns(): boolean {\n\t\treturn this.columns.every((c) => c.columnType !== ColumnType.Foreground || c.visible)\n\t}\n\n\t/**\n\t * Returns the next column which should become visible\n\t * @param visibleColumns All columns that will definitely be visible\n\t * @param allColumns All columns*\n\t */\n\tgetNextVisibleColumn(visibleColumns: ViewColumn[], allColumns: ViewColumn[]): ViewColumn | null {\n\t\t// First: try to find a background column which is not visible\n\t\tlet nextColumn = allColumns.find((column) => {\n\t\t\treturn column.columnType === ColumnType.Background && visibleColumns.indexOf(column) < 0\n\t\t})\n\n\t\tif (!nextColumn) {\n\t\t\t// Second: if no more background columns are available add the foreground column to the visible columns\n\t\t\tnextColumn = allColumns.find((column) => {\n\t\t\t\treturn column.columnType === ColumnType.Foreground && visibleColumns.indexOf(column) < 0\n\t\t\t})\n\t\t}\n\n\t\treturn nextColumn ?? null\n\t}\n\n\tgetBackgroundColumns(): ViewColumn[] {\n\t\treturn this.columns.filter((c) => c.columnType === ColumnType.Background)\n\t}\n\n\t/**\n\t * distributes the remaining space to all visible columns\n\t * @param visibleColumns\n\t * @param remainingSpace\n\t */\n\t_distributeRemainingSpace(visibleColumns: ViewColumn[], remainingSpace: number) {\n\t\tlet spacePerColumn = remainingSpace / visibleColumns.length\n\t\tvisibleColumns.forEach((visibleColumn: ViewColumn, index) => {\n\t\t\tif (visibleColumns.length - 1 === index) {\n\t\t\t\t// ignore max width for the last visible column\n\t\t\t\tvisibleColumn.setWidth(visibleColumn.minWidth + remainingSpace)\n\t\t\t} else {\n\t\t\t\tlet spaceForThisColumn = Math.min(spacePerColumn, visibleColumn.maxWidth - visibleColumn.minWidth)\n\t\t\t\tremainingSpace -= spaceForThisColumn\n\t\t\t\tvisibleColumn.setWidth(visibleColumn.minWidth + spaceForThisColumn)\n\t\t\t}\n\t\t})\n\t}\n\n\t_setWidthForHiddenColumns(visibleColumns: ViewColumn[]) {\n\t\t// if all columns are visible there is no need to set the width\n\t\tif (this.columns.length === visibleColumns.length) {\n\t\t\treturn\n\t\t}\n\n\t\t// if only one column is visible set the same width for all columns ignoring max width\n\t\tif (visibleColumns.length === 1) {\n\t\t\tthis.columns.forEach((column) => column.setWidth(visibleColumns[0].width))\n\t\t}\n\n\t\t// Reduce the width of the foreground button to keep always a small part of the background button visible.\n\t\tlet foreGroundColumn = this.columns.find((column) => column.columnType === ColumnType.Foreground)\n\n\t\tif (foreGroundColumn) {\n\t\t\tlet remainingSpace = window.innerWidth - foreGroundColumn.minWidth - size.hpad_large\n\t\t\tlet additionalSpaceForColumn = Math.min(remainingSpace, foreGroundColumn.maxWidth - foreGroundColumn.minWidth)\n\t\t\tforeGroundColumn.setWidth(foreGroundColumn.minWidth + additionalSpaceForColumn)\n\t\t}\n\t}\n\n\tasync focus(viewColumn: ViewColumn): Promise<unknown> {\n\t\t// await this._busy\n\t\t// if (this.focusedColumn === viewColumn) return\n\t\treturn this._busy\n\t\t\t.then(() => {\n\t\t\t\t// hide the foreground column if the column is in foreground\n\t\t\t\tif (this.focusedColumn.isInForeground) {\n\t\t\t\t\tthis._busy = this._slideForegroundColumn(this.focusedColumn, false)\n\t\t\t\t\treturn this._busy\n\t\t\t\t}\n\t\t\t})\n\t\t\t.then(() => {\n\t\t\t\tthis.focusedColumn = viewColumn\n\n\t\t\t\tif (\n\t\t\t\t\tviewColumn.columnType === ColumnType.Background &&\n\t\t\t\t\tthis._visibleBackgroundColumns.length === 1 &&\n\t\t\t\t\tthis._visibleBackgroundColumns.indexOf(viewColumn) < 0\n\t\t\t\t) {\n\t\t\t\t\tconst currentOffset = this._domSlidingPart.getBoundingClientRect().left\n\n\t\t\t\t\tthis._busy = this._slideBackgroundColumns(viewColumn, currentOffset, this.getOffset(viewColumn))\n\t\t\t\t} else if (viewColumn.columnType === ColumnType.Foreground && this._visibleBackgroundColumns.indexOf(viewColumn) < 0) {\n\t\t\t\t\tthis._busy = this._slideForegroundColumn(viewColumn, true)\n\t\t\t\t}\n\n\t\t\t\treturn this._busy\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tm.redraw()\n\t\t\t\tviewColumn.focus()\n\t\t\t}) // for updating header bar after animation\n\t}\n\n\twaitForAnimation(): Promise<unknown> {\n\t\treturn this._busy\n\t}\n\n\t/**\n\t * Executes a slide animation for the background buttons.\n\t */\n\t_slideBackgroundColumns(nextVisibleViewColumn: ViewColumn, oldOffset: number, newOffset: number): Promise<unknown> {\n\t\treturn animations\n\t\t\t.add(this._domSlidingPart, transform(TransformEnum.TranslateX, oldOffset, newOffset), {\n\t\t\t\teasing: ease.inOut,\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\t// replace the visible column\n\t\t\t\tconst [removed] = this._visibleBackgroundColumns.splice(0, 1, nextVisibleViewColumn)\n\n\t\t\t\tremoved.visible = false\n\t\t\t\tnextVisibleViewColumn.visible = true\n\t\t\t})\n\t}\n\n\t/**\n\t * Executes a slide animation for the foreground button.\n\t */\n\t_slideForegroundColumn(foregroundColumn: ViewColumn, toForeground: boolean): Promise<unknown> {\n\t\tif (!foregroundColumn._domColumn) return Promise.resolve()\n\n\t\tconst colRect = foregroundColumn._domColumn.getBoundingClientRect()\n\n\t\tconst oldOffset = colRect.left\n\t\tlet newOffset = foregroundColumn.getOffsetForeground(toForeground)\n\t\tthis._isModalBackgroundVisible = toForeground\n\t\treturn animations\n\t\t\t.add(neverNull(foregroundColumn._domColumn), transform(TransformEnum.TranslateX, oldOffset, newOffset), {\n\t\t\t\teasing: ease.in,\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tforegroundColumn.isInForeground = toForeground\n\t\t\t})\n\t}\n\n\tupdateOffsets() {\n\t\tlet offset = 0\n\n\t\tfor (let column of this.columns) {\n\t\t\tif (column.columnType === ColumnType.Background || column.visible) {\n\t\t\t\tcolumn.offset = offset\n\t\t\t\toffset += column.width\n\t\t\t}\n\t\t}\n\t}\n\n\tgetWidth(): number {\n\t\tlet lastColumn = this.columns[this.columns.length - 1]\n\t\treturn lastColumn.offset + lastColumn.width\n\t}\n\n\tgetOffset(column: ViewColumn): number {\n\t\treturn 0 - column.offset\n\t}\n\n\tisFocusPreviousPossible(): boolean {\n\t\treturn this.getPreviousColumn() != null\n\t}\n\n\tfocusPreviousColumn(): Promise<unknown> {\n\t\tif (this.isFocusPreviousPossible()) {\n\t\t\treturn this.focus(neverNull(this.getPreviousColumn()))\n\t\t} else {\n\t\t\treturn Promise.resolve()\n\t\t}\n\t}\n\n\tfocusNextColumn() {\n\t\tconst indexOfCurrent = this.columns.indexOf(this.focusedColumn)\n\n\t\tif (indexOfCurrent + 1 < this.columns.length) {\n\t\t\tthis.focus(this.columns[indexOfCurrent + 1])\n\t\t}\n\t}\n\n\tgetPreviousColumn(): ViewColumn | null {\n\t\tif (this.columns.indexOf(this._visibleBackgroundColumns[0]) > 0 && !this.focusedColumn.isInForeground) {\n\t\t\tlet visibleColumnIndex = this.columns.indexOf(this._visibleBackgroundColumns[0])\n\t\t\treturn this.columns[visibleColumnIndex - 1]\n\t\t}\n\n\t\treturn null\n\t}\n\n\tisFirstBackgroundColumnFocused(): boolean {\n\t\treturn this.columns.filter((column) => column.columnType === ColumnType.Background).indexOf(this.focusedColumn) === 0\n\t}\n\n\tisForegroundColumnFocused(): boolean {\n\t\treturn this.focusedColumn && this.focusedColumn.columnType === ColumnType.Foreground\n\t}\n\n\tallColumnsVisible(): boolean {\n\t\treturn this._visibleBackgroundColumns.length === this.columns.length\n\t}\n\n\t_attachTouchHandler(element: HTMLElement) {\n\t\tlet lastGestureInfo: GestureInfo | null\n\t\tlet oldGestureInfo: GestureInfo | null\n\t\tlet initialGestureInfo: GestureInfo | null\n\t\tconst VERTICAL = 1\n\t\tconst HORIZONTAL = 2\n\t\tlet directionLock: 0 | 1 | 2 = 0\n\n\t\tconst gestureEnd = (event: any) => {\n\t\t\tconst safeLastGestureInfo = lastGestureInfo\n\t\t\tconst safeOldGestureInfo = oldGestureInfo\n\n\t\t\tif (safeLastGestureInfo && safeOldGestureInfo && !this.allColumnsVisible()) {\n\t\t\t\tconst touch = event.changedTouches[0]\n\t\t\t\tconst mainCol = this._mainColumn._domColumn\n\n\t\t\t\tconst sideCol = this._getSideColDom()\n\n\t\t\t\tif (!mainCol || !sideCol) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst mainColRect = mainCol.getBoundingClientRect()\n\t\t\t\tconst velocity = (safeLastGestureInfo.x - safeOldGestureInfo.x) / (safeLastGestureInfo.time - safeOldGestureInfo.time)\n\n\t\t\t\tconst show = () => {\n\t\t\t\t\tthis.focusedColumn = this.columns[0]\n\t\t\t\t\tthis._busy = this._slideForegroundColumn(this.columns[0], true)\n\t\t\t\t\tthis._isModalBackgroundVisible = true\n\t\t\t\t}\n\n\t\t\t\tconst hide = () => {\n\t\t\t\t\tthis.focusedColumn = this.columns[1]\n\t\t\t\t\tthis._busy = this._slideForegroundColumn(this.columns[0], false)\n\t\t\t\t\tthis._isModalBackgroundVisible = false\n\t\t\t\t}\n\n\t\t\t\t// Gesture for the side column\n\t\t\t\tif (this.getBackgroundColumns()[0].visible || this.focusedColumn.isInForeground) {\n\t\t\t\t\t// Gesture was with enough velocity to show the menu\n\t\t\t\t\tif (velocity > 0.8) {\n\t\t\t\t\t\tshow() // Gesture was with enough velocity to hide the menu and we're not scrolling vertically\n\t\t\t\t\t} else if (velocity < -0.8 && directionLock !== VERTICAL) {\n\t\t\t\t\t\thide()\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Finger was released without much velocity so if it's further than some distance from edge, open menu. Otherwise, close it.\n\t\t\t\t\t\tif (touch.pageX > mainColRect.left + 100) {\n\t\t\t\t\t\t\tshow()\n\t\t\t\t\t\t} else if (directionLock !== VERTICAL) {\n\t\t\t\t\t\t\thide()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Gesture for sliding other columns\n\t\t\t\t\tif ((safeLastGestureInfo.x > window.innerWidth / 3 || velocity > 0.8) && directionLock !== VERTICAL) {\n\t\t\t\t\t\tthis.focusPreviousColumn()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst colRect = this._domSlidingPart.getBoundingClientRect()\n\n\t\t\t\t\t\t// Re-focus the column to reset offset changed by the gesture\n\t\t\t\t\t\tthis._busy = this._slideBackgroundColumns(this.focusedColumn, colRect.left, -this.focusedColumn.offset)\n\t\t\t\t\t\tthis.focus(this.focusedColumn)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis._busy.then(() => m.redraw())\n\t\t\t}\n\n\t\t\t// If this is the first touch and not another one\n\t\t\tif (safeLastGestureInfo && safeLastGestureInfo.identifier === event.changedTouches[0].identifier) {\n\t\t\t\tlastGestureInfo = null\n\t\t\t\toldGestureInfo = null\n\t\t\t\tinitialGestureInfo = null\n\t\t\t\tdirectionLock = 0\n\t\t\t}\n\t\t}\n\n\t\tconst listeners = {\n\t\t\ttouchstart: (event: any) => {\n\t\t\t\tif (lastGestureInfo) {\n\t\t\t\t\t// Already detecting a gesture, ignore second one\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst mainCol = this._mainColumn._domColumn\n\n\t\t\t\tconst sideCol = this._getSideColDom()\n\n\t\t\t\tif (!mainCol || !sideCol || this.allColumnsVisible()) {\n\t\t\t\t\tlastGestureInfo = null\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (event.touches.length === 1 && (this.columns[0].isInForeground || event.touches[0].pageX < 40)) {\n\t\t\t\t\t// Only stop propogation while the menu is not yet fully visible\n\t\t\t\t\tif (!this.columns[0].isInForeground) {\n\t\t\t\t\t\tevent.stopPropagation()\n\t\t\t\t\t}\n\n\t\t\t\t\tlastGestureInfo = initialGestureInfo = gestureInfoFromTouch(event.touches[0])\n\t\t\t\t}\n\t\t\t},\n\t\t\ttouchmove: (event: any) => {\n\t\t\t\tconst sideCol = this._getSideColDom()\n\n\t\t\t\tif (!sideCol || !this._mainColumn || this.allColumnsVisible()) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst gestureInfo = lastGestureInfo\n\t\t\t\tconst safeInitialGestureInfo = initialGestureInfo\n\n\t\t\t\tif (gestureInfo && safeInitialGestureInfo && event.touches.length === 1) {\n\t\t\t\t\tconst touch = event.touches[0]\n\t\t\t\t\tconst newTouchPos = touch.pageX\n\t\t\t\t\tconst sideColRect = sideCol.getBoundingClientRect()\n\t\t\t\t\toldGestureInfo = lastGestureInfo\n\t\t\t\t\tconst safeLastInfo = (lastGestureInfo = gestureInfoFromTouch(touch))\n\n\t\t\t\t\t// If we have horizonal lock or we don't have vertical lock but would like to acquire horizontal one, the lock horizontally\n\t\t\t\t\tif (directionLock === HORIZONTAL || (directionLock !== VERTICAL && Math.abs(safeLastInfo.x - safeInitialGestureInfo.x) > 30)) {\n\t\t\t\t\t\tdirectionLock = HORIZONTAL\n\n\t\t\t\t\t\t// Gesture for side column\n\t\t\t\t\t\tif (this.getBackgroundColumns()[0].visible || this.focusedColumn.isInForeground) {\n\t\t\t\t\t\t\tconst newTranslate = Math.min(sideColRect.left - (gestureInfo.x - newTouchPos), 0)\n\t\t\t\t\t\t\tsideCol.style.transform = `translateX(${newTranslate}px)`\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Gesture for background column\n\t\t\t\t\t\t\tconst slidingDomRect = this._domSlidingPart.getBoundingClientRect()\n\n\t\t\t\t\t\t\t// Do not allow to move column to the left\n\t\t\t\t\t\t\tconst newTranslate = Math.max(slidingDomRect.left - (gestureInfo.x - newTouchPos), -this.focusedColumn.offset)\n\t\t\t\t\t\t\tthis._domSlidingPart.style.transform = `translateX(${newTranslate}px)`\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Scroll events are not cancellable and browsees complain a lot\n\t\t\t\t\t\tif (event.cancelable !== false) event.preventDefault() // If we don't have a vertical lock but we would like to acquire one, get it\n\t\t\t\t\t} else if (directionLock !== VERTICAL && Math.abs(safeLastInfo.y - safeInitialGestureInfo.y) > 30) {\n\t\t\t\t\t\tdirectionLock = VERTICAL\n\t\t\t\t\t}\n\n\t\t\t\t\tevent.stopPropagation()\n\t\t\t\t}\n\t\t\t},\n\t\t\ttouchend: gestureEnd,\n\t\t\ttouchcancel: gestureEnd,\n\t\t}\n\n\t\tfor (let [name, listener] of Object.entries(listeners)) {\n\t\t\telement.addEventListener(name, listener, true)\n\t\t}\n\t}\n}\n","import m, { Children, ClassComponent, Vnode } from \"mithril\"\nimport { NavBar } from \"./base/NavBar.js\"\nimport { NavButton, NavButtonColor } from \"./base/NavButton.js\"\nimport { FeatureType } from \"../api/common/TutanotaConstants.js\"\nimport { BootIcons } from \"./base/icons/BootIcons.js\"\nimport { CALENDAR_PREFIX, CONTACTS_PREFIX, MAIL_PREFIX } from \"../misc/RouteChange.js\"\nimport { assertMainOrNode } from \"../api/common/Env.js\"\nimport { OfflineIndicatorDesktop } from \"./base/OfflineIndicator.js\"\nimport { OfflineIndicatorViewModel } from \"./base/OfflineIndicatorViewModel.js\"\nimport { NewsModel } from \"../misc/news/NewsModel.js\"\nimport { locator } from \"../api/main/MainLocator.js\"\nimport { ProgressBar } from \"./base/ProgressBar.js\"\nimport { DesktopBaseHeader } from \"./base/DesktopBaseHeader.js\"\n\nassertMainOrNode()\n\n/** Attrs that are used by different header components in the app.  */\nexport interface AppHeaderAttrs {\n\tnewsModel: NewsModel\n\tofflineIndicatorModel: OfflineIndicatorViewModel\n}\n\nexport interface HeaderAttrs extends AppHeaderAttrs {\n\trightView?: Children\n\thandleBackPress?: () => boolean\n\t/** search bar, only rendered when NOT using bottom navigation */\n\tsearchBar?: () => Children\n\t/** content in the center of the search bar, where title and offline status normally are */\n\tcenterContent?: () => Children\n}\n\nexport class Header implements ClassComponent<HeaderAttrs> {\n\tview({ attrs }: Vnode<HeaderAttrs>): Children {\n\t\treturn m(DesktopBaseHeader, [m(ProgressBar, { progress: attrs.offlineIndicatorModel.getProgress() }), this.renderNavigation(attrs)])\n\t}\n\n\t/**\n\t * render the search and navigation bar in three-column layouts. if there is a navigation, also render an offline indicator.\n\t * @private\n\t */\n\tprivate renderNavigation(attrs: HeaderAttrs): Children {\n\t\treturn m(\".flex-grow.flex.justify-end.items-center\", [\n\t\t\tattrs.searchBar ? attrs.searchBar() : null,\n\t\t\tm(OfflineIndicatorDesktop, attrs.offlineIndicatorModel.getCurrentAttrs()),\n\t\t\tm(\".nav-bar-spacer\"),\n\t\t\tm(NavBar, this.renderButtons()),\n\t\t])\n\t}\n\n\tprivate renderButtons(): Children {\n\t\t// We assign click listeners to buttons to move focus correctly if the view is already open\n\t\treturn [\n\t\t\tm(NavButton, {\n\t\t\t\tlabel: \"emails_label\",\n\t\t\t\ticon: () => BootIcons.Mail,\n\t\t\t\thref: MAIL_PREFIX,\n\t\t\t\tisSelectedPrefix: MAIL_PREFIX,\n\t\t\t\tcolors: NavButtonColor.Header,\n\t\t\t}),\n\t\t\t// not available for external mailboxes\n\t\t\tlocator.logins.isInternalUserLoggedIn() && !locator.logins.isEnabled(FeatureType.DisableContacts)\n\t\t\t\t? m(NavButton, {\n\t\t\t\t\t\tlabel: \"contacts_label\",\n\t\t\t\t\t\ticon: () => BootIcons.Contacts,\n\t\t\t\t\t\thref: CONTACTS_PREFIX,\n\t\t\t\t\t\tisSelectedPrefix: CONTACTS_PREFIX,\n\t\t\t\t\t\tcolors: NavButtonColor.Header,\n\t\t\t\t  })\n\t\t\t\t: null,\n\t\t\t// not available for external mailboxes\n\t\t\tlocator.logins.isInternalUserLoggedIn() && !locator.logins.isEnabled(FeatureType.DisableCalendar)\n\t\t\t\t? m(NavButton, {\n\t\t\t\t\t\tlabel: \"calendar_label\",\n\t\t\t\t\t\ticon: () => BootIcons.Calendar,\n\t\t\t\t\t\thref: CALENDAR_PREFIX,\n\t\t\t\t\t\tcolors: NavButtonColor.Header,\n\t\t\t\t\t\tclick: () => m.route.get().startsWith(CALENDAR_PREFIX),\n\t\t\t\t  })\n\t\t\t\t: null,\n\t\t]\n\t}\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\n\nexport type ListColumnAttrs = {\n\theaderContent: Children\n}\n\nexport class ListColumnWrapper implements Component<ListColumnAttrs> {\n\tview(vnode: Vnode<ListColumnAttrs>): Children {\n\t\treturn m(\".flex.flex-column.fill-absolute\", [\n\t\t\tvnode.attrs.headerContent ? m(\".flex.flex-column.justify-center.plr-safe-inset\", vnode.attrs.headerContent) : null,\n\t\t\tm(\".rel.flex-grow\", vnode.children),\n\t\t])\n\t}\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport { NewsId } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { NewsListItem } from \"./NewsListItem.js\"\nimport ColumnEmptyMessageBox from \"../../gui/base/ColumnEmptyMessageBox.js\"\nimport { theme } from \"../../gui/theme.js\"\nimport { Icons } from \"../../gui/base/icons/Icons.js\"\n\n/**\n * Renders the user's list of unacknowledged news.\n */\nexport interface NewsListAttrs {\n\tliveNewsListItems: Record<string, NewsListItem>\n\tliveNewsIds: NewsId[]\n}\n\nexport class NewsList implements Component<NewsListAttrs> {\n\tview(vnode: Vnode<NewsListAttrs>): Children {\n\t\tif (vnode.attrs.liveNewsIds.length === 0) {\n\t\t\treturn m(ColumnEmptyMessageBox, {\n\t\t\t\tmessage: \"noNews_msg\",\n\t\t\t\ticon: Icons.Bulb,\n\t\t\t\tcolor: theme.content_message_bg,\n\t\t\t})\n\t\t}\n\n\t\treturn m(\n\t\t\t\"\",\n\t\t\tvnode.attrs.liveNewsIds.map((liveNewsId) => {\n\t\t\t\tconst newsListItem = vnode.attrs.liveNewsListItems[liveNewsId.newsItemName]\n\n\t\t\t\treturn m(\".pt.pl-l.pr-l.flex.fill.border-grey.left.list-border-bottom\", { key: liveNewsId.newsItemId }, newsListItem.render(liveNewsId))\n\t\t\t}),\n\t\t)\n\t}\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport { Button, ButtonColor, ButtonType } from \"../base/Button.js\"\nimport { BootIcons } from \"../base/icons/BootIcons\"\nimport { showSupportDialog, showUpgradeDialog } from \"./NavFunctions\"\nimport { isIOSApp } from \"../../api/common/Env\"\nimport { LogoutUrl, SETTINGS_PREFIX } from \"../../misc/RouteChange\"\nimport { getSafeAreaInsetLeft } from \"../HtmlUtils\"\nimport { Icons } from \"../base/icons/Icons\"\nimport { AriaLandmarks, landmarkAttrs } from \"../AriaUtils\"\nimport { createDropdown } from \"../base/Dropdown.js\"\nimport { keyManager } from \"../../misc/KeyManager\"\nimport { CounterBadge } from \"../base/CounterBadge.js\"\nimport { px, size } from \"../size.js\"\nimport { theme } from \"../theme.js\"\nimport { showNewsDialog } from \"../../misc/news/NewsDialog.js\"\nimport { LoginController } from \"../../api/main/LoginController.js\"\nimport { NewsModel } from \"../../misc/news/NewsModel.js\"\nimport { DesktopSystemFacade } from \"../../native/common/generatedipc/DesktopSystemFacade.js\"\nimport { styles } from \"../styles.js\"\n\nexport interface DrawerMenuAttrs {\n\tlogins: LoginController\n\tnewsModel: NewsModel\n\tdesktopSystemFacade: DesktopSystemFacade | null\n}\n\nexport class DrawerMenu implements Component<DrawerMenuAttrs> {\n\tview(vnode: Vnode<DrawerMenuAttrs>): Children {\n\t\tconst { logins, newsModel, desktopSystemFacade } = vnode.attrs\n\t\tconst liveNewsCount = newsModel.liveNewsIds.length\n\n\t\treturn m(\n\t\t\t\"drawer-menu\",\n\t\t\t{\n\t\t\t\t...landmarkAttrs(AriaLandmarks.Contentinfo, \"drawer menu\"),\n\t\t\t\tstyle: {\n\t\t\t\t\t\"padding-left\": getSafeAreaInsetLeft(),\n\t\t\t\t\t\"border-top-right-radius\": styles.isDesktopLayout() ? px(size.border_radius_big) : \"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tm(\".flex.col.height-100p.items-center.pt.pb\", [\n\t\t\t\tm(\".flex-grow\"),\n\t\t\t\tlogins.isUserLoggedIn()\n\t\t\t\t\t? m(\".news-button\", [\n\t\t\t\t\t\t\tm(Button, {\n\t\t\t\t\t\t\t\ticon: () => Icons.Bulb,\n\t\t\t\t\t\t\t\tlabel: \"news_label\",\n\t\t\t\t\t\t\t\tclick: () => showNewsDialog(newsModel),\n\t\t\t\t\t\t\t\ttype: ButtonType.ActionLarge,\n\t\t\t\t\t\t\t\tcolors: ButtonColor.DrawerNav,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tliveNewsCount > 0\n\t\t\t\t\t\t\t\t? m(CounterBadge, {\n\t\t\t\t\t\t\t\t\t\tcount: liveNewsCount,\n\t\t\t\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\t\t\t\ttop: px(0),\n\t\t\t\t\t\t\t\t\t\t\tright: px(3),\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tcolor: \"white\",\n\t\t\t\t\t\t\t\t\t\tbackground: theme.list_accent_fg,\n\t\t\t\t\t\t\t\t  })\n\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t  ])\n\t\t\t\t\t: null,\n\t\t\t\tlogins.isGlobalAdminUserLoggedIn() && logins.getUserController().isPremiumAccount()\n\t\t\t\t\t? m(Button, {\n\t\t\t\t\t\t\ticon: () => Icons.Gift,\n\t\t\t\t\t\t\tlabel: \"buyGiftCard_label\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tm.route.set(\"/settings/subscription\")\n\t\t\t\t\t\t\t\timport(\"../../subscription/giftcards/PurchaseGiftCardDialog\").then(({ showPurchaseGiftCardDialog }) => {\n\t\t\t\t\t\t\t\t\treturn showPurchaseGiftCardDialog()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ttype: ButtonType.ActionLarge,\n\t\t\t\t\t\t\tcolors: ButtonColor.DrawerNav,\n\t\t\t\t\t  })\n\t\t\t\t\t: null,\n\t\t\t\tdesktopSystemFacade\n\t\t\t\t\t? m(Button, {\n\t\t\t\t\t\t\ticon: () => Icons.NewWindow,\n\t\t\t\t\t\t\tlabel: \"openNewWindow_action\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tdesktopSystemFacade.openNewWindow()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\ttype: ButtonType.ActionLarge,\n\t\t\t\t\t\t\tcolors: ButtonColor.DrawerNav,\n\t\t\t\t\t  })\n\t\t\t\t\t: null,\n\t\t\t\t!isIOSApp() && logins.isUserLoggedIn() && logins.getUserController().isFreeAccount()\n\t\t\t\t\t? m(Button, {\n\t\t\t\t\t\t\ticon: () => BootIcons.Premium,\n\t\t\t\t\t\t\tlabel: \"upgradePremium_label\",\n\t\t\t\t\t\t\tclick: () => showUpgradeDialog(),\n\t\t\t\t\t\t\ttype: ButtonType.ActionLarge,\n\t\t\t\t\t\t\tcolors: ButtonColor.DrawerNav,\n\t\t\t\t\t  })\n\t\t\t\t\t: null,\n\t\t\t\tm(Button, {\n\t\t\t\t\tlabel: \"showHelp_action\",\n\t\t\t\t\ticon: () => BootIcons.Help,\n\t\t\t\t\ttype: ButtonType.ActionLarge,\n\t\t\t\t\tclick: (e, dom) =>\n\t\t\t\t\t\tcreateDropdown({\n\t\t\t\t\t\t\twidth: 300,\n\t\t\t\t\t\t\tlazyButtons: () => [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tlabel: \"supportMenu_label\",\n\t\t\t\t\t\t\t\t\tclick: () => showSupportDialog(logins),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tlabel: \"keyboardShortcuts_title\",\n\t\t\t\t\t\t\t\t\tclick: () => keyManager.openF1Help(true),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t})(e, dom),\n\t\t\t\t\tnoBubble: true,\n\t\t\t\t\tcolors: ButtonColor.DrawerNav,\n\t\t\t\t}),\n\t\t\t\tlogins.isInternalUserLoggedIn()\n\t\t\t\t\t? m(Button, {\n\t\t\t\t\t\t\ticon: () => BootIcons.Settings,\n\t\t\t\t\t\t\tlabel: \"settings_label\",\n\t\t\t\t\t\t\tclick: () => m.route.set(SETTINGS_PREFIX),\n\t\t\t\t\t\t\ttype: ButtonType.ActionLarge,\n\t\t\t\t\t\t\tcolors: ButtonColor.DrawerNav,\n\t\t\t\t\t  })\n\t\t\t\t\t: null,\n\t\t\t\tm(Button, {\n\t\t\t\t\ticon: () => BootIcons.Logout,\n\t\t\t\t\tlabel: \"logout_label\",\n\t\t\t\t\tclick: () => m.route.set(LogoutUrl),\n\t\t\t\t\ttype: ButtonType.ActionLarge,\n\t\t\t\t\tcolors: ButtonColor.DrawerNav,\n\t\t\t\t}),\n\t\t\t]),\n\t\t)\n\t}\n}\n","import { ButtonAttrs, ButtonType } from \"../../gui/base/Button.js\"\nimport m, { Component } from \"mithril\"\nimport { DialogHeaderBar, DialogHeaderBarAttrs } from \"../../gui/base/DialogHeaderBar.js\"\nimport { lang } from \"../LanguageViewModel.js\"\nimport { Dialog, DialogType } from \"../../gui/base/Dialog.js\"\nimport { Keys } from \"../../api/common/TutanotaConstants.js\"\nimport { NewsList } from \"./NewsList.js\"\nimport { NewsModel } from \"./NewsModel.js\"\nimport { progressIcon } from \"../../gui/base/Icon.js\"\nimport { locator } from \"../../api/main/MainLocator.js\"\n\nexport function showNewsDialog(newsModel: NewsModel) {\n\tconst closeButton: ButtonAttrs = {\n\t\tlabel: \"close_alt\",\n\t\ttype: ButtonType.Secondary,\n\t\tclick: () => {\n\t\t\tcloseAction()\n\t\t},\n\t}\n\n\tconst closeAction = () => {\n\t\tdialog.close()\n\t}\n\tconst header: DialogHeaderBarAttrs = {\n\t\tleft: [closeButton],\n\t\tmiddle: () => lang.get(\"news_label\"),\n\t}\n\n\tlet loaded = false\n\tnewsModel.loadNewsIds().then(() => {\n\t\tloaded = true\n\t\tm.redraw()\n\t})\n\n\tconst child: Component = {\n\t\tview: () => {\n\t\t\treturn [\n\t\t\t\tm(\"\", [\n\t\t\t\t\tloaded\n\t\t\t\t\t\t? m(NewsList, {\n\t\t\t\t\t\t\t\tliveNewsIds: newsModel.liveNewsIds,\n\t\t\t\t\t\t\t\tliveNewsListItems: newsModel.liveNewsListItems,\n\t\t\t\t\t\t  })\n\t\t\t\t\t\t: m(\n\t\t\t\t\t\t\t\t\".flex-center.mt-l\",\n\t\t\t\t\t\t\t\tm(\".flex-v-center\", [m(\".full-width.flex-center\", progressIcon()), m(\"p\", lang.getMaybeLazy(\"pleaseWait_msg\"))]),\n\t\t\t\t\t\t  ),\n\t\t\t\t]),\n\t\t\t]\n\t\t},\n\t}\n\n\tconst dialog = new Dialog(DialogType.EditLarge, {\n\t\tview: () => {\n\t\t\treturn m(\"\", [m(\".dialog-header.plr-l\", m(DialogHeaderBar, header)), m(\".dialog-container.scroll\", m(\".fill-absolute\", m(child)))])\n\t\t},\n\t}).addShortcut({\n\t\tkey: Keys.ESC,\n\t\texec: () => {\n\t\t\tcloseAction()\n\t\t},\n\t\thelp: \"close_alt\",\n\t})\n\tdialog.show()\n}\n","import { DrawerMenu, DrawerMenuAttrs } from \"./nav/DrawerMenu.js\"\nimport { theme } from \"./theme.js\"\nimport m, { Children, Component, Vnode } from \"mithril\"\nimport type { TranslationKey } from \"../misc/LanguageViewModel.js\"\nimport { lang } from \"../misc/LanguageViewModel.js\"\nimport { AriaLandmarks, landmarkAttrs } from \"./AriaUtils.js\"\nimport type { clickHandler } from \"./base/GuiUtils.js\"\nimport type { lazy } from \"@tutao/tutanota-utils\"\nimport { FolderColumnHeaderButton } from \"./base/buttons/FolderColumnHeaderButton.js\"\nimport { Button, ButtonType } from \"./base/Button.js\"\n\nexport type Attrs = {\n\t/** Button to be displayed on top of the column*/\n\tbutton: { label: TranslationKey; click: clickHandler; type: ButtonType } | null | undefined\n\tcontent: Children\n\tariaLabel: TranslationKey | lazy<string>\n\tdrawer: DrawerMenuAttrs\n}\n\nexport class FolderColumnView implements Component<Attrs> {\n\tview({ attrs }: Vnode<Attrs>): Children {\n\t\treturn m(\".flex.height-100p.nav-bg\", [\n\t\t\tm(DrawerMenu, attrs.drawer),\n\t\t\tm(\".folder-column.flex-grow.overflow-x-hidden.flex.col\", landmarkAttrs(AriaLandmarks.Navigation, lang.getMaybeLazy(attrs.ariaLabel)), [\n\t\t\t\tthis.renderMainButton(attrs),\n\t\t\t\tm(\n\t\t\t\t\t\".scroll.overflow-x-hidden.flex.col.flex-grow\",\n\t\t\t\t\t{\n\t\t\t\t\t\tonscroll: (e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLElement\n\t\t\t\t\t\t\tif (attrs.button == null || target.scrollTop === 0) {\n\t\t\t\t\t\t\t\ttarget.style.borderTop = \"\"\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttarget.style.borderTop = `1px solid ${theme.content_border}`\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tattrs.content,\n\t\t\t\t),\n\t\t\t]),\n\t\t])\n\t}\n\n\tprivate renderMainButton(attrs: Attrs): Children {\n\t\treturn attrs.button\n\t\t\t? attrs.button.type === ButtonType.FolderColumnHeader\n\t\t\t\t? m(\n\t\t\t\t\t\t\".plr-button-double\",\n\t\t\t\t\t\tm(FolderColumnHeaderButton, {\n\t\t\t\t\t\t\tlabel: attrs.button.label,\n\t\t\t\t\t\t\tclick: attrs.button.click,\n\t\t\t\t\t\t}),\n\t\t\t\t  )\n\t\t\t\t: m(\n\t\t\t\t\t\t\".plr-button-double.mt.mb\",\n\t\t\t\t\t\tm(Button, {\n\t\t\t\t\t\t\ttype: attrs.button.type,\n\t\t\t\t\t\t\tlabel: attrs.button.label,\n\t\t\t\t\t\t\tclick: attrs.button.click,\n\t\t\t\t\t\t}),\n\t\t\t\t  )\n\t\t\t: null\n\t}\n}\n","import m, { Child, Children, Component, Vnode } from \"mithril\"\nimport type { TranslationKey } from \"../misc/LanguageViewModel\"\nimport { lang } from \"../misc/LanguageViewModel\"\nimport { theme } from \"./theme\"\nimport type { lazy } from \"@tutao/tutanota-utils\"\n\nexport type SidebarSectionAttrs = {\n\tname: TranslationKey | lazy<string>\n\tbutton?: Child\n}\n\nexport class SidebarSection implements Component<SidebarSectionAttrs> {\n\tview(vnode: Vnode<SidebarSectionAttrs>): Children {\n\t\tconst { name, button } = vnode.attrs\n\t\tconst content = vnode.children\n\t\treturn m(\n\t\t\t\".sidebar-section.mb\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\tcolor: theme.navigation_button,\n\t\t\t\t},\n\t\t\t},\n\t\t\t[\n\t\t\t\tm(\".folder-row.flex-space-between.plr-button.pt-s.button-height\", [\n\t\t\t\t\tm(\"small.b.align-self-center.text-ellipsis.plr-button\", lang.getMaybeLazy(name).toLocaleUpperCase()),\n\t\t\t\t\tbutton ?? null,\n\t\t\t\t]),\n\t\t\t\tcontent,\n\t\t\t],\n\t\t)\n\t}\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport { NavButton } from \"../base/NavButton.js\"\nimport { size } from \"../size\"\nimport { CALENDAR_PREFIX, CONTACTS_PREFIX, MAIL_PREFIX, SEARCH_PREFIX } from \"../../misc/RouteChange\"\nimport { FeatureType } from \"../../api/common/TutanotaConstants\"\nimport { BootIcons } from \"../base/icons/BootIcons\"\nimport { locator } from \"../../api/main/MainLocator.js\"\n\ntype Attrs = void\nconst fontSize = size.font_size_small\n\nexport class BottomNav implements Component<Attrs> {\n\tview(vnode: Vnode<Attrs>): Children {\n\t\t// Using bottom-nav class too to match it inside media queries like @print, otherwise it's not matched\n\t\treturn m(\"bottom-nav.bottom-nav.flex.items-center.z1\", [\n\t\t\tm(NavButton, {\n\t\t\t\tlabel: \"emails_label\",\n\t\t\t\ticon: () => BootIcons.Mail,\n\t\t\t\thref: MAIL_PREFIX,\n\t\t\t\tvertical: true,\n\t\t\t\tfontSize,\n\t\t\t}),\n\t\t\tlocator.logins.isInternalUserLoggedIn()\n\t\t\t\t? m(NavButton, {\n\t\t\t\t\t\tlabel: \"search_label\",\n\t\t\t\t\t\ticon: () => BootIcons.Search,\n\t\t\t\t\t\thref: m.route.get().startsWith(SEARCH_PREFIX)\n\t\t\t\t\t\t\t? m.route.get()\n\t\t\t\t\t\t\t: m.route.get().startsWith(CONTACTS_PREFIX)\n\t\t\t\t\t\t\t? \"/search/contact\"\n\t\t\t\t\t\t\t: \"/search/mail\",\n\t\t\t\t\t\tisSelectedPrefix: SEARCH_PREFIX,\n\t\t\t\t\t\tvertical: true,\n\t\t\t\t\t\tfontSize,\n\t\t\t\t  })\n\t\t\t\t: null,\n\t\t\tlocator.logins.isInternalUserLoggedIn() && !locator.logins.isEnabled(FeatureType.DisableContacts)\n\t\t\t\t? m(NavButton, {\n\t\t\t\t\t\tlabel: \"contacts_label\",\n\t\t\t\t\t\ticon: () => BootIcons.Contacts,\n\t\t\t\t\t\thref: () => CONTACTS_PREFIX,\n\t\t\t\t\t\tisSelectedPrefix: CONTACTS_PREFIX,\n\t\t\t\t\t\tvertical: true,\n\t\t\t\t\t\tfontSize,\n\t\t\t\t  })\n\t\t\t\t: null,\n\t\t\tlocator.logins.isInternalUserLoggedIn() && !locator.logins.isEnabled(FeatureType.DisableCalendar)\n\t\t\t\t? m(NavButton, {\n\t\t\t\t\t\tlabel: \"calendar_label\",\n\t\t\t\t\t\ticon: () => BootIcons.Calendar,\n\t\t\t\t\t\thref: CALENDAR_PREFIX,\n\t\t\t\t\t\tvertical: true,\n\t\t\t\t\t\tfontSize,\n\t\t\t\t  })\n\t\t\t\t: null,\n\t\t])\n\t}\n}\n","import m, { Children, ClassComponent, Component, CVnode, CVnodeDOM, Vnode, VnodeDOM } from \"mithril\"\nimport type { TranslationKey } from \"../misc/LanguageViewModel\"\nimport { lang } from \"../misc/LanguageViewModel\"\nimport { Icon } from \"./base/Icon\"\nimport { Icons } from \"./base/icons/Icons\"\nimport type { MaybeLazy } from \"@tutao/tutanota-utils\"\nimport { resolveMaybeLazy } from \"@tutao/tutanota-utils\"\n\nexport type ScrollSelectListAttrs<T> = {\n\titems: ReadonlyArray<T>\n\tselectedItem: T | null\n\tonItemSelected: (item: T) => unknown\n\temptyListMessage: MaybeLazy<TranslationKey>\n\twidth: number\n\trenderItem: (item: T) => Children\n\tonItemDoubleClicked: (item: T) => unknown\n}\n\nexport class ScrollSelectList<T> implements ClassComponent<ScrollSelectListAttrs<T>> {\n\tprivate selectedItem: T | null = null\n\n\tview(vnode: CVnode<ScrollSelectListAttrs<T>>): Children {\n\t\tconst a = vnode.attrs\n\t\treturn m(\n\t\t\t\".flex.flex-column.scroll-no-overlay\",\n\t\t\ta.items.length > 0\n\t\t\t\t? a.items.map((item) => this.renderRow(item, vnode))\n\t\t\t\t: m(\".row-selected.text-center.pt\", lang.get(resolveMaybeLazy(a.emptyListMessage))),\n\t\t)\n\t}\n\n\tonupdate(vnode: CVnodeDOM<ScrollSelectListAttrs<T>>) {\n\t\tconst newSelectedItem = vnode.attrs.selectedItem\n\n\t\tif (newSelectedItem !== this.selectedItem) {\n\t\t\tthis._onSelectionChanged(newSelectedItem, vnode.attrs.items, vnode.dom as HTMLElement)\n\t\t\t// Ensures that redraw happens after selected item changed this guarantess that the selected item is focused correctly.\n\t\t\t// Selecting the correct item in the list requires that the (possible filtered) list needs render first and then we\n\t\t\t// can scroll to the new selected item. Therefore we call onSelectionChange in onupdate callback.\n\t\t\tm.redraw()\n\t\t}\n\t}\n\n\trenderRow(item: T, vnode: Vnode<ScrollSelectListAttrs<T>>): Children {\n\t\tconst a = vnode.attrs\n\t\tconst isSelected = a.selectedItem === item\n\t\treturn m(\n\t\t\t\".flex.flex-column.click\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\tmaxWidth: a.width,\n\t\t\t\t},\n\t\t\t},\n\t\t\t[\n\t\t\t\tm(\n\t\t\t\t\t\".flex.template-list-row\" + (isSelected ? \".row-selected\" : \"\"),\n\t\t\t\t\t{\n\t\t\t\t\t\tonclick: (e: MouseEvent) => {\n\t\t\t\t\t\t\ta.onItemSelected(item)\n\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t},\n\t\t\t\t\t\tondblclick: (e: MouseEvent) => {\n\t\t\t\t\t\t\ta.onItemSelected(item)\n\t\t\t\t\t\t\ta.onItemDoubleClicked(item)\n\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t[\n\t\t\t\t\t\ta.renderItem(item),\n\t\t\t\t\t\tisSelected\n\t\t\t\t\t\t\t? m(Icon, {\n\t\t\t\t\t\t\t\t\ticon: Icons.ArrowForward,\n\t\t\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\t\t\tmarginTop: \"auto\",\n\t\t\t\t\t\t\t\t\t\tmarginBottom: \"auto\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t  })\n\t\t\t\t\t\t\t: m(\"\", {\n\t\t\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\t\t\twidth: \"17.1px\",\n\t\t\t\t\t\t\t\t\t\theight: \"16px\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t  }),\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t],\n\t\t)\n\t}\n\n\t_onSelectionChanged(selectedItem: T | null, items: ReadonlyArray<T>, scrollDom: HTMLElement) {\n\t\tthis.selectedItem = selectedItem\n\t\tif (selectedItem != null) {\n\t\t\tconst selectedIndex = items.indexOf(selectedItem)\n\n\t\t\tif (selectedIndex !== -1) {\n\t\t\t\tconst selectedDomElement = scrollDom.children.item(selectedIndex)\n\n\t\t\t\tif (selectedDomElement) {\n\t\t\t\t\tselectedDomElement.scrollIntoView({\n\t\t\t\t\t\tblock: \"nearest\",\n\t\t\t\t\t\tinline: \"nearest\",\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n","import m, { Children, ClassComponent, Vnode } from \"mithril\"\nimport { scrollListDom } from \"./base/GuiUtils.js\"\nimport { px, size } from \"./size.js\"\nimport { windowFacade } from \"../misc/WindowFacade.js\"\n\nconst EntryHeight = 60\n\nexport interface SearchDropDownAttrs<T extends Suggestion> {\n\tsuggestions: ReadonlyArray<T>\n\tselectedSuggestionIndex: number\n\tonSuggestionSelected: (idx: number) => void\n\n\t/** max amount of suggestions that can be visible without scrolling */\n\tmaxHeight: number | null\n}\n\nexport interface Suggestion {\n\tfirstRow: string | null\n\tsecondRow: string\n\tdisplay?: boolean\n}\n\nexport class SearchDropDown<T extends Suggestion> implements ClassComponent<SearchDropDownAttrs<T>> {\n\tprivate domSuggestions!: HTMLElement\n\tprivate keyboardHeight: number = 0\n\n\toncreate() {\n\t\twindowFacade.addKeyboardSizeListener((newSize) => {\n\t\t\t// *-------------------*  -\n\t\t\t// |                   |  |\n\t\t\t// |   -------------   |  - <- top\n\t\t\t// |   |           |   |\n\t\t\t// |   |-----------|   |\n\t\t\t// |-------------------|  - <- keyboardHeight\n\t\t\t// | q w e r t z u i o |  |\n\t\t\t// | a s d f g h j k l |  -\n\t\t\t//\n\t\t\t// On iOS screen is not resized when keyboard is opened. Instead we send a signal to WebView with keyboard height.\n\t\t\tthis.keyboardHeight = newSize\n\t\t})\n\t}\n\n\tview({ attrs }: Vnode<SearchDropDownAttrs<T>>): Children {\n\t\tif (attrs.selectedSuggestionIndex !== attrs.selectedSuggestionIndex && this.domSuggestions) {\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tscrollListDom(this.domSuggestions, EntryHeight, attrs.selectedSuggestionIndex)\n\t\t\t})\n\t\t}\n\n\t\t// We need to calculate how much space can be actually used for the dropdown. We cannot just add margin like we do with dialog\n\t\t// because the suggestions dropdown is absolutely positioned.\n\t\tlet dropdownHeight = EntryHeight * Math.min(attrs.maxHeight ?? Number.MAX_VALUE, attrs.suggestions.length)\n\t\tif (this.domSuggestions) {\n\t\t\tconst top = this.domSuggestions.getBoundingClientRect().top\n\t\t\tconst availableHeight = window.innerHeight - top - this.keyboardHeight - size.vpad\n\t\t\tdropdownHeight = Math.min(availableHeight, dropdownHeight)\n\t\t}\n\n\t\treturn m(\n\t\t\t`.abs.z4.full-width.elevated-bg.scroll.text-ellipsis${attrs.suggestions.length ? \".dropdown-shadow\" : \"\"}`,\n\t\t\t{\n\t\t\t\toncreate: (vnode) => (this.domSuggestions = vnode.dom as HTMLElement),\n\t\t\t\tstyle: {\n\t\t\t\t\ttransition: \"height 0.2s\",\n\t\t\t\t\theight: px(dropdownHeight),\n\t\t\t\t},\n\t\t\t},\n\t\t\tattrs.suggestions.map((suggestion, idx) => this.renderSuggestion(attrs, suggestion, idx)),\n\t\t)\n\t}\n\n\tprivate renderSuggestion(attrs: SearchDropDownAttrs<T>, { firstRow, secondRow, display }: T, idx: number): Children {\n\t\tconst selected = idx === attrs.selectedSuggestionIndex\n\n\t\tif (display !== undefined && !display) {\n\t\t\treturn null\n\t\t}\n\n\t\treturn m(\n\t\t\t\".pt-s.pb-s.click.content-hover\",\n\t\t\t{\n\t\t\t\tclass: selected ? \"content-accent-fg row-selected\" : \"\",\n\t\t\t\tonmousedown: () => attrs.onSuggestionSelected(idx),\n\t\t\t\tstyle: {\n\t\t\t\t\t\"padding-left\": selected ? px(size.hpad_large - 3) : px(size.hpad_large),\n\t\t\t\t\t\"border-left\": selected ? \"3px solid\" : null,\n\t\t\t\t\theight: px(EntryHeight),\n\t\t\t\t},\n\t\t\t},\n\t\t\t[m(\".small.full-width.text-ellipsis\", firstRow), m(\".name.full-width.text-ellipsis\", secondRow)],\n\t\t)\n\t}\n}\n","import m, { Children, ClassComponent, Vnode } from \"mithril\"\nimport { BubbleTextField } from \"./base/BubbleTextField.js\"\nimport { Recipient } from \"../api/common/recipients/Recipient.js\"\nimport { getMailAddressDisplayText } from \"../mail/model/MailUtils.js\"\nimport { px, size } from \"./size.js\"\nimport { progressIcon } from \"./base/Icon.js\"\nimport { lang, TranslationKey } from \"../misc/LanguageViewModel.js\"\nimport { stringToNameAndMailAddress } from \"../misc/parsing/MailAddressParser.js\"\nimport { DropdownChildAttrs } from \"./base/Dropdown.js\"\nimport { Contact } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { RecipientsSearchModel } from \"../misc/RecipientsSearchModel.js\"\nimport { getFirstOrThrow } from \"@tutao/tutanota-utils\"\nimport { Dialog } from \"./base/Dialog.js\"\nimport { SearchDropDown } from \"./SearchDropDown.js\"\nimport { findRecipientWithAddress } from \"../api/common/utils/CommonCalendarUtils.js\"\n\nexport interface MailRecipientsTextFieldAttrs {\n\tlabel: TranslationKey\n\ttext: string\n\tonTextChanged: (text: string) => void\n\trecipients: ReadonlyArray<Recipient>\n\tonRecipientAdded: (address: string, name: string | null, contact: Contact | null) => void\n\tonRecipientRemoved: (address: string) => void\n\tgetRecipientClickedDropdownAttrs?: (address: string) => Promise<DropdownChildAttrs[]>\n\tinjectionsRight?: Children | null\n\tdisabled: boolean\n\tsearch: RecipientsSearchModel\n\t/** Limit the search dropdown to this number of suggestions before starting to scroll */\n\tmaxSuggestionsToShow?: number\n}\n\n/**\n * A component for inputting a list of recipients\n * recipients are represented as bubbles, and a contact search dropdown is shown as the user types\n */\nexport class MailRecipientsTextField implements ClassComponent<MailRecipientsTextFieldAttrs> {\n\t// don't access me directly, use getter and setter\n\tprivate selectedSuggestionIdx = 0\n\tprivate focused = false\n\n\tview({ attrs }: Vnode<MailRecipientsTextFieldAttrs>): Children {\n\t\treturn [this.renderTextField(attrs), this.focused ? this.renderSuggestions(attrs) : null]\n\t}\n\n\tprivate renderTextField(attrs: MailRecipientsTextFieldAttrs): Children {\n\t\treturn m(BubbleTextField, {\n\t\t\tlabel: attrs.label,\n\t\t\ttext: attrs.text,\n\t\t\tonInput: (text) => {\n\t\t\t\tattrs.search.search(text).then(() => m.redraw())\n\n\t\t\t\t// if the new text length is more than one character longer,\n\t\t\t\t// it means the user pasted the text in, so we want to try and resolve a list of contacts\n\t\t\t\tconst { remainingText, newRecipients, errors } = text.length - attrs.text.length > 1 ? parsePastedInput(text) : parseTypedInput(text)\n\n\t\t\t\tfor (const { address, name } of newRecipients) {\n\t\t\t\t\tattrs.onRecipientAdded(address, name, null)\n\t\t\t\t}\n\n\t\t\t\tif (errors.length === 1 && newRecipients.length === 0) {\n\t\t\t\t\t// if there was a single recipient and it was invalid then just pretend nothing happened\n\t\t\t\t\tattrs.onTextChanged(getFirstOrThrow(errors))\n\t\t\t\t} else {\n\t\t\t\t\tif (errors.length > 0) {\n\t\t\t\t\t\tDialog.message(() => `${lang.get(\"invalidPastedRecipients_msg\")}\\n\\n${errors.join(\"\\n\")}`)\n\t\t\t\t\t}\n\t\t\t\t\tattrs.onTextChanged(remainingText)\n\t\t\t\t}\n\t\t\t},\n\t\t\titems: attrs.recipients.map((recipient) => recipient.address),\n\t\t\trenderBubbleText: (address: string) => {\n\t\t\t\tconst name = findRecipientWithAddress(attrs.recipients, address)?.name ?? null\n\t\t\t\treturn getMailAddressDisplayText(name, address, false)\n\t\t\t},\n\t\t\tgetBubbleDropdownAttrs: async (address) => (await attrs.getRecipientClickedDropdownAttrs?.(address)) ?? [],\n\t\t\tonBackspace: () => {\n\t\t\t\tif (attrs.text === \"\" && attrs.recipients.length > 0) {\n\t\t\t\t\tconst { address } = attrs.recipients.slice().pop()!\n\t\t\t\t\tattrs.onTextChanged(address)\n\t\t\t\t\tattrs.onRecipientRemoved(address)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t},\n\t\t\tonEnterKey: () => {\n\t\t\t\tthis.resolveInput(attrs, true)\n\t\t\t\treturn true\n\t\t\t},\n\t\t\tonUpKey: () => {\n\t\t\t\tthis.setSelectedSuggestionIdx(this.getSelectedSuggestionIdx(attrs) + 1)\n\t\t\t\treturn false\n\t\t\t},\n\t\t\tonDownKey: () => {\n\t\t\t\tthis.setSelectedSuggestionIdx(this.getSelectedSuggestionIdx(attrs) - 1)\n\t\t\t\treturn false\n\t\t\t},\n\t\t\tonFocus: () => {\n\t\t\t\tthis.focused = true\n\t\t\t},\n\t\t\tonBlur: () => {\n\t\t\t\tthis.focused = false\n\t\t\t\tthis.resolveInput(attrs, false)\n\t\t\t\treturn true\n\t\t\t},\n\t\t\tdisabled: attrs.disabled,\n\t\t\tinjectionsRight: m(\".flex.items-center\", [\n\t\t\t\t// Placeholder element for the suggestion progress icon with a fixed width and height to avoid flickering.\n\t\t\t\t// when reaching the end of the input line and when entering a text into the second line.\n\t\t\t\tm(\n\t\t\t\t\t\".flex.align-right.mr-s.flex.items-end.pb-s\",\n\t\t\t\t\t{\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\twidth: px(20), // in case the progress icon is not shown we reserve the width of the progress icon\n\t\t\t\t\t\t\theight: px(size.button_height_compact),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tattrs.search.isLoading() ? progressIcon() : null,\n\t\t\t\t),\n\t\t\t\tattrs.injectionsRight,\n\t\t\t]),\n\t\t})\n\t}\n\n\tprivate renderSuggestions(attrs: MailRecipientsTextFieldAttrs): Children {\n\t\treturn m(\n\t\t\t\".rel\",\n\t\t\tm(SearchDropDown, {\n\t\t\t\tsuggestions: attrs.search.results().map((recipient) => {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tfirstRow: recipient.name,\n\t\t\t\t\t\tsecondRow: recipient.address,\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t\tselectedSuggestionIndex: this.getSelectedSuggestionIdx(attrs),\n\t\t\t\tonSuggestionSelected: (idx) => this.selectSuggestion(attrs, idx),\n\t\t\t\tmaxHeight: attrs.maxSuggestionsToShow ?? null,\n\t\t\t}),\n\t\t)\n\t}\n\n\t/**\n\t * Resolves a typed in mail address or one of the suggested ones.\n\t * @param selectSuggestion boolean value indicating whether a suggestion should be selected or not. Should be true if a suggestion is explicitly selected by\n\t * for example hitting the enter key and false e.g. if the dialog is closed\n\t */\n\tprivate resolveInput(attrs: MailRecipientsTextFieldAttrs, selectSuggestion: boolean) {\n\t\tconst suggestions = attrs.search.results()\n\t\tif (suggestions.length > 0 && selectSuggestion) {\n\t\t\tthis.selectSuggestion(attrs, this.getSelectedSuggestionIdx(attrs))\n\t\t} else {\n\t\t\tconst parsed = parseMailAddress(attrs.text)\n\t\t\tif (parsed != null) {\n\t\t\t\tattrs.onRecipientAdded(parsed.address, parsed.name, null)\n\t\t\t\tattrs.onTextChanged(\"\")\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate selectSuggestion(attrs: MailRecipientsTextFieldAttrs, index: number) {\n\t\tconst selection = attrs.search.results()[index]\n\t\tif (selection == null) {\n\t\t\treturn\n\t\t}\n\n\t\tconst { address, name, contact } = selection\n\t\tattrs.onRecipientAdded(address, name, contact)\n\t\tattrs.search.clear()\n\t\tattrs.onTextChanged(\"\")\n\t}\n\n\tprivate getSelectedSuggestionIdx(attrs: MailRecipientsTextFieldAttrs): number {\n\t\treturn Math.min(Math.max(this.selectedSuggestionIdx, 0), attrs.search.results().length - 1)\n\t}\n\n\tprivate setSelectedSuggestionIdx(idx: number) {\n\t\tthis.selectedSuggestionIdx = idx\n\t}\n}\n\ninterface ParsedInput {\n\tremainingText: string\n\tnewRecipients: Array<{ address: string; name: string | null }>\n\terrors: Array<string>\n}\n\n/**\n * Parse a list of valid mail addresses separated by either a semicolon or a comma.\n * Invalid addresses will be returned as a separate list\n */\nfunction parsePastedInput(text: string): ParsedInput {\n\tconst separator = text.indexOf(\";\") !== -1 ? \";\" : \",\"\n\tconst textParts = text.split(separator).map((part) => part.trim())\n\n\tconst result: ParsedInput = {\n\t\tremainingText: \"\",\n\t\tnewRecipients: [],\n\t\terrors: [],\n\t}\n\n\tfor (let part of textParts) {\n\t\tpart = part.trim()\n\n\t\tif (part.length !== 0) {\n\t\t\tconst parsed = parseMailAddress(part)\n\n\t\t\tif (!parsed) {\n\t\t\t\tresult.errors.push(part)\n\t\t\t} else {\n\t\t\t\tresult.newRecipients.push(parsed)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result\n}\n\n/**\n * Parse text when it is typed by the user\n * When the final character is an expected delimiter (';', ',', ' '),\n * then we attempt to parse the preceding text. If it is a valid mail address,\n * it is successfully parsed\n * invalid input gets returned in `remainingText`, `errors` is always empty\n * @param text\n */\nfunction parseTypedInput(text: string): ParsedInput {\n\tconst lastCharacter = text.slice(-1)\n\n\t// on semicolon, comman or space we want to try to resolve the input text\n\tif (lastCharacter === \";\" || lastCharacter === \",\" || lastCharacter === \" \") {\n\t\tconst textMinusLast = text.slice(0, -1)\n\n\t\tconst result = parseMailAddress(textMinusLast)\n\t\tconst remainingText = result != null ? \"\" : textMinusLast\n\n\t\treturn {\n\t\t\tremainingText,\n\t\t\tnewRecipients: result ? [result] : [],\n\t\t\terrors: [],\n\t\t}\n\t} else {\n\t\treturn {\n\t\t\tremainingText: text,\n\t\t\tnewRecipients: [],\n\t\t\terrors: [],\n\t\t}\n\t}\n}\n\nfunction parseMailAddress(text: string): { address: string; name: string | null } | null {\n\ttext = text.trim()\n\n\tif (text === \"\") return null\n\n\tconst nameAndMailAddress = stringToNameAndMailAddress(text)\n\n\tif (nameAndMailAddress) {\n\t\tconst name = nameAndMailAddress.name ? nameAndMailAddress.name : null\n\n\t\treturn { name, address: nameAndMailAddress.mailAddress }\n\t} else {\n\t\treturn null\n\t}\n}\n","import m, { Children, Component, Vnode, VnodeDOM } from \"mithril\"\nimport { Attachment } from \"../mail/editor/SendMailModel.js\"\nimport { Button, ButtonType } from \"./base/Button.js\"\nimport { Icons } from \"./base/icons/Icons.js\"\nimport { formatStorageSize } from \"../misc/Formatter.js\"\nimport { defer, DeferredObject, Thunk } from \"@tutao/tutanota-utils\"\nimport { modal, ModalComponent } from \"./base/Modal.js\"\nimport { focusNext, focusPrevious, Shortcut } from \"../misc/KeyManager.js\"\nimport { PosRect } from \"./base/Dropdown.js\"\nimport { Keys } from \"../api/common/TutanotaConstants.js\"\nimport { px } from \"./size.js\"\nimport { Icon } from \"./base/Icon.js\"\nimport { theme } from \"./theme.js\"\nimport { animations, height, opacity, transform, TransformEnum, width } from \"./animation/Animations.js\"\nimport { ease } from \"./animation/Easing.js\"\nimport { getFileBaseName, getFileExtension } from \"../api/common/utils/FileUtils.js\"\nimport { getSafeAreaInsetBottom } from \"./HtmlUtils.js\"\n\nexport type AttachmentBubbleAttrs = {\n\tattachment: Attachment\n\tdownload: Thunk | null\n\topen: Thunk | null\n\tremove: Thunk | null\n}\n\nexport class AttachmentBubble implements Component<AttachmentBubbleAttrs> {\n\tprivate dom: HTMLElement | null = null\n\n\tview(vnode: Vnode<AttachmentBubbleAttrs>): Children {\n\t\tconst { attachment } = vnode.attrs\n\t\tconst extension = getFileExtension(attachment.name)\n\t\tconst rest = getFileBaseName(attachment.name)\n\t\treturn m(Button, {\n\t\t\tlabel: () => rest,\n\t\t\ttitle: () => attachment.name,\n\t\t\ticon: () => Icons.Attachment,\n\t\t\ttype: ButtonType.Bubble,\n\t\t\tstaticRightText: `${extension}, ${formatStorageSize(Number(attachment.size))}`,\n\t\t\tclick: async () => {\n\t\t\t\tawait showAttachmentDetailsPopup(this.dom!, vnode.attrs)\n\t\t\t\tthis.dom?.focus()\n\t\t\t},\n\t\t})\n\t}\n\n\toncreate(vnode: VnodeDOM<AttachmentBubbleAttrs>): void {\n\t\tthis.dom = vnode.dom as HTMLElement\n\t}\n}\n\nasync function showAttachmentDetailsPopup(dom: HTMLElement, attrs: AttachmentBubbleAttrs): Promise<void> {\n\tconst parentRect = dom.getBoundingClientRect()\n\tconst panel = new AttachmentDetailsPopup(parentRect, parentRect.width, attrs)\n\tpanel.show()\n\treturn panel.deferAfterClose\n}\n\nexport class AttachmentDetailsPopup implements ModalComponent {\n\tprivate readonly _shortcuts: Array<Shortcut> = []\n\tprivate domContent: HTMLElement | null = null\n\tprivate domPanel: HTMLElement | null = null\n\tprivate focusedFirst: boolean = false\n\tprivate closeDefer: DeferredObject<void> = defer()\n\n\tget deferAfterClose(): Promise<void> {\n\t\treturn this.closeDefer.promise\n\t}\n\n\tconstructor(private readonly targetRect: PosRect, private readonly targetWidth: number, private readonly attrs: AttachmentBubbleAttrs) {\n\t\tthis._shortcuts.push({\n\t\t\tkey: Keys.ESC,\n\t\t\texec: () => this.onClose(),\n\t\t\thelp: \"close_alt\",\n\t\t})\n\t\tthis._shortcuts.push({\n\t\t\tkey: Keys.TAB,\n\t\t\tshift: true,\n\t\t\texec: () => (this.domContent ? focusPrevious(this.domContent) : false),\n\t\t\thelp: \"selectPrevious_action\",\n\t\t})\n\t\tthis._shortcuts.push({\n\t\t\tkey: Keys.TAB,\n\t\t\tshift: false,\n\t\t\texec: () => (this.domContent ? focusNext(this.domContent) : false),\n\t\t\thelp: \"selectNext_action\",\n\t\t})\n\t\tif (attrs.open) {\n\t\t\tthis._shortcuts.push({\n\t\t\t\tkey: Keys.O,\n\t\t\t\texec: () => this.thenClose(attrs.open),\n\t\t\t\thelp: \"open_action\",\n\t\t\t})\n\t\t}\n\t\tif (attrs.download) {\n\t\t\tthis._shortcuts.push({\n\t\t\t\tkey: Keys.D,\n\t\t\t\texec: () => this.thenClose(attrs.download),\n\t\t\t\thelp: \"download_action\",\n\t\t\t})\n\t\t}\n\t\tif (attrs.remove) {\n\t\t\tthis._shortcuts.push({\n\t\t\t\tkey: Keys.DELETE,\n\t\t\t\texec: () => this.thenClose(attrs.remove),\n\t\t\t\thelp: \"remove_action\",\n\t\t\t})\n\t\t}\n\t\tthis.view = this.view.bind(this)\n\t}\n\n\tview(): Children {\n\t\treturn m(\n\t\t\t\".abs.bubble-color.plr-button.border-radius.overflow-hidden.flex.flex-column\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\twidth: px(this.targetWidth),\n\t\t\t\t\t// see hack description below\n\t\t\t\t\topacity: \"0\",\n\t\t\t\t},\n\t\t\t\toncreate: (vnode) => {\n\t\t\t\t\tthis.domPanel = vnode.dom as HTMLElement\n\t\t\t\t\t// This is a hack to get \"natural\" view size but render it without opacity first and then show the panel with inferred size.\n\t\t\t\t\t// also focus the first tabbable element in the content after the panel opens.\n\t\t\t\t\tsetTimeout(() => this.animatePanel().then(() => this.domContent && focusNext(this.domContent)), 24)\n\t\t\t\t},\n\t\t\t\tonclick: () => this.onClose(),\n\t\t\t},\n\t\t\tthis.renderContent(),\n\t\t)\n\t}\n\n\tprivate renderContent(): Children {\n\t\tconst { remove, open, download, attachment } = this.attrs\n\t\treturn m(\n\t\t\t\".flex.row.mb-s.pr\",\n\t\t\t{\n\t\t\t\toncreate: (vnode) => (this.domContent = vnode.dom as HTMLElement),\n\t\t\t},\n\t\t\t[\n\t\t\t\tm(Icon, {\n\t\t\t\t\ticon: Icons.Attachment,\n\t\t\t\t\tclass: \"pr-s\",\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tfill: theme.button_bubble_fg,\n\t\t\t\t\t\t\"background-color\": \"initial\",\n\t\t\t\t\t\tmarginTop: \"6px\",\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tm(\".flex.col.flex-grow\", [\n\t\t\t\t\tm(\n\t\t\t\t\t\t\".mb.break-all.smaller\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\tmarginTop: \"5px\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tattachment.name,\n\t\t\t\t\t),\n\t\t\t\t\tm(\".flex.row.justify-between.items-center.flex-grow\", [\n\t\t\t\t\t\tm(\"span.smaller\", `${formatStorageSize(Number(attachment.size))}`),\n\t\t\t\t\t\tm(\".no-wrap\", [\n\t\t\t\t\t\t\tremove ? m(Button, { type: ButtonType.Secondary, label: \"remove_action\", click: () => this.thenClose(remove) }) : null,\n\t\t\t\t\t\t\topen ? m(Button, { type: ButtonType.Secondary, label: \"open_action\", click: () => this.thenClose(open) }) : null,\n\t\t\t\t\t\t\tdownload ? m(Button, { type: ButtonType.Secondary, label: \"download_action\", click: () => this.thenClose(download) }) : null,\n\t\t\t\t\t\t]),\n\t\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t],\n\t\t)\n\t}\n\n\tprivate thenClose(action: Thunk | null): void {\n\t\taction?.()\n\t\tthis.onClose()\n\t}\n\n\tprivate async animatePanel(): Promise<void> {\n\t\tconst { targetRect, domPanel, domContent } = this\n\t\tif (domPanel == null || domContent == null) return\n\t\t// from .bubble class\n\t\tconst initialHeight = 30\n\t\tconst targetHeight = domContent.offsetHeight\n\t\t// for very short attachment bubbles, we need to set a min width so the buttons fit.\n\t\tconst targetWidth = Math.max(targetRect.width, 300)\n\t\tdomPanel.style.width = px(targetRect.width)\n\t\tdomPanel.style.height = px(initialHeight)\n\t\t// add half the difference between .button height of 44px and 30px for pixel-perfect positioning\n\t\tdomPanel.style.top = px(targetRect.top + 7)\n\t\tdomPanel.style.left = px(targetRect.left)\n\n\t\tconst mutations = [opacity(0, 1, true), height(initialHeight, targetHeight)]\n\t\tif (targetRect.width !== targetWidth) {\n\t\t\tmutations.push(width(targetRect.width, targetWidth))\n\t\t}\n\t\t// space below the panel after it fully extends minus a bit.\n\t\tconst spaceBelow = window.innerHeight - getSafeAreaInsetBottom() - targetRect.top - targetHeight - initialHeight\n\t\tif (spaceBelow < 0) {\n\t\t\tmutations.push(transform(TransformEnum.TranslateY, 0, spaceBelow))\n\t\t}\n\n\t\tawait animations.add(domPanel, mutations, {\n\t\t\teasing: ease.out,\n\t\t})\n\t}\n\n\tshow() {\n\t\tmodal.displayUnique(this, true)\n\t}\n\n\tbackgroundClick(e: MouseEvent): void {\n\t\tmodal.remove(this)\n\t}\n\n\tasync hideAnimation(): Promise<void> {\n\t\tif (this.domPanel == null) return\n\t\tconst startHeight = this.domPanel.offsetHeight\n\t\tconst startWidth = this.domPanel.offsetWidth\n\t\tawait animations.add(this.domPanel, [height(startHeight, 30), width(startWidth, this.targetWidth), opacity(1, 0, false)], {\n\t\t\teasing: ease.out,\n\t\t})\n\t}\n\n\tonClose(): void {\n\t\tmodal.remove(this)\n\t\tthis.closeDefer.resolve()\n\t}\n\n\tshortcuts(): Shortcut[] {\n\t\treturn this._shortcuts\n\t}\n\n\tpopState(e: Event): boolean {\n\t\tmodal.remove(this)\n\t\treturn false\n\t}\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport { styles } from \"./styles.js\"\n\nexport interface BackgroundColumnLayoutAttrs {\n\tmobileHeader: () => Children\n\tdesktopToolbar: () => Children\n\tcolumnLayout: Children\n\tbackgroundColor?: string\n}\n\n/**\n * A layout component that organizes the column.\n * Renders a frame for the layout and either mobile header or desktop toolbar.\n */\nexport class BackgroundColumnLayout implements Component<BackgroundColumnLayoutAttrs> {\n\tview({ attrs }: Vnode<BackgroundColumnLayoutAttrs>): Children {\n\t\treturn m(\n\t\t\t\".list-column.flex.col.fill-absolute\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\tbackgroundColor: attrs.backgroundColor,\n\t\t\t\t},\n\t\t\t},\n\t\t\t[styles.isUsingBottomNavigation() ? attrs.mobileHeader() : attrs.desktopToolbar(), m(\".flex-grow.rel\", attrs.columnLayout)],\n\t\t)\n\t}\n}\n","import { pureComponent } from \"./base/PureComponent.js\"\nimport m from \"mithril\"\nimport { px, size } from \"./size.js\"\nimport { responsiveCardHMargin } from \"./cards.js\"\n\n/** Toolbar layout that is used in the second/list column. */\nexport const DesktopListToolbar = pureComponent((__, children) => {\n\treturn m(\n\t\t\".flex.pt-xs.pb-xs.items-center.list-bg\",\n\t\t{\n\t\t\tstyle: {\n\t\t\t\t\"border-radius\": `${size.border_radius}px 0 0 ${size.border_radius}px`,\n\t\t\t\t// matching the list\n\t\t\t\tmarginLeft: `5px`,\n\t\t\t\tmarginBottom: px(size.hpad_large),\n\t\t\t},\n\t\t},\n\t\tchildren,\n\t)\n})\n\n/** Toolbar layout that is used in the third/viewer column. */\nexport const DesktopViewerToolbar = pureComponent((__, children) => {\n\t// The viewer below will (or at least should) reserve some space for the scrollbar. To match that we put `scrollbar-gutter: stable` and for it to have\n\t// effect we put `overflow-y: hidden`.\n\t//\n\t// The outer wrapper will give us margins and padding for the scrollbar and the inner one will actually have to visible background\n\t//\n\t// see comment for .scrollbar-gutter-stable-or-fallback\n\treturn m(\n\t\t\".scrollbar-gutter-stable-or-fallback.overflow-y-hidden\",\n\t\t{\n\t\t\tclass: responsiveCardHMargin(),\n\t\t\tstyle: {\n\t\t\t\tmarginLeft: 0,\n\t\t\t\tmarginBottom: px(size.hpad_large),\n\t\t\t},\n\t\t},\n\t\tm(\n\t\t\t\".flex.list-bg.pt-xs.pb-xs.plr-m\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\t\"border-radius\": `0 ${size.border_radius_big}px ${size.border_radius_big}px 0`,\n\t\t\t\t},\n\t\t\t},\n\t\t\t[\n\t\t\t\t// Height keeps the toolbar showing for consistency, even if there are no actions\n\t\t\t\tm(\".flex-grow\", { style: { height: px(size.button_height) } }),\n\t\t\t\tchildren,\n\t\t\t],\n\t\t),\n\t)\n})\n","import { pureComponent } from \"./base/PureComponent.js\"\nimport m from \"mithril\"\nimport { lang } from \"../misc/LanguageViewModel.js\"\n\nexport interface SelectAllCheckboxAttrs {\n\tselectAll: () => unknown\n\tselectNone: () => unknown\n\tselected: boolean\n}\n\nexport const SelectAllCheckbox = pureComponent((attrs: SelectAllCheckboxAttrs) => {\n\treturn m(\n\t\t\".flex.items-center.pl-s.mlr.button-height\",\n\t\tm(\"input.checkbox\", {\n\t\t\ttype: \"checkbox\",\n\t\t\ttitle: lang.get(\"selectAllLoaded_action\"),\n\t\t\t// I'm not sure this is the best condition but it will do for now\n\t\t\tchecked: attrs.selected,\n\t\t\tonchange: ({ target }: Event) => toggleSelectAll(attrs, (target as HTMLInputElement).checked),\n\t\t}),\n\t)\n})\n\nfunction toggleSelectAll(attrs: SelectAllCheckboxAttrs, selectAll: boolean): void {\n\tif (selectAll) {\n\t\tattrs.selectAll()\n\t} else {\n\t\tattrs.selectNone()\n\t}\n}\n","import { pureComponent } from \"./base/PureComponent.js\"\nimport m from \"mithril\"\n\n/** A layout that is used instead of bottom navigation in some situations. */\nexport const MobileBottomActionBar = pureComponent((_, children) => {\n\treturn m(\".bottom-nav.flex.items-center.plr-l.justify-between\", children)\n})\n","import { pureComponent } from \"./base/PureComponent.js\"\nimport m, { Children } from \"mithril\"\nimport { px, size } from \"./size.js\"\n\nexport interface BaseMobileHeaderAttrs {\n\tleft?: Children\n\tcenter?: Children\n\tright?: Children\n\tinjections?: Children\n}\n\n/**\n * A base component that should be used for all mobile headers.\n */\nexport const BaseMobileHeader = pureComponent(({ left, center, right, injections }: BaseMobileHeaderAttrs) => {\n\treturn m(\n\t\t\".flex.items-center.rel.button-height.mt-safe-inset.plr-safe-inset\",\n\t\t{\n\t\t\tstyle: {\n\t\t\t\theight: px(size.navbar_height_mobile),\n\t\t\t},\n\t\t},\n\t\t[\n\t\t\tleft ?? null,\n\t\t\t// normally min-width: is 0 but inside flex it's auto and we need to teach it how to shrink\n\t\t\tm(\".flex-grow.flex.items-center.min-width-0\", center ?? null),\n\t\t\tright ?? null,\n\t\t\tinjections ?? null,\n\t\t],\n\t)\n})\n","import { pureComponent } from \"./base/PureComponent.js\"\nimport m, { Children, Component, Vnode } from \"mithril\"\nimport { NBSP } from \"@tutao/tutanota-utils\"\nimport { AppHeaderAttrs } from \"./Header.js\"\nimport { BaseMobileHeader } from \"./BaseMobileHeader.js\"\nimport { IconButton } from \"./base/IconButton.js\"\nimport { BootIcons } from \"./base/icons/BootIcons.js\"\nimport { styles } from \"./styles.js\"\nimport { OfflineIndicatorMobile } from \"./base/OfflineIndicator.js\"\nimport { ProgressBar } from \"./base/ProgressBar.js\"\nimport { CounterBadge } from \"./base/CounterBadge.js\"\nimport { px } from \"./size.js\"\nimport { theme } from \"./theme.js\"\nimport { NewsModel } from \"../misc/news/NewsModel.js\"\n\nexport interface MobileHeaderAttrs extends AppHeaderAttrs {\n\tcolumnType: \"first\" | \"other\"\n\t/** Actions that should be displayed on the opposite side of menu/back button. */\n\tactions: Children\n\t/** Like actions that are only supposed to be displayed in multi-column layout */\n\tmulticolumnActions?: () => Children\n\t/**\n\t * An action that is displayed in the corner of the screen opposite of menu/back button, will be displayed in any column in single column layout or\n\t * in the second column in two column layout.\n\t */\n\tprimaryAction: () => Children\n\ttitle?: string\n\tbackAction: () => unknown\n}\n\n/**\n * A component that renders a \"standard\" mobile header. It has menu/back button with offline indicator, title and online status, sync progress and some\n * actions.\n *\n * It is intended to be used in both the first (\"list\") and the second (\"viewer\") columns. It will automatically figure whether it should display menu/back\n * button, title and actions.\n */\nexport class MobileHeader implements Component<MobileHeaderAttrs> {\n\tview({ attrs }: Vnode<MobileHeaderAttrs>): Children {\n\t\tconst firstVisibleColumn = attrs.columnType === \"first\" || styles.isSingleColumnLayout()\n\t\treturn m(BaseMobileHeader, {\n\t\t\tleft:\n\t\t\t\tattrs.columnType === \"first\"\n\t\t\t\t\t? m(MobileHeaderMenuButton, { newsModel: attrs.newsModel, backAction: attrs.backAction })\n\t\t\t\t\t: styles.isSingleColumnLayout()\n\t\t\t\t\t? m(IconButton, {\n\t\t\t\t\t\t\ttitle: \"back_action\",\n\t\t\t\t\t\t\ticon: BootIcons.Back,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tattrs.backAction()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t  })\n\t\t\t\t\t: null,\n\t\t\tcenter: firstVisibleColumn\n\t\t\t\t? m(MobileHeaderTitle, {\n\t\t\t\t\t\ttitle: attrs.title,\n\t\t\t\t\t\tbottom: m(OfflineIndicatorMobile, attrs.offlineIndicatorModel.getCurrentAttrs()),\n\t\t\t\t  })\n\t\t\t\t: null,\n\t\t\tright: [\n\t\t\t\tstyles.isSingleColumnLayout() ? null : attrs.multicolumnActions?.(),\n\t\t\t\tattrs.actions,\n\t\t\t\tstyles.isSingleColumnLayout() || attrs.columnType === \"other\" ? attrs.primaryAction() : null,\n\t\t\t],\n\t\t\tinjections: firstVisibleColumn ? m(ProgressBar, { progress: attrs.offlineIndicatorModel.getProgress() }) : null,\n\t\t})\n\t}\n}\n\nexport const MobileHeaderTitle = pureComponent(({ title, bottom }: { title?: string; bottom: Children }) => {\n\t// normally min-width: is 0 but inside flex it's auto and we need to teach it how to shrink\n\t// align-self: stretch restrict the child to the parent width\n\t// text-ellipsis already sets min-width to 0\n\treturn m(\".flex.col.items-start.min-width-0\", [m(\".font-weight-600.text-ellipsis.align-self-stretch\", title ?? NBSP), bottom])\n})\n\nexport const MobileHeaderMenuButton = pureComponent(({ newsModel, backAction }: { newsModel: NewsModel; backAction: () => unknown }) => {\n\treturn m(\".rel\", [\n\t\tm(IconButton, {\n\t\t\ttitle: \"menu_label\",\n\t\t\ticon: BootIcons.MoreVertical,\n\t\t\tclick: () => {\n\t\t\t\tbackAction()\n\t\t\t},\n\t\t}),\n\t\tm(CounterBadge, {\n\t\t\tcount: newsModel.liveNewsIds.length,\n\t\t\tposition: {\n\t\t\t\ttop: px(4),\n\t\t\t\tright: px(5),\n\t\t\t},\n\t\t\tcolor: \"white\",\n\t\t\tbackground: theme.list_accent_fg,\n\t\t}),\n\t])\n})\n","import m, { Children, ClassComponent, Vnode } from \"mithril\"\nimport type { SearchBar, SearchBarAttrs } from \"../search/SearchBar.js\"\nimport { LazyLoaded } from \"@tutao/tutanota-utils\"\n\n/**\n * Lazy wrapper around SearchBar which unfortunately resides in the search chunk right now and cannot be imported from some files.\n *\n * Ideally this would be a generic component but it's not simple to implement.\n */\nexport class LazySearchBar implements ClassComponent<SearchBarAttrs> {\n\tprivate static searchBar: LazyLoaded<SearchBar> = new LazyLoaded(async () => {\n\t\tconst { searchBar } = await import(\"../search/SearchBar.js\")\n\t\tm.redraw()\n\t\treturn searchBar\n\t})\n\n\toninit(vnode: Vnode<SearchBarAttrs, this>): any {\n\t\tLazySearchBar.searchBar.load()\n\t}\n\n\tview(vnode: Vnode<SearchBarAttrs, this>): Children | void | null {\n\t\tconst searchBar = LazySearchBar.searchBar.getSync()\n\t\tif (searchBar) {\n\t\t\treturn m(searchBar, vnode.attrs)\n\t\t} else {\n\t\t\treturn null\n\t\t}\n\t}\n}\n","import { pureComponent } from \"./base/PureComponent.js\"\nimport { SelectAllCheckbox, SelectAllCheckboxAttrs } from \"./SelectAllCheckbox.js\"\nimport m from \"mithril\"\nimport { BaseMobileHeader } from \"./BaseMobileHeader.js\"\nimport { IconButton } from \"./base/IconButton.js\"\nimport { Icons } from \"./base/icons/Icons.js\"\n\ntype MultiselectMobileHeaderAttrs = SelectAllCheckboxAttrs & {\n\tmessage: string\n}\n/** A special header that is used for multiselect state on mobile. */\nexport const MultiselectMobileHeader = pureComponent((attrs: MultiselectMobileHeaderAttrs) => {\n\tconst { selectAll, selectNone, selected, message } = attrs\n\treturn m(BaseMobileHeader, {\n\t\tleft: m(SelectAllCheckbox, { selectNone, selectAll, selected }),\n\t\tcenter: m(\".font-weight-600\", message),\n\t\tright: m(IconButton, {\n\t\t\ticon: Icons.Cancel,\n\t\t\ttitle: \"cancel_action\",\n\t\t\tclick: () => selectNone(),\n\t\t}),\n\t})\n})\n","import { pureComponent } from \"./base/PureComponent.js\"\nimport m from \"mithril\"\nimport { IconButton } from \"./base/IconButton.js\"\nimport { Icons } from \"./base/icons/Icons.js\"\n\nexport const EnterMultiselectIconButton = pureComponent(({ clickAction }: { clickAction: () => unknown }) =>\n\tm(IconButton, {\n\t\ticon: Icons.AddCheckCirle,\n\t\ttitle: \"selectMultiple_action\",\n\t\tclick: () => {\n\t\t\tclickAction()\n\t\t},\n\t}),\n)\n","import { CredentialEncryptionMode } from \"../../misc/credentials/CredentialEncryptionMode\"\nimport { Dialog, DialogType } from \"../base/Dialog\"\nimport type { CredentialsProvider } from \"../../misc/credentials/CredentialsProvider.js\"\nimport m, { Children, Component, Vnode } from \"mithril\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport { theme } from \"../theme\"\nimport { DialogHeaderBar } from \"../base/DialogHeaderBar\"\nimport type { RadioSelectorOption } from \"../base/RadioSelector\"\nimport { RadioSelector } from \"../base/RadioSelector\"\nimport { ButtonType } from \"../base/Button.js\"\nimport { CredentialAuthenticationError } from \"../../api/common/error/CredentialAuthenticationError\"\nimport { KeyPermanentlyInvalidatedError } from \"../../api/common/error/KeyPermanentlyInvalidatedError\"\nimport { liveDataAttrs } from \"../AriaUtils\"\nimport type { DeferredObject } from \"@tutao/tutanota-utils\"\nimport { defer } from \"@tutao/tutanota-utils\"\nimport { windowFacade } from \"../../misc/WindowFacade\"\n\nconst DEFAULT_MODE = CredentialEncryptionMode.DEVICE_LOCK\n\nexport async function showCredentialsEncryptionModeDialog(credentialsProvider: CredentialsProvider) {\n\tawait CredentialEncryptionMethodDialog.showAndWaitForSelection(credentialsProvider)\n}\n\nclass CredentialEncryptionMethodDialog {\n\treadonly _credentialsProvider: CredentialsProvider\n\t_error: string | null\n\treadonly _finished: DeferredObject<void>\n\treadonly _dialog: Dialog\n\treadonly _supportedModes: ReadonlyArray<CredentialEncryptionMode>\n\treadonly _previousSelection: CredentialEncryptionMode | null\n\n\t/** @private */\n\tconstructor(\n\t\tcredentialsProvider: CredentialsProvider,\n\t\tsupportedModes: ReadonlyArray<CredentialEncryptionMode>,\n\t\tpreviousSelection: CredentialEncryptionMode | null,\n\t) {\n\t\tthis._credentialsProvider = credentialsProvider\n\t\tthis._supportedModes = supportedModes\n\t\tthis._previousSelection = previousSelection\n\t\tthis._error = null\n\t\tthis._finished = defer()\n\t\tthis._dialog = new Dialog(DialogType.EditMedium, {\n\t\t\tview: () => {\n\t\t\t\t// We need custom dialog because:\n\t\t\t\t// - We don't need large dialog\n\t\t\t\t// - We want our selector button in the body and not in the header and it must stick to the bottom of the dialog\n\t\t\t\t//   (large dialog scrolls its contents and that's *not* what we want for that button).\n\t\t\t\treturn m(\"\", [\n\t\t\t\t\t// Only allow skipping if it's first time user selects mode (not from settings)\n\t\t\t\t\tpreviousSelection == null\n\t\t\t\t\t\t? m(\n\t\t\t\t\t\t\t\t\".dialog-header.plr-l\",\n\t\t\t\t\t\t\t\tm(DialogHeaderBar, {\n\t\t\t\t\t\t\t\t\tleft: () => [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tlabel: \"skip_action\",\n\t\t\t\t\t\t\t\t\t\t\tclick: () => this._onModeSelected(DEFAULT_MODE),\n\t\t\t\t\t\t\t\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t\t\t\t\t\t\t\t} as const,\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t  )\n\t\t\t\t\t\t: null,\n\t\t\t\t\tm(\n\t\t\t\t\t\t\".rel\",\n\t\t\t\t\t\tm(SelectCredentialsEncryptionModeView, {\n\t\t\t\t\t\t\terror: this._error,\n\t\t\t\t\t\t\tonModeSelected: (mode) => this._onModeSelected(mode),\n\t\t\t\t\t\t\tsupportedModes: this._supportedModes,\n\t\t\t\t\t\t\tpreviousSelection: this._previousSelection ?? DEFAULT_MODE,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t])\n\t\t\t},\n\t\t})\n\t\tthis._dialog.setCloseHandler(() => {\n\t\t\tthis._finished.resolve()\n\t\t\tthis._dialog.close()\n\t\t})\n\t}\n\n\tstatic async showAndWaitForSelection(credentialsProvider: CredentialsProvider) {\n\t\tconst supportedModes = await credentialsProvider.getSupportedEncryptionModes()\n\t\tconst previousSelection = credentialsProvider.getCredentialsEncryptionMode()\n\t\tconst credentialsDialog = new CredentialEncryptionMethodDialog(credentialsProvider, supportedModes, previousSelection)\n\n\t\tcredentialsDialog._dialog.show()\n\n\t\tawait credentialsDialog._finished.promise\n\t}\n\n\tasync _onModeSelected(mode: CredentialEncryptionMode) {\n\t\ttry {\n\t\t\tawait this._credentialsProvider.setCredentialsEncryptionMode(mode)\n\n\t\t\tthis._dialog.close()\n\n\t\t\tthis._finished.resolve()\n\t\t} catch (e) {\n\t\t\tif (e instanceof CredentialAuthenticationError) {\n\t\t\t\tthis._error = e.message\n\t\t\t\tm.redraw()\n\t\t\t} else if (e instanceof KeyPermanentlyInvalidatedError) {\n\t\t\t\tawait this._credentialsProvider.clearCredentials(e)\n\n\t\t\t\tthis._dialog.close()\n\n\t\t\t\tawait Dialog.message(\"credentialsKeyInvalidated_msg\")\n\t\t\t\twindowFacade.reload({})\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype SelectCredentialEncryptionModeDialogAttrs = {\n\tpreviousSelection: CredentialEncryptionMode\n\tonModeSelected: (arg0: CredentialEncryptionMode) => unknown\n\tsupportedModes: ReadonlyArray<CredentialEncryptionMode>\n\terror: string | null\n}\n\nclass SelectCredentialsEncryptionModeView implements Component<SelectCredentialEncryptionModeDialogAttrs> {\n\t_currentMode: CredentialEncryptionMode\n\n\tconstructor({ attrs }: Vnode<SelectCredentialEncryptionModeDialogAttrs>) {\n\t\tthis._currentMode = attrs.previousSelection\n\t}\n\n\tview({ attrs }: Vnode<SelectCredentialEncryptionModeDialogAttrs>): Children {\n\t\tconst options = this._getSupportedOptions(attrs)\n\n\t\treturn [\n\t\t\tm(\n\t\t\t\t\".flex.col.pt.scroll.plr-l\",\n\t\t\t\t{\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tposition: \"relative\",\n\t\t\t\t\t\theight: \"100%\",\n\t\t\t\t\t\tpaddingBottom: \"64px\", // Padding to not overlap the button below\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t[\n\t\t\t\t\tattrs.error ? m(\".small.center.statusTextColor.pb-s\", liveDataAttrs(), attrs.error) : null,\n\t\t\t\t\tm(\"\", lang.get(\"credentialsEncryptionModeSelection_msg\")),\n\t\t\t\t\tm(\n\t\t\t\t\t\t\".mt\",\n\t\t\t\t\t\tm(RadioSelector, {\n\t\t\t\t\t\t\toptions,\n\t\t\t\t\t\t\tselectedOption: this._currentMode,\n\t\t\t\t\t\t\tonOptionSelected: (mode: CredentialEncryptionMode) => {\n\t\t\t\t\t\t\t\tthis._currentMode = mode\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t],\n\t\t\t),\n\t\t\tthis._renderSelectButton(() => attrs.onModeSelected(this._currentMode)),\n\t\t]\n\t}\n\n\t_getSupportedOptions(attrs: SelectCredentialEncryptionModeDialogAttrs): Array<RadioSelectorOption<CredentialEncryptionMode>> {\n\t\tconst options = [\n\t\t\t{\n\t\t\t\tname: \"credentialsEncryptionModeDeviceLock_label\",\n\t\t\t\tvalue: CredentialEncryptionMode.DEVICE_LOCK,\n\t\t\t\thelpText: \"credentialsEncryptionModeDeviceLockHelp_msg\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"credentialsEncryptionModeDeviceCredentials_label\",\n\t\t\t\tvalue: CredentialEncryptionMode.SYSTEM_PASSWORD,\n\t\t\t\thelpText: \"credentialsEncryptionModeDeviceCredentialsHelp_msg\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"credentialsEncryptionModeBiometrics_label\",\n\t\t\t\tvalue: CredentialEncryptionMode.BIOMETRICS,\n\t\t\t\thelpText: \"credentialsEncryptionModeBiometricsHelp_msg\",\n\t\t\t},\n\t\t] as const\n\t\treturn options.filter((option) => attrs.supportedModes.includes(option.value))\n\t}\n\n\t_renderSelectButton(onclick: () => unknown) {\n\t\treturn m(\n\t\t\t\"button.full-width.center.pb-s.pt-s.b.flex.items-center.justify-center\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\tbackgroundColor: theme.content_accent,\n\t\t\t\t\tcolor: \"white\",\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tbottom: \"0\",\n\t\t\t\t\tleft: \"0\",\n\t\t\t\t\tright: \"0\",\n\t\t\t\t\theight: \"60px\",\n\t\t\t\t\ttextTransform: \"uppercase\",\n\t\t\t\t},\n\t\t\t\tonclick,\n\t\t\t},\n\t\t\tlang.get(\"ok_action\"),\n\t\t)\n\t}\n}\n","import { assertMainOrNode } from \"../../api/common/Env\"\nimport { assert } from \"@tutao/tutanota-utils\"\nimport { WorkerRandomizer } from \"../../api/worker/WorkerImpl\"\n\nassertMainOrNode()\n\n// exported for tests\n// size of dictionary is within the 2Byte range\nexport const NUMBER_OF_BYTES: number = 2\nexport const BYTE_RANGE: number = Math.pow(2, 8 * NUMBER_OF_BYTES)\n\nexport class PasswordGenerator {\n\tconstructor(private randomizer: WorkerRandomizer, private dictionary: Array<string>) {}\n\n\tasync generateRandomPassphrase(): Promise<string> {\n\t\tconst usedWords = new Set()\n\n\t\twhile (usedWords.size < 6) {\n\t\t\tconst word = await this.pickRandomWordFromDictionary()\n\t\t\tusedWords.add(word)\n\t\t}\n\n\t\treturn Array.from(usedWords).join(\" \")\n\t}\n\n\tasync pickRandomWordFromDictionary(): Promise<string> {\n\t\tconst length = this.dictionary.length\n\t\treturn this.dictionary[await this.generateRandomNumberInRange(length)]\n\t}\n\n\t// The Randomizer generates a number within range := {0, ..., BYTE_RANGE - 1} (1Byte -> {0, ..., 255} for BYTE_RANGE = 256)\n\t// To scale the number n to our desired range, we can divide n by the BYTE_RANGE, resulting in a number n with 0 <= n < 1\n\t// @param 'range' is the length of the dictionary. Multiplying the above number by the range will result in a number in range := {0, ..., range - 1}\n\t// This is necessary to keep the distribution of numbers even, as well as ensuring that we do not access any invalid Index\n\tasync generateRandomNumberInRange(range: number): Promise<number> {\n\t\tassert(range > 0, \"range must be greater than 0\")\n\t\tconst byteNumber = await this.randomizer.generateRandomNumber(NUMBER_OF_BYTES)\n\t\treturn Math.floor((byteNumber / BYTE_RANGE) * range)\n\t}\n}\n","import { TutanotaError } from \"../common/error/TutanotaError\"\nimport type { TranslationKeyType } from \"../../misc/TranslationKey\"\nimport { assertMainOrNode } from \"../common/Env\"\nimport { AvailablePlanType } from \"../common/TutanotaConstants.js\"\n\nassertMainOrNode()\n\n/**\n * Thrown when the user is trying to go over their plan limits.\n */\nexport class UpgradeRequiredError extends TutanotaError {\n\t/**\n\t * @param message TranslationKey of a message for the user.\n\t * @param plans Array of AvailablePlanTypes the user can upgrade to in order to be able to do what they are trying to do.\n\t */\n\tconstructor(readonly message: TranslationKeyType, readonly plans: Array<AvailablePlanType>) {\n\t\tsuper(\"UpgradeRequiredError\", message)\n\t}\n}\n","import type { ContactModel } from \"../../contacts/model/ContactModel.js\"\nimport type { LoginController } from \"./LoginController.js\"\nimport type { MailFacade } from \"../worker/facades/lazy/MailFacade.js\"\nimport type { EntityClient } from \"../common/EntityClient.js\"\nimport { createNewContact, isTutanotaMailAddress } from \"../../mail/model/MailUtils.js\"\nimport { getContactDisplayName } from \"../../contacts/model/ContactUtils.js\"\nimport { PartialRecipient, Recipient, RecipientType } from \"../common/recipients/Recipient.js\"\nimport { LazyLoaded } from \"@tutao/tutanota-utils\"\nimport { Contact, ContactTypeRef } from \"../entities/tutanota/TypeRefs\"\nimport { cleanMailAddress } from \"../common/utils/CommonCalendarUtils.js\"\n\n/**\n * A recipient that can be resolved to obtain contact and recipient type\n * It is defined as an interface, because it should only be created using RecipientsModel.resolve\n * rather than directly constructing one\n */\nexport interface ResolvableRecipient extends Recipient {\n\t/** get the resolved value of the recipient, when it's ready */\n\tresolved(): Promise<Recipient>\n\n\t/** check if resolution is complete */\n\tisResolved(): boolean\n\n\t/** provide a handler to run when resolution is done, handy for chaining */\n\twhenResolved(onResolved: (resolvedRecipient: Recipient) => void): this\n\n\t/** update the contact. will override whatever contact gets resolved */\n\tsetContact(contact: Contact): void\n\n\t/** update the name. will override whatever the name has resolved to */\n\tsetName(name: string): void\n}\n\nexport enum ResolveMode {\n\tLazy,\n\tEager,\n}\n\nexport class RecipientsModel {\n\tconstructor(\n\t\tprivate readonly contactModel: ContactModel,\n\t\tprivate readonly loginController: LoginController,\n\t\tprivate readonly mailFacade: MailFacade,\n\t\tprivate readonly entityClient: EntityClient,\n\t) {}\n\n\t/**\n\t * Start resolving a recipient\n\t * If resolveLazily === true, Then resolution will not be initiated (i.e. no server calls will be made) until the first call to `resolved`\n\t */\n\tresolve(recipient: PartialRecipient, resolveMode: ResolveMode): ResolvableRecipient {\n\t\treturn new ResolvableRecipientImpl(recipient, this.contactModel, this.loginController, this.mailFacade, this.entityClient, resolveMode)\n\t}\n}\n\nclass ResolvableRecipientImpl implements ResolvableRecipient {\n\tprivate _address: string\n\tprivate _name: string | null\n\tprivate readonly lazyType: LazyLoaded<RecipientType>\n\tprivate readonly lazyContact: LazyLoaded<Contact | null>\n\n\tprivate readonly initialType: RecipientType = RecipientType.UNKNOWN\n\tprivate readonly initialContact: Contact | null = null\n\n\tprivate overrideContact: Contact | null = null\n\n\tget address(): string {\n\t\treturn this._address\n\t}\n\n\tget name(): string {\n\t\treturn this._name ?? \"\"\n\t}\n\n\tget type(): RecipientType {\n\t\treturn this.lazyType.getSync() ?? this.initialType\n\t}\n\n\tget contact(): Contact | null {\n\t\treturn this.lazyContact.getSync() ?? this.initialContact\n\t}\n\n\tconstructor(\n\t\targ: PartialRecipient,\n\t\tprivate readonly contactModel: ContactModel,\n\t\tprivate readonly loginController: LoginController,\n\t\tprivate readonly mailFacade: MailFacade,\n\t\tprivate readonly entityClient: EntityClient,\n\t\tresolveMode: ResolveMode,\n\t) {\n\t\tif (isTutanotaMailAddress(arg.address) || arg.type === RecipientType.INTERNAL) {\n\t\t\tthis.initialType = RecipientType.INTERNAL\n\t\t\tthis._address = cleanMailAddress(arg.address)\n\t\t} else if (arg.type) {\n\t\t\tthis.initialType = arg.type\n\t\t\tthis._address = arg.address\n\t\t} else {\n\t\t\tthis._address = arg.address\n\t\t}\n\n\t\tthis._name = arg.name ?? null\n\n\t\tif (!(arg.contact instanceof Array)) {\n\t\t\tthis.initialContact = arg.contact ?? null\n\t\t}\n\n\t\tthis.lazyType = new LazyLoaded(() => this.resolveType())\n\t\tthis.lazyContact = new LazyLoaded(async () => {\n\t\t\tconst contact = await this.resolveContact(arg.contact)\n\t\t\tif (contact != null && this._name == null) {\n\t\t\t\tthis._name = getContactDisplayName(contact)\n\t\t\t}\n\t\t\treturn contact\n\t\t})\n\n\t\tif (resolveMode === ResolveMode.Eager) {\n\t\t\tthis.lazyType.load()\n\t\t\tthis.lazyContact.load()\n\t\t}\n\t}\n\n\tsetName(newName: string) {\n\t\tthis._name = newName\n\t}\n\n\tsetContact(newContact: Contact) {\n\t\tthis.overrideContact = newContact\n\t\tthis.lazyContact.reload()\n\t}\n\n\tasync resolved(): Promise<Recipient> {\n\t\tawait Promise.all([this.lazyType.getAsync(), this.lazyContact.getAsync()])\n\t\treturn {\n\t\t\taddress: this.address,\n\t\t\tname: this.name,\n\t\t\ttype: this.type,\n\t\t\tcontact: this.contact,\n\t\t}\n\t}\n\n\tisResolved(): boolean {\n\t\t// We are only resolved when both type and contact are non-null and finished\n\t\treturn this.lazyType.isLoaded() && this.lazyContact.isLoaded()\n\t}\n\n\twhenResolved(handler: (resolvedRecipient: Recipient) => void): this {\n\t\tthis.resolved().then(handler)\n\t\treturn this\n\t}\n\n\t/**\n\t * Determine whether recipient is INTERNAL or EXTERNAL based on the existence of key data (external recipients don't have any)\n\t */\n\tprivate async resolveType(): Promise<RecipientType> {\n\t\tif (this.initialType === RecipientType.UNKNOWN) {\n\t\t\tconst cleanedAddress = cleanMailAddress(this.address)\n\t\t\tconst keyData = await this.mailFacade.getRecipientKeyData(cleanedAddress)\n\t\t\tif (keyData != null) {\n\t\t\t\t// we know this is one of ours, so it's safe to clean it up\n\t\t\t\tthis._address = cleanedAddress\n\t\t\t\treturn RecipientType.INTERNAL\n\t\t\t} else {\n\t\t\t\treturn RecipientType.EXTERNAL\n\t\t\t}\n\t\t} else {\n\t\t\treturn this.initialType\n\t\t}\n\t}\n\n\t/**\n\t * Resolve the recipients contact.\n\t * If {@param contact} is an Id, the contact will be loaded directly\n\t * Otherwise, the contact will be searched for in the ContactModel\n\t */\n\tprivate async resolveContact(contact: Contact | IdTuple | None): Promise<Contact | null> {\n\t\ttry {\n\t\t\tif (this.overrideContact) {\n\t\t\t\treturn this.overrideContact\n\t\t\t} else if ((await this.contactModel.contactListId()) == null) {\n\t\t\t\tconsole.log(\"can't resolve contacts for users with no contact list id\")\n\t\t\t\treturn null\n\t\t\t} else if (contact instanceof Array) {\n\t\t\t\treturn await this.entityClient.load(ContactTypeRef, contact)\n\t\t\t} else if (contact == null) {\n\t\t\t\tconst foundContact = await this.contactModel.searchForContact(this.address)\n\t\t\t\tif (foundContact) {\n\t\t\t\t\treturn foundContact\n\t\t\t\t} else {\n\t\t\t\t\t// we don't want to create a mixed-case contact if the address is an internal one.\n\t\t\t\t\t// after lazyType is loaded, if it resolves to RecipientType.INTERNAL, we have the\n\t\t\t\t\t// cleaned address in this.address.\n\t\t\t\t\tawait this.lazyType\n\t\t\t\t\treturn createNewContact(this.loginController.getUserController().user, this.address, this.name)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn contact\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.log(\"error resolving contact\", e)\n\t\t\treturn null\n\t\t}\n\t}\n}\n","import { InfoLink, lang } from \"../../LanguageViewModel.js\"\nimport { getWebRoot, isApp } from \"../../../api/common/Env.js\"\nimport { locator } from \"../../../api/main/MainLocator.js\"\nimport { copyToClipboard } from \"../../ClipboardUtils.js\"\nimport { showSnackBar } from \"../../../gui/base/SnackBar.js\"\nimport { createReferralCodePostIn } from \"../../../api/entities/sys/TypeRefs.js\"\nimport { ReferralCodeService } from \"../../../api/entities/sys/Services.js\"\nimport { TextField, TextFieldAttrs } from \"../../../gui/base/TextField.js\"\nimport m, { Children, Component, Vnode } from \"mithril\"\nimport { IconButton } from \"../../../gui/base/IconButton.js\"\nimport { BootIcons } from \"../../../gui/base/icons/BootIcons.js\"\nimport { ButtonSize } from \"../../../gui/base/ButtonSize.js\"\nimport { Icons } from \"../../../gui/base/icons/Icons.js\"\nimport { ifAllowedTutanotaLinks } from \"../../../gui/base/GuiUtils.js\"\nimport { UserController } from \"../../../api/main/UserController.js\"\n\nexport type ReferralLinkAttrs = {\n\treferralLink: string\n}\n\n/**\n * Component to display the sharable referral link.\n */\nexport class ReferralLinkViewer implements Component<ReferralLinkAttrs> {\n\tview(vnode: Vnode<ReferralLinkAttrs>): Children {\n\t\treturn m(\".scroll\", [\n\t\t\tm(\".h4\", lang.get(\"referralSettings_label\")),\n\t\t\tm(\"\", lang.get(\"referralLinkLong_msg\")),\n\t\t\tm(TextField, this.getReferralLinkTextFieldAttrs(vnode.attrs.referralLink)),\n\t\t])\n\t}\n\n\tgetReferralLinkTextFieldAttrs(referralLink: string): TextFieldAttrs {\n\t\treturn {\n\t\t\tdisabled: true,\n\t\t\tlabel: \"referralLink_label\",\n\t\t\tvalue: referralLink,\n\t\t\tinjectionsRight: () => this.renderButtons(referralLink),\n\t\t\thelpLabel: () =>\n\t\t\t\tifAllowedTutanotaLinks(locator.logins, InfoLink.ReferralLink, (link) => [\n\t\t\t\t\tm(\"span\", lang.get(\"moreInfo_msg\") + \" \"),\n\t\t\t\t\tm(\"span.text-break\", [m(`a[href=${link}][target=_blank]`, link)]),\n\t\t\t\t]),\n\t\t}\n\t}\n\n\tprivate renderButtons(referralLink: string): Children {\n\t\tif (referralLink === \"\") {\n\t\t\treturn [] // referral link not available yet\n\t\t}\n\n\t\treturn [\n\t\t\tm(IconButton, {\n\t\t\t\ttitle: \"copy_action\",\n\t\t\t\tclick: () => this.copyAction(referralLink),\n\t\t\t\ticon: Icons.Copy,\n\t\t\t\tsize: ButtonSize.Compact,\n\t\t\t}),\n\t\t\tm(IconButton, {\n\t\t\t\ttitle: \"share_action\",\n\t\t\t\tclick: () => this.shareAction(referralLink),\n\t\t\t\ticon: BootIcons.Share,\n\t\t\t\tsize: ButtonSize.Compact,\n\t\t\t}),\n\t\t]\n\t}\n\n\tprivate async copyAction(referralLink: string): Promise<void> {\n\t\tawait copyToClipboard(referralLink)\n\t\tawait showSnackBar({\n\t\t\tmessage: \"linkCopied_msg\",\n\t\t\tbutton: {\n\t\t\t\tlabel: \"close_alt\",\n\t\t\t\tclick: () => {},\n\t\t\t},\n\t\t})\n\t}\n\n\tprivate async shareAction(referralLink: string): Promise<void> {\n\t\tif (isApp()) {\n\t\t\t// open native share dialog on mobile\n\t\t\tconst shareMessage = this.getReferralLinkMessage(referralLink)\n\t\t\treturn locator.systemFacade.shareText(shareMessage, lang.get(\"referralSettings_label\")).then()\n\t\t} else {\n\t\t\t// otherwise share via MailEditor\n\t\t\timport(\"../../../mail/editor/MailEditor.js\").then((mailEditorModule) => mailEditorModule.writeInviteMail(referralLink))\n\t\t}\n\t}\n\n\tprivate getReferralLinkMessage(referralLink: string): string {\n\t\treturn lang.get(\"referralLinkShare_msg\", {\n\t\t\t\"{referralLink}\": referralLink,\n\t\t})\n\t}\n}\n\n/**\n * Get the referral link for the logged-in user\n */\nexport async function getReferralLink(userController: UserController): Promise<string> {\n\tconst customer = await userController.loadCustomer()\n\tconst referralCode = customer.referralCode ? customer.referralCode : await requestNewReferralCode()\n\treturn `${getWebRoot()}/signup?ref=${referralCode}`\n}\n\nasync function requestNewReferralCode(): Promise<string> {\n\tconst { referralCode } = await locator.serviceExecutor.post(ReferralCodeService, createReferralCodePostIn())\n\treturn referralCode\n}\n","import type { HtmlSanitizer } from \"./HtmlSanitizer.js\"\nimport { noOp } from \"@tutao/tutanota-utils\"\n\nexport class SanitizedTextViewModel {\n\tprivate sanitizedText: string | null = null\n\n\tconstructor(private text: string, private readonly sanitizer: HtmlSanitizer, private readonly uiUpdateCallback: () => void = noOp) {}\n\n\tset content(v: string) {\n\t\tthis.sanitizedText = null\n\t\tthis.text = v\n\t\tthis.uiUpdateCallback()\n\t}\n\n\tget content(): string {\n\t\tif (this.sanitizedText == null) {\n\t\t\tthis.sanitizedText = this.sanitizer.sanitizeHTML(this.text, { blockExternalContent: false }).html\n\t\t}\n\t\treturn this.sanitizedText\n\t}\n}\n","import { AccountType, FeatureType, GroupType, LegacyPlans, OperationType, PlanType } from \"../common/TutanotaConstants\"\nimport type { Base64Url } from \"@tutao/tutanota-utils\"\nimport { downcast, first, mapAndFilterNull, neverNull, ofClass } from \"@tutao/tutanota-utils\"\nimport { MediaType } from \"../common/EntityFunctions\"\nimport { assertMainOrNode, getApiOrigin, isDesktop } from \"../common/Env\"\nimport type { EntityUpdateData } from \"./EventController\"\nimport { isUpdateForTypeRef } from \"./EventController\"\nimport { NotFoundError } from \"../common/error/RestError\"\nimport { locator } from \"./MainLocator\"\nimport { isSameId } from \"../common/utils/EntityUtils\"\nimport { getWhitelabelCustomizations } from \"../../misc/WhitelabelCustomizations\"\nimport { EntityClient } from \"../common/EntityClient\"\nimport { CloseSessionService, PlanService } from \"../entities/sys/Services\"\nimport {\n\tAccountingInfo,\n\tAccountingInfoTypeRef,\n\tcreateCloseSessionServicePost,\n\tCustomer,\n\tCustomerInfo,\n\tCustomerInfoTypeRef,\n\tCustomerTypeRef,\n\tDomainInfo,\n\tGroupInfo,\n\tGroupInfoTypeRef,\n\tGroupMembership,\n\tPlanConfiguration,\n\tUser,\n\tUserTypeRef,\n\tWhitelabelConfig,\n\tWhitelabelConfigTypeRef,\n} from \"../entities/sys/TypeRefs\"\nimport {\n\tcreateUserSettingsGroupRoot,\n\tTutanotaProperties,\n\tTutanotaPropertiesTypeRef,\n\tUserSettingsGroupRoot,\n\tUserSettingsGroupRootTypeRef,\n} from \"../entities/tutanota/TypeRefs\"\nimport { typeModels as sysTypeModels } from \"../entities/sys/TypeModels\"\nimport { SessionType } from \"../common/SessionType\"\nimport { isCustomizationEnabledForCustomer } from \"../common/utils/Utils.js\"\nimport { IServiceExecutor } from \"../common/ServiceRequest.js\"\n\nassertMainOrNode()\n\nexport class UserController {\n\tprivate planConfig: PlanConfiguration | null\n\n\tconstructor(\n\t\t// should be readonly but is needed for a workaround in CalendarModel\n\t\tpublic user: User,\n\t\tprivate _userGroupInfo: GroupInfo,\n\t\tpublic readonly sessionId: IdTuple,\n\t\tprivate _props: TutanotaProperties,\n\t\tpublic readonly accessToken: Base64Url,\n\t\tprivate _userSettingsGroupRoot: UserSettingsGroupRoot,\n\t\tpublic readonly sessionType: SessionType,\n\t\tprivate readonly entityClient: EntityClient,\n\t\tprivate readonly serviceExecutor: IServiceExecutor,\n\t) {\n\t\tthis.planConfig = null\n\t}\n\n\tget userId(): Id {\n\t\treturn this.user._id\n\t}\n\n\tget props(): TutanotaProperties {\n\t\treturn this._props\n\t}\n\n\tget userGroupInfo(): GroupInfo {\n\t\treturn this._userGroupInfo\n\t}\n\n\tget userSettingsGroupRoot(): UserSettingsGroupRoot {\n\t\treturn this._userSettingsGroupRoot\n\t}\n\n\t/**\n\t * Checks if the current user is an admin of the customer.\n\t * @return True if the user is an admin\n\t */\n\tisGlobalAdmin(): boolean {\n\t\tif (this.isInternalUser()) {\n\t\t\treturn this.user.memberships.find((m) => m.groupType === GroupType.Admin) != null\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tisGlobalOrLocalAdmin(): boolean {\n\t\tif (this.isInternalUser()) {\n\t\t\treturn this.user.memberships.find((m) => m.groupType === GroupType.Admin || m.groupType === GroupType.LocalAdmin) != null\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Checks if the account type of the logged in user is FREE.\n\t * @returns True if the account type is FREE otherwise false\n\t */\n\tisFreeAccount(): boolean {\n\t\treturn this.user.accountType === AccountType.FREE\n\t}\n\n\tisPremiumAccount(): boolean {\n\t\treturn this.user.accountType === AccountType.PREMIUM\n\t}\n\n\t/**\n\t * Provides the information if an internal user is logged in.\n\t * @return True if an internal user is logged in, false if no user or an external user is logged in.\n\t */\n\tisInternalUser(): boolean {\n\t\treturn this.user.accountType !== AccountType.EXTERNAL\n\t}\n\n\tloadCustomer(): Promise<Customer> {\n\t\treturn this.entityClient.load(CustomerTypeRef, neverNull(this.user.customer))\n\t}\n\n\tasync loadCustomerInfo(): Promise<CustomerInfo> {\n\t\tconst customer = await this.loadCustomer()\n\t\treturn await this.entityClient.load(CustomerInfoTypeRef, customer.customerInfo)\n\t}\n\n\tasync getPlanType(): Promise<PlanType> {\n\t\tconst customerInfo = await this.loadCustomerInfo()\n\t\treturn downcast(customerInfo.plan)\n\t}\n\n\tasync getPlanConfig(): Promise<PlanConfiguration> {\n\t\tif (this.planConfig === null) {\n\t\t\tconst planServiceGetOut = await this.serviceExecutor.get(PlanService, null)\n\t\t\tthis.planConfig = planServiceGetOut.config\n\t\t}\n\t\treturn downcast(this.planConfig)\n\t}\n\n\tpublic isLegacyPlan(type: PlanType): boolean {\n\t\treturn LegacyPlans.includes(type)\n\t}\n\n\tasync isNewPaidPlan(): Promise<boolean> {\n\t\tconst type = await this.getPlanType()\n\t\treturn !this.isLegacyPlan(type) && type !== PlanType.Free\n\t}\n\n\tasync useLegacyBookingItem(): Promise<boolean> {\n\t\tconst customerInfo = await this.loadCustomerInfo()\n\t\tconst type: PlanType = downcast(customerInfo.plan)\n\t\treturn !(this.isLegacyPlan(type) && customerInfo.customPlan == null) && type !== PlanType.Free\n\t}\n\n\t/**\n\t * Checks if the current plan allows adding users and groups.\n\t */\n\tasync canHaveUsers(): Promise<boolean> {\n\t\tconst customer = await this.loadCustomer()\n\t\tconst planType = await this.getPlanType()\n\t\tconst planConfig = await this.getPlanConfig()\n\n\t\treturn this.isLegacyPlan(planType) || planConfig.multiUser || isCustomizationEnabledForCustomer(customer, FeatureType.MultipleUsers)\n\t}\n\n\tasync loadAccountingInfo(): Promise<AccountingInfo> {\n\t\tconst customerInfo = await this.loadCustomerInfo()\n\t\treturn await this.entityClient.load(AccountingInfoTypeRef, customerInfo.accountingInfo)\n\t}\n\n\tgetMailGroupMemberships(): GroupMembership[] {\n\t\treturn this.user.memberships.filter((membership) => membership.groupType === GroupType.Mail)\n\t}\n\n\tgetCalendarMemberships(): GroupMembership[] {\n\t\treturn this.user.memberships.filter((membership) => membership.groupType === GroupType.Calendar)\n\t}\n\n\tgetUserMailGroupMembership(): GroupMembership {\n\t\treturn this.getMailGroupMemberships()[0]\n\t}\n\n\tgetLocalAdminGroupMemberships(): GroupMembership[] {\n\t\treturn this.user.memberships.filter((membership) => membership.groupType === GroupType.LocalAdmin)\n\t}\n\n\tgetTemplateMemberships(): GroupMembership[] {\n\t\treturn this.user.memberships.filter((membership) => membership.groupType === GroupType.Template)\n\t}\n\n\tasync entityEventsReceived(updates: ReadonlyArray<EntityUpdateData>, eventOwnerGroupId: Id): Promise<void> {\n\t\tfor (const update of updates) {\n\t\t\tconst { instanceListId, instanceId, operation } = update\n\t\t\tif (operation === OperationType.UPDATE && isUpdateForTypeRef(UserTypeRef, update) && isSameId(this.user.userGroup.group, eventOwnerGroupId)) {\n\t\t\t\tthis.user = await this.entityClient.load(UserTypeRef, this.user._id)\n\t\t\t} else if (\n\t\t\t\toperation === OperationType.UPDATE &&\n\t\t\t\tisUpdateForTypeRef(GroupInfoTypeRef, update) &&\n\t\t\t\tisSameId(this.userGroupInfo._id, [neverNull(instanceListId), instanceId])\n\t\t\t) {\n\t\t\t\tthis._userGroupInfo = await this.entityClient.load(GroupInfoTypeRef, this._userGroupInfo._id)\n\t\t\t} else if (isUpdateForTypeRef(TutanotaPropertiesTypeRef, update) && operation === OperationType.UPDATE) {\n\t\t\t\tthis._props = await this.entityClient.loadRoot(TutanotaPropertiesTypeRef, this.user.userGroup.group)\n\t\t\t} else if (isUpdateForTypeRef(UserSettingsGroupRootTypeRef, update)) {\n\t\t\t\tthis._userSettingsGroupRoot = await this.entityClient.load(UserSettingsGroupRootTypeRef, this.user.userGroup.group)\n\t\t\t} else if (isUpdateForTypeRef(CustomerInfoTypeRef, update)) {\n\t\t\t\tif (operation === OperationType.CREATE) {\n\t\t\t\t\t// After premium upgrade customer info is deleted and created with new id. We want to make sure that it's cached for offline login.\n\t\t\t\t\tawait this.entityClient.load(CustomerInfoTypeRef, [update.instanceListId, update.instanceId])\n\t\t\t\t}\n\t\t\t\t// cached plan config might be outdated now\n\t\t\t\tthis.planConfig = null\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Delete the session (only if it's a non-persistent session\n\t * @param sync whether or not to delete in the main thread. For example, will be true when logging out due to closing the tab\n\t */\n\tasync deleteSession(sync: boolean): Promise<void> {\n\t\t// in case the tab is closed we need to delete the session in the main thread (synchronous rest request)\n\t\tif (sync) {\n\t\t\tif (this.sessionType !== SessionType.Persistent) {\n\t\t\t\tawait this.deleteSessionSync()\n\t\t\t}\n\t\t} else {\n\t\t\tif (this.sessionType !== SessionType.Persistent) {\n\t\t\t\tawait locator.loginFacade.deleteSession(this.accessToken).catch((e) => console.log(\"Error ignored on Logout:\", e))\n\t\t\t}\n\t\t\tawait locator.worker.reset()\n\t\t}\n\t}\n\n\tdeleteSessionSync(): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst sendBeacon = navigator.sendBeacon // Save sendBeacon to variable to satisfy type checker\n\n\t\t\tif (sendBeacon) {\n\t\t\t\ttry {\n\t\t\t\t\tconst path = `${getApiOrigin()}/rest/sys/${CloseSessionService.name.toLowerCase()}`\n\t\t\t\t\tconst requestObject = createCloseSessionServicePost({\n\t\t\t\t\t\taccessToken: this.accessToken,\n\t\t\t\t\t\tsessionId: this.sessionId,\n\t\t\t\t\t})\n\t\t\t\t\tdelete downcast(requestObject)[\"_type\"] // Remove extra field which is not part of the data model\n\n\t\t\t\t\t// Send as Blob to be able to set content type otherwise sends 'text/plain'\n\t\t\t\t\tconst queued = sendBeacon.call(\n\t\t\t\t\t\tnavigator,\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tnew Blob([JSON.stringify(requestObject)], {\n\t\t\t\t\t\t\ttype: MediaType.Json,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\tconsole.log(\"queued closing session: \", queued)\n\t\t\t\t\tresolve()\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.log(\"Failed to send beacon\", e)\n\t\t\t\t\treject(e)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Fall back to sync XHR if\n\t\t\t\tconst path = \"/rest/sys/session/\" + this.sessionId[0] + \"/\" + this.sessionId[1]\n\t\t\t\tconst xhr = new XMLHttpRequest()\n\t\t\t\txhr.open(\"DELETE\", getApiOrigin() + path, false) // sync requests increase reliability when invoked in onunload\n\n\t\t\t\txhr.setRequestHeader(\"accessToken\", this.accessToken)\n\t\t\t\txhr.setRequestHeader(\"v\", sysTypeModels.Session.version)\n\n\t\t\t\txhr.onload = function () {\n\t\t\t\t\t// XMLHttpRequestProgressEvent, but not needed\n\t\t\t\t\tif (xhr.status === 200) {\n\t\t\t\t\t\tconsole.log(\"deleted session\")\n\t\t\t\t\t\tresolve()\n\t\t\t\t\t} else if (xhr.status === 401) {\n\t\t\t\t\t\tconsole.log(\"authentication failed => session is already deleted\")\n\t\t\t\t\t\tresolve()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.error(\"could not delete session \" + xhr.status)\n\t\t\t\t\t\treject(new Error(\"could not delete session \" + xhr.status))\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\txhr.onerror = function () {\n\t\t\t\t\tconsole.error(\"failed to request delete session\")\n\t\t\t\t\treject(new Error(\"failed to request delete session\"))\n\t\t\t\t}\n\n\t\t\t\txhr.send()\n\t\t\t}\n\t\t})\n\t}\n\n\tasync isWhitelabelAccount(): Promise<boolean> {\n\t\t// isTutanotaDomain always returns true on desktop\n\t\tif (!isDesktop()) {\n\t\t\treturn !!getWhitelabelCustomizations(window)\n\t\t}\n\n\t\tconst customerInfo = await this.loadCustomerInfo()\n\t\treturn customerInfo.domainInfos.some((domainInfo) => domainInfo.whitelabelConfig)\n\t}\n\n\tasync loadWhitelabelConfig(): Promise<\n\t\t| {\n\t\t\t\twhitelabelConfig: WhitelabelConfig\n\t\t\t\tdomainInfo: DomainInfo\n\t\t  }\n\t\t| null\n\t\t| undefined\n\t> {\n\t\t// The model allows for multiple domainInfos to have whitelabel configs\n\t\t// but in reality on the server only a single custom configuration is allowed\n\t\t// therefore the result of the filtering all domainInfos with no whitelabelConfig\n\t\t// can only be an array of length 0 or 1\n\t\tconst customerInfo = await this.loadCustomerInfo()\n\t\tconst domainInfoAndConfig = first(\n\t\t\tmapAndFilterNull(\n\t\t\t\tcustomerInfo.domainInfos,\n\t\t\t\t(domainInfo) =>\n\t\t\t\t\tdomainInfo.whitelabelConfig && {\n\t\t\t\t\t\tdomainInfo,\n\t\t\t\t\t\twhitelabelConfig: domainInfo.whitelabelConfig,\n\t\t\t\t\t},\n\t\t\t),\n\t\t)\n\n\t\tif (domainInfoAndConfig) {\n\t\t\tconst whitelabelConfig = await locator.entityClient.load(WhitelabelConfigTypeRef, domainInfoAndConfig.whitelabelConfig)\n\t\t\treturn {\n\t\t\t\tdomainInfo: domainInfoAndConfig.domainInfo,\n\t\t\t\twhitelabelConfig,\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport type UserControllerInitData = {\n\tuser: User\n\tuserGroupInfo: GroupInfo\n\tsessionId: IdTuple\n\taccessToken: Base64Url\n\tsessionType: SessionType\n}\n// noinspection JSUnusedGlobalSymbols\n// dynamically imported\nexport async function initUserController({ user, userGroupInfo, sessionId, accessToken, sessionType }: UserControllerInitData): Promise<UserController> {\n\tconst entityClient = locator.entityClient\n\tconst [props, userSettingsGroupRoot] = await Promise.all([\n\t\tentityClient.loadRoot(TutanotaPropertiesTypeRef, user.userGroup.group),\n\t\tentityClient\n\t\t\t.load(UserSettingsGroupRootTypeRef, user.userGroup.group)\n\t\t\t.catch(\n\t\t\t\tofClass(NotFoundError, () =>\n\t\t\t\t\tentityClient\n\t\t\t\t\t\t.setup(null, createUserSettingsGroupRoot({ _ownerGroup: user.userGroup.group }))\n\t\t\t\t\t\t.then(() => entityClient.load(UserSettingsGroupRootTypeRef, user.userGroup.group)),\n\t\t\t\t),\n\t\t\t),\n\t])\n\treturn new UserController(user, userGroupInfo, sessionId, props, accessToken, userSettingsGroupRoot, sessionType, entityClient, locator.serviceExecutor)\n}\n","import { PartialRecipient, Recipient } from \"../api/common/recipients/Recipient.js\"\nimport { RecipientsModel, ResolveMode } from \"../api/main/RecipientsModel.js\"\nimport { ContactModel } from \"../contacts/model/ContactModel.js\"\nimport { isMailAddress } from \"./FormatValidator.js\"\nimport { ofClass } from \"@tutao/tutanota-utils\"\nimport { DbError } from \"../api/common/error/DbError.js\"\nimport { locator } from \"../api/main/MainLocator.js\"\nimport { ContactTypeRef } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { Mode } from \"../api/common/Env.js\"\nimport { PermissionError } from \"../api/common/error/PermissionError.js\"\nimport { LoginIncompleteError } from \"../api/common/error/LoginIncompleteError.js\"\nimport { MobileSystemFacade } from \"../native/common/generatedipc/MobileSystemFacade.js\"\nimport { findRecipientWithAddress } from \"../api/common/utils/CommonCalendarUtils.js\"\n\nconst MaxNativeSuggestions = 10\n\nexport class RecipientsSearchModel {\n\tprivate searchResults: Array<Recipient> = []\n\tprivate _selectedIdx: number = 0\n\tprivate loading: Promise<void> | null = null\n\n\tprivate currentQuery = \"\"\n\tprivate previousQuery = \"\"\n\n\tconstructor(\n\t\tprivate readonly recipientsModel: RecipientsModel,\n\t\tprivate readonly contactModel: ContactModel,\n\t\tprivate readonly systemFacade: MobileSystemFacade | null,\n\t) {}\n\n\tresults(): ReadonlyArray<Recipient> {\n\t\treturn this.searchResults\n\t}\n\n\tisLoading(): boolean {\n\t\treturn this.loading != null\n\t}\n\n\tclear() {\n\t\tthis.searchResults = []\n\t\tthis._selectedIdx = 0\n\t\tthis.loading = null\n\t\tthis.currentQuery = \"\"\n\t\tthis.previousQuery = \"\"\n\t}\n\n\tasync search(value: string): Promise<void> {\n\t\tconst query = value.trim()\n\n\t\tthis.currentQuery = query\n\n\t\tif (this.loading != null) {\n\t\t} else if (query.length > 0 && !(this.previousQuery.length > 0 && query.indexOf(this.previousQuery) === 0 && this.searchResults.length === 0)) {\n\t\t\tthis.loading = this.findContacts(query.toLowerCase())\n\t\t\t\t.then((newSuggestions) => {\n\t\t\t\t\t// Only update search result if search query has not been changed during search and update in all other cases\n\t\t\t\t\tif (query === this.currentQuery) {\n\t\t\t\t\t\tthis.searchResults = newSuggestions\n\t\t\t\t\t\tthis.previousQuery = query\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.finally(() => (this.loading = null))\n\t\t} else if (query.length === 0 && query !== this.previousQuery) {\n\t\t\tthis.searchResults = []\n\t\t\tthis.previousQuery = query\n\t\t}\n\n\t\tawait this.loading\n\t}\n\n\tprivate async findContacts(query: string): Promise<Array<Recipient>> {\n\t\tif (isMailAddress(query, false)) {\n\t\t\treturn []\n\t\t}\n\n\t\t// ensure match word order for email addresses mainly\n\t\tconst contacts = await this.contactModel\n\t\t\t.searchForContacts(`\"${query}\"`, \"recipient\", 10)\n\t\t\t.catch(\n\t\t\t\tofClass(DbError, async () => {\n\t\t\t\t\tconst listId = await this.contactModel.contactListId()\n\t\t\t\t\tif (listId) {\n\t\t\t\t\t\treturn locator.entityClient.loadAll(ContactTypeRef, listId)\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.catch(ofClass(LoginIncompleteError, () => []))\n\n\t\tlet suggestions = [] as Array<Recipient>\n\t\tfor (const contact of contacts) {\n\t\t\tconst name = `${contact.firstName} ${contact.lastName}`.trim()\n\n\t\t\tconst filter =\n\t\t\t\tname.toLowerCase().indexOf(query) !== -1\n\t\t\t\t\t? (address: string) => isMailAddress(address.trim(), false)\n\t\t\t\t\t: (address: string) => isMailAddress(address.trim(), false) && address.toLowerCase().indexOf(query) !== -1\n\n\t\t\tconst recipientsOfContact = contact.mailAddresses\n\t\t\t\t.map(({ address }) => address)\n\t\t\t\t.filter(filter)\n\t\t\t\t.map((address) => this.recipientsModel.resolve({ name, address, contact }, ResolveMode.Lazy))\n\n\t\t\tsuggestions = suggestions.concat(recipientsOfContact)\n\t\t}\n\n\t\tif (env.mode === Mode.App) {\n\t\t\tconst nativeContacts = await this.findNativeContacts(query)\n\n\t\t\tconst contactSuggestions = nativeContacts\n\t\t\t\t.filter((contact) => isMailAddress(contact.address, false) && !findRecipientWithAddress(suggestions, contact.address))\n\t\t\t\t.slice(0, MaxNativeSuggestions)\n\t\t\t\t.map((recipient) => this.recipientsModel.resolve(recipient, ResolveMode.Lazy))\n\n\t\t\tsuggestions.push(...contactSuggestions)\n\t\t}\n\n\t\treturn suggestions.sort((suggestion1, suggestion2) => suggestion1.name.localeCompare(suggestion2.name))\n\t}\n\n\tprivate async findNativeContacts(text: string): Promise<Array<PartialRecipient>> {\n\t\tif (!this.systemFacade) {\n\t\t\treturn []\n\t\t}\n\t\tconst recipients = await this.systemFacade.findSuggestions(text).catch(ofClass(PermissionError, () => []))\n\t\treturn recipients.map(({ name, mailAddress }) => ({ name, address: mailAddress }))\n\t}\n}\n","import { InfoLink, lang } from \"../LanguageViewModel.js\"\nimport m, { Children, Component, Vnode } from \"mithril\"\n\nexport type MoreInfoLinkAttrs = {\n\tlink: InfoLink\n}\n\n/**\n * This component shows a \"More info\" message with a link to a given destination.\n */\nexport class MoreInfoLink implements Component<MoreInfoLinkAttrs> {\n\tview(vnode: Vnode<MoreInfoLinkAttrs>): Children {\n\t\treturn m(\n\t\t\t\"p\",\n\t\t\tlang.get(\"moreInfo_msg\") + \" \",\n\t\t\tm(\"small.text-break\", [\n\t\t\t\tm(\n\t\t\t\t\t\"a\",\n\t\t\t\t\t{\n\t\t\t\t\t\thref: vnode.attrs.link,\n\t\t\t\t\t\ttarget: \"_blank\",\n\t\t\t\t\t},\n\t\t\t\t\tvnode.attrs.link,\n\t\t\t\t),\n\t\t\t]),\n\t\t)\n\t}\n}\n","import { NewsListItem } from \"../NewsListItem.js\"\nimport m, { Children } from \"mithril\"\nimport { NewsId } from \"../../../api/entities/tutanota/TypeRefs.js\"\nimport { locator } from \"../../../api/main/MainLocator.js\"\nimport { InfoLink, lang } from \"../../LanguageViewModel.js\"\nimport { Dialog } from \"../../../gui/base/Dialog.js\"\nimport { Button, ButtonAttrs, ButtonType } from \"../../../gui/base/Button.js\"\nimport { NewsModel } from \"../NewsModel.js\"\nimport { UsageTestModel } from \"../../UsageTestModel.js\"\nimport { MoreInfoLink } from \"../MoreInfoLink.js\"\n\n/**\n * News item that informs users about the usage data opt-in.\n */\nexport class UsageOptInNews implements NewsListItem {\n\tconstructor(private readonly newsModel: NewsModel, private readonly usageTestModel: UsageTestModel) {}\n\n\tisShown(): Promise<boolean> {\n\t\treturn Promise.resolve(locator.usageTestModel.showOptInIndicator())\n\t}\n\n\trender(newsId: NewsId): Children {\n\t\tconst closeAction = (optedIn?: boolean) => {\n\t\t\tthis.newsModel\n\t\t\t\t.acknowledgeNews(newsId.newsItemId)\n\t\t\t\t.then(() => {\n\t\t\t\t\tif (optedIn) {\n\t\t\t\t\t\tDialog.message(\"userUsageDataOptInThankYouOptedIn_msg\")\n\t\t\t\t\t} else if (optedIn !== undefined) {\n\t\t\t\t\t\tDialog.message(\"userUsageDataOptInThankYouOptedOut_msg\")\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.then(m.redraw)\n\t\t}\n\n\t\tconst buttonAttrs: Array<ButtonAttrs> = [\n\t\t\t{\n\t\t\t\tlabel: \"decideLater_action\",\n\t\t\t\tclick: () => closeAction(),\n\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: \"deactivate_action\",\n\t\t\t\tclick: () => {\n\t\t\t\t\tconst decision = false\n\t\t\t\t\tthis.usageTestModel.setOptInDecision(decision).then(() => closeAction(decision))\n\t\t\t\t},\n\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: \"activate_action\",\n\t\t\t\tclick: () => {\n\t\t\t\t\tconst decision = true\n\t\t\t\t\tthis.usageTestModel.setOptInDecision(decision).then(() => closeAction(decision))\n\t\t\t\t},\n\t\t\t\ttype: ButtonType.Primary,\n\t\t\t},\n\t\t]\n\n\t\treturn m(\".full-width\", [\n\t\t\tm(\".h4\", lang.get(\"userUsageDataOptIn_title\")),\n\t\t\tm(\".pb\", lang.get(\"userUsageDataOptInExplanation_msg\")),\n\t\t\tm(\"ul.usage-test-opt-in-bullets\", [\n\t\t\t\tm(\"li\", lang.get(\"userUsageDataOptInStatement1_msg\")),\n\t\t\t\tm(\"li\", lang.get(\"userUsageDataOptInStatement2_msg\")),\n\t\t\t\tm(\"li\", lang.get(\"userUsageDataOptInStatement3_msg\")),\n\t\t\t\tm(\"li\", lang.get(\"userUsageDataOptInStatement4_msg\")),\n\t\t\t]),\n\t\t\tm(MoreInfoLink, { link: InfoLink.Privacy }),\n\t\t\tm(\n\t\t\t\t\".flex-end.flex-no-grow-no-shrink-auto.flex-wrap\",\n\t\t\t\tbuttonAttrs.map((a) => m(Button, a)),\n\t\t\t),\n\t\t])\n\t}\n}\n","import { NewsListItem } from \"../NewsListItem.js\"\nimport m, { Children } from \"mithril\"\nimport { NewsId } from \"../../../api/entities/tutanota/TypeRefs.js\"\nimport { lang } from \"../../LanguageViewModel.js\"\nimport { Button, ButtonType } from \"../../../gui/base/Button.js\"\nimport { NewsModel } from \"../NewsModel.js\"\nimport type { RecoverCodeField } from \"../../../settings/login/RecoverCodeDialog.js\"\nimport { Dialog, DialogType } from \"../../../gui/base/Dialog.js\"\nimport { AccessBlockedError, NotAuthenticatedError } from \"../../../api/common/error/RestError.js\"\nimport { daysToMillis, LazyLoaded, noOp, ofClass } from \"@tutao/tutanota-utils\"\nimport { copyToClipboard } from \"../../ClipboardUtils.js\"\nimport { UserController } from \"../../../api/main/UserController.js\"\nimport { progressIcon } from \"../../../gui/base/Icon.js\"\nimport { UserManagementFacade } from \"../../../api/worker/facades/lazy/UserManagementFacade.js\"\nimport { isApp } from \"../../../api/common/Env.js\"\n\n/**\n * News item that informs admin users about their recovery code.\n */\nexport class RecoveryCodeNews implements NewsListItem {\n\tprivate recoveryCode: string | null = null\n\tprivate readonly recoverCodeField = new LazyLoaded(async () => {\n\t\tconst { RecoverCodeField } = await import(\"../../../settings/login/RecoverCodeDialog.js\")\n\t\tm.redraw()\n\t\treturn RecoverCodeField\n\t})\n\n\tconstructor(\n\t\tprivate readonly newsModel: NewsModel,\n\t\tprivate readonly userController: UserController,\n\t\tprivate readonly userManagementFacade: UserManagementFacade,\n\t) {}\n\n\tisShown(newsId: NewsId): Promise<boolean> {\n\t\tconst customerCreationTime = this.userController.userGroupInfo.created.getTime()\n\t\treturn Promise.resolve(this.userController.isGlobalAdmin() && Date.now() - customerCreationTime > daysToMillis(14))\n\t}\n\n\trender(newsId: NewsId): Children {\n\t\tconst recoveryCode = this.recoveryCode\n\t\t// toggle the load if it's not started yet\n\t\tthis.recoverCodeField.getAsync()\n\n\t\t// Will (always) be null on the first call of render() since getAsync() was just called for the first time.\n\t\t// When the redraw is triggered in the load function, it will be populated and rendered correctly.\n\t\tconst RecoverCodeField = this.recoverCodeField.getSync()\n\n\t\treturn m(\".full-width\", [\n\t\t\tm(\n\t\t\t\t\".h4\",\n\t\t\t\t{\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\"text-transform\": \"capitalize\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tlang.get(\"recoveryCode_label\"),\n\t\t\t),\n\t\t\tm(\"\", lang.get(\"recoveryCodeReminder_msg\")),\n\t\t\trecoveryCode\n\t\t\t\t? RecoverCodeField\n\t\t\t\t\t? m(RecoverCodeField, {\n\t\t\t\t\t\t\tshowMessage: false,\n\t\t\t\t\t\t\trecoverCode: recoveryCode as string,\n\t\t\t\t\t\t\tshowButtons: false,\n\t\t\t\t\t  })\n\t\t\t\t\t: m(\".flex.justify-center\", progressIcon())\n\t\t\t\t: null,\n\t\t\tm(\".flex-end.flex-no-grow-no-shrink-auto.flex-wrap\", [\n\t\t\t\trecoveryCode\n\t\t\t\t\t? [this.renderCopyButton(recoveryCode), this.renderPrintButton(), this.confirmButton(newsId)]\n\t\t\t\t\t: [this.renderDoneButton(newsId), this.renderDisplayButton()],\n\t\t\t]),\n\t\t])\n\t}\n\n\tprivate renderDoneButton(newsId: NewsId) {\n\t\treturn m(Button, {\n\t\t\tlabel: \"done_action\",\n\t\t\ttype: ButtonType.Secondary,\n\t\t\tclick: () =>\n\t\t\t\tDialog.showActionDialog({\n\t\t\t\t\ttype: DialogType.EditSmall,\n\t\t\t\t\tokAction: async (dialog) => {\n\t\t\t\t\t\tdialog.close()\n\t\t\t\t\t\tthis.newsModel.acknowledgeNews(newsId.newsItemId).then(m.redraw)\n\t\t\t\t\t},\n\t\t\t\t\ttitle: lang.get(\"recoveryCode_label\"),\n\t\t\t\t\tallowCancel: true,\n\t\t\t\t\tchild: () => m(\"p\", lang.get(\"recoveryCodeConfirmation_msg\")),\n\t\t\t\t}),\n\t\t})\n\t}\n\n\tprivate renderPrintButton(): Children {\n\t\tif (isApp() || typeof window.print !== \"function\") {\n\t\t\treturn null\n\t\t}\n\n\t\treturn m(Button, {\n\t\t\tlabel: \"print_action\",\n\t\t\ttype: ButtonType.Secondary,\n\t\t\tclick: () => {\n\t\t\t\twindow.print()\n\t\t\t},\n\t\t})\n\t}\n\n\tprivate renderCopyButton(recoveryCode: string): Children {\n\t\treturn m(Button, {\n\t\t\tlabel: \"copy_action\",\n\t\t\ttype: ButtonType.Secondary,\n\t\t\tclick: () => {\n\t\t\t\tcopyToClipboard(recoveryCode)\n\t\t\t},\n\t\t})\n\t}\n\n\tprivate renderDisplayButton(): Children {\n\t\treturn m(Button, {\n\t\t\tlabel: \"recoveryCodeDisplay_action\",\n\t\t\tclick: async () => {\n\t\t\t\tthis.getRecoverCodeDialogAfterPasswordVerification(this.userController)\n\t\t\t},\n\t\t\ttype: ButtonType.Primary,\n\t\t})\n\t}\n\n\tprivate confirmButton(newsId: NewsId): Children {\n\t\treturn m(Button, {\n\t\t\tlabel: \"paymentDataValidation_action\",\n\t\t\tclick: async () => {\n\t\t\t\tawait this.newsModel.acknowledgeNews(newsId.newsItemId)\n\t\t\t\tm.redraw()\n\t\t\t},\n\t\t\ttype: ButtonType.Primary,\n\t\t})\n\t}\n\n\tprivate getRecoverCodeDialogAfterPasswordVerification(userController: UserController) {\n\t\tconst dialog = Dialog.showRequestPasswordDialog({\n\t\t\taction: (pw) => {\n\t\t\t\tconst hasRecoveryCode = !!userController.user.auth?.recoverCode\n\n\t\t\t\treturn (hasRecoveryCode ? this.userManagementFacade.getRecoverCode(pw) : this.userManagementFacade.createRecoveryCode(pw))\n\t\t\t\t\t.then((recoverCode) => {\n\t\t\t\t\t\tdialog.close()\n\t\t\t\t\t\tthis.recoveryCode = recoverCode\n\t\t\t\t\t\treturn \"\"\n\t\t\t\t\t})\n\t\t\t\t\t.catch(ofClass(NotAuthenticatedError, () => lang.get(\"invalidPassword_msg\")))\n\t\t\t\t\t.catch(ofClass(AccessBlockedError, () => lang.get(\"tooManyAttempts_msg\")))\n\t\t\t\t\t.finally(m.redraw)\n\t\t\t},\n\t\t\tcancel: {\n\t\t\t\ttextId: \"cancel_action\",\n\t\t\t\taction: noOp,\n\t\t\t},\n\t\t})\n\t}\n}\n","import { NewsListItem } from \"../NewsListItem.js\"\nimport { NewsId } from \"../../../api/entities/tutanota/TypeRefs.js\"\nimport Mithril from \"mithril\"\nimport m from \"mithril\"\nimport { isAndroidApp, isIOSApp } from \"../../../api/common/Env.js\"\nimport { lang } from \"../../LanguageViewModel.js\"\nimport { Button, ButtonType } from \"../../../gui/base/Button.js\"\nimport { NewsModel } from \"../NewsModel.js\"\nimport { showCredentialsEncryptionModeDialog } from \"../../../gui/dialogs/SelectCredentialsEncryptionModeDialog.js\"\nimport { CredentialsProvider } from \"../../credentials/CredentialsProvider.js\"\nimport { Dialog } from \"../../../gui/base/Dialog.js\"\n\nconst playstoreLink = \"https://play.google.com/store/apps/details?id=de.tutao.tutanota\"\nconst appstoreLink = \"https://apps.apple.com/app/tutanota/id922429609\"\n\n/**\n * News item that reminds the user of configuring pin/ biometrics\n */\nexport class PinBiometricsNews implements NewsListItem {\n\tconstructor(private readonly newsModel: NewsModel, private readonly credentialsProvider: CredentialsProvider, private readonly userId: Id) {}\n\n\tisShown(newsId: NewsId): Promise<boolean> {\n\t\treturn Promise.resolve((isIOSApp() || isAndroidApp()) && !this.newsModel.hasAcknowledgedNewsForDevice(newsId.newsItemId))\n\t}\n\n\trender(newsId: NewsId): Mithril.Children {\n\t\tconst displayedLink = isAndroidApp() ? playstoreLink : appstoreLink\n\t\treturn m(\".full-width\", [\n\t\t\tm(\".h4\", { style: { \"text-transform\": \"capitalize\" } }, lang.get(\"pinBiometrics_action\")),\n\t\t\tm(\"p\", lang.get(\"pinBiometrics1_msg\", { \"{secureNowAction}\": lang.get(\"secureNow_action\") })),\n\t\t\tm(\"p\", lang.get(\"pinBiometrics2_msg\")),\n\t\t\tm(\"p\", [m(\".text-break\", [m(\"a\", { href: displayedLink, target: \"_blank\" }, displayedLink)])]),\n\t\t\tm(\"p\", lang.get(\"pinBiometrics3_msg\")),\n\t\t\tm(\".flex-end.flex-no-grow-no-shrink-auto.flex-wrap\", [\n\t\t\t\tthis.renderLaterButton(newsId),\n\t\t\t\tthis.renderDismissButton(newsId),\n\t\t\t\tthis.renderConfirmButton(newsId),\n\t\t\t]),\n\t\t])\n\t}\n\n\tprivate renderLaterButton(newsId: NewsId) {\n\t\treturn m(Button, {\n\t\t\tlabel: \"decideLater_action\",\n\t\t\ttype: ButtonType.Secondary,\n\t\t\tclick: async () => {\n\t\t\t\tawait this.newsModel.acknowledgeNews(newsId.newsItemId)\n\t\t\t\tm.redraw()\n\t\t\t},\n\t\t})\n\t}\n\n\tprivate renderDismissButton(newsId: NewsId) {\n\t\treturn m(Button, {\n\t\t\tlabel: \"noThanks_action\",\n\t\t\ttype: ButtonType.Secondary,\n\t\t\tclick: async () => {\n\t\t\t\tthis.newsModel.acknowledgeNewsForDevice(newsId.newsItemId)\n\t\t\t\tawait this.newsModel.acknowledgeNews(newsId.newsItemId)\n\t\t\t\tm.redraw()\n\t\t\t},\n\t\t})\n\t}\n\n\tprivate renderConfirmButton(newsId: NewsId) {\n\t\treturn m(Button, {\n\t\t\tlabel: \"secureNow_action\",\n\t\t\tclick: async () => {\n\t\t\t\tif ((await this.credentialsProvider.getCredentialsInfoByUserId(this.userId)) === null) {\n\t\t\t\t\tawait Dialog.message(() => lang.get(\"needSavedCredentials_msg\", { \"{storePasswordAction}\": lang.get(\"storePassword_action\") }))\n\t\t\t\t} else {\n\t\t\t\t\tawait showCredentialsEncryptionModeDialog(this.credentialsProvider)\n\n\t\t\t\t\tthis.newsModel.acknowledgeNewsForDevice(newsId.newsItemId)\n\t\t\t\t\tawait this.newsModel.acknowledgeNews(newsId.newsItemId)\n\t\t\t\t\tm.redraw()\n\t\t\t\t}\n\t\t\t},\n\t\t\ttype: ButtonType.Primary,\n\t\t})\n\t}\n}\n","import { NewsListItem } from \"../NewsListItem.js\"\nimport m, { Children } from \"mithril\"\nimport { NewsId } from \"../../../api/entities/tutanota/TypeRefs.js\"\nimport { Button, ButtonAttrs, ButtonType } from \"../../../gui/base/Button.js\"\nimport { NewsModel } from \"../NewsModel.js\"\nimport { getReferralLink, ReferralLinkViewer } from \"./ReferralLinkViewer.js\"\nimport { DateProvider } from \"../../../api/common/DateProvider.js\"\nimport { generatedIdToTimestamp } from \"../../../api/common/utils/EntityUtils.js\"\nimport { getDayShifted, neverNull } from \"@tutao/tutanota-utils\"\nimport { UserController } from \"../../../api/main/UserController.js\"\n\nconst REFERRAL_NEWS_DISPLAY_THRESHOLD_DAYS = 7\n\n/**\n * News item that informs users about option to refer friends. Only shown after the customer exists at least 7 days.\n *\n * Not shown for non-admin users.\n */\nexport class ReferralLinkNews implements NewsListItem {\n\tprivate referralLink: string = \"\"\n\n\tconstructor(private readonly newsModel: NewsModel, private readonly dateProvider: DateProvider, private readonly userController: UserController) {}\n\n\tasync isShown(): Promise<boolean> {\n\t\t// Do not show this for business customers yet (not allowed to create referral links)\n\t\tif ((await this.userController.loadCustomer()).businessUse === true) {\n\t\t\treturn false\n\t\t}\n\n\t\t// Create the referral link\n\t\tthis.referralLink = await getReferralLink(this.userController)\n\n\t\t// Decode the date the user was generated from the timestamp in the user ID\n\t\tconst customerCreatedTime = generatedIdToTimestamp(neverNull(this.userController.user.customer))\n\t\treturn (\n\t\t\tthis.userController.isGlobalAdmin() &&\n\t\t\tgetDayShifted(new Date(customerCreatedTime), REFERRAL_NEWS_DISPLAY_THRESHOLD_DAYS) <= new Date(this.dateProvider.now())\n\t\t)\n\t}\n\n\trender(newsId: NewsId): Children {\n\t\tconst buttonAttrs: Array<ButtonAttrs> = [\n\t\t\t{\n\t\t\t\tlabel: \"close_alt\",\n\t\t\t\tclick: () => this.newsModel.acknowledgeNews(newsId.newsItemId).then(m.redraw),\n\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t},\n\t\t]\n\n\t\treturn m(\".full-width\", [\n\t\t\tm(ReferralLinkViewer, { referralLink: this.referralLink }),\n\t\t\tm(\n\t\t\t\t\".flex-end.flex-no-grow-no-shrink-auto.flex-wrap\",\n\t\t\t\tbuttonAttrs.map((a) => m(Button, a)),\n\t\t\t),\n\t\t])\n\t}\n}\n","import { NewsListItem } from \"../NewsListItem.js\"\nimport m, { Children } from \"mithril\"\nimport { NewsId } from \"../../../api/entities/tutanota/TypeRefs.js\"\nimport { InfoLink, lang } from \"../../LanguageViewModel.js\"\nimport { Button, ButtonAttrs, ButtonType } from \"../../../gui/base/Button.js\"\nimport { NewsModel } from \"../NewsModel.js\"\nimport { UserController } from \"../../../api/main/UserController.js\"\nimport { showUpgradeWizardOrSwitchSubscriptionDialog } from \"../../SubscriptionDialogs.js\"\n\n/**\n * News item that informs admin users about the new pricing model.\n */\nexport class NewPlansNews implements NewsListItem {\n\tconstructor(private readonly newsModel: NewsModel, private readonly userController: UserController) {}\n\n\tasync isShown(): Promise<boolean> {\n\t\tif (!this.userController.isGlobalAdmin()) {\n\t\t\treturn false\n\t\t}\n\n\t\t// // Do not show this for customers that are already on a new plan\n\t\treturn !(await this.userController.isNewPaidPlan())\n\t}\n\n\trender(newsId: NewsId): Children {\n\t\tconst lnk = InfoLink.Privacy\n\n\t\tconst acknowledgeAction = () => {\n\t\t\tthis.newsModel.acknowledgeNews(newsId.newsItemId).then(m.redraw)\n\t\t}\n\n\t\tconst buttonAttrs: Array<ButtonAttrs> = [\n\t\t\t{\n\t\t\t\tlabel: \"decideLater_action\",\n\t\t\t\tclick: () => acknowledgeAction(),\n\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: \"showMoreUpgrade_action\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tawait showUpgradeWizardOrSwitchSubscriptionDialog(this.userController)\n\t\t\t\t\tif (await this.userController.isNewPaidPlan()) {\n\t\t\t\t\t\tacknowledgeAction()\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\ttype: ButtonType.Primary,\n\t\t\t},\n\t\t]\n\n\t\treturn m(\".full-width\", [\n\t\t\tm(\".h4\", lang.get(\"newPlansNews_title\")),\n\t\t\tm(\n\t\t\t\t\".pb\",\n\t\t\t\tlang.get(\"newPlansExplanation_msg\", {\n\t\t\t\t\t\"{plan1}\": \"Revolutionary\",\n\t\t\t\t\t\"{plan2}\": \"Legend\",\n\t\t\t\t}),\n\t\t\t),\n\t\t\tm(\".pb\", lang.get(\"newPlansOfferExplanation_msg\")),\n\t\t\tm(\n\t\t\t\t\".flex-end.flex-no-grow-no-shrink-auto.flex-wrap\",\n\t\t\t\tbuttonAttrs.map((a) => m(Button, a)),\n\t\t\t),\n\t\t])\n\t}\n}\n","import { NewsListItem } from \"../NewsListItem.js\"\nimport m, { Children } from \"mithril\"\nimport { NewsId } from \"../../../api/entities/tutanota/TypeRefs.js\"\nimport { InfoLink, lang } from \"../../LanguageViewModel.js\"\nimport { Button, ButtonAttrs, ButtonType } from \"../../../gui/base/Button.js\"\nimport { NewsModel } from \"../NewsModel.js\"\nimport { UserController } from \"../../../api/main/UserController.js\"\nimport { showUpgradeWizardOrSwitchSubscriptionDialog } from \"../../SubscriptionDialogs.js\"\n\n/**\n * News item that informs admin users that the new pricing offer is ending soon.\n */\nexport class NewPlansOfferEndingNews implements NewsListItem {\n\tconstructor(private readonly newsModel: NewsModel, private readonly userController: UserController) {}\n\n\tasync isShown(): Promise<boolean> {\n\t\tif (!this.userController.isGlobalAdmin()) {\n\t\t\treturn false\n\t\t}\n\n\t\t// // Do not show this for customers that are already on a new plan\n\t\treturn !(await this.userController.isNewPaidPlan())\n\t}\n\n\trender(newsId: NewsId): Children {\n\t\tconst acknowledgeAction = () => {\n\t\t\tthis.newsModel.acknowledgeNews(newsId.newsItemId).then(m.redraw)\n\t\t}\n\n\t\tconst buttonAttrs: Array<ButtonAttrs> = [\n\t\t\t{\n\t\t\t\tlabel: \"decideLater_action\",\n\t\t\t\tclick: () => acknowledgeAction(),\n\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t},\n\t\t\t{\n\t\t\t\tlabel: \"showMoreUpgrade_action\",\n\t\t\t\tclick: async () => {\n\t\t\t\t\tawait showUpgradeWizardOrSwitchSubscriptionDialog(this.userController)\n\t\t\t\t\tif (await this.userController.isNewPaidPlan()) {\n\t\t\t\t\t\tacknowledgeAction()\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\ttype: ButtonType.Primary,\n\t\t\t},\n\t\t]\n\n\t\treturn m(\".full-width\", [\n\t\t\tm(\".h4\", lang.get(\"newPlansOfferEndingNews_title\")),\n\t\t\tm(\n\t\t\t\t\".pb\",\n\t\t\t\tlang.get(\"newPlansExplanationPast_msg\", {\n\t\t\t\t\t\"{plan1}\": \"Revolutionary\",\n\t\t\t\t\t\"{plan2}\": \"Legend\",\n\t\t\t\t}),\n\t\t\t),\n\t\t\tm(\".pb\", lang.get(\"newPlansOfferEnding_msg\")),\n\t\t\tm(\n\t\t\t\t\".flex-end.flex-no-grow-no-shrink-auto.flex-wrap\",\n\t\t\t\tbuttonAttrs.map((a) => m(Button, a)),\n\t\t\t),\n\t\t])\n\t}\n}\n","import type { Contact, ContactList } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { ContactListTypeRef, ContactTypeRef } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { createRestriction } from \"../../search/model/SearchUtils\"\nimport { flat, groupBy, LazyLoaded, ofClass, promiseMap } from \"@tutao/tutanota-utils\"\nimport { NotAuthorizedError, NotFoundError } from \"../../api/common/error/RestError\"\nimport { DbError } from \"../../api/common/error/DbError\"\nimport { EntityClient } from \"../../api/common/EntityClient\"\nimport type { LoginController } from \"../../api/main/LoginController\"\nimport { compareOldestFirst, elementIdPart, listIdPart } from \"../../api/common/utils/EntityUtils\"\nimport type { SearchFacade } from \"../../api/worker/search/SearchFacade\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport { LoginIncompleteError } from \"../../api/common/error/LoginIncompleteError\"\nimport { cleanMailAddress } from \"../../api/common/utils/CommonCalendarUtils.js\"\n\nassertMainOrNode()\n\nexport interface ContactModel {\n\t/**\n\t * Provides the first contact (starting with oldest contact) that contains the given email address. Uses the index search if available, otherwise loads all contacts.\n\t */\n\tsearchForContact(mailAddress: string): Promise<Contact | null>\n\n\t/** Id of the contact list. Is null for external users. */\n\tcontactListId(): Promise<Id | null>\n\n\tsearchForContacts(query: string, field: string, minSuggestionCount: number): Promise<Contact[]>\n}\n\nexport class ContactModelImpl implements ContactModel {\n\t_entityClient: EntityClient\n\t_searchFacade: SearchFacade\n\t_contactListId: LazyLoaded<Id | null>\n\tprivate loginController: LoginController\n\n\tconstructor(searchFacade: SearchFacade, entityClient: EntityClient, loginController: LoginController) {\n\t\tthis._searchFacade = searchFacade\n\t\tthis._entityClient = entityClient\n\t\tthis._contactListId = lazyContactListId(loginController, this._entityClient)\n\t\tthis.loginController = loginController\n\t}\n\n\tcontactListId(): Promise<Id | null> {\n\t\treturn this._contactListId.getAsync()\n\t}\n\n\tasync searchForContact(mailAddress: string): Promise<Contact | null> {\n\t\t//searching for contacts depends on searchFacade._db to be initialized. If the user has not logged in online the respective promise will never resolve.\n\t\tif (!this.loginController.isFullyLoggedIn()) {\n\t\t\tthrow new LoginIncompleteError(\"cannot search for contacts as online login is not completed\")\n\t\t}\n\t\tconst cleanedMailAddress = cleanMailAddress(mailAddress)\n\t\tlet result\n\t\ttry {\n\t\t\tresult = await this._searchFacade.search('\"' + cleanedMailAddress + '\"', createRestriction(\"contact\", null, null, \"mailAddress\", null), 0)\n\t\t} catch (e) {\n\t\t\t// If IndexedDB is not supported or isn't working for some reason we load contacts from the server and\n\t\t\t// search manually.\n\t\t\tif (e instanceof DbError) {\n\t\t\t\tconst listId = await this.contactListId()\n\n\t\t\t\tif (listId) {\n\t\t\t\t\tconst contacts = await this._entityClient.loadAll(ContactTypeRef, listId)\n\t\t\t\t\treturn contacts.find((contact) => contact.mailAddresses.some((a) => cleanMailAddress(a.address) === cleanedMailAddress)) ?? null\n\t\t\t\t} else {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthrow e\n\t\t\t}\n\t\t}\n\t\t// the result is sorted from newest to oldest, but we want to return the oldest first like before\n\t\tresult.results.sort(compareOldestFirst)\n\n\t\tfor (const contactId of result.results) {\n\t\t\ttry {\n\t\t\t\tconst contact = await this._entityClient.load(ContactTypeRef, contactId)\n\t\t\t\tif (contact.mailAddresses.some((a) => cleanMailAddress(a.address) === cleanedMailAddress)) {\n\t\t\t\t\treturn contact\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof NotFoundError || e instanceof NotAuthorizedError) {\n\t\t\t\t\tcontinue\n\t\t\t\t} else {\n\t\t\t\t\tthrow e\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\t/**\n\t * @pre locator.search.indexState().indexingSupported\n\t */\n\tasync searchForContacts(query: string, field: string, minSuggestionCount: number): Promise<Contact[]> {\n\t\tif (!this.loginController.isFullyLoggedIn()) {\n\t\t\tthrow new LoginIncompleteError(\"cannot search for contacts as online login is not completed\")\n\t\t}\n\t\tconst result = await this._searchFacade.search(query, createRestriction(\"contact\", null, null, field, null), minSuggestionCount)\n\t\tconst resultsByListId = groupBy(result.results, listIdPart)\n\t\tconst loadedContacts = await promiseMap(\n\t\t\tresultsByListId,\n\t\t\t([listId, idTuples]) => {\n\t\t\t\t// we try to load all contacts from the same list in one request\n\t\t\t\treturn this._entityClient.loadMultiple(ContactTypeRef, listId, idTuples.map(elementIdPart)).catch(\n\t\t\t\t\tofClass(NotAuthorizedError, (e) => {\n\t\t\t\t\t\tconsole.log(\"tried to access contact without authorization\", e)\n\t\t\t\t\t\treturn []\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t},\n\t\t\t{\n\t\t\t\tconcurrency: 3,\n\t\t\t},\n\t\t)\n\t\treturn flat(loadedContacts)\n\t}\n}\n\nexport function lazyContactListId(logins: LoginController, entityClient: EntityClient): LazyLoaded<Id | null> {\n\treturn new LazyLoaded(() => {\n\t\treturn entityClient\n\t\t\t.loadRoot(ContactListTypeRef, logins.getUserController().user.userGroup.group)\n\t\t\t.then((contactList: ContactList) => {\n\t\t\t\treturn contactList.contacts\n\t\t\t})\n\t\t\t.catch(\n\t\t\t\tofClass(NotFoundError, (e) => {\n\t\t\t\t\tif (!logins.getUserController().isInternalUser()) {\n\t\t\t\t\t\treturn null // external users have no contact list.\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t)\n\t})\n}\n"],"names":["contactId","socialUrlType","http","worldwidew","isSchemePrefixed","socialId","indexOf","isWwwDotPrefixed","type","trim","allowMultiple","allowedExtensions","fileInput","document","getElementById","body","neverNull","removeChild","newFileInput","createElement","setAttribute","map","e","join","style","display","promise","Promise","resolve","addEventListener","readLocalFiles","target","files","then","catch","async","console","log","Dialog","message","appendChild","click","listModel","selected","_a","areAllSelected","selectNone","selectAll","hours","minutes","amPm","minutesString","pad","date","lang","formats","dateTime","format","dateWithWeekdayAndYear","locator","logins","isInternalUserLoggedIn","isEnabled","FeatureType","ReplyOnly","character","iterator","value","peek","next","sliceStart","Math","max","position","sliceEnd","min","iteratee","length","ParserError","slice","parserA","parserB","iteratorPosition","separatorParser","valueParser","result","push","parser","mailtoUrl","url","URL","createMailAddressFromString","address","nameAndMailAddress","stringToNameAndMailAddress","name","mailAddress","addresses","pathname","split","decodedAddress","decodeURIComponent","filter","Boolean","recipients","to","undefined","cc","bcc","attach","subject","searchParams","entries","pair","paramName","toLowerCase","paramValue","replace","nextAddresses","warn","maxAttachmentSize","MAX_ATTACHMENT_SIZE","totalSize","attachableFiles","tooBigFiles","forEach","file","Number","size","conversationType","key","get","sortedLanguages","entityClient","loginController","getUserController","loadCustomer","customer","load","CustomerPropertiesTypeRef","properties","customerProperties","sL","notificationMailTemplates","find","nmt","language","code","mailboxProperties","senderAddress","_b","mailAddressProperties","a","senderName","model","mails","firstMail","first","mailboxDetails","getMailboxDetailsForMail","folders","getIndentedList","f","folder","getListId","folderInfo","indentLevel","level","MAX_FOLDER_INDENT_LEVEL","repeat","getFolderName","getFolderIconByType","getMailFolderType","startOfToday","Date","setHours","getTime","formatTime","DAY_IN_MILLIS","getFullYear","dateWithoutYear","dateWithMonth","mail","preferNameOnly","state","isExcludedMailAddress","sender","getMailAddressDisplayText","getSenderHeading","isLegacyMail","allRecipients","toRecipients","concat","ccRecipients","bccRecipients","recipientCount","parseInt","recipient","firstRecipient","getRecipientHeading","dom","visible","confidential","endsWith","MailBodyTypeRef","b","MailWrapper","isDetailsDraft","MailDetailsDraftTypeRef","mailDetailsDraft","d","details","mailDetailsId","mailDetails","loadMultiple","MailDetailsBlobTypeRef","listIdPart","elementIdPart","mailWrapper","isLegacy","headersId","getMail","headers","getLegacyMailHeaders","MailHeadersTypeRef","getDetails","getMailHeaders","isDesktop","folderSystem","omitLast","folderPath","getPathToFolder","_id","pop","replyType","styles","isSingleColumnLayout","props","cleanValue","inboxRules","rule","passwordStrength","PASSWORD_MIN_SECURE_VALUE","password","recipientInfo","reserved","getEnabledMailAddressesWithUser","userGroupInfo","getMailboxName","PASSWORD_MAX_VALUE","getPasswordStrength","dateWithWeekdayAndTime","userSettings","timeFormat","mailMembership","getUserMailGroupMembership","MailboxGroupRootTypeRef","group","grouproot","outOfOfficeNotification","OutOfOfficeNotificationTypeRef","notification","currentDate","enabled","startDate","endDate","text","MailFilterType","Read","unread","Unread","WithAttachments","attachments","DesktopConfigKey","module","import","n","fd","current","getCurrentSpellcheckLanguage","av","items","getItems","newLang","showDropDownSelectionDialog","desktopSettingsFacade","setStringConfigValue","spellcheck","selectedItem","i","query","restriction","selectedId","category","searchCategoryForRestriction","params","start","end","listId","list","field","path","route","startsWith","m","parsePathname","filterInt","fieldString","SEARCH_MAIL_FIELDS","Error","createRestriction","searchResult","moreResults","lastReadSearchIndexRow","every","word","id","defaultSender","contains","getEnabledMailAddressesForGroupInfo","downcast","reportMovedMails","typeNameMapping","getInboxRuleTypeNameMapping","t","timeRange","formatDate","getDayShifted","input","cleaned","match","digits","c","test","reverse","num","reduce","acc","cur","idx","WEBSITE_BASE_URL","fetch","href","userController","isUserMailbox","mailGroupInfo","isSameSearchRestriction","getHours","getMinutes","getSeconds","dateTimeShort","formatDateTimeShort","displayName","WEBAUTHN_STRING_MAX_BYTE_LENGTH","stringToUtf8Uint8Array","byteLength","dictionary","appState","window","tutao","baseUrl","location","protocol","hostname","port","prefixWithoutFile","response","json","pwGenerator","PasswordGenerator","random","insertPasswordOkAction","dialog","close","updateAction","generateRandomPassphrase","redraw","showActionDialog","title","child","view","PasswordGeneratorDialog","okAction","isApp","dateString","startOfYesterday","formatDateWithWeekday","DOMAIN_OR_TLD_REGEXP","domain","cleanMailAddress","entity","update","isUpdateForTypeRef","_type","instanceListId","isSameId","instanceId","getCleanedMailAddress","parts","organizationMessageEnabled","monthWithFullYear","contact","birthdayIso","isoDate","formatBirthdayNumeric","isoDateToBirthday","isUsingBottomNavigation","classList","remove","add","firstName","lastName","company","assertMainOrNode","WorkerClient","constructor","this","_deferredInitialized","defer","_isInitialized","initialized","env","mode","worker","Worker","_dispatcher","MessageDispatcher","WorkerTransport","queueCommands","postRequest","Request","getInitialEntropy","client","browserData","onerror","CryptoError","workerImpl","WorkerImpl","globalThis","testWorker","init","_queue","_transport","postMessage","msg","handleMessage","execNative","native","invokeNative","args","error","handleUncaughtError","objToError","facade","exposeLocalDelayed","loginListener","connectivityModel","progressTracker","eventController","operationProgressTracker","infoMessageHandler","getWorkerInterface","exposeRemote","request","_postRequest","restRequest","Array","from","arguments","reset","valueList","Uint32Array","crypto","getRandomValues","entropy","source","data","typeRef","isSameTypeRefByAttr","application","TAG","EventController","countersStream","stream","entityListeners","Set","addEntityListener","listener","has","removeEntityListener","delete","getCountersStream","identity","entityUpdates","eventOwnerGroupId","loginsUpdates","isUserLoggedIn","entityEventsReceived","entityUpdatesData","noOp","EntropyCollector","entropyFacade","scheduler","stopped","entropyCache","mouse","clientX","clientY","addEntropy","keyDown","keyCode","touch","touches","accelerometer","accelerationIncludingGravity","x","y","z","screen","orientation","angle","performance","now","valueOf","addPerformanceTimingValues","schedulePeriodic","sendEntropyToWorker","SEND_INTERVAL","getEntries","added","entry","toJSON","addNativeRandomValues","nbrOf32BitValues","stop","removeEventListener","SearchModel","searchFacade","_searchFacade","lastQuery","lastSelectedGroupInfoResult","lastSelectedWhitelabelChildrenInfoResult","indexingSupported","indexState","initializing","mailIndexEnabled","progress","currentMailIndexTimestamp","NOTHING_INDEXED_TIMESTAMP","aimedMailIndexTimestamp","indexedMailCount","failedIndexingUpTo","_lastQuery","_lastSearchPromise","_groupInfoRestrictionListId","searchQuery","minSuggestionCount","maxResults","isSameTypeRef","MailTypeRef","results","currentIndexTimestamp","matchWordOrder","moreResultsEntries","search","ofClass","DbError","isNewSearch","setGroupInfoRestrictionListId","getGroupInfoRestrictionListId","isSameAttributeIds","attributeIds","arrayEquals","DOMAIN_PART_REGEX","DOMAIN_REGEXP","RegExp","STRICT_USERNAME_MAIL_ADDR_REGEXP","EMAIL_ADDR_REGEXP","isMailAddress","string","strictUserName","isDomainName","domainName","isRegularExpression","startIndex","endIndex","cleanedMailAddress","substring","lastIndexOf","isTutanotaMailAddress","TUTANOTA_MAIL_ADDRESS_DOMAINS","some","tutaDomain","createNewContact","user","firstAndLastName","fullName","separator","fullNameToFirstAndLastName","addr","nameData","toUpperCase","mailAddressToFirstAndLastName","createContact","_owner","_ownerGroup","assertNotNull","memberships","groupType","GroupType","Contact","ma","createContactMailAddress","customTypeName","mailAddresses","areParticipantsRestricted","restrictions","participantGroupInfos","folderType","MailFolderType","CUSTOM","INBOX","SENT","TRASH","ARCHIVE","SPAM","DRAFT","mailboxDetail","mailGroup","getDefaultSender","getGroupInfoDisplayName","RecipientField","exports","UserError","TutanotaError","super","getMaybeLazy","MailModel","notifications","mailFacade","inboxRuleHandler","mailboxCounters","initialization","mailboxPropertiesPromises","Map","initListeners","lazyMemoized","updates","_mailboxCountersUpdates","_init","mailBoxDetailsPromises","getMailGroupMemberships","mailboxDetailsFromMembership","all","membership","mailboxGroupRoot","GroupInfoTypeRef","groupInfo","GroupTypeRef","mailbox","MailBoxTypeRef","loadFolders","FolderSystem","folderListId","loadAll","MailFolderTypeRef","InternalCommunication","getMailboxDetailsForMailListId","mailListId","detail","getMailboxDetails","md","getFolderByMailListId","mailGroupId","userMailGroupMembership","getMailboxFolders","getMailFolder","removeAllEmpty","updateMailFolderParent","assertSystemFolderOfType","reportType","reportMail","NotFoundError","targetMailFolder","moveMails","sourceMailFolder","mailChunks","splitInChunks","MAX_NBR_MOVE_DELETE_MAIL_SERVICE","mailChunk","mailsPerFolder","groupBy","_moveMails","isMovingMailsAllowed","isInternalUser","isExportingMailsAllowed","DisableMailExport","promiseMap","LockedError","concurrency","trashFolder","getSystemFolderByType","isSpamOrTrashFolder","_finallyDeleteMails","mailFolder","mailIds","deleteMails","operation","UserTypeRef","newMemberships","Mail","containsEventOfType","mailId","findAndApplyMatchingRule","isLeader","newId","_showNotification","counters","normalized","counterValues","count","showNotification","actions","_","set","focus","getCounterValue","mailGroupCounter","checkMailForPhishing","links","trash","descendants","getDescendantFoldersOfParent","sort","l","r","someNonEmpty","deleted","descendant","loadRange","GENERATED_MAX_ID","getCustomFoldersOfParent","getElementId","finallyDeleteCustomMailFolder","ProgrammingError","String","deleteFolder","PreconditionFailedError","unreadMails","fixupCounterForMailList","clearFolder","unsubscribe","existingPromise","loadOrCreateMailboxProperties","finally","setup","createMailboxProperties","existingId","MailboxPropertiesTypeRef","migrateFromOldSenderName","legacySenderName","getMailboxDetailsForMailGroup","createMailAddressProperties","options","onclick","Notification","permission","actualOptions","Object","assign","icon","NotificationIcon","requestPermission","filename","parsers","p","makeZeroOrMoreParser","anotherParser","parseResult","mapParser","mapper","allowed","includes","contextCentre","contextRadius","context","values","done","CalendarModel","alarmScheduler","serviceExecutor","mailModel","calendarFacade","fileController","pendingAlarmRequests","userAlarmToAlarmInfo","event","alarmInfos","zone","groupRoot","doCreate","newEvent","newAlarms","existingEvent","uid","startTime","repeatRule","repeatRule2","endType","endValue","frequency","interval","timeZone","dates","dates2","isSameExclusions","excludedDates","updateCalendarEvent","CalendarEventTypeRef","progressMonitor","calendarMemberships","Calendar","notFoundMemberships","groupInstances","CalendarGroupRootTypeRef","workDone","calendarInfos","longEvents","LazyLoaded","shared","mship","MembershipService","createMembershipRemoveData","findPrivateCalendar","a4","calendarInfo","loadCalendarInfos","createCalendar","color","addCalendar","userSettingsGroupRoot","newGroupSettings","createGroupSettings","groupSettings","removeTechnicalFields","assignEventId","_ownerEncSessionKey","createDateWrapper","_permissions","saveCalendarEvent","erase","getUserMailboxDetails","calendarEventUpdates","invites","CalendarEventUpdateTypeRef","invite","handleCalendarEventUpdate","fileId","FileTypeRef","dataFile","getAsDataFile","parseCalendarFile","a5","parsedCalendarData","getCalendarDataForUpdate","processCalendarUpdate","NotAuthorizedError","calendarData","contents","dbEvent","getEventByUid","method","CalendarMethod","REPLY","replyAttendee","findAttendeeInAddresses","attendees","clone","dbAttendee","status","doUpdateEvent","REQUEST","organizer","sequence","updateEventWithExternal","CANCEL","endTime","summary","description","alarms","loadAlarms","alarmInfo","updateEvent","scheduleAlarmsLocally","loadAndProcessCalendarUpdates","localAlarmsEnabled","eventsWithInfos","loadAlarmEvents","userAlarmInfos","userAlarmInfo","scheduleUserAlarmInfo","alarmInfoList","ids","alarmInfoId","UserAlarmInfoTypeRef","calendar","deleteCalendar","entityEventData","elementId","calendarRef","deferredEvent","getFromMap","calendarEvent","cancelUserAlarmInfo","DisableCalendar","alarmIdentifier","scheduleAlarm","userAlarmInfoId","identifier","cancelAlarm","ProgressTracker","onProgressUpdate","monitors","idCounter","registerMonitorSync","work","monitor","ProgressMonitor","percentage","onProgress","amount","getMonitor","completedAmount","totalWork","total","completedWork","workCompleted","MinimizedMailEditorViewModel","_minimizedEditors","minimizeMailEditor","sendMailModel","dispose","saveStatus","closeOverlayFunction","editor","lastThrow","reopenMinimizedEditor","show","removeMinimizedEditor","getMinimizedEditors","getEditorForDraft","draft","getDraft","CredentialsProvider","credentialsEncryption","storage","keyMigrator","databaseKeyFactory","sqliteCipherFacade","interWindowEventSender","credentialsAndKey","encryptedCredentials","encrypt","store","userId","persistentCredentials","loadByUserId","credentialInfo","decrypted","decrypt","databaseKey","generateKey","reEncrypted","credential","opts","deleteOfflineDb","localUserDataInvalidated","deleteDb","deleteByUserId","encryptionMode","getCredentialsEncryptionMode","oldKeyEncrypted","getCredentialsEncryptionKey","oldEncryptionMode","getCredentialEncryptionMode","newlyEncryptedKey","migrateCredentialsKey","setCredentialsEncryptionKey","setCredentialEncryptionMode","getSupportedEncryptionModes","reason","storedCredentials","storedCredential","CredentialsKeyProvider","nativeCredentials","credentialsStorage","deviceEncryptionFacade","encryptedCredentialsKey","decryptUsingKeychain","getEncryptionMode","credentialsKey","encryptUsingKeychain","NativeCredentialsEncryption","credentialsKeyProvider","credentials","encryptedPassword","getCredentialsKey","base64accessToken","accessToken","encryptedAccessToken","encryptedAccessTokenBase64","uint8ArrayToBase64","encryptedDatabaseKeyBase64","encryptedDatabaseKey","login","utf8Uint8ArrayToString","base64ToUint8Array","KeyPermanentlyInvalidatedError","stack","DefaultCredentialsKeyMigrator","oldMode","newMode","oldKeyDecryptedKeyBase64","StubCredentialsKeyMigrator","usingKeychainAuthentication","CredentialsEncryptionStub","PASSWORD_MIN_VALUE","_BAD_SEQUENCES","_BAD_STRINGS","badStrings","nbrOfLowerChars","_getNbrOfOccurrences","nbrOfConsecutiveLowerChars","_getLongestResult","nbrOfUpperChars","nbrOfConsecutiveUpperChars","nbrOfDigits","nbrOfConsecutiveDigits","nbrOfOtherChars","nbrOfConsecutiveOtherChars","nbrOfConsecutiveSame","minNbrOfCharsPerType","nbrOfMissingLowerChars","nbrOfMissingUpperChars","nbrOfMissingDigits","nbrOfMissingOtherChars","nbrOfSameChars","characterObject","_getNbrOfSameChars","nbrOfSequenceDigits","_getNbrOfSequenceChars","nbrOfBadStringDigits","strength","round","scaleToVisualPasswordStrength","scale","sequences","reverseToo","s","s1","maxFoundLen","sequenceLen","substringToCheck","regexp","val","CompletenessIndicator","attrs","border","theme","content_button","width","height","percentageCompleted","showProgressDialog","messageIdOrMessageFunction","action","progressStream","progressDialog","tabindex","oncreate","vnode","setTimeout","progressIcon","setCloseHandler","minDialogVisibilityMillis","isAdminClient","diff","delay","DefaultAnimationTime","FileController","blobFacade","fileFacade","observeProgress","tutanotaFiles","downloadedFiles","isOffline","downloadedFile","downloadAndDecrypt","handleDownloadErrors","openDownloadedFiles","writeDownloadedFiles","ConnectionError","cleanUp","isLegacyFile","downloadFileContent","bytes","createReferencingInstance","convertToDataFile","downloadAndDecryptDataFile","doDownload","blobs","errorAction","isOfflineError","fileList","nativeFiles","nativeFile","reject","reader","FileReader","onloadend","evt","readyState","DONE","Uint8Array","readAsArrayBuffer","zipDataFiles","dataFiles","zip","default","deduplicatedMap","deduplicateFilenames","df","sanitizeFilename","shift","binary","zipData","generateAsync","createDataFile","openDataFileInBrowser","webkitURL","mimeType","browser","browserVersion","blob","Blob","createObjectURL","download","revokeObjectURL","isIos","downloadPromise","legacyDownload","readAsDataURL","tutanotaFile","guiDownload","CancelledError","SecondFactorAuthView","webauthn","canLogin","otp","renderWebauthn","_renderOtp","_renderRecover","TextField","label","codeFieldValue","autocompleteAs","oninput","onValueChanged","injectionsRight","inProgress","renderWebauthnLogin","_renderOtherDomainLogin","doWebauthnButtonAttrs","doWebauthn","Button","Icon","large","fill","content_accent","src","SecondFactorImage","otherLoginDomain","onRecover","preventDefault","appIdToLoginDomain","appId","SecondFactorAuthDialog","webauthnClient","loginFacade","authData","onClose","waitingForSecondFactorDialog","webauthnState","otpState","static","abortCurrentOperation","u2fChallenge","challenges","challenge","SecondFactorType","u2f","otpChallenge","totp","u2fSupported","isSupported","canLoginWithU2f","canAttempt","cannotAttempt","canAttemptChallenge","getFirstOrThrow","allowOkWithReturn","newValue","recoverLogin","onConfirmOtp","cancelAction","cancel","createSecondFactorAuthData","session","sessionId","otpCode","authenticateWithSecondFactor","NotAuthenticatedError","BadRequestError","AccessBlockedError","cancelCreateSession","webauthnResponseData","authenticate","WebauthnError","SecondFactorHandler","_eventController","_entityClient","_webauthnClient","_loginFacade","_otherLoginSessionId","_otherLoginDialog","_otherLoginListenerInitialized","_waitingForSecondFactorDialog","setupAcceptOtherClientLoginListener","_entityEventsReceived","SessionTypeRef","_showConfirmLoginDialog","loginIpAddress","clientIdentifier","closeWaitingForSecondFactorDialog","WEBAUTHN_RP_ID","U2f_APPID_SUFFIX","U2F_APPID","WebauthnClient","clientWebRoot","partitionAsync","keys","k","canAttemptChallengeForRpId","canAttemptChallengeForU2FAppId","getChallenge","registrationResult","register","attestationObject","parseAttestationObject","publicKey","parsePublicKey","createU2fRegisteredDevice","keyHandle","rawId","rpId","serializePublicKey","compromised","counter","signal","allowedKeys","signResult","sign","selectAuthenticationUrl","createWebauthnResponseData","clientData","clientDataJSON","signature","authenticatorData","selectedClientUrl","webauthnKey","isLegacyU2fKey","legacyU2fKeyToBaseUrl","assert","raw","decode","dataView","DataView","ArrayBuffer","index","setUint8","credentialIdLength","getUint16","publicKeyBytes","buffer","useMaps","encoded","COSEAlgorithmIdentifier","BrowserWebauthn","api","currentOperationSignal","rpIdFromHostname","appidFromHostname","BigInt","polyfilled","publicKeyCredentialCreationOptions","rp","pubKeyCredParams","alg","ES256","authenticatorSelection","authenticatorAttachment","userVerification","timeout","attestation","AbortController","create","allowCredentials","publicKeyCredentialRequestOptions","extensions","appid","assertion","publicKeyCredential","assertionResponse","abort","getApiOrigin","Stage","number","minPings","maxPings","collectedMetrics","metricConfigs","completeStage","setMetric","metric","setMetricConfig","metricConfig","ObsoleteStage","UsageTest","testId","testName","variant","active","stages","lastCompletedStage","meta","allowEarlyRestarts","sentPings","started","recordTime","forceRestart","getStage","isStarted","stageNum","stage","addStage","renderVariant","variants","complete","pingAdapter","currentStage","secondsPassed","lastPingDate","toString","sendPing","ObsoleteUsageTest","obsoleteStage","UsageTestController","tests","obsoleteUsageTest","addTest","addTests","setTests","clear","getTest","testIdOrName","getObsoleteTest","EphemeralUsageTestStorage","assignments","testDeviceId","getAssignments","getTestDeviceId","storeAssignments","persistedAssignmentData","storeTestDeviceId","UsageTestModel","storages","dateProvider","usageTestController","storageBehavior","lastOptInDecision","lastPing","updateCustomerProperties","UserSettingsGroupRootTypeRef","updatedOptInDecision","usageDataOptedIn","loadActiveUsageTests","setVariant","CustomerTypeRef","setStorageBehavior","isCustomerOptedOut","usageDataOptedOut","showOptInIndicator","decision","createUserSettingsGroupRoot","doLoadActiveUsageTests","getOptInDecision","persistedData","modelVersion","usageModelVersion","updatedAt","assignmentsToTests","loadAssignments","resolveTypeReference","UsageTestAssignmentTypeRef","version","createUsageTestAssignmentIn","put","UsageTestAssignmentService","suspensionBehavior","post","SuspensionError","usageTestAssignment","sendPings","stageConfig","metrics","configValues","metricConfigValue","doSendPing","createUsageTestMetricData","createUsageTestParticipationIn","UsageTestParticipationService","storedAssignments","assignment","PageContextLoginListener","secondFactorHandler","loginPromise","fullLoginFailed","waitForFullLogin","onPartialLoginSuccess","reloginForExpiredSession","onRetryLogin","onSecondFactorChallenge","showSecondFactorAuthenticationDialog","getFullLoginFailed","FileControllerBrowser","assertOnlyDataFiles","fileToSave","sortableTimestamp","FileControllerNative","fileApp","isElectronClient","isTest","assertOnlyFileReferences","deleteFile","fileReference","writeDataFile","isAndroidApp","putFileIntoDownloadsFolder","isIOSApp","open","downloadFileContentNative","downloadAndDecryptNative","processDownloadedFilesIOS","processDownloadedFilesDesktop","openFiles","readDataFile","fileInTemp","NewsModel","newsListItemFactory","liveNewsIds","liveNewsListItems","NewsService","newsItemId","newsItemIds","newsItemName","newsListItem","isShown","createNewsIn","loadNewsIds","acknowledgeNewsForDevice","acknowledgeNewsItemForDevice","hasAcknowledgedNewsForDevice","hasAcknowledgedNewsItemForDevice","WebsocketConnectivityModel","eventBus","wsState","leaderStatus","wsConnectionState","wsConnection","tryReconnect","closeIfOpen","enableAutomaticState","option","OperationProgressTracker","progressPerOp","operationId","startNewOperation","progressValue","InfoMessageHandler","searchModel","showNotificationOverlay","translationKey","moveMailDataPerFolder","applyingRules","sendMoveMailRequest","moveToTargetFolder","targetFolder","applyMatchingRules","debounce","InboxRuleHandler","applyRulesOnServer","_errors","isInboxList","isPremiumAccount","inboxRule","rules","asyncFind","ruleType","differentEnvelopeSender","_checkEmailAddresses","getMailDetails","_checkContainsRule","mailHeaders","checkInboxRule","v","_findMatchingRule","getFolderById","moveMailData","folderMoveMailData","createMoveMailData","mailDetailsBlobId","_matchesRegularExpression","flags","pattern","ThrottledRouter","throttledRoute","debounceStart","throttleRoute","getFullPath","routeTo","ScopedRouter","router","scope","RecipientsModel","contactModel","NoZoneDateProvider","sendMailModelSyncFactory","offlineIndicatorModel","offlineIndicatorViewModel","newsModel","inboxRuleHanlder","SearchViewModel","conversationViewModelFactory","searchRouter","scopedSearchRouter","indexerFacade","mailOpenedListener","ReceivedGroupInvitationsModel","CalendarViewModel","calendarInvitations","getMailboxProperties","calendarEventModel","calendarModel","deviceConfig","SendMailModel","recipientsModel","noZoneDateProvider","responseTo","makeCalendarEventModel","getTimeZone","calendarUpdateDistributor","a6","a7","sendMailModelFactory","RecipientsSearchModel","systemFacade","MailViewerViewModel","showFolder","configFacade","workerFacade","ExternalLoginViewModel","E","credentialsProvider","getNativeInterface","pushService","commonSystemFacade","themeFacade","MailAddressTableModel","M","nameChanger","ownMailAddressNameChanger","mailAddressFacade","adminNameChanger","OwnMailAddressNameChanger","O","AnotherUserMailAddressNameChanger","j","desktopSystemFacade","getExposedNativeInterface","isBrowser","exposedNativeInterfaces","requestType","nativeInterfaces","OfflineIndicatorViewModel","cacheStorage","mailViewModel","MailViewModel","w","throttledRouter","SearchRouter","S","unscopedSearchRouter","onEmailOpened","sendSocketMessage","contactViewModel","ContactViewModel","ConversationViewModel","factory","mailViewerViewModelFactory","AlarmSchedulerImpl","a8","DefaultDateProvider","_workerDeferred","bootstrapWorker","_createInstances","_entropyCollector","customerFacade","giftCardFacade","groupManagementFacade","shareFacade","counterFacade","bookingFacade","userManagementFacade","contactFormFacade","restInterface","cryptoFacade","LoginController","EntityClient","Const","WebDesktopFacade","WebMobileFacade","WebCommonNativeFacade","WebInterWindowEventFacade","WebAuthnFacadeSendDispatcher","h","createNativeInterfaces","createDesktopInterfaces","N","windowFacade","desktopInterfaces","searchTextFacade","webAuthn","getWebRoot","isOfflineStorageAvailable","sqlCipherFacade","navigator","nativeApp","NativeCredentialsFacadeSendDispatcher","g","credentialsKeyMigrator","DatabaseKeyFactory","createCredentialsProvider","_d","_c","usageTestModel","UsageOptInNews","RecoveryCodeNews","PinBiometricsNews","ReferralLinkNews","NewPlansNews","NewPlansOfferEndingNews","ContactModelImpl","minimizedMailModel","SchedulerImpl","ShortcutDialog","shortcuts","textFieldAttrs","shortcut","Keys","META","ctrl","CTRL","SHIFT","alt","ALT","makeShortcutName","help","disabled","showNotAvailableForFreeDialog","acceptedPlans","NewPaidPlans","wizard","customerInfo","loadCustomerInfo","plan","NewBusinessPlans","NewPersonalPlans","showUpgradeWizard","createNotAvailableForFreeClickHandler","available","showMoreStorageNeededOrderDialog","isGlobalAdmin","confirm","isFreeAccount","usedStorage","readUsedUserStorage","getAvailableMatchingPlans","plansWithMoreStorage","config","storageGb","MEMORY_GB_FACTOR","isEmpty","showPlanUpgradeRequiredDialog","showSwitchPlanDialog","getPlanType","showUpgradeWizardOrSwitchSubscriptionDialog","U","bookings","BookingTypeRef","showSwitchDialog","loadAccountingInfo","copyToClipboard","clipboard","writeText","el","contentEditable","readOnly","range","createRange","selectNodeContents","getSelection","removeAllRanges","addRange","setSelectionRange","execCommand","iosCopyToClipboard","textArea","select","err","fallbackCopyToClipboard","promptForFeedbackAndSend","loggedIn","ignoreChecked","sendLogs","logs","getLogAttachments","preparedContent","prepareFeedbackContent","detailsExpanded","userMessage","errorOkAction","notificationOverlay.show","Checkbox","checked","onChecked","okActionTextId","helpLabel","heading","logDialog","closeLogDialog","largeDialog","right","middle","addShortcut","ESC","exec","showLogDialog","uint8ArrayToString","ExpanderButton","expanded","onExpandedChange","ExpanderPanel","content","ret","ignored","sendFeedbackMail","showErrorDialogNotLoggedIn","marginTop","px","escapedBody","Option","innerHTML","createDraft","bodyText","senderMailAddress","previousMessageId","replyTos","sendDraft","timestamp","clientInfoString","errorToString","versionNumber","typedKeys","AccountType","typeName","accountType","Mode","Browser","Test","platformId","toUTCString","userAgent","global","logger","mainEntries","mainLogFile","createLogFile","workerLogEntries","getLog","workerLogFile","nativeLog","nativeLogFile","headerAttrs","left","editDialog","canceledPremiumAccount","unknownErrorDialogActive","notConnectedDialogActive","invalidSoftwareVersionActive","loginDialogActive","isLoggingOut","serviceUnavailableDialogActive","requestTimeoutDialogActive","shownQuotaError","showingImportError","ignoredMessages","handleUncaughtErrorImpl","choice","reload","handleImportError","showUserError","showSnackBar","button","InvalidSoftwareVersionError","AccessDeactivatedError","AccessExpiredError","SessionExpiredError","OutOfSyncError","sessionType","logout","noAutoLogin","InsufficientStorageError","errorMessage","ServiceUnavailableError","RequestTimeoutError","IndexingNotSupportedError","QuotaExceededError","OfflineDbClosedError","ignoredError","waitForPartialLogin","oldSessionType","oldCredentials","getCredentialsByUserId","deleteIfExists","sessionReset","resetSession","showRequestPasswordDialog","pw","newSessionData","createSession","getLoginErrorMessage","textId","testError","getFonts","fonts","scrollbarWidthHeight","checkApprovalStatus","includeInvoiceNotPaidForAdmin","defaultStatus","approvalStatus","getCustomerApprovalStatus","ApprovalStatus","REGISTRATION_APPROVED","REGISTRATION_APPROVAL_NEEDED","DELAYED","REGISTRATION_APPROVAL_NEEDED_AND_INITIALLY_ACCESSED","DELAYED_AND_INITIALLY_ACCESSED","generatedIdToTimestamp","INVOICE_NOT_PAID","SPAM_SENDER","PAID_SUBSCRIPTION_NEEDED","reminder","confirmed","isExternalLogin","TooManyRequestsError","CredentialAuthenticationError","handleExpectedLoginError","handler","getLoginErrorStateAndMessage","getReferralCodeFromParams","urlParams","ref","getRegistrationDataIdFromParams","hashParams","token","registerStyle","font_size_small","hpad_small","vpad_xs","bottom","content_bg","content_fg","opacity","transition","outline","margin","html","padding","background","cursor","overflow","font_size_base","line_height","font_size_smaller","list_border","content_border","vpad","vpad_small","hpad","vpad_large","vpad_xl","hpad_medium","hpad_button","hpad_large","vpad_ml","button_floating_size","hpad_nav_button","noselect","navigation_bg","content_message_bg","getElevatedBackground","list_bg","list_accent_fg","content_button_icon_selected","content_button_icon","top","overflowAuto","isMobileDevice","button_height","flex","border_radius","border_radius_big","border_radius_small","icon_size_medium","icon_size_large","stateBgLike","stateBgHover","icon_size_xl","icon_message_box","button_height_compact","stateBgFocus","stateBgActive","transform","navbar_height","navigation_border","positionValue","bottom_nav_bar","header_bg","button_icon_bg_size","dot_size","header_logo_height","position_absolute","border_selection","list_row_height","list_alternate_bg","speak","visibility","header_box_shadow_bg","isDesktopDevice","text_bubble_tpad","button_height_bubble","button_bubble_bg","button_bubble_fg","mark","resize","td","column_width_s_desktop","animation","appearance","checkbox_size","FontIcons","calendar_hour_height","calendar_hour_width","list_message_bg","calendar_event_border","column_width_s_mobile","navigation_button","drawer_menu_width","getNavigationMenuBg","desktop_layout_width","hpad_large_mobile","calendar_hour_width_mobile","pre","keyManager","registerShortcuts","MAIL_PREFIX","C","DisableContacts","CONTACTS_PREFIX","CALENDAR_PREFIX","SETTINGS_PREFIX","L","LogoutUrl","urlHash","loadRedeemGiftCardWizard","resetAction","subscriptionParams","subscription","getSubscriptionParameters","registrationDataId","referralCode","loadSignupWizard","simpleDate","dateWithWeekday","time","formatStorageSize","sizeInBytes","unitIndex","floor","lastPath","oninit","requestedPath","onNewUrl","onbeforeupdate","pureComponent","isDesktopLayout","DesktopBaseHeader","landmarkAttrs","trust","logo","gestureInfoFromTouch","pageX","pageY","viewColumns","parentName","resizeListener","_updateVisibleBackgroundColumns","handleHistoryEvent","prev","getPreviousColumn","columnType","focusPreviousColumn","isForegroundColumnFocused","focusNextColumn","addResizeListener","addHistoryEventListener","onremove","removeResizeListener","removeHistoryEventListener","_getSideColDom","columns","_domColumn","_mainColumn","column","focusedColumn","_visibleBackgroundColumns","_busy","_parentName","_isModalBackgroundVisible","setRole","_getColumnRole","mainSliderColumns","_getColumnsForMainSlider","allBackgroundColumnsAreVisible","_attachTouchHandler","isInForeground","header","_domSlidingPart","getWidth","getOffset","rightBorder","bottomNav","_getColumnsForOverlay","_createModalBackground","getMainColumn","zIndex","animations","alpha","modal_bg","onbeforeremove","visibleColumns","remainingSpace","innerWidth","minWidth","nextVisibleColumn","getNextVisibleColumn","_distributeRemainingSpace","_setWidthForHiddenColumns","updateOffsets","allColumnsVisible","requestAnimationFrame","getVisibleBackgroundColumns","isUsingOverlayColumns","allColumns","nextColumn","getBackgroundColumns","spacePerColumn","visibleColumn","setWidth","spaceForThisColumn","maxWidth","foreGroundColumn","additionalSpaceForColumn","viewColumn","_slideForegroundColumn","currentOffset","getBoundingClientRect","_slideBackgroundColumns","waitForAnimation","nextVisibleViewColumn","oldOffset","newOffset","easing","ease","inOut","removed","splice","foregroundColumn","toForeground","getOffsetForeground","in","offset","lastColumn","isFocusPreviousPossible","indexOfCurrent","visibleColumnIndex","isFirstBackgroundColumnFocused","element","lastGestureInfo","oldGestureInfo","initialGestureInfo","directionLock","gestureEnd","safeLastGestureInfo","safeOldGestureInfo","changedTouches","mainCol","sideCol","mainColRect","velocity","hide","colRect","listeners","touchstart","stopPropagation","touchmove","gestureInfo","safeInitialGestureInfo","newTouchPos","sideColRect","safeLastInfo","abs","newTranslate","slidingDomRect","cancelable","touchend","touchcancel","getContactDisplayName","nickname","birthday","year","month","day","simpleDateWithoutYear","ProgressBar","getProgress","renderNavigation","searchBar","OfflineIndicatorDesktop","getCurrentAttrs","NavBar","renderButtons","NavButton","isSelectedPrefix","colors","headerContent","children","isInMultiselect","updateDomBg","highlight","backgroundColor","paddingTop","paddingBottom","marginRight","onSelectedChangeRef","call","onpointerdown","onpointerup","onpointercancel","onpointerleave","duration","NewsList","ColumnEmptyMessageBox","liveNewsId","render","DrawerMenu","liveNewsCount","getSafeAreaInsetLeft","closeButton","closeAction","loaded","DialogHeaderBar","showNewsDialog","CounterBadge","isGlobalAdminUserLoggedIn","showPurchaseGiftCardDialog","openNewWindow","upgradeWizard","createDropdown","lazyButtons","supportModule","showSupportDialog","openF1Help","noBubble","drawer","ariaLabel","renderMainButton","onscroll","scrollTop","borderTop","FolderColumnHeaderButton","toLocaleUpperCase","fontSize","responsiveCardHMargin","vertical","SEARCH_PREFIX","item","renderRow","resolveMaybeLazy","emptyListMessage","onupdate","newSelectedItem","_onSelectionChanged","isSelected","onItemSelected","ondblclick","onItemDoubleClicked","renderItem","marginBottom","scrollDom","selectedIndex","selectedDomElement","scrollIntoView","block","inline","SearchDropDown","keyboardHeight","addKeyboardSizeListener","newSize","selectedSuggestionIndex","domSuggestions","scrollListDom","dropdownHeight","maxHeight","MAX_VALUE","suggestions","availableHeight","innerHeight","suggestion","renderSuggestion","firstRow","secondRow","class","onmousedown","onSuggestionSelected","parseMailAddress","selectedSuggestionIdx","focused","renderTextField","renderSuggestions","BubbleTextField","onInput","remainingText","newRecipients","errors","textParts","part","parsed","parsePastedInput","lastCharacter","textMinusLast","parseTypedInput","onRecipientAdded","onTextChanged","renderBubbleText","findRecipientWithAddress","getBubbleDropdownAttrs","getRecipientClickedDropdownAttrs","onBackspace","onRecipientRemoved","onEnterKey","resolveInput","onUpKey","setSelectedSuggestionIdx","getSelectedSuggestionIdx","onDownKey","onFocus","onBlur","isLoading","selectSuggestion","maxSuggestionsToShow","selection","attachment","extension","getFileExtension","rest","getFileBaseName","staticRightText","parentRect","panel","AttachmentDetailsPopup","deferAfterClose","showAttachmentDetailsPopup","closeDefer","targetRect","targetWidth","_shortcuts","domContent","domPanel","focusedFirst","TAB","focusPrevious","focusNext","thenClose","D","DELETE","bind","animatePanel","renderContent","targetHeight","offsetHeight","mutations","spaceBelow","getSafeAreaInsetBottom","out","modal","displayUnique","backgroundClick","startHeight","startWidth","offsetWidth","popState","mobileHeader","desktopToolbar","columnLayout","__","marginLeft","SelectAllCheckbox","onchange","toggleSelectAll","BaseMobileHeader","center","injections","navbar_height_mobile","firstVisibleColumn","MobileHeaderMenuButton","backAction","IconButton","MobileHeaderTitle","OfflineIndicatorMobile","multicolumnActions","primaryAction","NBSP","LazySearchBar","getSync","clickAction","loadState","loading","rangeSelectionAnchorElement","rawStateStream","unfilteredItems","filteredItems","inMultiselect","loadingStatus","ListLoadingState","Idle","loadingAll","selectedItems","activeElement","stateStream","foundIndex","binarySearch","sortCompare","activeIndex","getSelectedAsArray","memoizedWithHiddenArgument","getUnfilteredAsArray","rawState","updateState","newStatePart","waitUtilInit","deferred","doLoad","Loading","ConnectionLost","lastItem","last","newItems","topId","PageSize","newUnfilteredItems","newFilteredItems","applyFilter","Done","setFilter","reapplyFilter","newSelectedItems","isFiltered","loadSingle","settledThen","addToLoadedEntities","updateLoadedEntity","deleteLoadedEntity","positionToUpdateUnfiltered","findIndex","positionToUpdateFiltered","oldItem","activeElementUpdated","newActiveElement","onSingleSelection","onSingleExclusiveSelection","onSingleInclusiveSelection","itemId","shouldStop","foundItem","loadMore","selectRangeTowards","clickedItemIndex","nearestSelectedIndex","currentSelectedItemIndex","assertNonNull","itemsToAddToSelection","setAddAll","selectPrevious","multiselect","oldActiveItem","newActiveItem","findLast","previousActiveIndex","selectNext","isItemSelected","findBy","enterMultiselect","isLoadedCompletely","cancelLoadAll","isEmptyAndDone","stopLoading","DEFAULT_MODE","showCredentialsEncryptionModeDialog","CredentialEncryptionMethodDialog","showAndWaitForSelection","supportedModes","previousSelection","_credentialsProvider","_supportedModes","_previousSelection","_error","_finished","_dialog","_onModeSelected","SelectCredentialsEncryptionModeView","onModeSelected","credentialsDialog","setCredentialsEncryptionMode","clearCredentials","_currentMode","_getSupportedOptions","liveDataAttrs","RadioSelector","selectedOption","onOptionSelected","_renderSelectButton","helpText","textTransform","BYTE_RANGE","pow","randomizer","usedWords","pickRandomWordFromDictionary","generateRandomNumberInRange","byteNumber","generateRandomNumber","minHeight","rel","getStringConfigValue","languages","af","getSpellcheckLanguages","langCode","locale","cy","fo","hy","nb","sh","sq","ta","tg","pt","getMissingLanguage","localeCompare","plans","ResolveMode","ResolvableRecipientImpl","_address","_name","lazyType","initialType","lazyContact","initialContact","arg","resolveMode","overrideContact","resolveType","resolveContact","Eager","setName","newName","setContact","newContact","getAsync","isResolved","isLoaded","whenResolved","resolved","cleanedAddress","getRecipientKeyData","contactListId","ContactTypeRef","foundContact","searchForContact","ReferralLinkViewer","getReferralLinkTextFieldAttrs","referralLink","ifAllowedTutanotaLinks","link","copyAction","shareAction","shareMessage","getReferralLinkMessage","shareText","mailEditorModule","writeInviteMail","getReferralLink","ReferralCodeService","createReferralCodePostIn","requestNewReferralCode","FIXED_FREE_SEARCH_DAYS","SEARCH_CATEGORIES","WhitelabelChildTypeRef","typeModels","associations","getFreeSearchStartDate","getStartOfDay","searchCategory","fieldData","sanitizer","uiUpdateCallback","sanitizedText","sanitizeHTML","blockExternalContent","UserController","_userGroupInfo","_props","_userSettingsGroupRoot","planConfig","Admin","isGlobalOrLocalAdmin","LocalAdmin","FREE","PREMIUM","EXTERNAL","CustomerInfoTypeRef","planServiceGetOut","PlanService","isLegacyPlan","LegacyPlans","PlanType","Free","customPlan","planType","getPlanConfig","multiUser","isCustomizationEnabledForCustomer","MultipleUsers","AccountingInfoTypeRef","accountingInfo","getCalendarMemberships","getLocalAdminGroupMemberships","getTemplateMemberships","Template","userGroup","TutanotaPropertiesTypeRef","loadRoot","sync","deleteSessionSync","deleteSession","sendBeacon","CloseSessionService","requestObject","createCloseSessionServicePost","queued","JSON","stringify","xhr","XMLHttpRequest","setRequestHeader","sysTypeModels","Session","onload","send","getWhitelabelCustomizations","domainInfos","domainInfo","whitelabelConfig","domainInfoAndConfig","mapAndFilterNull","WhitelabelConfigTypeRef","searchResults","_selectedIdx","currentQuery","previousQuery","findContacts","newSuggestions","contacts","searchForContacts","LoginIncompleteError","recipientsOfContact","Lazy","App","contactSuggestions","findNativeContacts","suggestion1","suggestion2","findSuggestions","PermissionError","MoreInfoLink","newsId","optedIn","acknowledgeNews","buttonAttrs","setOptInDecision","recoveryCode","recoverCodeField","RecoverCodeField","R","customerCreationTime","created","daysToMillis","showMessage","recoverCode","showButtons","renderCopyButton","renderPrintButton","confirmButton","renderDoneButton","renderDisplayButton","allowCancel","print","getRecoverCodeDialogAfterPasswordVerification","auth","getRecoverCode","createRecoveryCode","displayedLink","renderLaterButton","renderDismissButton","renderConfirmButton","getCredentialsInfoByUserId","businessUse","customerCreatedTime","isNewPaidPlan","acknowledgeAction","lazyContactListId","ContactListTypeRef","contactList","_contactListId","isFullyLoggedIn","compareOldestFirst","resultsByListId","loadedContacts","idTuples","flat"],"mappings":"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"}