{"version":3,"file":"contacts-fff23b94.js","sources":["../../../src/contacts/view/ContactGuiUtils.ts","../../../src/contacts/ContactAggregateEditor.ts","../../../src/contacts/ContactEditor.ts","../../../src/contacts/view/ContactRow.ts","../../../src/contacts/view/ContactListView.ts","../../../src/contacts/view/MultiContactViewer.ts","../../../src/contacts/view/ContactMergeView.ts","../../../src/contacts/ContactMergeUtils.ts","../../../src/contacts/VCardExporter.ts","../../../src/contacts/view/ContactViewer.ts","../../../src/contacts/view/ContactCardViewer.ts","../../../src/contacts/view/MobileContactActionBar.ts","../../../src/contacts/view/ContactViewerActions.ts","../../../src/contacts/VCardImporter.ts","../../../src/contacts/view/ImportAsVCard.ts","../../../src/contacts/view/ContactView.ts","../../../src/contacts/view/ContactViewModel.ts"],"sourcesContent":["import { ContactAddressType, ContactPhoneNumberType, ContactSocialType } from \"../../api/common/TutanotaConstants\"\nimport type { TranslationKey } from \"../../misc/LanguageViewModel\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport type { Contact } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { sortCompareByReverseId } from \"../../api/common/utils/EntityUtils\"\n\nexport const ContactMailAddressTypeToLabel: Record<ContactAddressType, TranslationKey> = {\n\t[ContactAddressType.PRIVATE]: \"private_label\",\n\t[ContactAddressType.WORK]: \"work_label\",\n\t[ContactAddressType.OTHER]: \"other_label\",\n\t[ContactAddressType.CUSTOM]: \"custom_label\",\n}\n\nexport function getContactAddressTypeLabel(type: ContactAddressType, custom: string): string {\n\tif (type === ContactAddressType.CUSTOM) {\n\t\treturn custom\n\t} else {\n\t\treturn lang.get(ContactMailAddressTypeToLabel[type])\n\t}\n}\n\nexport const ContactPhoneNumberTypeToLabel: Record<ContactPhoneNumberType, TranslationKey> = {\n\t[ContactPhoneNumberType.PRIVATE]: \"private_label\",\n\t[ContactPhoneNumberType.WORK]: \"work_label\",\n\t[ContactPhoneNumberType.MOBILE]: \"mobile_label\",\n\t[ContactPhoneNumberType.FAX]: \"fax_label\",\n\t[ContactPhoneNumberType.OTHER]: \"other_label\",\n\t[ContactPhoneNumberType.CUSTOM]: \"custom_label\",\n}\n\nexport function getContactPhoneNumberTypeLabel(type: ContactPhoneNumberType, custom: string): string {\n\tif (type === ContactPhoneNumberType.CUSTOM) {\n\t\treturn custom\n\t} else {\n\t\treturn lang.get(ContactPhoneNumberTypeToLabel[type])\n\t}\n}\n\nexport const ContactSocialTypeToLabel: Record<ContactSocialType, TranslationKey> = {\n\t[ContactSocialType.TWITTER]: \"twitter_label\",\n\t[ContactSocialType.FACEBOOK]: \"facebook_label\",\n\t[ContactSocialType.XING]: \"xing_label\",\n\t[ContactSocialType.LINKED_IN]: \"linkedin_label\",\n\t[ContactSocialType.OTHER]: \"other_label\",\n\t[ContactSocialType.CUSTOM]: \"custom_label\",\n}\n\nexport function getContactSocialTypeLabel(type: ContactSocialType, custom: string): string {\n\tif (type === ContactSocialType.CUSTOM) {\n\t\treturn custom\n\t} else {\n\t\treturn lang.get(ContactSocialTypeToLabel[type])\n\t}\n}\n\nexport type ContactComparator = (arg0: Contact, arg1: Contact) => number\n\n/**\n * Sorts by the following preferences:\n * 1. first name\n * 2. second name\n * 3. first email address\n * 4. id\n * Missing fields are sorted below existing fields\n */\nexport function compareContacts(contact1: Contact, contact2: Contact, sortByFirstName: boolean = true): number {\n\tlet c1First = contact1.firstName.trim()\n\tlet c2First = contact2.firstName.trim()\n\tlet c1Last = contact1.lastName.trim()\n\tlet c2Last = contact2.lastName.trim()\n\tlet c1MailLength = contact1.mailAddresses.length\n\tlet c2MailLength = contact2.mailAddresses.length\n\tlet [c1Primary, c1Secondary] = sortByFirstName ? [c1First, c1Last] : [c1Last, c1First]\n\tlet [c2Primary, c2Secondary] = sortByFirstName ? [c2First, c2Last] : [c2Last, c2First]\n\n\t// If the contact doesn't have either the first or the last name, use company as the first name. We cannot just make a string out of it\n\t// and compare it because we would lose priority of first name over last name and set name over unset name.\n\tif (!c1Primary && !c1Secondary) {\n\t\tc1Primary = contact1.company\n\t}\n\n\tif (!c2Primary && !c2Secondary) {\n\t\tc2Primary = contact2.company\n\t}\n\n\tif (c1Primary && !c2Primary) {\n\t\treturn -1\n\t} else if (c2Primary && !c1Primary) {\n\t\treturn 1\n\t} else {\n\t\tlet result = c1Primary.localeCompare(c2Primary)\n\n\t\tif (result === 0) {\n\t\t\tif (c1Secondary && !c2Secondary) {\n\t\t\t\treturn -1\n\t\t\t} else if (c2Secondary && !c1Secondary) {\n\t\t\t\treturn 1\n\t\t\t} else {\n\t\t\t\tresult = c1Secondary.localeCompare(c2Secondary)\n\t\t\t}\n\t\t}\n\n\t\tif (result === 0) {\n\t\t\t// names are equal or no names in contact\n\t\t\tif (c1MailLength > 0 && c2MailLength === 0) {\n\t\t\t\treturn -1\n\t\t\t} else if (c2MailLength > 0 && c1MailLength === 0) {\n\t\t\t\treturn 1\n\t\t\t} else if (c1MailLength === 0 && c2MailLength === 0) {\n\t\t\t\t// see Multiselect with shift and up arrow not working properly #152 at github\n\t\t\t\treturn sortCompareByReverseId(contact1, contact2)\n\t\t\t} else {\n\t\t\t\tresult = contact1.mailAddresses[0].address.trim().localeCompare(contact2.mailAddresses[0].address.trim())\n\n\t\t\t\tif (result === 0) {\n\t\t\t\t\t// see Multiselect with shift and up arrow not working properly #152 at github\n\t\t\t\t\treturn sortCompareByReverseId(contact1, contact2)\n\t\t\t\t} else {\n\t\t\t\t\treturn result\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\treturn result\n\t\t}\n\t}\n}\n","import type { TextFieldType } from \"../gui/base/TextField.js\"\nimport { TextField } from \"../gui/base/TextField.js\"\nimport type { TranslationKey } from \"../misc/LanguageViewModel\"\nimport { lang } from \"../misc/LanguageViewModel\"\nimport m, { Children, Component, Vnode, VnodeDOM } from \"mithril\"\nimport { Icons } from \"../gui/base/icons/Icons\"\nimport { animations, height, opacity } from \"../gui/animation/Animations\"\nimport { attachDropdown } from \"../gui/base/Dropdown.js\"\nimport { IconButton } from \"../gui/base/IconButton.js\"\nimport { BootIcons } from \"../gui/base/icons/BootIcons.js\"\nimport { ButtonSize } from \"../gui/base/ButtonSize.js\"\n\nexport type AggregateEditorAttrs<AggregateType> = {\n\tvalue: string\n\tcancelAction: () => unknown\n\tkey: string\n\tanimateCreate?: boolean\n\tanimateCancel?: boolean\n\tallowCancel?: boolean\n\tfieldType: TextFieldType\n\tonUpdate: (newValue: string) => unknown\n\tlabel: string\n\thelpLabel: TranslationKey\n\ttypeLabels: ReadonlyArray<[AggregateType, TranslationKey]>\n\tonTypeSelected: (arg0: AggregateType) => unknown\n}\n\nexport class ContactAggregateEditor implements Component<AggregateEditorAttrs<any>> {\n\toncreate(vnode: VnodeDOM<AggregateEditorAttrs<any>>) {\n\t\tconst animate = typeof vnode.attrs.animateCreate === \"boolean\" ? vnode.attrs.animateCreate : true\n\t\tif (animate) this.animate(vnode.dom as HTMLElement, true)\n\t}\n\n\tasync onbeforeremove(vnode: VnodeDOM<AggregateEditorAttrs<any>>): Promise<void> {\n\t\tawait this.animate(vnode.dom as HTMLElement, false)\n\t}\n\n\tview(vnode: Vnode<AggregateEditorAttrs<any>>): Children {\n\t\tconst attrs = vnode.attrs\n\t\treturn m(\".flex.items-center.child-grow\", [\n\t\t\tm(TextField, {\n\t\t\t\tvalue: attrs.value,\n\t\t\t\tlabel: () => attrs.label,\n\t\t\t\ttype: attrs.fieldType,\n\t\t\t\thelpLabel: () => lang.get(attrs.helpLabel),\n\t\t\t\tinjectionsRight: () => this._moreButtonFor(attrs),\n\t\t\t\toninput: (value) => attrs.onUpdate(value),\n\t\t\t}),\n\t\t\tthis._cancelButtonFor(attrs),\n\t\t])\n\t}\n\n\t_doesAllowCancel(attrs: AggregateEditorAttrs<any>): boolean {\n\t\treturn typeof attrs.allowCancel === \"boolean\" ? attrs.allowCancel : true\n\t}\n\n\t_cancelButtonFor(attrs: AggregateEditorAttrs<unknown>): Children {\n\t\tif (this._doesAllowCancel(attrs)) {\n\t\t\treturn m(IconButton, {\n\t\t\t\ttitle: \"remove_action\",\n\t\t\t\tclick: () => attrs.cancelAction(),\n\t\t\t\ticon: Icons.Cancel,\n\t\t\t})\n\t\t} else {\n\t\t\t// placeholder so that the text field does not jump around\n\t\t\treturn m(\".icon-button\")\n\t\t}\n\t}\n\n\t_moreButtonFor(attrs: AggregateEditorAttrs<any>): Children {\n\t\treturn m(\n\t\t\tIconButton,\n\t\t\tattachDropdown({\n\t\t\t\tmainButtonAttrs: {\n\t\t\t\t\ttitle: \"more_label\",\n\t\t\t\t\ticon: BootIcons.Expand,\n\t\t\t\t\tsize: ButtonSize.Compact,\n\t\t\t\t},\n\t\t\t\tchildAttrs: () =>\n\t\t\t\t\tattrs.typeLabels.map(([key, value]) => {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tlabel: value,\n\t\t\t\t\t\t\tclick: () => attrs.onTypeSelected(key),\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t}),\n\t\t)\n\t}\n\n\tanimate(domElement: HTMLElement, fadein: boolean): Promise<any> {\n\t\tlet childHeight = domElement.offsetHeight\n\n\t\tif (fadein) {\n\t\t\tdomElement.style.opacity = \"0\"\n\t\t}\n\n\t\tconst opacityP = animations.add(domElement, fadein ? opacity(0, 1, true) : opacity(1, 0, true))\n\t\tconst heightP = animations.add(domElement, fadein ? height(0, childHeight) : height(childHeight, 0))\n\t\theightP.then(() => {\n\t\t\tdomElement.style.height = \"\"\n\t\t})\n\t\treturn Promise.all([opacityP, heightP])\n\t}\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport { Dialog } from \"../gui/base/Dialog\"\nimport type { TranslationKey } from \"../misc/LanguageViewModel\"\nimport { lang } from \"../misc/LanguageViewModel\"\nimport { isMailAddress } from \"../misc/FormatValidator\"\nimport { formatBirthdayNumeric, formatBirthdayOfContact } from \"./model/ContactUtils\"\nimport { ContactAddressType, ContactPhoneNumberType, ContactSocialType, GroupType, Keys } from \"../api/common/TutanotaConstants\"\nimport type { Contact, ContactAddress, ContactMailAddress, ContactPhoneNumber, ContactSocialId } from \"../api/entities/tutanota/TypeRefs.js\"\nimport {\n\tcreateBirthday,\n\tcreateContact,\n\tcreateContactAddress,\n\tcreateContactMailAddress,\n\tcreateContactPhoneNumber,\n\tcreateContactSocialId,\n} from \"../api/entities/tutanota/TypeRefs.js\"\nimport { assertNotNull, clone, downcast, findAndRemove, lastIndex, lastThrow, noOp, typedEntries } from \"@tutao/tutanota-utils\"\nimport { assertMainOrNode } from \"../api/common/Env\"\nimport { windowFacade } from \"../misc/WindowFacade\"\nimport { LockedError, NotFoundError, PayloadTooLargeError } from \"../api/common/error/RestError\"\nimport type { ButtonAttrs } from \"../gui/base/Button.js\"\nimport { ButtonType } from \"../gui/base/Button.js\"\nimport { birthdayToIsoDate } from \"../api/common/utils/BirthdayUtils\"\nimport {\n\tContactMailAddressTypeToLabel,\n\tContactPhoneNumberTypeToLabel,\n\tContactSocialTypeToLabel,\n\tgetContactAddressTypeLabel,\n\tgetContactPhoneNumberTypeLabel,\n\tgetContactSocialTypeLabel,\n} from \"./view/ContactGuiUtils\"\nimport { parseBirthday } from \"../misc/DateParser\"\nimport type { TextFieldAttrs } from \"../gui/base/TextField.js\"\nimport { Autocomplete, TextField, TextFieldType } from \"../gui/base/TextField.js\"\nimport { EntityClient } from \"../api/common/EntityClient\"\nimport { timestampToGeneratedId } from \"../api/common/utils/EntityUtils\"\nimport { ContactAggregateEditor } from \"./ContactAggregateEditor\"\nimport { DefaultAnimationTime } from \"../gui/animation/Animations\"\nimport { DialogHeaderBarAttrs } from \"../gui/base/DialogHeaderBar\"\nimport { ProgrammingError } from \"../api/common/error/ProgrammingError.js\"\nimport { ToggleButton } from \"../gui/base/buttons/ToggleButton.js\"\nimport { Icons } from \"../gui/base/icons/Icons.js\"\nimport { ButtonSize } from \"../gui/base/ButtonSize.js\"\nimport { locator } from \"../api/main/MainLocator.js\"\n\nassertMainOrNode()\n\nconst TAG = \"[ContactEditor]\"\n\nexport class ContactEditor {\n\tprivate readonly dialog: Dialog\n\tprivate hasInvalidBirthday: boolean\n\tprivate readonly mailAddresses: Array<[ContactMailAddress, Id]>\n\tprivate readonly phoneNumbers: Array<[ContactPhoneNumber, Id]>\n\tprivate readonly addresses: Array<[ContactAddress, Id]>\n\tprivate readonly socialIds: Array<[ContactSocialId, Id]>\n\tprivate birthday: string\n\tprivate isPasswordRevealed: boolean = false\n\twindowCloseUnsubscribe: () => unknown\n\tprivate readonly isNewContact: boolean\n\tprivate readonly contact: Contact\n\tprivate readonly listId: Id\n\n\tprivate saving: boolean = false\n\n\t/*\n\t * The contact that should be update or the contact list that the new contact should be written to must be provided\n\t * @param entityClient\n\t * @param contact An existing or new contact. If null a new contact is created.\n\t * @param listId The list id of the new contact.\n\t * @param newContactIdReceiver. Is called receiving the contact id as soon as the new contact was saved.\n\t */\n\tconstructor(\n\t\tprivate readonly entityClient: EntityClient,\n\t\tcontact: Contact | null,\n\t\tlistId?: Id,\n\t\tprivate readonly newContactIdReceiver: ((contactId: Id) => unknown) | null = null,\n\t) {\n\t\tthis.contact = contact ? clone(contact) : createContact()\n\t\tthis.isNewContact = contact?._id == null\n\n\t\tif (this.isNewContact && listId == null) {\n\t\t\tthrow new ProgrammingError(\"must provide contact with Id to edit or listId for the new contact\")\n\t\t} else {\n\t\t\tthis.listId = listId ? listId : assertNotNull(contact, \"got an existing contact without id\")._id[0]\n\t\t}\n\n\t\tconst id = (entity: { _id: Id }) => entity._id || this.newId()\n\n\t\tthis.mailAddresses = this.contact.mailAddresses.map((address) => [address, id(address)])\n\t\tthis.mailAddresses.push(this.newMailAddress())\n\t\tthis.phoneNumbers = this.contact.phoneNumbers.map((phoneNumber) => [phoneNumber, id(phoneNumber)])\n\t\tthis.phoneNumbers.push(this.newPhoneNumber())\n\t\tthis.addresses = this.contact.addresses.map((address) => [address, id(address)])\n\t\tthis.addresses.push(this.newAddress())\n\t\tthis.socialIds = this.contact.socialIds.map((socialId) => [socialId, id(socialId)])\n\t\tthis.socialIds.push(this.newSocialId())\n\t\tthis.hasInvalidBirthday = false\n\t\tthis.birthday = formatBirthdayOfContact(this.contact) || \"\"\n\t\tthis.dialog = this.createDialog()\n\t\tthis.windowCloseUnsubscribe = noOp\n\t}\n\n\toncreate() {\n\t\tthis.windowCloseUnsubscribe = windowFacade.addWindowCloseListener(() => {})\n\t}\n\n\tonremove() {\n\t\tthis.windowCloseUnsubscribe()\n\t}\n\n\tview(): Children {\n\t\treturn m(\"#contact-editor\", [\n\t\t\tm(\".wrapping-row\", [this.renderFirstNameField(), this.renderLastNameField()]),\n\t\t\tm(\".wrapping-row\", [this.renderTitleField(), this.renderBirthdayField()]),\n\t\t\tm(\".wrapping-row\", [this.renderRoleField(), this.renderCompanyField(), this.renderNickNameField(), this.renderCommentField()]),\n\t\t\tm(\".wrapping-row\", [\n\t\t\t\tm(\".mail.mt-xl\", [\n\t\t\t\t\tm(\".h4\", lang.get(\"email_label\")),\n\t\t\t\t\tm(\".aggregateEditors\", [\n\t\t\t\t\t\tthis.mailAddresses.map(([address, id], index) => {\n\t\t\t\t\t\t\tconst lastEditor = index === lastIndex(this.mailAddresses)\n\t\t\t\t\t\t\treturn this.renderMailAddressesEditor(id, !lastEditor, address)\n\t\t\t\t\t\t}),\n\t\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t\tm(\".phone.mt-xl\", [\n\t\t\t\t\tm(\".h4\", lang.get(\"phone_label\")),\n\t\t\t\t\tm(\".aggregateEditors\", [\n\t\t\t\t\t\tthis.phoneNumbers.map(([phoneNumber, id], index) => {\n\t\t\t\t\t\t\tconst lastEditor = index === lastIndex(this.phoneNumbers)\n\t\t\t\t\t\t\treturn this.renderPhonesEditor(id, !lastEditor, phoneNumber)\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\tm(\".wrapping-row\", [\n\t\t\t\tm(\".address.mt-xl\", [\n\t\t\t\t\tm(\".h4\", lang.get(\"address_label\")),\n\t\t\t\t\tm(\".aggregateEditors\", [\n\t\t\t\t\t\tthis.addresses.map(([address, id], index) => {\n\t\t\t\t\t\t\tconst lastEditor = index === lastIndex(this.addresses)\n\t\t\t\t\t\t\treturn this.renderAddressesEditor(id, !lastEditor, address)\n\t\t\t\t\t\t}),\n\t\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t\tm(\".social.mt-xl\", [\n\t\t\t\t\tm(\".h4\", lang.get(\"social_label\")),\n\t\t\t\t\tm(\".aggregateEditors\", [\n\t\t\t\t\t\tthis.socialIds.map(([socialId, id], index) => {\n\t\t\t\t\t\t\tconst lastEditor = index === lastIndex(this.socialIds)\n\t\t\t\t\t\t\treturn this.renderSocialsEditor(id, !lastEditor, socialId)\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.renderPresharedPasswordField(),\n\t\t\tm(\".pb\"),\n\t\t])\n\t}\n\n\tshow() {\n\t\tthis.dialog.show()\n\t}\n\n\tprivate close() {\n\t\tthis.dialog.close()\n\t}\n\n\t/**\n\t * * validate the input data\n\t * * create or update the contact, depending on status\n\t * * if successful, close the dialog.\n\t *\n\t * will not call the save function again if the operation is already running\n\t * @private\n\t */\n\tprivate async validateAndSave(): Promise<void> {\n\t\tif (this.hasInvalidBirthday) {\n\t\t\treturn Dialog.message(\"invalidBirthday_msg\")\n\t\t}\n\n\t\tif (this.saving) {\n\t\t\t// not showing a message. if the resource is locked, we'll show one when appropriate.\n\t\t\treturn\n\t\t}\n\t\tthis.saving = true\n\n\t\tthis.contact.mailAddresses = this.mailAddresses.map((e) => e[0]).filter((e) => e.address.trim().length > 0)\n\t\tthis.contact.phoneNumbers = this.phoneNumbers.map((e) => e[0]).filter((e) => e.number.trim().length > 0)\n\t\tthis.contact.addresses = this.addresses.map((e) => e[0]).filter((e) => e.address.trim().length > 0)\n\t\tthis.contact.socialIds = this.socialIds.map((e) => e[0]).filter((e) => e.socialId.trim().length > 0)\n\t\ttry {\n\t\t\tif (this.isNewContact) {\n\t\t\t\tawait this.saveNewContact()\n\t\t\t} else {\n\t\t\t\tawait this.updateExistingContact()\n\t\t\t}\n\t\t\tthis.close()\n\t\t} catch (e) {\n\t\t\tthis.saving = false\n\t\t\tif (e instanceof PayloadTooLargeError) {\n\t\t\t\treturn Dialog.message(\"requestTooLarge_msg\")\n\t\t\t}\n\t\t\tif (e instanceof LockedError) {\n\t\t\t\treturn Dialog.message(\"operationStillActive_msg\")\n\t\t\t}\n\t\t}\n\t\t// if we got here, we're closing the dialog and don't have to reset this.saving\n\t}\n\n\tprivate async updateExistingContact(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.entityClient.update(this.contact)\n\t\t} catch (e) {\n\t\t\tif (e instanceof NotFoundError) {\n\t\t\t\tconsole.log(TAG, `could not update contact ${this.contact._id}: not found`)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async saveNewContact(): Promise<void> {\n\t\tthis.contact._area = \"0\" // legacy\n\t\tthis.contact.autoTransmitPassword = \"\" // legacy\n\t\tthis.contact._owner = locator.logins.getUserController().user._id\n\t\tthis.contact._ownerGroup = assertNotNull(\n\t\t\tlocator.logins.getUserController().user.memberships.find((m) => m.groupType === GroupType.Contact),\n\t\t\t\"did not find contact group membership\",\n\t\t).group\n\t\tconst contactId = await this.entityClient.setup(this.listId, this.contact)\n\t\tif (this.newContactIdReceiver) {\n\t\t\tthis.newContactIdReceiver(contactId)\n\t\t}\n\t}\n\n\tprivate renderMailAddressesEditor(id: Id, allowCancel: boolean, mailAddress: ContactMailAddress): Children {\n\t\tlet helpLabel: TranslationKey\n\n\t\tif (mailAddress.address.trim().length > 0 && !isMailAddress(mailAddress.address.trim(), false)) {\n\t\t\thelpLabel = \"invalidInputFormat_msg\"\n\t\t} else {\n\t\t\thelpLabel = \"emptyString_msg\"\n\t\t}\n\n\t\tconst typeLabels: Array<[ContactAddressType, TranslationKey]> = typedEntries(ContactMailAddressTypeToLabel)\n\t\treturn m(ContactAggregateEditor, {\n\t\t\tvalue: mailAddress.address,\n\t\t\tfieldType: TextFieldType.Text,\n\t\t\tlabel: getContactAddressTypeLabel(downcast(mailAddress.type), mailAddress.customTypeName),\n\t\t\thelpLabel,\n\t\t\tcancelAction: () => {\n\t\t\t\tfindAndRemove(this.mailAddresses, (t) => t[1] === id)\n\t\t\t},\n\t\t\tonUpdate: (value) => {\n\t\t\t\tmailAddress.address = value\n\t\t\t\tif (mailAddress === lastThrow(this.mailAddresses)[0]) this.mailAddresses.push(this.newAddress())\n\t\t\t},\n\t\t\tanimateCreate: !mailAddress.address,\n\t\t\tallowCancel,\n\t\t\tkey: id,\n\t\t\ttypeLabels,\n\t\t\tonTypeSelected: (type) => this.onTypeSelected(type === ContactAddressType.CUSTOM, type, mailAddress),\n\t\t})\n\t}\n\n\tprivate renderPhonesEditor(id: Id, allowCancel: boolean, phoneNumber: ContactPhoneNumber): Children {\n\t\tconst typeLabels = typedEntries(ContactPhoneNumberTypeToLabel)\n\t\treturn m(ContactAggregateEditor, {\n\t\t\tvalue: phoneNumber.number,\n\t\t\tfieldType: TextFieldType.Text,\n\t\t\tlabel: getContactPhoneNumberTypeLabel(downcast(phoneNumber.type), phoneNumber.customTypeName),\n\t\t\thelpLabel: \"emptyString_msg\",\n\t\t\tcancelAction: () => {\n\t\t\t\tfindAndRemove(this.phoneNumbers, (t) => t[1] === id)\n\t\t\t},\n\t\t\tonUpdate: (value) => {\n\t\t\t\tphoneNumber.number = value\n\t\t\t\tif (phoneNumber === lastThrow(this.phoneNumbers)[0]) this.phoneNumbers.push(this.newPhoneNumber())\n\t\t\t},\n\t\t\tanimateCreate: !phoneNumber.number,\n\t\t\tallowCancel,\n\t\t\tkey: id,\n\t\t\ttypeLabels,\n\t\t\tonTypeSelected: (type) => this.onTypeSelected(type === ContactPhoneNumberType.CUSTOM, type, phoneNumber),\n\t\t})\n\t}\n\n\tprivate renderAddressesEditor(id: Id, allowCancel: boolean, address: ContactAddress): Children {\n\t\tconst typeLabels = typedEntries(ContactMailAddressTypeToLabel)\n\t\treturn m(ContactAggregateEditor, {\n\t\t\tvalue: address.address,\n\t\t\tfieldType: TextFieldType.Area,\n\t\t\tlabel: getContactAddressTypeLabel(downcast(address.type), address.customTypeName),\n\t\t\thelpLabel: \"emptyString_msg\",\n\t\t\tcancelAction: () => {\n\t\t\t\tfindAndRemove(this.addresses, (t) => t[1] === id)\n\t\t\t},\n\t\t\tonUpdate: (value) => {\n\t\t\t\taddress.address = value\n\t\t\t\tif (address === lastThrow(this.addresses)[0]) this.addresses.push(this.newAddress())\n\t\t\t},\n\t\t\tanimateCreate: !address.address,\n\t\t\tallowCancel,\n\t\t\tkey: id,\n\t\t\ttypeLabels,\n\t\t\tonTypeSelected: (type) => this.onTypeSelected(type === ContactAddressType.CUSTOM, type, address),\n\t\t})\n\t}\n\n\tprivate renderSocialsEditor(id: Id, allowCancel: boolean, socialId: ContactSocialId): Children {\n\t\tconst typeLabels = typedEntries(ContactSocialTypeToLabel)\n\t\treturn m(ContactAggregateEditor, {\n\t\t\tvalue: socialId.socialId,\n\t\t\tfieldType: TextFieldType.Text,\n\t\t\tlabel: getContactSocialTypeLabel(downcast<ContactSocialType>(socialId.type), socialId.customTypeName),\n\t\t\thelpLabel: \"emptyString_msg\",\n\t\t\tcancelAction: () => {\n\t\t\t\tfindAndRemove(this.socialIds, (t) => t[1] === id)\n\t\t\t},\n\t\t\tonUpdate: (value) => {\n\t\t\t\tsocialId.socialId = value\n\t\t\t\tif (socialId === lastThrow(this.socialIds)[0]) this.socialIds.push(this.newSocialId())\n\t\t\t},\n\t\t\tanimateCreate: !socialId.socialId,\n\t\t\tallowCancel,\n\t\t\tkey: id,\n\t\t\ttypeLabels,\n\t\t\tonTypeSelected: (type) => this.onTypeSelected(type === ContactSocialType.CUSTOM, type, socialId),\n\t\t})\n\t}\n\n\tprivate renderCommentField(): Children {\n\t\treturn m(StandaloneField, {\n\t\t\tlabel: \"comment_label\",\n\t\t\tvalue: this.contact.comment,\n\t\t\toninput: (value) => (this.contact.comment = value),\n\t\t\ttype: TextFieldType.Area,\n\t\t})\n\t}\n\n\tprivate renderFirstNameField(): Children {\n\t\treturn m(StandaloneField, {\n\t\t\tlabel: \"firstName_placeholder\",\n\t\t\tvalue: this.contact.firstName,\n\t\t\toninput: (value) => (this.contact.firstName = value),\n\t\t})\n\t}\n\n\tprivate renderNickNameField(): Children {\n\t\treturn m(StandaloneField, {\n\t\t\tlabel: \"nickname_placeholder\",\n\t\t\tvalue: this.contact.nickname ?? \"\",\n\t\t\toninput: (value) => (this.contact.nickname = value),\n\t\t})\n\t}\n\n\tprivate renderLastNameField(): Children {\n\t\treturn m(StandaloneField, {\n\t\t\tlabel: \"lastName_placeholder\",\n\t\t\tvalue: this.contact.lastName,\n\t\t\toninput: (value) => (this.contact.lastName = value),\n\t\t})\n\t}\n\n\tprivate renderBirthdayField(): Children {\n\t\tlet birthdayHelpText = () => {\n\t\t\tlet bday = createBirthday()\n\t\t\tbday.day = \"22\"\n\t\t\tbday.month = \"9\"\n\t\t\tbday.year = \"2000\"\n\t\t\treturn this.hasInvalidBirthday\n\t\t\t\t? lang.get(\"invalidDateFormat_msg\", {\n\t\t\t\t\t\t\"{1}\": formatBirthdayNumeric(bday),\n\t\t\t\t  })\n\t\t\t\t: \"\"\n\t\t}\n\n\t\treturn m(StandaloneField, {\n\t\t\tlabel: \"birthday_alt\",\n\t\t\tvalue: this.birthday,\n\t\t\thelpLabel: birthdayHelpText,\n\t\t\toninput: (value) => {\n\t\t\t\tthis.birthday = value\n\t\t\t\tif (value.trim().length === 0) {\n\t\t\t\t\tthis.contact.birthdayIso = null\n\t\t\t\t\tthis.hasInvalidBirthday = false\n\t\t\t\t} else {\n\t\t\t\t\tlet birthday = parseBirthday(value)\n\n\t\t\t\t\tif (birthday) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.contact.birthdayIso = birthdayToIsoDate(birthday)\n\t\t\t\t\t\t\tthis.hasInvalidBirthday = false\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tthis.hasInvalidBirthday = true\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.hasInvalidBirthday = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\tprivate renderCompanyField(): Children {\n\t\treturn m(StandaloneField, {\n\t\t\tlabel: \"company_label\",\n\t\t\tvalue: this.contact.company,\n\t\t\toninput: (value) => (this.contact.company = value),\n\t\t})\n\t}\n\n\tprivate renderRoleField(): Children {\n\t\treturn m(StandaloneField, {\n\t\t\tlabel: \"role_placeholder\",\n\t\t\tvalue: this.contact.role,\n\t\t\toninput: (value) => (this.contact.role = value),\n\t\t})\n\t}\n\n\tprivate renderTitleField(): Children {\n\t\treturn m(StandaloneField, {\n\t\t\tlabel: \"title_placeholder\",\n\t\t\tvalue: this.contact.title || \"\",\n\t\t\toninput: (value) => (this.contact.title = value),\n\t\t})\n\t}\n\n\tprivate renderPresharedPasswordField(): Children {\n\t\tif (!this.isNewContact && !this.contact.presharedPassword) {\n\t\t\treturn null\n\t\t}\n\n\t\treturn m(\".wrapping-row\", [\n\t\t\tm(\".passwords.mt-xl\", [\n\t\t\t\tm(\".h4\", lang.get(\"presharedPassword_label\")),\n\t\t\t\tm(TextField, {\n\t\t\t\t\ttype: this.isPasswordRevealed ? TextFieldType.Text : TextFieldType.Password,\n\t\t\t\t\tlabel: \"password_label\",\n\t\t\t\t\tvalue: this.contact.presharedPassword ?? \"\",\n\t\t\t\t\tautocompleteAs: Autocomplete.newPassword,\n\t\t\t\t\toninput: (value) => (this.contact.presharedPassword = value),\n\t\t\t\t\tinjectionsRight: () => this.renderRevealIcon(),\n\t\t\t\t}),\n\t\t\t]),\n\t\t\tm(\".spacer\"),\n\t\t])\n\t}\n\n\tprivate createCloseButtonAttrs(): ButtonAttrs {\n\t\treturn {\n\t\t\tlabel: \"close_alt\",\n\t\t\tclick: (e, dom) => this.close(),\n\t\t\ttype: ButtonType.Secondary,\n\t\t}\n\t}\n\n\tprivate newPhoneNumber(): [ContactPhoneNumber, Id] {\n\t\tconst phoneNumber = createContactPhoneNumber({\n\t\t\ttype: ContactPhoneNumberType.MOBILE,\n\t\t\tcustomTypeName: \"\",\n\t\t\tnumber: \"\",\n\t\t})\n\t\treturn [phoneNumber, this.newId()]\n\t}\n\n\tprivate newMailAddress(): [ContactMailAddress, Id] {\n\t\tconst mailAddress = createContactMailAddress({\n\t\t\ttype: ContactAddressType.WORK,\n\t\t\tcustomTypeName: \"\",\n\t\t\taddress: \"\",\n\t\t})\n\t\treturn [mailAddress, this.newId()]\n\t}\n\n\tprivate newAddress(): [ContactAddress, Id] {\n\t\tconst address = createContactAddress({\n\t\t\ttype: ContactAddressType.WORK,\n\t\t\tcustomTypeName: \"\",\n\t\t\taddress: \"\",\n\t\t})\n\t\treturn [address, this.newId()]\n\t}\n\n\tprivate newSocialId(): [ContactSocialId, Id] {\n\t\tconst socialId = createContactSocialId({\n\t\t\ttype: ContactSocialType.TWITTER,\n\t\t\tcustomTypeName: \"\",\n\t\t\tsocialId: \"\",\n\t\t})\n\t\treturn [socialId, this.newId()]\n\t}\n\n\tprivate newId(): Id {\n\t\treturn timestampToGeneratedId(Date.now())\n\t}\n\n\tprivate onTypeSelected<K, T extends { type: K; customTypeName: string }>(isCustom: boolean, key: K, aggregate: T): void {\n\t\tif (isCustom) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tDialog.showTextInputDialog(\"customLabel_label\", \"customLabel_label\", null, aggregate.customTypeName).then((name) => {\n\t\t\t\t\taggregate.customTypeName = name\n\t\t\t\t\taggregate.type = key\n\t\t\t\t})\n\t\t\t}, DefaultAnimationTime) // wait till the dropdown is hidden\n\t\t} else {\n\t\t\taggregate.type = key\n\t\t}\n\t}\n\n\tprivate renderRevealIcon(): Children {\n\t\treturn m(ToggleButton, {\n\t\t\ttitle: this.isPasswordRevealed ? \"concealPassword_action\" : \"revealPassword_action\",\n\t\t\ttoggled: this.isPasswordRevealed,\n\t\t\tonToggled: (_, e) => {\n\t\t\t\tthis.isPasswordRevealed = !this.isPasswordRevealed\n\t\t\t\te.stopPropagation()\n\t\t\t},\n\t\t\ticon: this.isPasswordRevealed ? Icons.NoEye : Icons.Eye,\n\t\t\tsize: ButtonSize.Compact,\n\t\t})\n\t}\n\n\tprivate createDialog(): Dialog {\n\t\tconst headerBarAttrs: DialogHeaderBarAttrs = {\n\t\t\tleft: [this.createCloseButtonAttrs()],\n\t\t\tmiddle: () => this.contact.firstName + \" \" + this.contact.lastName,\n\t\t\tright: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"save_action\",\n\t\t\t\t\tclick: () => this.validateAndSave(),\n\t\t\t\t\ttype: ButtonType.Primary,\n\t\t\t\t},\n\t\t\t],\n\t\t}\n\t\treturn Dialog.largeDialog(headerBarAttrs, this)\n\t\t\t.addShortcut({\n\t\t\t\tkey: Keys.ESC,\n\t\t\t\texec: () => this.close(),\n\t\t\t\thelp: \"close_alt\",\n\t\t\t})\n\t\t\t.addShortcut({\n\t\t\t\tkey: Keys.S,\n\t\t\t\tctrl: true,\n\t\t\t\texec: () => {\n\t\t\t\t\t// noinspection JSIgnoredPromiseFromCall\n\t\t\t\t\tthis.validateAndSave()\n\t\t\t\t},\n\t\t\t\thelp: \"save_action\",\n\t\t\t})\n\t\t\t.setCloseHandler(() => this.close())\n\t}\n}\n\n/** Renders TextField with wrapper and padding element to align them all. */\nclass StandaloneField implements Component<TextFieldAttrs> {\n\tview({ attrs }: Vnode<TextFieldAttrs>): Children {\n\t\treturn m(\".flex.child-grow\", [m(TextField, attrs), m(\".icon-button\")])\n\t}\n}\n","import { Contact } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport {\n\tcheckboxOpacity,\n\tscaleXHide,\n\tscaleXShow,\n\tselectableRowAnimParams,\n\tSelectableRowContainer,\n\tSelectableRowSelectedSetter,\n\tshouldAlwaysShowMultiselectCheckbox,\n} from \"../../gui/SelectableRowContainer.js\"\nimport { getContactListName } from \"../model/ContactUtils.js\"\nimport { NBSP, noOp } from \"@tutao/tutanota-utils\"\nimport m, { Children } from \"mithril\"\nimport { px, size } from \"../../gui/size.js\"\nimport { VirtualRow } from \"../../gui/base/ListUtils.js\"\n\nconst shiftByForCheckbox = px(size.checkbox_size + size.hpad)\nconst translateXShow = `translateX(${shiftByForCheckbox})`\nconst translateXHide = \"translateX(0)\"\n\nexport class ContactRow implements VirtualRow<Contact> {\n\ttop: number\n\tdomElement: HTMLElement | null = null // set from List\n\n\tentity: Contact | null\n\tprivate selectionUpdater!: SelectableRowSelectedSetter\n\tprivate domName!: HTMLElement\n\tprivate domAddress!: HTMLElement\n\tprivate checkboxDom!: HTMLInputElement\n\tprivate checkboxWasVisible = shouldAlwaysShowMultiselectCheckbox()\n\n\tconstructor(private readonly onSelected: (entity: Contact, selected: boolean) => unknown) {\n\t\tthis.top = 0\n\t\tthis.entity = null\n\t}\n\n\tupdate(contact: Contact, selected: boolean, isInMultiSelect: boolean): void {\n\t\tthis.entity = contact\n\t\tthis.selectionUpdater(selected, isInMultiSelect)\n\t\tthis.showCheckboxAnimated(shouldAlwaysShowMultiselectCheckbox() || isInMultiSelect)\n\t\tcheckboxOpacity(this.checkboxDom, selected)\n\t\tthis.checkboxDom.checked = selected && isInMultiSelect\n\n\t\tthis.domName.textContent = getContactListName(contact)\n\t\tthis.domAddress.textContent = contact.mailAddresses && contact.mailAddresses.length > 0 ? contact.mailAddresses[0].address : NBSP\n\t}\n\n\t/**\n\t * Only the structure is managed by mithril. We set all contents on our own (see update) in order to avoid the vdom overhead (not negligible on mobiles)\n\t */\n\trender(): Children {\n\t\treturn m(\n\t\t\tSelectableRowContainer,\n\t\t\t{\n\t\t\t\toncreate: (vnode) => {\n\t\t\t\t\tPromise.resolve().then(() => this.showCheckbox(shouldAlwaysShowMultiselectCheckbox()))\n\t\t\t\t},\n\t\t\t\tonSelectedChangeRef: (updater) => (this.selectionUpdater = updater),\n\t\t\t},\n\t\t\tm(\".mt-xs.abs\", [\n\t\t\t\tm(\"input.checkbox.list-checkbox\", {\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\ttransformOrigin: \"left\",\n\t\t\t\t\t},\n\t\t\t\t\tonclick: (e: MouseEvent) => {\n\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t// e.redraw = false\n\t\t\t\t\t},\n\t\t\t\t\tonchange: () => {\n\t\t\t\t\t\tthis.entity && this.onSelected(this.entity, this.checkboxDom.checked)\n\t\t\t\t\t},\n\t\t\t\t\toncreate: (vnode) => {\n\t\t\t\t\t\tthis.checkboxDom = vnode.dom as HTMLInputElement\n\t\t\t\t\t\tcheckboxOpacity(this.checkboxDom, false)\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t]),\n\t\t\tm(\".flex.col.overflow-hidden.flex-grow\", [\n\t\t\t\tm(\".text-ellipsis.badge-line-height\", {\n\t\t\t\t\toncreate: (vnode) => (this.domName = vnode.dom as HTMLElement),\n\t\t\t\t}),\n\t\t\t\tm(\".text-ellipsis.smaller.mt-xxs\", {\n\t\t\t\t\toncreate: (vnode) => (this.domAddress = vnode.dom as HTMLElement),\n\t\t\t\t}),\n\t\t\t]),\n\t\t)\n\t}\n\n\tprivate showCheckboxAnimated(show: boolean) {\n\t\tif (this.checkboxWasVisible === show) return\n\t\tif (show) {\n\t\t\tthis.domName.style.paddingRight = shiftByForCheckbox\n\t\t\tthis.domAddress.style.paddingRight = shiftByForCheckbox\n\n\t\t\tconst nameAnim = this.domName.animate({ transform: [translateXHide, translateXShow] }, selectableRowAnimParams)\n\t\t\tconst addressAnim = this.domAddress.animate({ transform: [translateXHide, translateXShow] }, selectableRowAnimParams)\n\t\t\tconst checkboxAnim = this.checkboxDom.animate({ transform: [scaleXHide, scaleXShow] }, selectableRowAnimParams)\n\n\t\t\tPromise.all([nameAnim.finished, addressAnim.finished, checkboxAnim.finished]).then(() => {\n\t\t\t\tnameAnim.cancel()\n\t\t\t\taddressAnim.cancel()\n\t\t\t\tcheckboxAnim.cancel()\n\t\t\t\tthis.showCheckbox(show)\n\t\t\t}, noOp)\n\t\t} else {\n\t\t\tthis.domName.style.paddingRight = \"0\"\n\t\t\tthis.domAddress.style.paddingRight = \"0\"\n\n\t\t\tconst nameAnim = this.domName.animate({ transform: [translateXShow, translateXHide] }, selectableRowAnimParams)\n\t\t\tconst addressAnim = this.domAddress.animate({ transform: [translateXShow, translateXHide] }, selectableRowAnimParams)\n\t\t\tconst checkboxAnim = this.checkboxDom.animate({ transform: [scaleXShow, scaleXHide] }, selectableRowAnimParams)\n\n\t\t\tPromise.all([nameAnim.finished, addressAnim.finished, checkboxAnim.finished]).then(() => {\n\t\t\t\tnameAnim.cancel()\n\t\t\t\taddressAnim.cancel()\n\t\t\t\tcheckboxAnim.cancel()\n\t\t\t\tthis.showCheckbox(show)\n\t\t\t}, noOp)\n\t\t}\n\t\tthis.checkboxWasVisible = show\n\t}\n\n\tprivate showCheckbox(show: boolean) {\n\t\tlet translate\n\t\tlet scale\n\t\tlet padding\n\t\tif (show) {\n\t\t\ttranslate = translateXShow\n\t\t\tscale = scaleXShow\n\t\t\tpadding = shiftByForCheckbox\n\t\t} else {\n\t\t\ttranslate = translateXHide\n\t\t\tscale = scaleXHide\n\t\t\tpadding = \"0\"\n\t\t}\n\n\t\tthis.domAddress.style.transform = translate\n\t\tthis.domName.style.transform = translate\n\t\tthis.domAddress.style.paddingRight = padding\n\t\tthis.domName.style.paddingRight = padding\n\t\tthis.checkboxDom.style.transform = scale\n\t}\n}\n","import m, { Children, ClassComponent, Vnode } from \"mithril\"\nimport type { Contact } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { size } from \"../../gui/size\"\nimport { ListColumnWrapper } from \"../../gui/ListColumnWrapper\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport { MultiselectMode, List, ListAttrs, RenderConfig, ViewHolder } from \"../../gui/base/List.js\"\nimport { ContactRow } from \"./ContactRow.js\"\nimport { ContactViewModel } from \"./ContactViewModel.js\"\nimport ColumnEmptyMessageBox from \"../../gui/base/ColumnEmptyMessageBox.js\"\nimport { theme } from \"../../gui/theme.js\"\nimport { BootIcons } from \"../../gui/base/icons/BootIcons.js\"\n\nassertMainOrNode()\n\nexport interface ContactListViewAttrs {\n\tonSingleSelection: () => unknown\n\tcontactViewModel: ContactViewModel\n}\n\nexport class ContactListView implements ClassComponent<ContactListViewAttrs> {\n\tprivate contactViewModel: ContactViewModel | null = null\n\n\tview({ attrs: { contactViewModel, onSingleSelection } }: Vnode<ContactListViewAttrs>): Children {\n\t\tthis.contactViewModel = contactViewModel\n\t\treturn m(\n\t\t\tListColumnWrapper,\n\t\t\t{\n\t\t\t\theaderContent: null,\n\t\t\t},\n\t\t\tcontactViewModel.listModel.isEmptyAndDone()\n\t\t\t\t? m(ColumnEmptyMessageBox, {\n\t\t\t\t\t\tcolor: theme.list_message_bg,\n\t\t\t\t\t\tmessage: \"noContacts_msg\",\n\t\t\t\t\t\ticon: BootIcons.Contacts,\n\t\t\t\t  })\n\t\t\t\t: m(List, {\n\t\t\t\t\t\trenderConfig: this.renderConfig,\n\t\t\t\t\t\tstate: contactViewModel.listState(),\n\t\t\t\t\t\t// should not be called anyway\n\t\t\t\t\t\tonLoadMore: () => {},\n\t\t\t\t\t\tonRetryLoading: () => {\n\t\t\t\t\t\t\tcontactViewModel.listModel.retryLoading()\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonSingleSelection: (item: Contact) => {\n\t\t\t\t\t\t\tcontactViewModel.listModel.onSingleSelection(item)\n\t\t\t\t\t\t\tonSingleSelection()\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonSingleTogglingMultiselection: (item: Contact) => {\n\t\t\t\t\t\t\tcontactViewModel.listModel.onSingleInclusiveSelection(item)\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonRangeSelectionTowards: (item: Contact) => {\n\t\t\t\t\t\t\tcontactViewModel.listModel.selectRangeTowards(item)\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonStopLoading() {\n\t\t\t\t\t\t\tcontactViewModel.listModel.stopLoading()\n\t\t\t\t\t\t},\n\t\t\t\t  } satisfies ListAttrs<Contact, KindaContactRow>),\n\t\t)\n\t}\n\n\tprivate readonly renderConfig: RenderConfig<Contact, KindaContactRow> = {\n\t\titemHeight: size.list_row_height,\n\t\tmultiselectionAllowed: MultiselectMode.Enabled,\n\t\tswipe: null,\n\t\tcreateElement: (dom) => {\n\t\t\treturn new KindaContactRow(dom, (item) => this.contactViewModel?.listModel.onSingleExclusiveSelection(item))\n\t\t},\n\t}\n}\n\nexport class KindaContactRow implements ViewHolder<Contact> {\n\treadonly cr: ContactRow\n\tdomElement: HTMLElement\n\tentity: Contact | null = null\n\n\tconstructor(dom: HTMLElement, onToggleSelection: (item: Contact) => unknown) {\n\t\tthis.cr = new ContactRow(onToggleSelection)\n\t\tthis.domElement = dom\n\t\tm.render(dom, this.cr.render())\n\t}\n\n\tupdate(item: Contact, selected: boolean, multiselect: boolean) {\n\t\tthis.entity = item\n\t\tthis.cr.update(item, selected, multiselect)\n\t}\n\n\trender(): Children {\n\t\treturn this.cr.render()\n\t}\n}\n","import m, { Component, Vnode } from \"mithril\"\nimport ColumnEmptyMessageBox from \"../../gui/base/ColumnEmptyMessageBox\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport { BootIcons } from \"../../gui/base/icons/BootIcons\"\nimport { theme } from \"../../gui/theme\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport { Contact } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { Button, ButtonType } from \"../../gui/base/Button.js\"\n\nassertMainOrNode()\n\nexport interface MultiContactViewerAttrs {\n\tselectedEntities: Contact[]\n\tselectNone: () => unknown\n}\n\n/**\n * The ContactViewer displays the action buttons for multiple selected contacts.\n */\nexport class MultiContactViewer implements Component<MultiContactViewerAttrs> {\n\tview({ attrs }: Vnode<MultiContactViewerAttrs>) {\n\t\treturn [\n\t\t\tm(ColumnEmptyMessageBox, {\n\t\t\t\tmessage: () => getContactSelectionMessage(attrs.selectedEntities),\n\t\t\t\ticon: BootIcons.Contacts,\n\t\t\t\tcolor: theme.content_message_bg,\n\t\t\t\tbottomContent:\n\t\t\t\t\tattrs.selectedEntities.length > 0\n\t\t\t\t\t\t? m(Button, {\n\t\t\t\t\t\t\t\tlabel: \"cancel_action\",\n\t\t\t\t\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t\t\t\t\t\tclick: () => attrs.selectNone(),\n\t\t\t\t\t\t  })\n\t\t\t\t\t\t: undefined,\n\t\t\t\tbackgroundColor: theme.navigation_bg,\n\t\t\t}),\n\t\t]\n\t}\n}\n\nexport function getContactSelectionMessage(selectedEntities: Contact[]): string {\n\tif (selectedEntities.length === 0) {\n\t\treturn lang.get(\"noContact_msg\")\n\t} else {\n\t\treturn lang.get(\"nbrOfContactsSelected_msg\", {\n\t\t\t\"{1}\": selectedEntities.length,\n\t\t})\n\t}\n}\n","import m, { ChildArray, Children } from \"mithril\"\nimport { Dialog } from \"../../gui/base/Dialog\"\nimport { windowFacade } from \"../../misc/WindowFacade\"\nimport { Icons } from \"../../gui/base/icons/Icons\"\nimport { ContactAddressType, ContactMergeAction, getContactSocialType, Keys } from \"../../api/common/TutanotaConstants\"\nimport type { TranslationKey } from \"../../misc/LanguageViewModel\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport { formatBirthdayOfContact } from \"../model/ContactUtils\"\nimport { defer, DeferredObject, delay, downcast, Thunk } from \"@tutao/tutanota-utils\"\nimport { HtmlEditor, HtmlEditorMode } from \"../../gui/editor/HtmlEditor\"\nimport { Button, ButtonType } from \"../../gui/base/Button.js\"\nimport type { Contact } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { getContactAddressTypeLabel, getContactPhoneNumberTypeLabel, getContactSocialTypeLabel } from \"./ContactGuiUtils\"\nimport { TextField } from \"../../gui/base/TextField.js\"\nimport { TextDisplayArea } from \"../../gui/base/TextDisplayArea\"\nimport { DialogHeaderBarAttrs } from \"../../gui/base/DialogHeaderBar\"\nimport { IconButton } from \"../../gui/base/IconButton.js\"\n\nexport class ContactMergeView {\n\tdialog: Dialog\n\tcontact1: Contact\n\tcontact2: Contact\n\tresolveFunction: DeferredObject<ContactMergeAction>[\"resolve\"] | null = null // must be called after the user action\n\n\twindowCloseUnsubscribe: Thunk | null = null\n\n\tconstructor(contact1: Contact, contact2: Contact) {\n\t\tthis.contact1 = contact1\n\t\tthis.contact2 = contact2\n\n\t\tconst cancelAction = () => {\n\t\t\tthis._close(ContactMergeAction.Cancel)\n\t\t}\n\n\t\tconst headerBarAttrs = {\n\t\t\tleft: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"cancel_action\",\n\t\t\t\t\tclick: cancelAction,\n\t\t\t\t\ttype: ButtonType.Secondary,\n\t\t\t\t},\n\t\t\t],\n\t\t\tright: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"skip_action\",\n\t\t\t\t\tclick: () => this._close(ContactMergeAction.Skip),\n\t\t\t\t\ttype: ButtonType.Primary,\n\t\t\t\t},\n\t\t\t],\n\t\t\tmiddle: () => lang.get(\"merge_action\"),\n\t\t}\n\t\tthis.dialog = Dialog.largeDialog(headerBarAttrs as DialogHeaderBarAttrs, this)\n\t\t\t.setCloseHandler(cancelAction)\n\t\t\t.addShortcut({\n\t\t\t\tkey: Keys.ESC,\n\t\t\t\texec: () => {\n\t\t\t\t\tthis._close(ContactMergeAction.Cancel)\n\t\t\t\t\treturn false\n\t\t\t\t},\n\t\t\t\thelp: \"close_alt\",\n\t\t\t})\n\t}\n\n\tview(): Children {\n\t\tconst { mailAddresses: mailAddresses1, phones: phones1, addresses: addresses1, socials: socials1 } = this._createContactFields(this.contact1)\n\n\t\tconst { mailAddresses: mailAddresses2, phones: phones2, addresses: addresses2, socials: socials2 } = this._createContactFields(this.contact2)\n\n\t\t//empty.. placeholders are used if one contact has an attribute while the other does not have it, so an empty one is shown for comparison\n\t\tlet emptyFieldPlaceholder = m(TextField, {\n\t\t\tlabel: \"emptyString_msg\",\n\t\t\tvalue: \"\",\n\t\t\tdisabled: true,\n\t\t})\n\t\tlet emptyHTMLFieldPlaceholder = m(\n\t\t\tnew HtmlEditor(\"emptyString_msg\").showBorders().setValue(\"\").setEnabled(false).setMode(HtmlEditorMode.HTML).setHtmlMonospace(false),\n\t\t)\n\n\t\tlet titleFields = this._createTextFields(this.contact1.title, this.contact2.title, \"title_placeholder\")\n\n\t\tlet firstNameFields = this._createTextFields(this.contact1.firstName, this.contact2.firstName, \"firstName_placeholder\")\n\n\t\tlet lastNameFields = this._createTextFields(this.contact1.lastName, this.contact2.lastName, \"lastName_placeholder\")\n\n\t\tlet nicknameFields = this._createTextFields(this.contact1.nickname, this.contact2.nickname, \"nickname_placeholder\")\n\n\t\tlet companyFields = this._createTextFields(this.contact1.company, this.contact2.company, \"company_label\")\n\n\t\tlet roleFields = this._createTextFields(this.contact1.role, this.contact2.role, \"role_placeholder\")\n\n\t\tlet birthdayFields = this._createTextFields(formatBirthdayOfContact(this.contact1), formatBirthdayOfContact(this.contact2), \"birthday_alt\")\n\n\t\tlet presharedPasswordFields = this._createTextFields(\n\t\t\tthis.contact1.presharedPassword && this.contact1.presharedPassword.length > 0 ? \"***\" : \"\",\n\t\t\tthis.contact2.presharedPassword && this.contact2.presharedPassword.length > 0 ? \"***\" : \"\",\n\t\t\t\"presharedPassword_label\",\n\t\t)\n\n\t\tlet commentField1: Children = null\n\t\tlet commentField2: Children = null\n\n\t\tif (this.contact1.comment || this.contact2.comment) {\n\t\t\tcommentField1 = m(TextDisplayArea, {\n\t\t\t\tlabel: \"comment_label\",\n\t\t\t\tvalue: this.contact1.comment,\n\t\t\t})\n\t\t\tcommentField2 = m(TextDisplayArea, {\n\t\t\t\tlabel: \"comment_label\",\n\t\t\t\tvalue: this.contact2.comment,\n\t\t\t})\n\t\t}\n\n\t\treturn m(\n\t\t\t\"#contact-editor\",\n\t\t\t{\n\t\t\t\toncreate: () => (this.windowCloseUnsubscribe = windowFacade.addWindowCloseListener(() => {})),\n\t\t\t\tonremove: () => this.windowCloseUnsubscribe?.(),\n\t\t\t},\n\t\t\t[\n\t\t\t\tm(\".flex-center.mt\", [\n\t\t\t\t\tm(\".full-width.max-width-s\", [\n\t\t\t\t\t\tm(Button, {\n\t\t\t\t\t\t\tlabel: \"mergeContacts_action\",\n\t\t\t\t\t\t\tclick: () => this._close(ContactMergeAction.Merge),\n\t\t\t\t\t\t\ttype: ButtonType.Login,\n\t\t\t\t\t\t}),\n\t\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t\tm(\".non-wrapping-row\", [\n\t\t\t\t\tm(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t/*first contact */\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\tm(\".items-center\", [\n\t\t\t\t\t\t\t\tm(\".items-base.flex-space-between\", [\n\t\t\t\t\t\t\t\t\tm(\".h4.mt-l\", lang.get(\"firstMergeContact_label\")),\n\t\t\t\t\t\t\t\t\tthis._createDeleteContactButton(ContactMergeAction.DeleteFirst),\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\tm(\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t/*second contact */\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\tm(\".items-center\", [\n\t\t\t\t\t\t\t\tm(\".items-base.flex-space-between\", [\n\t\t\t\t\t\t\t\t\tm(\".h4.mt-l\", lang.get(\"secondMergeContact_label\")),\n\t\t\t\t\t\t\t\t\tthis._createDeleteContactButton(ContactMergeAction.DeleteSecond),\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\ttitleFields ? m(\".non-wrapping-row\", titleFields) : null,\n\t\t\t\tfirstNameFields ? m(\".non-wrapping-row\", firstNameFields) : null,\n\t\t\t\tlastNameFields ? m(\".non-wrapping-row\", lastNameFields) : null,\n\t\t\t\tnicknameFields ? m(\".non-wrapping-row\", nicknameFields) : null,\n\t\t\t\tcompanyFields ? m(\".non-wrapping-row\", companyFields) : null,\n\t\t\t\tbirthdayFields ? m(\".non-wrapping-row\", birthdayFields) : null,\n\t\t\t\troleFields ? m(\".non-wrapping-row\", roleFields) : null,\n\t\t\t\tmailAddresses1.length > 0 || mailAddresses2.length > 0\n\t\t\t\t\t? m(\".non-wrapping-row\", [\n\t\t\t\t\t\t\tm(\".mail.mt-l\", [m(\"\", lang.get(\"email_label\")), mailAddresses1.length > 0 ? mailAddresses1 : emptyFieldPlaceholder]),\n\t\t\t\t\t\t\tm(\".mail.mt-l\", [m(\"\", lang.get(\"email_label\")), mailAddresses2.length > 0 ? mailAddresses2 : emptyFieldPlaceholder]),\n\t\t\t\t\t  ])\n\t\t\t\t\t: null,\n\t\t\t\tphones1.length > 0 || phones2.length > 0\n\t\t\t\t\t? m(\".non-wrapping-row\", [\n\t\t\t\t\t\t\tm(\".phone.mt-l\", [m(\"\", lang.get(\"phone_label\")), m(\".aggregateEditors\", [phones1.length > 0 ? phones1 : emptyFieldPlaceholder])]),\n\t\t\t\t\t\t\tm(\".phone.mt-l\", [m(\"\", lang.get(\"phone_label\")), m(\".aggregateEditors\", [phones2.length > 0 ? phones2 : emptyFieldPlaceholder])]),\n\t\t\t\t\t  ])\n\t\t\t\t\t: null,\n\t\t\t\taddresses1.length > 0 || addresses2.length > 0\n\t\t\t\t\t? m(\".non-wrapping-row\", [\n\t\t\t\t\t\t\tm(\".address.mt-l.flex.flex-column\", [\n\t\t\t\t\t\t\t\tm(\"\", lang.get(\"address_label\")),\n\t\t\t\t\t\t\t\tm(\".aggregateEditors.flex.flex-column.flex-grow\", [addresses1.length > 0 ? addresses1 : emptyHTMLFieldPlaceholder]),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tm(\".address.mt-l\", [\n\t\t\t\t\t\t\t\tm(\"\", lang.get(\"address_label\")),\n\t\t\t\t\t\t\t\tm(\".aggregateEditors.flex.flex-column.flex-grow\", [addresses2.length > 0 ? addresses2 : emptyHTMLFieldPlaceholder]),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t  ])\n\t\t\t\t\t: null,\n\t\t\t\tsocials1.length > 0 || socials2.length > 0\n\t\t\t\t\t? m(\".non-wrapping-row\", [\n\t\t\t\t\t\t\tm(\".social.mt-l\", [\n\t\t\t\t\t\t\t\tm(\"\", lang.get(\"social_label\")),\n\t\t\t\t\t\t\t\tm(\".aggregateEditors\", socials1.length > 0 ? socials1 : emptyFieldPlaceholder),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\tm(\".social.mt-l\", [\n\t\t\t\t\t\t\t\tm(\"\", lang.get(\"social_label\")),\n\t\t\t\t\t\t\t\tm(\".aggregateEditors\", socials2.length > 0 ? socials2 : emptyFieldPlaceholder),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t  ])\n\t\t\t\t\t: null,\n\t\t\t\tcommentField1 && commentField2\n\t\t\t\t\t? m(\".non-wrapping-row\", [m(\".mt-l.flex.flex-column\", [commentField1]), m(\".mt-l.flex.flex-column\", [commentField2])])\n\t\t\t\t\t: null,\n\t\t\t\tpresharedPasswordFields ? m(\".non-wrapping-row\", presharedPasswordFields) : null,\n\t\t\t\tm(\n\t\t\t\t\t\"\",\n\t\t\t\t\t{\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\theight: \"5px\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t/*Used as spacer so the last gui-element is not touching the window border*/\n\t\t\t\t),\n\t\t\t],\n\t\t)\n\t}\n\n\t_createContactFields(contact: Contact): {\n\t\tmailAddresses: ChildArray\n\t\tphones: ChildArray\n\t\taddresses: ChildArray\n\t\tsocials: ChildArray\n\t} {\n\t\tconst mailAddresses = contact.mailAddresses.map((element) => {\n\t\t\treturn m(TextField, {\n\t\t\t\tlabel: () => getContactAddressTypeLabel(element.type as any, element.customTypeName),\n\t\t\t\tvalue: element.address,\n\t\t\t\tdisabled: true,\n\t\t\t})\n\t\t})\n\t\tconst phones = contact.phoneNumbers.map((element) => {\n\t\t\treturn m(TextField, {\n\t\t\t\tlabel: () => getContactPhoneNumberTypeLabel(element.type as any, element.customTypeName),\n\t\t\t\tvalue: element.number,\n\t\t\t\tdisabled: true,\n\t\t\t})\n\t\t})\n\t\tconst addresses = contact.addresses.map((element) => {\n\t\t\t// Manually implement text area to make it stretch vertically. TextField is unable to do that.\n\t\t\treturn m(TextDisplayArea, {\n\t\t\t\tvalue: element.address,\n\t\t\t\tlabel: () => getContactAddressTypeLabel(downcast<ContactAddressType>(element.type), element.customTypeName),\n\t\t\t})\n\t\t})\n\t\tconst socials = contact.socialIds.map((element) => {\n\t\t\treturn m(TextField, {\n\t\t\t\tlabel: () => getContactSocialTypeLabel(getContactSocialType(element), element.customTypeName),\n\t\t\t\tvalue: element.socialId,\n\t\t\t\tdisabled: true,\n\t\t\t})\n\t\t})\n\t\treturn {\n\t\t\tmailAddresses,\n\t\t\tphones,\n\t\t\taddresses,\n\t\t\tsocials,\n\t\t}\n\t}\n\n\t_createTextFields(value1: string | null, value2: string | null, labelTextId: TranslationKey): Children {\n\t\tif (value1 || value2) {\n\t\t\treturn [\n\t\t\t\tm(TextField, {\n\t\t\t\t\tlabel: labelTextId,\n\t\t\t\t\tvalue: value1 || \"\",\n\t\t\t\t\tdisabled: true,\n\t\t\t\t}),\n\t\t\t\tm(TextField, {\n\t\t\t\t\tlabel: labelTextId,\n\t\t\t\t\tvalue: value2 || \"\",\n\t\t\t\t\tdisabled: true,\n\t\t\t\t}),\n\t\t\t]\n\t\t} else {\n\t\t\treturn null\n\t\t}\n\t}\n\n\t_createDeleteContactButton(action: ContactMergeAction): Children {\n\t\treturn m(IconButton, {\n\t\t\ttitle: \"delete_action\",\n\t\t\tclick: () => {\n\t\t\t\tDialog.confirm(\"deleteContact_msg\").then((confirmed) => {\n\t\t\t\t\tif (confirmed) {\n\t\t\t\t\t\tthis._close(action)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t},\n\t\t\ticon: Icons.Trash,\n\t\t})\n\t}\n\n\tshow(): Promise<ContactMergeAction> {\n\t\tthis.dialog.show()\n\t\tlet d = defer<ContactMergeAction>()\n\t\tthis.resolveFunction = d.resolve\n\t\treturn d.promise\n\t}\n\n\t_close(action: ContactMergeAction): void {\n\t\tthis.dialog.close()\n\t\tdelay(200).then(() => {\n\t\t\tthis.resolveFunction?.(action)\n\t\t})\n\t}\n}\n","import { ContactComparisonResult, IndifferentContactComparisonResult } from \"../api/common/TutanotaConstants\"\nimport { neverNull } from \"@tutao/tutanota-utils\"\nimport { isoDateToBirthday } from \"../api/common/utils/BirthdayUtils\"\nimport type { Contact } from \"../api/entities/tutanota/TypeRefs.js\"\nimport type { ContactMailAddress } from \"../api/entities/tutanota/TypeRefs.js\"\nimport type { Birthday } from \"../api/entities/tutanota/TypeRefs.js\"\nimport type { ContactAddress } from \"../api/entities/tutanota/TypeRefs.js\"\nimport type { ContactPhoneNumber } from \"../api/entities/tutanota/TypeRefs.js\"\nimport type { ContactSocialId } from \"../api/entities/tutanota/TypeRefs.js\"\n\n/**\n * returns all contacts that are deletable because another contact exists that is exactly the same, and all contacts that look similar and therfore may be merged.\n * contacts are never returned in both \"mergable\" and \"deletable\"\n * contact similarity is checked transitively, i.e. if a similar to b and b similar to c, then a similar to c\n */\nexport function getMergeableContacts(inputContacts: Contact[]): {\n\tmergeable: Contact[][]\n\tdeletable: Contact[]\n} {\n\tlet mergableContacts: Contact[][] = []\n\tlet duplicateContacts: Contact[] = []\n\tlet contacts = inputContacts.slice()\n\tlet firstContactIndex = 0\n\n\twhile (firstContactIndex < contacts.length - 1) {\n\t\tlet currentMergableContacts: Contact[] = []\n\t\tlet firstContact = contacts[firstContactIndex]\n\t\tcurrentMergableContacts.push(firstContact)\n\t\tlet secondContactIndex = firstContactIndex + 1\n\n\t\t// run through all contacts after the first and compare them with the first (+ all others already in the currentMergableArray)\n\t\twhile (secondContactIndex < contacts.length) {\n\t\t\tlet secondContact = contacts[secondContactIndex]\n\n\t\t\tif (firstContact._id[1] !== secondContact._id[1]) {\n\t\t\t\t// should not happen, just to be safe\n\t\t\t\tlet overallResult = ContactComparisonResult.Unique\n\n\t\t\t\t// compare the current second contact with all in the currentMergableArray to find out if the overall comparison result is equal, similar or unique\n\t\t\t\tfor (let i = 0; i < currentMergableContacts.length; i++) {\n\t\t\t\t\tlet result = _compareContactsForMerge(currentMergableContacts[i], secondContact)\n\n\t\t\t\t\tif (result === ContactComparisonResult.Equal) {\n\t\t\t\t\t\toverallResult = ContactComparisonResult.Equal\n\t\t\t\t\t\tbreak // equal is always the final result\n\t\t\t\t\t} else if (result === ContactComparisonResult.Similar) {\n\t\t\t\t\t\toverallResult = ContactComparisonResult.Similar // continue checking the other contacts in currentMergableContacts to see if there is an equal one\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// the contacts are unique, so we do not have to check the others\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (overallResult === ContactComparisonResult.Equal) {\n\t\t\t\t\tduplicateContacts.push(secondContact)\n\t\t\t\t\tcontacts.splice(secondContactIndex, 1)\n\t\t\t\t} else if (overallResult === ContactComparisonResult.Similar) {\n\t\t\t\t\tcurrentMergableContacts.push(secondContact)\n\t\t\t\t\tcontacts.splice(secondContactIndex, 1)\n\t\t\t\t} else {\n\t\t\t\t\tsecondContactIndex++\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (currentMergableContacts.length > 1) {\n\t\t\tmergableContacts.push(currentMergableContacts)\n\t\t}\n\n\t\tfirstContactIndex++\n\t}\n\n\treturn {\n\t\tmergeable: mergableContacts,\n\t\tdeletable: duplicateContacts,\n\t}\n}\n\n/**\n * merges two contacts (eliminatedContact is merged into keptContact). outside this function keptContact must be updated on the server and eliminatedContact must be deleted\n */\nexport function mergeContacts(keptContact: Contact, eliminatedContact: Contact): void {\n\tkeptContact.firstName = _getMergedNameField(keptContact.firstName, eliminatedContact.firstName)\n\tkeptContact.lastName = _getMergedNameField(keptContact.lastName, eliminatedContact.lastName)\n\tkeptContact.title = neverNull(_getMergedOtherField(keptContact.title, eliminatedContact.title, \", \"))\n\tkeptContact.comment = neverNull(_getMergedOtherField(keptContact.comment, eliminatedContact.comment, \"\\n\\n\"))\n\tkeptContact.company = neverNull(_getMergedOtherField(keptContact.company, eliminatedContact.company, \", \"))\n\tkeptContact.nickname = _getMergedOtherField(keptContact.nickname, eliminatedContact.nickname, \", \")\n\tkeptContact.role = neverNull(_getMergedOtherField(keptContact.role, eliminatedContact.role, \", \"))\n\tkeptContact.birthdayIso = _getMergedBirthdays(keptContact.birthdayIso, eliminatedContact.birthdayIso)\n\tkeptContact.mailAddresses = _getMergedEmailAddresses(keptContact.mailAddresses, eliminatedContact.mailAddresses)\n\tkeptContact.phoneNumbers = _getMergedPhoneNumbers(keptContact.phoneNumbers, eliminatedContact.phoneNumbers)\n\tkeptContact.socialIds = _getMergedSocialIds(keptContact.socialIds, eliminatedContact.socialIds)\n\tkeptContact.addresses = _getMergedAddresses(keptContact.addresses, eliminatedContact.addresses)\n\tkeptContact.presharedPassword = neverNull(_getMergedOtherField(keptContact.presharedPassword, eliminatedContact.presharedPassword, \"\")) // the passwords are never different and not null\n}\n\n/**\n * Result is unique if preshared passwords are not equal and are not empty.\n * Result is equal if all fields are equal or empty (types are ignored).\n * Result is similar if one of:\n * 1. name result is equal or similar and birthday result is similar or oneEmpty or equal or bothEmpty\n * 2. name result (bothEmpty or oneEmpty) and mail or phone result is similar or equal and birthday result is similar or oneEmpty or equal or bothEmpty\n * Otherwise the result is unique\n * Export for testing\n */\nexport function _compareContactsForMerge(contact1: Contact, contact2: Contact): ContactComparisonResult {\n\tlet nameResult = _compareFullName(contact1, contact2)\n\n\tlet mailResult = _compareMailAddresses(contact1.mailAddresses, contact2.mailAddresses)\n\n\tlet phoneResult = _comparePhoneNumbers(contact1.phoneNumbers, contact2.phoneNumbers)\n\n\tlet birthdayResult = _compareBirthdays(contact1, contact2)\n\n\tlet residualContactFieldsEqual = _areResidualContactFieldsEqual(contact1, contact2)\n\n\tif (\n\t\tbirthdayResult !== ContactComparisonResult.Unique &&\n\t\t(!contact1.presharedPassword || !contact2.presharedPassword || contact1.presharedPassword === contact2.presharedPassword)\n\t) {\n\t\tif (\n\t\t\t(nameResult === ContactComparisonResult.Equal || nameResult === IndifferentContactComparisonResult.BothEmpty) &&\n\t\t\t(mailResult === ContactComparisonResult.Equal || mailResult === IndifferentContactComparisonResult.BothEmpty) &&\n\t\t\t(phoneResult === ContactComparisonResult.Equal || phoneResult === IndifferentContactComparisonResult.BothEmpty) &&\n\t\t\tresidualContactFieldsEqual\n\t\t) {\n\t\t\tif (birthdayResult === IndifferentContactComparisonResult.BothEmpty || birthdayResult === ContactComparisonResult.Equal) {\n\t\t\t\treturn ContactComparisonResult.Equal\n\t\t\t} else {\n\t\t\t\treturn ContactComparisonResult.Similar\n\t\t\t}\n\t\t} else if (nameResult === ContactComparisonResult.Equal || nameResult === ContactComparisonResult.Similar) {\n\t\t\treturn ContactComparisonResult.Similar\n\t\t} else if (\n\t\t\t(nameResult === IndifferentContactComparisonResult.BothEmpty || nameResult === IndifferentContactComparisonResult.OneEmpty) &&\n\t\t\t(mailResult === ContactComparisonResult.Similar ||\n\t\t\t\tphoneResult === ContactComparisonResult.Similar ||\n\t\t\t\tmailResult === ContactComparisonResult.Equal ||\n\t\t\t\tphoneResult === ContactComparisonResult.Equal)\n\t\t) {\n\t\t\treturn ContactComparisonResult.Similar\n\t\t} else {\n\t\t\treturn ContactComparisonResult.Unique\n\t\t}\n\t} else {\n\t\treturn ContactComparisonResult.Unique\n\t}\n}\n\n/**\n * Names are equal if the last names are available and equal and first names are equal or first names are available and equal and last names are equal.\n * Names are similar if the last names are available and equal and at least one first name is empty or like equal but case insensitive.\n * Returns null if the contacts names are not comparable, i.e. one of the contacts first and last names are empty.\n * Export for testing\n */\nexport function _compareFullName(contact1: Contact, contact2: Contact): ContactComparisonResult | IndifferentContactComparisonResult {\n\tif (contact1.firstName === contact2.firstName && contact1.lastName === contact2.lastName && (contact1.lastName || contact1.firstName)) {\n\t\treturn ContactComparisonResult.Equal\n\t} else if (!contact1.firstName && !contact1.lastName && !contact2.firstName && !contact2.lastName) {\n\t\treturn IndifferentContactComparisonResult.BothEmpty\n\t} else if ((!contact1.firstName && !contact1.lastName) || (!contact2.firstName && !contact2.lastName)) {\n\t\treturn IndifferentContactComparisonResult.OneEmpty\n\t} else if (\n\t\tcontact1.firstName.toLowerCase() === contact2.firstName.toLowerCase() &&\n\t\tcontact1.lastName.toLowerCase() === contact2.lastName.toLowerCase() &&\n\t\tcontact1.lastName\n\t) {\n\t\treturn ContactComparisonResult.Similar\n\t} else if ((!contact1.firstName || !contact2.firstName) && contact1.lastName.toLowerCase() === contact2.lastName.toLowerCase() && contact1.lastName) {\n\t\treturn ContactComparisonResult.Similar\n\t} else {\n\t\treturn ContactComparisonResult.Unique\n\t}\n}\n\n/**\n * Provides name1 if it is not empty, otherwise name2\n * Export for testing\n */\nexport function _getMergedNameField(name1: string, name2: string): string {\n\tif (name1) {\n\t\treturn name1\n\t} else {\n\t\treturn name2\n\t}\n}\n\n/**\n * If the mail addresses (type is ignored) are all equal (order in array is ignored), the addresses are equal.\n * If at least one mail address is equal and all others are unique, the result is similar. If the mail addresses are equal (only case insensitive), then the result is also similar.\n * If one mail address list is empty, the result is oneEmpty because the mail addresses are not comparable.\n * If both are empty the result is both empty because the mail addresses are not comparable.\n * Otherwise the result is unique.\n * Export for testing\n */\nexport function _compareMailAddresses(\n\tcontact1MailAddresses: ContactMailAddress[],\n\tcontact2MailAddresses: ContactMailAddress[],\n): ContactComparisonResult | IndifferentContactComparisonResult {\n\treturn _compareValues(\n\t\tcontact1MailAddresses.map((m) => m.address),\n\t\tcontact2MailAddresses.map((m) => m.address),\n\t)\n}\n\n/**\n * Export for testing\n */\nexport function _getMergedEmailAddresses(mailAddresses1: ContactMailAddress[], mailAddresses2: ContactMailAddress[]): ContactMailAddress[] {\n\tlet filteredMailAddresses2 = mailAddresses2.filter((ma2) => {\n\t\treturn !mailAddresses1.find((ma1) => ma1.address.toLowerCase() === ma2.address.toLowerCase())\n\t})\n\treturn mailAddresses1.concat(filteredMailAddresses2)\n}\n\n/**\n * Export for testing\n */\nexport function _comparePhoneNumbers(\n\tcontact1PhoneNumbers: ContactPhoneNumber[],\n\tcontact2PhoneNumbers: ContactPhoneNumber[],\n): ContactComparisonResult | IndifferentContactComparisonResult {\n\treturn _compareValues(\n\t\tcontact1PhoneNumbers.map((m) => m.number),\n\t\tcontact2PhoneNumbers.map((m) => m.number),\n\t)\n}\n\n/**\n * Export for testing\n */\nexport function _getMergedPhoneNumbers(phoneNumbers1: ContactPhoneNumber[], phoneNumbers2: ContactPhoneNumber[]): ContactPhoneNumber[] {\n\tlet filteredNumbers2 = phoneNumbers2.filter((ma2) => {\n\t\tconst isIncludedInPhoneNumbers1 = phoneNumbers1.find((ma1) => ma1.number.replace(/\\s/g, \"\") === ma2.number.replace(/\\s/g, \"\"))\n\t\treturn !isIncludedInPhoneNumbers1\n\t})\n\treturn phoneNumbers1.concat(filteredNumbers2)\n}\n\n/**\n * used for clarifying of the unique and equal cases in compareContacts\n * Export for testing\n * returns similar only if socialids ore addresses are similar. Return of similar is basicaly not needed\n */\nexport function _areResidualContactFieldsEqual(contact1: Contact, contact2: Contact): boolean {\n\treturn (\n\t\t_isEqualOtherField(contact1.comment, contact2.comment) &&\n\t\t_isEqualOtherField(contact1.company, contact2.company) &&\n\t\t_isEqualOtherField(contact1.nickname, contact2.nickname) &&\n\t\t_isEqualOtherField(contact1.role, contact2.role) &&\n\t\t_isEqualOtherField(contact1.title, contact2.title) &&\n\t\t_isEqualOtherField(contact1.presharedPassword, contact2.presharedPassword) &&\n\t\t_areSocialIdsEqual(contact1.socialIds, contact2.socialIds) &&\n\t\t_areAddressesEqual(contact1.addresses, contact2.addresses)\n\t)\n}\n\nfunction _areSocialIdsEqual(contact1SocialIds: ContactSocialId[], contact2SocialIds: ContactSocialId[]): boolean {\n\tlet result = _compareValues(\n\t\tcontact1SocialIds.map((m) => m.socialId),\n\t\tcontact2SocialIds.map((m) => m.socialId),\n\t)\n\n\treturn result === IndifferentContactComparisonResult.BothEmpty || result === ContactComparisonResult.Equal\n}\n\n/**\n * Export for testing\n */\nexport function _getMergedSocialIds(socialIds1: ContactSocialId[], socialIds2: ContactSocialId[]): ContactSocialId[] {\n\tlet filteredSocialIds2 = socialIds2.filter((ma2) => {\n\t\treturn !socialIds1.find((ma1) => ma1.socialId === ma2.socialId)\n\t})\n\treturn socialIds1.concat(filteredSocialIds2)\n}\n\nfunction _areAddressesEqual(contact1Addresses: ContactAddress[], contact2Addresses: ContactAddress[]): boolean {\n\tlet result = _compareValues(\n\t\tcontact1Addresses.map((m) => m.address),\n\t\tcontact2Addresses.map((m) => m.address),\n\t)\n\n\treturn result === IndifferentContactComparisonResult.BothEmpty || result === ContactComparisonResult.Equal\n}\n\n/**\n * Export for testing\n */\nexport function _getMergedAddresses(addresses1: ContactAddress[], addresses2: ContactAddress[]): ContactAddress[] {\n\tlet filteredAddresses2 = addresses2.filter((ma2) => {\n\t\treturn !addresses1.find((ma1) => ma1.address === ma2.address)\n\t})\n\treturn addresses1.concat(filteredAddresses2)\n}\n\n/**\n * Export for testing\n */\nexport function _compareBirthdays(contact1: Contact, contact2: Contact): ContactComparisonResult | IndifferentContactComparisonResult {\n\tconst b1 = _convertIsoBirthday(contact1.birthdayIso)\n\n\tconst b2 = _convertIsoBirthday(contact2.birthdayIso)\n\n\tif (b1 && b2) {\n\t\tif (b1.day === b2.day && b1.month === b2.month) {\n\t\t\tif (b1.year === b2.year) {\n\t\t\t\treturn ContactComparisonResult.Equal\n\t\t\t} else if (b1.year && b2.year && b1.year !== b2.year) {\n\t\t\t\t// if we detect that one birthday has more information (year) we use that date\n\t\t\t\treturn ContactComparisonResult.Unique\n\t\t\t} else {\n\t\t\t\treturn ContactComparisonResult.Similar\n\t\t\t}\n\t\t} else {\n\t\t\treturn ContactComparisonResult.Unique\n\t\t}\n\t} else if ((contact1.birthdayIso && !contact2.birthdayIso) || (!contact1.birthdayIso && contact2.birthdayIso)) {\n\t\treturn IndifferentContactComparisonResult.OneEmpty\n\t} else {\n\t\treturn IndifferentContactComparisonResult.BothEmpty\n\t}\n}\n\nfunction _convertIsoBirthday(isoBirthday: string | null): Birthday | null {\n\tif (isoBirthday) {\n\t\ttry {\n\t\t\treturn isoDateToBirthday(isoBirthday)\n\t\t} catch (e) {\n\t\t\tconsole.log(\"failed to parse birthday\", e)\n\t\t\treturn null\n\t\t}\n\t} else {\n\t\treturn null\n\t}\n}\n\nfunction _compareValues(values1: string[], values2: string[]): ContactComparisonResult | IndifferentContactComparisonResult {\n\tif (values1.length === 0 && values2.length === 0) {\n\t\treturn IndifferentContactComparisonResult.BothEmpty\n\t} else if (values1.length === 0 || values2.length === 0) {\n\t\treturn IndifferentContactComparisonResult.OneEmpty\n\t}\n\n\tlet equalAddresses = values2.filter((c2) => values1.find((c1) => c1.trim() === c2.trim()))\n\n\tif (values1.length === values2.length && values1.length === equalAddresses.length) {\n\t\treturn ContactComparisonResult.Equal\n\t}\n\n\tlet equalAddressesInsensitive = values2.filter((c2) => values1.find((c1) => c1.trim().toLowerCase() === c2.trim().toLowerCase()))\n\n\tif (equalAddressesInsensitive.length > 0) {\n\t\treturn ContactComparisonResult.Similar\n\t}\n\n\treturn ContactComparisonResult.Unique\n}\n\n/**\n * Returns equal if both values are equal and unique otherwise\n */\nfunction _isEqualOtherField(otherAttribute1: string | null, otherAttribute2: string | null): boolean {\n\t// regard null as \"\"\n\tif (otherAttribute1 == null) {\n\t\totherAttribute1 = \"\"\n\t}\n\n\tif (otherAttribute2 == null) {\n\t\totherAttribute2 = \"\"\n\t}\n\n\treturn otherAttribute1 === otherAttribute2\n}\n\n/**\n * Provides the value that exists or both separated by the given separator if both have some content\n * Export for testing\n */\nexport function _getMergedOtherField(otherAttribute1: string | null, otherAttribute2: string | null, separator: string): string | null {\n\tif (otherAttribute1 === otherAttribute2) {\n\t\treturn otherAttribute2\n\t} else if (otherAttribute1 && otherAttribute2) {\n\t\treturn otherAttribute1 + separator + otherAttribute2\n\t} else if (!otherAttribute1 && otherAttribute2) {\n\t\treturn otherAttribute2\n\t} else {\n\t\treturn otherAttribute1\n\t}\n}\n\n/**\n * Export for testing\n */\nexport function _getMergedBirthdays(birthday1: string | null, birthday2: string | null): string | null {\n\tconst b1 = _convertIsoBirthday(birthday1)\n\n\tconst b2 = _convertIsoBirthday(birthday2)\n\n\tif (b1 && b2) {\n\t\tif (b1.year) {\n\t\t\treturn birthday1\n\t\t} else if (b2.year) {\n\t\t\treturn birthday2\n\t\t} else {\n\t\t\treturn birthday1\n\t\t}\n\t} else if (birthday1) {\n\t\treturn birthday1\n\t} else if (birthday2) {\n\t\treturn birthday2\n\t} else {\n\t\treturn null\n\t}\n}\n","import { convertToDataFile } from \"../api/common/DataFile\"\nimport { createFile } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { stringToUtf8Uint8Array } from \"@tutao/tutanota-utils\"\nimport { ContactAddressType, ContactPhoneNumberType } from \"../api/common/TutanotaConstants\"\nimport type { Contact, ContactSocialId, ContactPhoneNumber, ContactAddress, ContactMailAddress } from \"../api/entities/tutanota/TypeRefs.js\"\nimport { assertMainOrNode } from \"../api/common/Env\"\nimport { locator } from \"../api/main/MainLocator\"\nimport { getSocialUrl } from \"./model/ContactUtils.js\"\n\nassertMainOrNode()\n\nexport function exportContacts(contacts: Contact[]): Promise<void> {\n\tlet vCardFile = contactsToVCard(contacts)\n\tlet data = stringToUtf8Uint8Array(vCardFile)\n\tlet tmpFile = createFile()\n\ttmpFile.name = \"vCard3.0.vcf\"\n\ttmpFile.mimeType = \"vCard/rfc2426\"\n\ttmpFile.size = String(data.byteLength)\n\treturn locator.fileController.saveDataFile(convertToDataFile(tmpFile, data))\n}\n\n/**\n * Converts an array of contacts to a vCard 3.0 compatible string.\n *\n * @param contacts\n * @returns vCard 3.0 compatible string which is the vCard of each all contacts concatanted.\n */\nexport function contactsToVCard(contacts: Contact[]): string {\n\tlet vCardFile = \"\"\n\tcontacts.forEach((contact) => {\n\t\tvCardFile += _contactToVCard(contact)\n\t})\n\treturn vCardFile\n}\n\n/**\n * Export for testing\n */\nexport function _contactToVCard(contact: Contact): string {\n\tlet contactToVCardString = \"BEGIN:VCARD\\nVERSION:3.0\\n\" //must be invcluded in vCard3.0\n\n\t//FN tag must be included in vCard3.0\n\tlet fnString = \"FN:\"\n\tfnString += contact.title ? _getVCardEscaped(contact.title) + \" \" : \"\"\n\tfnString += contact.firstName ? _getVCardEscaped(contact.firstName) + \" \" : \"\"\n\tfnString += contact.lastName ? _getVCardEscaped(contact.lastName) : \"\"\n\tcontactToVCardString += _getFoldedString(fnString.trim()) + \"\\n\"\n\t//N tag must be included in vCard3.0\n\tlet nString = \"N:\"\n\tnString += contact.lastName ? _getVCardEscaped(contact.lastName) + \";\" : \";\"\n\tnString += contact.firstName ? _getVCardEscaped(contact.firstName) + \";;\" : \";;\"\n\tnString += contact.title ? _getVCardEscaped(contact.title) + \";\" : \";\"\n\tcontactToVCardString += _getFoldedString(nString) + \"\\n\"\n\tcontactToVCardString += contact.nickname ? _getFoldedString(\"NICKNAME:\" + _getVCardEscaped(contact.nickname)) + \"\\n\" : \"\"\n\n\t//adds oldBirthday converted into a string if present else if available new birthday format is added to contactToVCardString\n\tif (contact.birthdayIso) {\n\t\tconst bday = contact.birthdayIso\n\t\t// we use 1111 as marker if no year has been defined as vcard 3.0 does not support dates without year\n\t\t// vcard 4.0 supports iso date without year\n\t\tconst bdayExported = bday.startsWith(\"--\") ? bday.replace(\"--\", \"1111-\") : bday\n\t\tcontactToVCardString += \"BDAY:\" + bdayExported + \"\\n\"\n\t}\n\n\tcontactToVCardString += _vCardFormatArrayToString(_addressesToVCardAddresses(contact.addresses), \"ADR\")\n\tcontactToVCardString += _vCardFormatArrayToString(_addressesToVCardAddresses(contact.mailAddresses), \"EMAIL\")\n\tcontactToVCardString += _vCardFormatArrayToString(_phoneNumbersToVCardPhoneNumbers(contact.phoneNumbers), \"TEL\")\n\tcontactToVCardString += _vCardFormatArrayToString(_socialIdsToVCardSocialUrls(contact.socialIds), \"URL\")\n\tcontactToVCardString += contact.role ? _getFoldedString(\"ROLE:\" + _getVCardEscaped(contact.role)) + \"\\n\" : \"\"\n\tcontactToVCardString += contact.company ? _getFoldedString(\"ORG:\" + _getVCardEscaped(contact.company)) + \"\\n\" : \"\"\n\tcontactToVCardString += contact.comment ? _getFoldedString(\"NOTE:\" + _getVCardEscaped(contact.comment)) + \"\\n\" : \"\"\n\tcontactToVCardString += \"END:VCARD\\n\\n\" //must be included in vCard3.0\n\n\treturn contactToVCardString\n}\n\n/**\n * export for testing\n * Works for mail addresses the same as for addresses\n * Returns all mail-addresses/addresses and their types in an object array\n */\nexport function _addressesToVCardAddresses(addresses: ContactMailAddress[] | ContactAddress[]): {\n\tKIND: string\n\tCONTENT: string\n}[] {\n\treturn addresses.map((ad) => {\n\t\tlet kind = \"\"\n\n\t\tswitch (ad.type) {\n\t\t\tcase ContactAddressType.PRIVATE:\n\t\t\t\tkind = \"home\"\n\t\t\t\tbreak\n\n\t\t\tcase ContactAddressType.WORK:\n\t\t\t\tkind = \"work\"\n\t\t\t\tbreak\n\n\t\t\tdefault:\n\t\t}\n\n\t\treturn {\n\t\t\tKIND: kind,\n\t\t\tCONTENT: ad.address,\n\t\t}\n\t})\n}\n\n/**\n * export for testing\n * Returns all phone numbers and their types in an object array\n */\nexport function _phoneNumbersToVCardPhoneNumbers(numbers: ContactPhoneNumber[]): {\n\tKIND: string\n\tCONTENT: string\n}[] {\n\treturn numbers.map((num) => {\n\t\tlet kind = \"\"\n\n\t\tswitch (num.type) {\n\t\t\tcase ContactPhoneNumberType.PRIVATE:\n\t\t\t\tkind = \"home\"\n\t\t\t\tbreak\n\n\t\t\tcase ContactPhoneNumberType.WORK:\n\t\t\t\tkind = \"work\"\n\t\t\t\tbreak\n\n\t\t\tcase ContactPhoneNumberType.MOBILE:\n\t\t\t\tkind = \"cell\"\n\t\t\t\tbreak\n\n\t\t\tcase ContactPhoneNumberType.FAX:\n\t\t\t\tkind = \"fax\"\n\t\t\t\tbreak\n\n\t\t\tdefault:\n\t\t}\n\n\t\treturn {\n\t\t\tKIND: kind,\n\t\t\tCONTENT: num.number,\n\t\t}\n\t})\n}\n\n/**\n *  export for testing\n *  Returns all socialIds as a vCard Url in an object array\n *  Type is not defined here. URL tag has no fitting type implementation\n */\nexport function _socialIdsToVCardSocialUrls(socialIds: ContactSocialId[]): {\n\tKIND: string\n\tCONTENT: string\n}[] {\n\treturn socialIds.map((sId) => {\n\t\t//IN VCARD 3.0 is no type for URLS\n\t\treturn {\n\t\t\tKIND: \"\",\n\t\t\tCONTENT: getSocialUrl(sId),\n\t\t}\n\t})\n}\n\n/**\n * export for testing\n * Returns a multiple line string from the before created object arrays of addresses, mail addresses and socialIds\n */\nexport function _vCardFormatArrayToString(\n\ttypeAndContentArray: {\n\t\tKIND: string\n\t\tCONTENT: string\n\t}[],\n\ttagContent: string,\n): string {\n\treturn typeAndContentArray.reduce((result, elem) => {\n\t\tif (elem.KIND) {\n\t\t\treturn result + _getFoldedString(tagContent + \";TYPE=\" + elem.KIND + \":\" + _getVCardEscaped(elem.CONTENT)) + \"\\n\"\n\t\t} else {\n\t\t\treturn result + _getFoldedString(tagContent + \":\" + _getVCardEscaped(elem.CONTENT)) + \"\\n\"\n\t\t}\n\t}, \"\")\n}\n\n/**\n * Adds line breaks and padding in a CONTENT line to adhere to the vCard\n * specifications.\n *\n * @param text The text to fold.\n * @returns The same text but folded every 75 characters.\n * @see https://datatracker.ietf.org/doc/html/rfc6350#section-3.2\n */\nfunction _getFoldedString(text: string): string {\n\tlet separateLinesArray: string[] = []\n\n\twhile (text.length > 75) {\n\t\tseparateLinesArray.push(text.substring(0, 75))\n\t\ttext = text.substring(75, text.length)\n\t}\n\n\tseparateLinesArray.push(text)\n\ttext = separateLinesArray.join(\"\\n \")\n\treturn text\n}\n\nfunction _getVCardEscaped(content: string): string {\n\tcontent = content.replace(/\\n/g, \"\\\\n\")\n\tcontent = content.replace(/;/g, \"\\\\;\")\n\tcontent = content.replace(/,/g, \"\\\\,\")\n\treturn content\n}\n","import m, { Children, ClassComponent, Vnode } from \"mithril\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport { TextField, TextFieldType } from \"../../gui/base/TextField.js\"\nimport { Icons } from \"../../gui/base/icons/Icons\"\nimport type { ContactAddressType } from \"../../api/common/TutanotaConstants\"\nimport { ContactPhoneNumberType, getContactSocialType } from \"../../api/common/TutanotaConstants\"\nimport type { Contact, ContactAddress, ContactPhoneNumber, ContactSocialId } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { downcast, memoized, NBSP, noOp } from \"@tutao/tutanota-utils\"\nimport { getContactAddressTypeLabel, getContactPhoneNumberTypeLabel, getContactSocialTypeLabel } from \"./ContactGuiUtils\"\nimport { formatBirthdayOfContact, getSocialUrl } from \"../model/ContactUtils\"\nimport { assertMainOrNode } from \"../../api/common/Env\"\nimport { IconButton } from \"../../gui/base/IconButton.js\"\nimport { ButtonSize } from \"../../gui/base/ButtonSize.js\"\nimport { PartialRecipient } from \"../../api/common/recipients/Recipient.js\"\n\nassertMainOrNode()\n\nexport interface ContactViewerAttrs {\n\tcontact: Contact\n\tonWriteMail: (to: PartialRecipient) => unknown\n}\n\n/**\n *  Displays information about a single contact\n */\nexport class ContactViewer implements ClassComponent<ContactViewerAttrs> {\n\tprivate readonly contactAppellation = memoized((contact: Contact) => {\n\t\tconst title = contact.title ? `${contact.title} ` : \"\"\n\t\t// const nickname = contact.nickname ? ` | \"${contact.nickname}\"` : \"\"\n\t\tconst fullName = `${contact.firstName} ${contact.lastName}`\n\t\treturn (title + fullName).trim()\n\t})\n\n\tprivate readonly formattedBirthday = memoized((contact: Contact) => {\n\t\treturn this.hasBirthday(contact) ? formatBirthdayOfContact(contact) : null\n\t})\n\n\tprivate hasBirthday(contact: Contact): boolean {\n\t\treturn contact.birthdayIso != null\n\t}\n\n\tview({ attrs }: Vnode<ContactViewerAttrs>): Children {\n\t\tconst { contact, onWriteMail } = attrs\n\t\treturn m(\".plr-l.pb-floating.mlr-safe-inset\", [\n\t\t\tm(\"\", [\n\t\t\t\tm(\n\t\t\t\t\t\".flex-space-between.flex-wrap.mt-m\",\n\t\t\t\t\tm(\".left.flex-grow-shrink-150\", [\n\t\t\t\t\t\tm(\".h2.selectable.text-break\", [\n\t\t\t\t\t\t\tthis.contactAppellation(contact),\n\t\t\t\t\t\t\tNBSP, // alignment in case nothing is present here\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tcontact.nickname ? m(\"\", `\"${contact.nickname}\"`) : null,\n\t\t\t\t\t\tm(\n\t\t\t\t\t\t\t\"\",\n\t\t\t\t\t\t\tinsertBetween([contact.role ? m(\"span\", contact.role) : null, contact.company ? m(\"span\", contact.company) : null], () =>\n\t\t\t\t\t\t\t\tm(\n\t\t\t\t\t\t\t\t\t\"span.plr-s\",\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\t\t\t\tfontWeight: \"900\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\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),\n\t\t\t\t\t\tthis.hasBirthday(contact) ? m(\"\", this.formattedBirthday(contact)) : null,\n\t\t\t\t\t]),\n\t\t\t\t),\n\t\t\t\tm(\"hr.hr.mt.mb\"),\n\t\t\t]),\n\t\t\tthis.renderMailAddressesAndPhones(contact, onWriteMail),\n\t\t\tthis.renderAddressesAndSocialIds(contact),\n\t\t\tthis.renderComment(contact),\n\t\t])\n\t}\n\n\tprivate renderAddressesAndSocialIds(contact: Contact): Children {\n\t\tconst addresses = contact.addresses.map((element) => this.renderAddress(element))\n\t\tconst socials = contact.socialIds.map((element) => this.renderSocialId(element))\n\t\treturn addresses.length > 0 || socials.length > 0\n\t\t\t? m(\".wrapping-row\", [\n\t\t\t\t\tm(\".address.mt-l\", addresses.length > 0 ? [m(\".h4\", lang.get(\"address_label\")), m(\".aggregateEditors\", addresses)] : null),\n\t\t\t\t\tm(\".social.mt-l\", socials.length > 0 ? [m(\".h4\", lang.get(\"social_label\")), m(\".aggregateEditors\", socials)] : null),\n\t\t\t  ])\n\t\t\t: null\n\t}\n\n\tprivate renderMailAddressesAndPhones(contact: Contact, onWriteMail: ContactViewerAttrs[\"onWriteMail\"]): Children {\n\t\tconst mailAddresses = contact.mailAddresses.map((element) => this.renderMailAddress(contact, element, onWriteMail))\n\t\tconst phones = contact.phoneNumbers.map((element) => this.renderPhoneNumber(element))\n\t\treturn mailAddresses.length > 0 || phones.length > 0\n\t\t\t? m(\".wrapping-row\", [\n\t\t\t\t\tm(\".mail.mt-l\", mailAddresses.length > 0 ? [m(\".h4\", lang.get(\"email_label\")), m(\".aggregateEditors\", [mailAddresses])] : null),\n\t\t\t\t\tm(\".phone.mt-l\", phones.length > 0 ? [m(\".h4\", lang.get(\"phone_label\")), m(\".aggregateEditors\", [phones])] : null),\n\t\t\t  ])\n\t\t\t: null\n\t}\n\n\tprivate renderComment(contact: Contact): Children {\n\t\treturn contact.comment && contact.comment.trim().length > 0\n\t\t\t? [m(\".h4.mt-l\", lang.get(\"comment_label\")), m(\"p.mt-l.text-prewrap.text-break.selectable\", contact.comment)]\n\t\t\t: null\n\t}\n\n\tprivate renderSocialId(contactSocialId: ContactSocialId): Children {\n\t\tconst showButton = m(IconButton, {\n\t\t\ttitle: \"showURL_alt\",\n\t\t\tclick: noOp,\n\t\t\ticon: Icons.ArrowForward,\n\t\t\tsize: ButtonSize.Compact,\n\t\t})\n\t\treturn m(TextField, {\n\t\t\tlabel: () => getContactSocialTypeLabel(getContactSocialType(contactSocialId), contactSocialId.customTypeName),\n\t\t\tvalue: contactSocialId.socialId,\n\t\t\tdisabled: true,\n\t\t\tinjectionsRight: () => m(`a[href=${getSocialUrl(contactSocialId)}][target=_blank]`, showButton),\n\t\t})\n\t}\n\n\tprivate renderMailAddress(contact: Contact, address: ContactAddress, onWriteMail: ContactViewerAttrs[\"onWriteMail\"]): Children {\n\t\tconst newMailButton = m(IconButton, {\n\t\t\ttitle: \"sendMail_alt\",\n\t\t\tclick: () => onWriteMail({ name: `${contact.firstName} ${contact.lastName}`.trim(), address: address.address, contact: contact }),\n\t\t\ticon: Icons.PencilSquare,\n\t\t\tsize: ButtonSize.Compact,\n\t\t})\n\t\treturn m(TextField, {\n\t\t\tlabel: () => getContactAddressTypeLabel(address.type as any, address.customTypeName),\n\t\t\tvalue: address.address,\n\t\t\tdisabled: true,\n\t\t\tinjectionsRight: () => [newMailButton],\n\t\t})\n\t}\n\n\tprivate renderPhoneNumber(phone: ContactPhoneNumber): Children {\n\t\tconst callButton = m(IconButton, {\n\t\t\ttitle: \"callNumber_alt\",\n\t\t\tclick: () => null,\n\t\t\ticon: Icons.Call,\n\t\t\tsize: ButtonSize.Compact,\n\t\t})\n\t\treturn m(TextField, {\n\t\t\tlabel: () => getContactPhoneNumberTypeLabel(phone.type as ContactPhoneNumberType, phone.customTypeName),\n\t\t\tvalue: phone.number,\n\t\t\tdisabled: true,\n\t\t\tinjectionsRight: () => m(`a[href=\"tel:${phone.number}\"][target=_blank]`, callButton),\n\t\t})\n\t}\n\n\tprivate renderAddress(address: ContactAddress): Children {\n\t\tlet prepAddress: string\n\n\t\tif (address.address.indexOf(\"\\n\") !== -1) {\n\t\t\tprepAddress = encodeURIComponent(address.address.split(\"\\n\").join(\" \"))\n\t\t} else {\n\t\t\tprepAddress = encodeURIComponent(address.address)\n\t\t}\n\n\t\tconst showButton = m(IconButton, {\n\t\t\ttitle: \"showAddress_alt\",\n\t\t\tclick: () => null,\n\t\t\ticon: Icons.Pin,\n\t\t\tsize: ButtonSize.Compact,\n\t\t})\n\t\treturn m(TextField, {\n\t\t\tlabel: () => getContactAddressTypeLabel(downcast<ContactAddressType>(address.type), address.customTypeName),\n\t\t\tvalue: address.address,\n\t\t\tdisabled: true,\n\t\t\ttype: TextFieldType.Area,\n\t\t\tinjectionsRight: () => m(`a[href=\"https://www.openstreetmap.org/search?query=${prepAddress}\"][target=_blank]`, showButton),\n\t\t})\n\t}\n}\n\nfunction insertBetween(array: Children[], spacer: () => Children) {\n\tlet ret: Children = []\n\n\tfor (let e of array) {\n\t\tif (e != null) {\n\t\t\tif (ret.length > 0) {\n\t\t\t\tret.push(spacer())\n\t\t\t}\n\n\t\t\tret.push(e)\n\t\t}\n\t}\n\n\treturn ret\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport { theme } from \"../../gui/theme.js\"\nimport { Contact } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { ContactViewer } from \"./ContactViewer.js\"\nimport { PartialRecipient } from \"../../api/common/recipients/Recipient.js\"\nimport { responsiveCardHMargin } from \"../../gui/cards.js\"\n\nexport interface ContactCardAttrs {\n\tcontact: Contact\n\tonWriteMail: (to: PartialRecipient) => unknown\n}\n\n/** Wraps contact viewer in a nice card. */\nexport class ContactCardViewer implements Component<ContactCardAttrs> {\n\tview({ attrs }: Vnode<ContactCardAttrs>): Children {\n\t\tconst { contact, onWriteMail } = attrs\n\t\treturn [\n\t\t\tm(\n\t\t\t\t\".border-radius-big.rel\",\n\t\t\t\t{\n\t\t\t\t\tclass: responsiveCardHMargin(),\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tbackgroundColor: theme.content_bg,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tm(ContactViewer, { contact, onWriteMail }),\n\t\t\t),\n\t\t\tm(\".mt-l\"),\n\t\t]\n\t}\n}\n","import m, { Children, Component, Vnode } from \"mithril\"\nimport { IconButton } from \"../../gui/base/IconButton.js\"\nimport { Icons } from \"../../gui/base/icons/Icons.js\"\n\nexport interface MobileContactActionBarAttrs {\n\teditAction: () => unknown\n\tdeleteAction: () => unknown\n}\n\n/** Toolbar with contact actions at the bottom of single-column layout. */\nexport class MobileContactActionBar implements Component<MobileContactActionBarAttrs> {\n\tview(vnode: Vnode<MobileContactActionBarAttrs>): Children {\n\t\tconst { attrs } = vnode\n\n\t\treturn m(\n\t\t\t\".bottom-nav.bottom-action-bar.flex.items-center.plr-l\",\n\t\t\t{\n\t\t\t\tstyle: {\n\t\t\t\t\tjustifyContent: \"space-around\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t[\n\t\t\t\tm(IconButton, {\n\t\t\t\t\ttitle: \"delete_action\",\n\t\t\t\t\ticon: Icons.Trash,\n\t\t\t\t\tclick: attrs.deleteAction,\n\t\t\t\t}),\n\t\t\t\tm(IconButton, {\n\t\t\t\t\ttitle: \"edit_action\",\n\t\t\t\t\ticon: Icons.Edit,\n\t\t\t\t\tclick: attrs.editAction,\n\t\t\t\t}),\n\t\t\t],\n\t\t)\n\t}\n}\n","import m, { Children, Component, Vnode, VnodeDOM } from \"mithril\"\nimport { Contact } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { IconButton } from \"../../gui/base/IconButton.js\"\nimport { Icons } from \"../../gui/base/icons/Icons.js\"\nimport { keyManager, Shortcut } from \"../../misc/KeyManager.js\"\nimport { Keys } from \"../../api/common/TutanotaConstants.js\"\n\nexport interface ContactViewToolbarAttrs {\n\tcontacts: Contact[]\n\tonEdit: (contact: Contact) => unknown\n\tonDelete: (contacts: Contact[]) => unknown\n\tonMerge: (left: Contact, right: Contact) => unknown\n\tonExport: (contacts: Contact[]) => unknown\n}\n\n/**\n * Displays actions for contact or multiple contacts.\n * Also registers shortcuts\n */\nexport class ContactViewerActions implements Component<ContactViewToolbarAttrs> {\n\tprivate shortcuts: Array<Shortcut> = []\n\n\tview({ attrs }: Vnode<ContactViewToolbarAttrs>): Children {\n\t\tconst { contacts, onDelete, onEdit, onMerge, onExport } = attrs\n\t\tconst actionButtons: Children[] = []\n\t\tif (this.canEdit(contacts)) {\n\t\t\tactionButtons.push(\n\t\t\t\tm(IconButton, {\n\t\t\t\t\ttitle: \"edit_action\",\n\t\t\t\t\tclick: () => onEdit(contacts[0]),\n\t\t\t\t\ticon: Icons.Edit,\n\t\t\t\t}),\n\t\t\t)\n\t\t} else if (this.canMerge(contacts)) {\n\t\t\tactionButtons.push(\n\t\t\t\tm(IconButton, {\n\t\t\t\t\ttitle: \"merge_action\",\n\t\t\t\t\tclick: () => onMerge(contacts[0], contacts[1]),\n\t\t\t\t\ticon: Icons.People,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\n\t\tif (this.canExport(contacts)) {\n\t\t\tactionButtons.push(\n\t\t\t\tm(IconButton, {\n\t\t\t\t\ttitle: \"export_action\",\n\t\t\t\t\tclick: () => onExport(contacts),\n\t\t\t\t\ticon: Icons.Export,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t\tif (this.canDelete(contacts)) {\n\t\t\tactionButtons.push(\n\t\t\t\tm(IconButton, {\n\t\t\t\t\ttitle: \"delete_action\",\n\t\t\t\t\tclick: () => onDelete(contacts),\n\t\t\t\t\ticon: Icons.Trash,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t\treturn actionButtons\n\t}\n\n\tonupdate(vnode: VnodeDOM<ContactViewToolbarAttrs>) {\n\t\tkeyManager.unregisterShortcuts(this.shortcuts)\n\t\tthis.shortcuts.length = 0\n\t\tconst { contacts, onEdit, onDelete, onMerge, onExport } = vnode.attrs\n\t\tif (this.canEdit(contacts)) {\n\t\t\tthis.shortcuts.push({\n\t\t\t\tkey: Keys.E,\n\t\t\t\texec: () => {\n\t\t\t\t\tonEdit(contacts[0])\n\t\t\t\t},\n\t\t\t\thelp: \"edit_action\",\n\t\t\t})\n\t\t}\n\n\t\tif (this.canDelete(contacts)) {\n\t\t\tthis.shortcuts.push({\n\t\t\t\tkey: Keys.DELETE,\n\t\t\t\texec: () => {\n\t\t\t\t\tonDelete(contacts)\n\t\t\t\t},\n\t\t\t\thelp: \"delete_action\",\n\t\t\t})\n\t\t}\n\n\t\tif (this.canMerge(contacts)) {\n\t\t\tthis.shortcuts.push({\n\t\t\t\tkey: Keys.M,\n\t\t\t\tctrl: true,\n\t\t\t\texec: () => {\n\t\t\t\t\tonMerge(contacts[0], contacts[1])\n\t\t\t\t},\n\t\t\t\thelp: \"merge_action\",\n\t\t\t})\n\t\t}\n\n\t\tif (this.canExport(contacts)) {\n\t\t\tthis.shortcuts.push({\n\t\t\t\tkey: Keys.E,\n\t\t\t\tctrl: true,\n\t\t\t\texec: () => {\n\t\t\t\t\tonExport(contacts)\n\t\t\t\t},\n\t\t\t\thelp: \"export_action\",\n\t\t\t})\n\t\t}\n\t\tkeyManager.registerShortcuts(this.shortcuts)\n\t}\n\n\tprivate canExport(contacts: Contact[]) {\n\t\treturn contacts.length > 0\n\t}\n\n\tprivate canMerge(contacts: Contact[]) {\n\t\treturn contacts.length === 2\n\t}\n\n\tprivate canDelete(contacts: Contact[]) {\n\t\treturn contacts.length > 0\n\t}\n\n\tprivate canEdit(contacts: Contact[]) {\n\t\treturn contacts.length === 1\n\t}\n}\n","import type { Contact } from \"../api/entities/tutanota/TypeRefs.js\"\nimport {\n\tBirthday,\n\tcreateBirthday,\n\tcreateContact,\n\tcreateContactAddress,\n\tcreateContactMailAddress,\n\tcreateContactPhoneNumber,\n\tcreateContactSocialId,\n} from \"../api/entities/tutanota/TypeRefs.js\"\nimport { ContactAddressType, ContactPhoneNumberType, ContactSocialType } from \"../api/common/TutanotaConstants\"\nimport { decodeBase64, decodeQuotedPrintable } from \"@tutao/tutanota-utils\"\nimport { birthdayToIsoDate, isValidBirthday } from \"../api/common/utils/BirthdayUtils\"\nimport { ParsingError } from \"../api/common/error/ParsingError\"\nimport { assertMainOrNode } from \"../api/common/Env\"\n\nassertMainOrNode()\n\n/**\n * split file content with multiple vCards into a list of vCard strings\n * @param vCardFileData\n */\nexport function vCardFileToVCards(vCardFileData: string): string[] | null {\n\tlet V4 = \"\\nVERSION:4.0\"\n\tlet V3 = \"\\nVERSION:3.0\"\n\tlet V2 = \"\\nVERSION:2.1\"\n\tlet B = \"BEGIN:VCARD\\n\"\n\tlet E = \"END:VCARD\"\n\tvCardFileData = vCardFileData.replace(/begin:vcard/g, \"BEGIN:VCARD\")\n\tvCardFileData = vCardFileData.replace(/end:vcard/g, \"END:VCARD\")\n\tvCardFileData = vCardFileData.replace(/version:2.1/g, \"VERSION:2.1\")\n\n\tif (\n\t\tvCardFileData.indexOf(\"BEGIN:VCARD\") > -1 &&\n\t\tvCardFileData.indexOf(E) > -1 &&\n\t\t(vCardFileData.indexOf(V4) > -1 || vCardFileData.indexOf(V3) > -1 || vCardFileData.indexOf(V2) > -1)\n\t) {\n\t\tvCardFileData = vCardFileData.replace(/\\r/g, \"\")\n\t\tvCardFileData = vCardFileData.replace(/\\n /g, \"\") //folding symbols removed\n\n\t\tvCardFileData = vCardFileData.replace(/\\nEND:VCARD\\n\\n/g, \"\")\n\t\tvCardFileData = vCardFileData.replace(/\\nEND:VCARD\\n/g, \"\")\n\t\tvCardFileData = vCardFileData.replace(/\\nEND:VCARD/g, \"\")\n\t\tvCardFileData = vCardFileData.substring(vCardFileData.indexOf(B) + B.length)\n\t\treturn vCardFileData.split(B)\n\t} else {\n\t\treturn null\n\t}\n}\n\nexport function vCardEscapingSplit(details: string): string[] {\n\tdetails = details.replace(/\\\\\\\\/g, \"--bslashbslash++\")\n\tdetails = details.replace(/\\\\;/g, \"--semiColonsemiColon++\")\n\tdetails = details.replace(/\\\\:/g, \"--dPunktdPunkt++\")\n\tlet array = details.split(\";\")\n\tarray = array.map((elem) => {\n\t\treturn elem.trim()\n\t})\n\treturn array\n}\n\nexport function vCardReescapingArray(details: string[]): string[] {\n\treturn details.map((a) => {\n\t\ta = a.replace(/\\-\\-bslashbslash\\+\\+/g, \"\\\\\")\n\t\ta = a.replace(/\\-\\-semiColonsemiColon\\+\\+/g, \";\")\n\t\ta = a.replace(/\\-\\-dPunktdPunkt\\+\\+/g, \":\")\n\t\ta = a.replace(/\\\\n/g, \"\\n\")\n\t\ta = a.replace(/\\\\,/g, \",\")\n\t\treturn a\n\t})\n}\n\nexport function vCardEscapingSplitAdr(addressDetails: string): string[] {\n\taddressDetails = addressDetails.replace(/\\\\\\\\/g, \"--bslashbslash++\")\n\taddressDetails = addressDetails.replace(/\\\\;/g, \"--semiColonsemiColon++\")\n\tlet array = addressDetails.split(\";\")\n\treturn array.map((elem) => {\n\t\tif (elem.trim().length > 0) {\n\t\t\treturn elem.trim().concat(\"\\n\")\n\t\t} else {\n\t\t\t// needed for only Space elements in Address\n\t\t\treturn \"\"\n\t\t}\n\t})\n}\n\nfunction _decodeTag(encoding: string, charset: string, text: string): string {\n\tlet decoder = (cs: string, l: string) => l\n\n\tswitch (encoding.toLowerCase()) {\n\t\tcase \"quoted-printable:\":\n\t\t\tdecoder = decodeQuotedPrintable\n\t\t\tbreak\n\n\t\tcase \"base64:\":\n\t\t\tdecoder = decodeBase64\n\t}\n\n\treturn text\n\t\t.split(\";\")\n\t\t.map((line) => decoder(charset, line))\n\t\t.join(\";\")\n}\n\n/**\n * @returns The list of created Contact instances (but not yet saved) or null if vCardFileData is not a valid vCard string.\n */\nexport function vCardListToContacts(vCardList: string[], ownerGroupId: Id): Contact[] {\n\tlet contacts: Contact[] = []\n\n\tfor (let i = 0; i < vCardList.length; i++) {\n\t\tlet contact = createContact()\n\t\tcontact._area = \"0\" // legacy\n\n\t\tcontact._owner = ownerGroupId // legacy\n\n\t\tcontact.autoTransmitPassword = \"\"\n\t\tcontact._ownerGroup = ownerGroupId\n\t\tlet vCardLines = vCardList[i].split(\"\\n\")\n\n\t\tfor (let j = 0; j < vCardLines.length; j++) {\n\t\t\tlet indexAfterTag = vCardLines[j].indexOf(\":\")\n\t\t\tlet tagAndTypeString = vCardLines[j].substring(0, indexAfterTag).toUpperCase()\n\t\t\tlet tagName = tagAndTypeString.split(\";\")[0]\n\t\t\tlet tagValue = vCardLines[j].substring(indexAfterTag + 1)\n\t\t\tlet encodingObj = vCardLines[j].split(\";\").find((line) => line.includes(\"ENCODING=\"))\n\t\t\tlet encoding = encodingObj ? encodingObj.split(\"=\")[1] : \"\"\n\t\t\tlet charsetObj = vCardLines[j].split(\";\").find((line) => line.includes(\"CHARSET=\"))\n\t\t\tlet charset = charsetObj ? charsetObj.split(\"=\")[1] : \"utf-8\"\n\t\t\ttagValue = _decodeTag(encoding, charset, tagValue)\n\n\t\t\tswitch (tagName) {\n\t\t\t\tcase \"N\":\n\t\t\t\t\tlet nameDetails = vCardReescapingArray(vCardEscapingSplit(tagValue))\n\n\t\t\t\t\tfor (let i = nameDetails.length; nameDetails.length < 3; i++) {\n\t\t\t\t\t\tnameDetails.push(\"\")\n\t\t\t\t\t}\n\n\t\t\t\t\tcontact.lastName = nameDetails[0]\n\t\t\t\t\tcontact.firstName = (nameDetails[1] + \" \" + nameDetails[2]).trim() // nameDetails[2] (second first name) may be empty\n\n\t\t\t\t\tcontact.title = nameDetails[3]\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"FN\":\n\t\t\t\t\t//Thunderbird can export FULLNAME tag if that is given with the email address automatic contact creation. If there is no first name or second name the namestring will be saved as full name.\n\t\t\t\t\tif (contact.firstName === \"\" && contact.lastName === \"\" && contact.title == null) {\n\t\t\t\t\t\tlet fullName = vCardReescapingArray(vCardEscapingSplit(tagValue))\n\t\t\t\t\t\tcontact.firstName = fullName.join(\" \").replace(/\"/g, \"\") //Thunderbird saves the Fullname in \"quoteations marks\" they are deleted here\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"BDAY\":\n\t\t\t\t\tlet indexOfT = tagValue.indexOf(\"T\")\n\t\t\t\t\tlet bDayDetails: Birthday | null = null\n\n\t\t\t\t\tif (tagValue.match(/--\\d{4}/g)) {\n\t\t\t\t\t\tbDayDetails = createBirthday()\n\t\t\t\t\t\tbDayDetails.month = tagValue.substring(2, 4)\n\t\t\t\t\t\tbDayDetails.day = tagValue.substring(4, 6)\n\t\t\t\t\t} else if (tagValue.match(/\\d{4}-\\d{2}-\\d{2}/g)) {\n\t\t\t\t\t\tlet bDay = tagValue.substring(0, indexOfT !== -1 ? indexOfT : tagValue.length).split(\"-\")\n\t\t\t\t\t\tbDayDetails = createBirthday()\n\t\t\t\t\t\tbDayDetails.year = bDay[0].trim()\n\t\t\t\t\t\tbDayDetails.month = bDay[1].trim()\n\t\t\t\t\t\tbDayDetails.day = bDay[2].trim()\n\t\t\t\t\t} else if (tagValue.match(/\\d{8}/g)) {\n\t\t\t\t\t\tbDayDetails = createBirthday()\n\t\t\t\t\t\tbDayDetails.year = tagValue.substring(0, 4)\n\t\t\t\t\t\tbDayDetails.month = tagValue.substring(4, 6)\n\t\t\t\t\t\tbDayDetails.day = tagValue.substring(6, 8)\n\t\t\t\t\t}\n\n\t\t\t\t\tif (bDayDetails && bDayDetails.year === \"1111\") {\n\t\t\t\t\t\t// we use 1111 as marker if no year has been defined as vcard 3.0 does not support dates without year\n\t\t\t\t\t\tbDayDetails.year = null\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcontact.birthdayIso = bDayDetails && isValidBirthday(bDayDetails) ? birthdayToIsoDate(bDayDetails) : null\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (e instanceof ParsingError) {\n\t\t\t\t\t\t\tconsole.log(\"failed to parse birthday\", 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\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"ORG\":\n\t\t\t\t\tlet orgDetails = vCardReescapingArray(vCardEscapingSplit(tagValue))\n\t\t\t\t\tcontact.company = orgDetails.join(\" \")\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"NOTE\":\n\t\t\t\t\tlet note = vCardReescapingArray(vCardEscapingSplit(tagValue))\n\t\t\t\t\tcontact.comment = note.join(\" \")\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"ADR\":\n\t\t\t\tcase \"ITEM1.ADR\": // necessary for apple vcards\n\n\t\t\t\tcase \"ITEM2.ADR\":\n\t\t\t\t\t// necessary for apple vcards\n\t\t\t\t\tif (tagAndTypeString.indexOf(\"HOME\") > -1) {\n\t\t\t\t\t\t_addAddress(tagValue, contact, ContactAddressType.PRIVATE)\n\t\t\t\t\t} else if (tagAndTypeString.indexOf(\"WORK\") > -1) {\n\t\t\t\t\t\t_addAddress(tagValue, contact, ContactAddressType.WORK)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t_addAddress(tagValue, contact, ContactAddressType.OTHER)\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"EMAIL\":\n\t\t\t\tcase \"ITEM1.EMAIL\": // necessary for apple and protonmail vcards\n\n\t\t\t\tcase \"ITEM2.EMAIL\":\n\t\t\t\t\t// necessary for apple vcards\n\t\t\t\t\tif (tagAndTypeString.indexOf(\"HOME\") > -1) {\n\t\t\t\t\t\t_addMailAddress(tagValue, contact, ContactAddressType.PRIVATE)\n\t\t\t\t\t} else if (tagAndTypeString.indexOf(\"WORK\") > -1) {\n\t\t\t\t\t\t_addMailAddress(tagValue, contact, ContactAddressType.WORK)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t_addMailAddress(tagValue, contact, ContactAddressType.OTHER)\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"TEL\":\n\t\t\t\tcase \"ITEM1.TEL\": // necessary for apple vcards\n\n\t\t\t\tcase \"ITEM2.TEL\":\n\t\t\t\t\t// necessary for apple vcards\n\t\t\t\t\ttagValue = tagValue.replace(/[\\u2000-\\u206F]/g, \"\")\n\n\t\t\t\t\tif (tagAndTypeString.indexOf(\"HOME\") > -1) {\n\t\t\t\t\t\t_addPhoneNumber(tagValue, contact, ContactPhoneNumberType.PRIVATE)\n\t\t\t\t\t} else if (tagAndTypeString.indexOf(\"WORK\") > -1) {\n\t\t\t\t\t\t_addPhoneNumber(tagValue, contact, ContactPhoneNumberType.WORK)\n\t\t\t\t\t} else if (tagAndTypeString.indexOf(\"FAX\") > -1) {\n\t\t\t\t\t\t_addPhoneNumber(tagValue, contact, ContactPhoneNumberType.FAX)\n\t\t\t\t\t} else if (tagAndTypeString.indexOf(\"CELL\") > -1) {\n\t\t\t\t\t\t_addPhoneNumber(tagValue, contact, ContactPhoneNumberType.MOBILE)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t_addPhoneNumber(tagValue, contact, ContactPhoneNumberType.OTHER)\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"URL\":\n\t\t\t\tcase \"ITEM1.URL\": // necessary for apple vcards\n\n\t\t\t\tcase \"ITEM2.URL\":\n\t\t\t\t\t// necessary for apple vcards\n\t\t\t\t\tlet website = createContactSocialId()\n\t\t\t\t\twebsite.type = ContactSocialType.OTHER\n\t\t\t\t\twebsite.socialId = vCardReescapingArray(vCardEscapingSplit(tagValue)).join(\"\")\n\t\t\t\t\twebsite.customTypeName = \"\"\n\t\t\t\t\tcontact.socialIds.push(website)\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"NICKNAME\":\n\t\t\t\t\tlet nick = vCardReescapingArray(vCardEscapingSplit(tagValue))\n\t\t\t\t\tcontact.nickname = nick.join(\" \")\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"PHOTO\":\n\t\t\t\t\t// if (indexAfterTag < tagValue.indexOf(\":\")) {\n\t\t\t\t\t// \tindexAfterTag = tagValue.indexOf(\":\")\n\t\t\t\t\t// }\n\t\t\t\t\t// /*Here will be the photo import*/\n\t\t\t\t\tbreak\n\n\t\t\t\tcase \"ROLE\":\n\t\t\t\tcase \"TITLE\":\n\t\t\t\t\tlet role = vCardReescapingArray(vCardEscapingSplit(tagValue))\n\t\t\t\t\tcontact.role += (\" \" + role.join(\" \")).trim()\n\t\t\t\t\tbreak\n\n\t\t\t\tdefault:\n\t\t\t}\n\t\t}\n\n\t\tcontacts[i] = contact\n\t}\n\n\tfunction _addAddress(vCardAddressValue: string, contact: Contact, type: ContactAddressType) {\n\t\tlet address = createContactAddress()\n\t\taddress.type = type\n\t\tlet addressDetails = vCardReescapingArray(vCardEscapingSplitAdr(vCardAddressValue))\n\t\taddress.address = addressDetails.join(\"\").trim()\n\t\taddress.customTypeName = \"\"\n\t\tcontact.addresses.push(address)\n\t}\n\n\tfunction _addPhoneNumber(vCardPhoneNumberValue: string, contact: Contact, type: ContactPhoneNumberType) {\n\t\tlet phoneNumber = createContactPhoneNumber()\n\t\tphoneNumber.type = type\n\t\tphoneNumber.number = vCardPhoneNumberValue\n\t\tphoneNumber.customTypeName = \"\"\n\t\tcontact.phoneNumbers.push(phoneNumber)\n\t}\n\n\tfunction _addMailAddress(vCardMailAddressValue: string, contact: Contact, type: ContactAddressType) {\n\t\tlet email = createContactMailAddress()\n\t\temail.type = type\n\t\temail.address = vCardMailAddressValue\n\t\temail.customTypeName = \"\"\n\t\tcontact.mailAddresses.push(email)\n\t}\n\n\treturn contacts\n}\n","import { showFileChooser } from \"../../file/FileController.js\"\nimport { assertNotNull, flat, utf8Uint8ArrayToString } from \"@tutao/tutanota-utils\"\nimport { vCardFileToVCards, vCardListToContacts } from \"../VCardImporter.js\"\nimport { showProgressDialog } from \"../../gui/dialogs/ProgressDialog.js\"\nimport { locator } from \"../../api/main/MainLocator.js\"\nimport { GroupType } from \"../../api/common/TutanotaConstants.js\"\nimport { Dialog } from \"../../gui/base/Dialog.js\"\nimport { lang } from \"../../misc/LanguageViewModel.js\"\nimport { SetupMultipleError } from \"../../api/common/error/SetupMultipleError.js\"\nimport { ContactModel } from \"../model/ContactModel.js\"\nimport { ContactTypeRef } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { exportContacts } from \"../VCardExporter.js\"\n\nexport function importAsVCard() {\n\tshowFileChooser(true, [\"vcf\"]).then((contactFiles) => {\n\t\tlet numberOfContacts: number\n\n\t\ttry {\n\t\t\tif (contactFiles.length > 0) {\n\t\t\t\tlet vCardsList = contactFiles.map((contactFile) => {\n\t\t\t\t\tlet vCardFileData = utf8Uint8ArrayToString(contactFile.data)\n\t\t\t\t\tlet vCards = vCardFileToVCards(vCardFileData)\n\n\t\t\t\t\tif (vCards == null) {\n\t\t\t\t\t\tthrow new Error(\"no vcards found\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn vCards\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\treturn showProgressDialog(\n\t\t\t\t\t\"pleaseWait_msg\",\n\t\t\t\t\tPromise.resolve().then(() => {\n\t\t\t\t\t\tconst flatvCards = flat(vCardsList)\n\t\t\t\t\t\tconst contactMembership = assertNotNull(\n\t\t\t\t\t\t\tlocator.logins.getUserController().user.memberships.find((m) => m.groupType === GroupType.Contact),\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst contactList = vCardListToContacts(flatvCards, contactMembership.group)\n\t\t\t\t\t\tnumberOfContacts = contactList.length\n\t\t\t\t\t\treturn locator.contactModel.contactListId().then((contactListId) =>\n\t\t\t\t\t\t\tlocator.entityClient.setupMultipleEntities(contactListId, contactList).then(() => {\n\t\t\t\t\t\t\t\t// actually a success message\n\t\t\t\t\t\t\t\tDialog.message(() =>\n\t\t\t\t\t\t\t\t\tlang.get(\"importVCardSuccess_msg\", {\n\t\t\t\t\t\t\t\t\t\t\"{1}\": numberOfContacts,\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)\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.log(e)\n\n\t\t\tif (e instanceof SetupMultipleError) {\n\t\t\t\tDialog.message(() =>\n\t\t\t\t\tlang.get(\"importContactsError_msg\", {\n\t\t\t\t\t\t\"{amount}\": e.failedInstances.length + \"\",\n\t\t\t\t\t\t\"{total}\": numberOfContacts + \"\",\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tDialog.message(\"importVCardError_msg\")\n\t\t\t}\n\t\t}\n\t})\n}\n\n/**\n *Creates a vCard file with all contacts if at least one contact exists\n */\nexport function exportAsVCard(contactModel: ContactModel): Promise<void> {\n\treturn showProgressDialog(\n\t\t\"pleaseWait_msg\",\n\t\tcontactModel.contactListId().then((contactListId) => {\n\t\t\tif (!contactListId) return 0\n\t\t\treturn locator.entityClient.loadAll(ContactTypeRef, contactListId).then((allContacts) => {\n\t\t\t\tif (allContacts.length === 0) {\n\t\t\t\t\treturn 0\n\t\t\t\t} else {\n\t\t\t\t\treturn exportContacts(allContacts).then(() => allContacts.length)\n\t\t\t\t}\n\t\t\t})\n\t\t}),\n\t).then((nbrOfContacts) => {\n\t\tif (nbrOfContacts === 0) {\n\t\t\tDialog.message(\"noContacts_msg\")\n\t\t}\n\t})\n}\n","import m, { Children, Vnode } from \"mithril\"\nimport { ViewSlider } from \"../../gui/nav/ViewSlider.js\"\nimport { ColumnType, ViewColumn } from \"../../gui/base/ViewColumn\"\nimport { AppHeaderAttrs, Header } from \"../../gui/Header.js\"\nimport { ButtonColor, ButtonType } from \"../../gui/base/Button.js\"\nimport { ContactEditor } from \"../ContactEditor\"\nimport type { Contact } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { ContactTypeRef } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { ContactListView } from \"./ContactListView\"\nimport { lang } from \"../../misc/LanguageViewModel\"\nimport { clear, noOp, ofClass } from \"@tutao/tutanota-utils\"\nimport { ContactMergeAction, Keys } from \"../../api/common/TutanotaConstants\"\nimport { assertMainOrNode, isApp } from \"../../api/common/Env\"\nimport type { Shortcut } from \"../../misc/KeyManager\"\nimport { keyManager } from \"../../misc/KeyManager\"\nimport { Icons } from \"../../gui/base/icons/Icons\"\nimport { Dialog } from \"../../gui/base/Dialog\"\nimport { LockedError, NotFoundError } from \"../../api/common/error/RestError\"\nimport { getContactSelectionMessage, MultiContactViewer } from \"./MultiContactViewer\"\nimport { BootIcons } from \"../../gui/base/icons/BootIcons\"\nimport { showProgressDialog } from \"../../gui/dialogs/ProgressDialog\"\nimport { locator } from \"../../api/main/MainLocator\"\nimport { ContactMergeView } from \"./ContactMergeView\"\nimport { getMergeableContacts, mergeContacts } from \"../ContactMergeUtils\"\nimport { exportContacts } from \"../VCardExporter\"\nimport { isNavButtonSelected, NavButton, NavButtonAttrs } from \"../../gui/base/NavButton.js\"\nimport { styles } from \"../../gui/styles\"\nimport { size } from \"../../gui/size\"\nimport { FolderColumnView } from \"../../gui/FolderColumnView.js\"\nimport { getGroupInfoDisplayName } from \"../../api/common/utils/GroupUtils\"\nimport { SidebarSection } from \"../../gui/SidebarSection\"\nimport { attachDropdown, createDropdown, DropdownButtonAttrs } from \"../../gui/base/Dropdown.js\"\nimport { IconButton } from \"../../gui/base/IconButton.js\"\nimport { ButtonSize } from \"../../gui/base/ButtonSize.js\"\nimport { BottomNav } from \"../../gui/nav/BottomNav.js\"\nimport { DrawerMenuAttrs } from \"../../gui/nav/DrawerMenu.js\"\nimport { BaseTopLevelView } from \"../../gui/BaseTopLevelView.js\"\nimport { TopLevelAttrs, TopLevelView } from \"../../TopLevelView.js\"\nimport { stateBgHover } from \"../../gui/builtinThemes.js\"\nimport { ContactCardViewer } from \"./ContactCardViewer.js\"\nimport { MobileContactActionBar } from \"./MobileContactActionBar.js\"\nimport { appendEmailSignature } from \"../../mail/signature/Signature.js\"\nimport { PartialRecipient } from \"../../api/common/recipients/Recipient.js\"\nimport { newMailEditorFromTemplate } from \"../../mail/editor/MailEditor.js\"\nimport { BackgroundColumnLayout } from \"../../gui/BackgroundColumnLayout.js\"\nimport { theme } from \"../../gui/theme.js\"\nimport { DesktopListToolbar, DesktopViewerToolbar } from \"../../gui/DesktopToolbars.js\"\nimport { SelectAllCheckbox } from \"../../gui/SelectAllCheckbox.js\"\nimport { ContactViewerActions } from \"./ContactViewerActions.js\"\nimport { MobileBottomActionBar } from \"../../gui/MobileBottomActionBar.js\"\nimport { exportAsVCard, importAsVCard } from \"./ImportAsVCard.js\"\nimport { MobileHeader } from \"../../gui/MobileHeader.js\"\nimport { LazySearchBar } from \"../../misc/LazySearchBar.js\"\nimport { MultiselectMobileHeader } from \"../../gui/MultiselectMobileHeader.js\"\nimport { MultiselectMode } from \"../../gui/base/List.js\"\nimport { EnterMultiselectIconButton } from \"../../gui/EnterMultiselectIconButton.js\"\nimport { selectionAttrsForList } from \"../../misc/ListModel.js\"\nimport { ContactViewModel } from \"./ContactViewModel.js\"\nimport { listSelectionKeyboardShortcuts } from \"../../gui/base/ListUtils.js\"\n\nassertMainOrNode()\n\nexport interface ContactViewAttrs extends TopLevelAttrs {\n\tdrawerAttrs: DrawerMenuAttrs\n\theader: AppHeaderAttrs\n\tcontactViewModel: ContactViewModel\n}\n\nexport class ContactView extends BaseTopLevelView implements TopLevelView<ContactViewAttrs> {\n\tprivate listColumn: ViewColumn\n\tprivate contactColumn: ViewColumn\n\tprivate folderColumn: ViewColumn\n\tprivate viewSlider: ViewSlider\n\tprivate contactViewModel: ContactViewModel\n\n\toncreate: TopLevelView[\"oncreate\"]\n\tonremove: TopLevelView[\"onremove\"]\n\n\tconstructor(vnode: Vnode<ContactViewAttrs>) {\n\t\tsuper()\n\t\tthis.contactViewModel = vnode.attrs.contactViewModel\n\n\t\tthis.folderColumn = new ViewColumn(\n\t\t\t{\n\t\t\t\tview: () =>\n\t\t\t\t\tm(FolderColumnView, {\n\t\t\t\t\t\tdrawer: vnode.attrs.drawerAttrs,\n\t\t\t\t\t\tbutton: styles.isUsingBottomNavigation()\n\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\ttype: ButtonType.FolderColumnHeader,\n\t\t\t\t\t\t\t\t\tlabel: \"newContact_action\",\n\t\t\t\t\t\t\t\t\tclick: () => this.createNewContact(),\n\t\t\t\t\t\t\t  },\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\tm(\n\t\t\t\t\t\t\t\tSidebarSection,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tname: () => getGroupInfoDisplayName(locator.logins.getUserController().userGroupInfo),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tthis.createContactFoldersExpanderChildren(),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t\tariaLabel: \"folderTitle_label\",\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\tColumnType.Foreground,\n\t\t\tsize.first_col_min_width,\n\t\t\tsize.first_col_max_width,\n\t\t\t() => lang.get(\"folderTitle_label\"),\n\t\t)\n\t\tthis.listColumn = new ViewColumn(\n\t\t\t{\n\t\t\t\tview: () =>\n\t\t\t\t\tm(BackgroundColumnLayout, {\n\t\t\t\t\t\tbackgroundColor: theme.navigation_bg,\n\t\t\t\t\t\tcolumnLayout: m(ContactListView, {\n\t\t\t\t\t\t\tcontactViewModel: this.contactViewModel,\n\t\t\t\t\t\t\tonSingleSelection: () => {\n\t\t\t\t\t\t\t\tthis.viewSlider.focus(this.contactColumn)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}),\n\t\t\t\t\t\tdesktopToolbar: () => this.renderListToolbar(),\n\t\t\t\t\t\tmobileHeader: () =>\n\t\t\t\t\t\t\tthis.contactViewModel.listModel.state.inMultiselect\n\t\t\t\t\t\t\t\t? m(MultiselectMobileHeader, {\n\t\t\t\t\t\t\t\t\t\t...selectionAttrsForList(this.contactViewModel.listModel),\n\t\t\t\t\t\t\t\t\t\tmessage: getContactSelectionMessage(this.getSelectedContacts()),\n\t\t\t\t\t\t\t\t  })\n\t\t\t\t\t\t\t\t: m(MobileHeader, {\n\t\t\t\t\t\t\t\t\t\t...vnode.attrs.header,\n\t\t\t\t\t\t\t\t\t\tbackAction: () => this.viewSlider.focusPreviousColumn(),\n\t\t\t\t\t\t\t\t\t\tcolumnType: \"first\",\n\t\t\t\t\t\t\t\t\t\ttitle: this.listColumn.getTitle(),\n\t\t\t\t\t\t\t\t\t\tactions: m(\".flex\", [\n\t\t\t\t\t\t\t\t\t\t\tthis.renderSortByButton(),\n\t\t\t\t\t\t\t\t\t\t\tm(EnterMultiselectIconButton, {\n\t\t\t\t\t\t\t\t\t\t\t\tclickAction: () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tthis.contactViewModel.listModel.enterMultiselect()\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t\tprimaryAction: () => this.renderHeaderRightView(),\n\t\t\t\t\t\t\t\t  }),\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\tColumnType.Background,\n\t\t\tsize.second_col_min_width,\n\t\t\tsize.second_col_max_width,\n\t\t\t() => lang.get(\"contacts_label\"),\n\t\t)\n\n\t\tthis.contactColumn = new ViewColumn(\n\t\t\t{\n\t\t\t\tview: () => {\n\t\t\t\t\tconst contacts = this.getSelectedContacts()\n\t\t\t\t\treturn m(BackgroundColumnLayout, {\n\t\t\t\t\t\tbackgroundColor: theme.navigation_bg,\n\t\t\t\t\t\tdesktopToolbar: () => m(DesktopViewerToolbar, this.contactViewerActions(contacts)),\n\t\t\t\t\t\tmobileHeader: () =>\n\t\t\t\t\t\t\tm(MobileHeader, {\n\t\t\t\t\t\t\t\t...vnode.attrs.header,\n\t\t\t\t\t\t\t\tbackAction: () => this.viewSlider.focusPreviousColumn(),\n\t\t\t\t\t\t\t\tactions: null,\n\t\t\t\t\t\t\t\tmulticolumnActions: () => this.contactViewerActions(contacts),\n\t\t\t\t\t\t\t\tprimaryAction: () => this.renderHeaderRightView(),\n\t\t\t\t\t\t\t\ttitle: lang.get(\"contacts_label\"),\n\t\t\t\t\t\t\t\tcolumnType: \"other\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\tcolumnLayout:\n\t\t\t\t\t\t\t// see comment for .scrollbar-gutter-stable-or-fallback\n\t\t\t\t\t\t\tm(\n\t\t\t\t\t\t\t\t\".fill-absolute.flex.col.overflow-y-scroll\",\n\t\t\t\t\t\t\t\tthis.showingListView()\n\t\t\t\t\t\t\t\t\t? m(MultiContactViewer, {\n\t\t\t\t\t\t\t\t\t\t\tselectedEntities: contacts,\n\t\t\t\t\t\t\t\t\t\t\tselectNone: () => this.contactViewModel.listModel.selectNone(),\n\t\t\t\t\t\t\t\t\t  })\n\t\t\t\t\t\t\t\t\t: m(ContactCardViewer, {\n\t\t\t\t\t\t\t\t\t\t\tcontact: contacts[0],\n\t\t\t\t\t\t\t\t\t\t\tonWriteMail: writeMail,\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\tColumnType.Background,\n\t\t\tsize.third_col_min_width,\n\t\t\tsize.third_col_max_width,\n\t\t\tundefined,\n\t\t\t() => lang.get(\"contacts_label\"),\n\t\t)\n\t\tthis.viewSlider = new ViewSlider([this.folderColumn, this.listColumn, this.contactColumn], \"ContactView\")\n\n\t\tconst shortcuts = this.getShortcuts()\n\t\tthis.oncreate = (vnode) => {\n\t\t\tkeyManager.registerShortcuts(shortcuts)\n\t\t}\n\n\t\tthis.onremove = () => {\n\t\t\tkeyManager.unregisterShortcuts(shortcuts)\n\t\t}\n\t}\n\n\tprivate contactViewerActions(contacts: Contact[]) {\n\t\treturn m(ContactViewerActions, {\n\t\t\tcontacts,\n\t\t\tonEdit: (c) => this.editContact(c),\n\t\t\tonExport: exportContacts,\n\t\t\tonDelete: deleteContacts,\n\t\t\tonMerge: confirmMerge,\n\t\t})\n\t}\n\n\tprivate showingListView() {\n\t\treturn this.getSelectedContacts().length === 0 || this.contactViewModel.listModel.state.inMultiselect\n\t}\n\n\tview({ attrs }: Vnode<ContactViewAttrs>): Children {\n\t\treturn m(\n\t\t\t\"#contact.main-view\",\n\t\t\tm(this.viewSlider, {\n\t\t\t\theader: styles.isSingleColumnLayout()\n\t\t\t\t\t? null\n\t\t\t\t\t: m(Header, {\n\t\t\t\t\t\t\tsearchBar: () =>\n\t\t\t\t\t\t\t\tm(LazySearchBar, {\n\t\t\t\t\t\t\t\t\tplaceholder: lang.get(\"searchContacts_placeholder\"),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...attrs.header,\n\t\t\t\t\t  }),\n\t\t\t\tbottomNav:\n\t\t\t\t\tstyles.isSingleColumnLayout() && this.viewSlider.focusedColumn === this.contactColumn && !this.showingListView()\n\t\t\t\t\t\t? m(MobileContactActionBar, {\n\t\t\t\t\t\t\t\teditAction: () => this.editSelectedContact(),\n\t\t\t\t\t\t\t\tdeleteAction: () => this._deleteSelected(),\n\t\t\t\t\t\t  })\n\t\t\t\t\t\t: styles.isSingleColumnLayout() &&\n\t\t\t\t\t\t  this.viewSlider.focusedColumn === this.listColumn &&\n\t\t\t\t\t\t  //this.showingListView()\n\t\t\t\t\t\t  this.contactViewModel.listModel.state.inMultiselect\n\t\t\t\t\t\t? m(MobileBottomActionBar, this.contactViewerActions(this.getSelectedContacts()))\n\t\t\t\t\t\t: m(BottomNav),\n\t\t\t}),\n\t\t)\n\t}\n\n\tprivate getSelectedContacts() {\n\t\treturn this.contactViewModel.listModel.getSelectedAsArray()\n\t}\n\n\tcreateNewContact(): void {\n\t\tnew ContactEditor(locator.entityClient, null, this.contactViewModel.contactListId, (contactId) => {\n\t\t\t// FIXME show new contact\n\t\t\t// contactList.list.scrollToIdAndSelectWhenReceived(contactId)\n\t\t}).show()\n\t}\n\n\tprivate editSelectedContact() {\n\t\tconst firstSelected = this.getSelectedContacts()[0]\n\t\tif (!firstSelected) return\n\t\tthis.editContact(firstSelected)\n\t}\n\n\tprivate editContact(contact: Contact) {\n\t\tnew ContactEditor(locator.entityClient, contact).show()\n\t}\n\n\tprivate renderHeaderRightView(): Children {\n\t\treturn m(IconButton, {\n\t\t\ttitle: \"newContact_action\",\n\t\t\tclick: () => this.createNewContact(),\n\t\t\ticon: Icons.Add,\n\t\t})\n\t}\n\n\tprivate getShortcuts() {\n\t\tlet shortcuts: Shortcut[] = [\n\t\t\t...listSelectionKeyboardShortcuts(MultiselectMode.Enabled, () => this.contactViewModel.listModel),\n\t\t\t{\n\t\t\t\tkey: Keys.DELETE,\n\t\t\t\texec: () => {\n\t\t\t\t\tthis._deleteSelected()\n\t\t\t\t\treturn true\n\t\t\t\t},\n\t\t\t\thelp: \"deleteContacts_action\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tkey: Keys.N,\n\t\t\t\texec: () => this.createNewContact(),\n\t\t\t\thelp: \"newContact_action\",\n\t\t\t},\n\t\t]\n\n\t\treturn shortcuts\n\t}\n\n\tcreateContactFoldersExpanderChildren(): Children {\n\t\tconst button: NavButtonAttrs = {\n\t\t\tlabel: \"all_contacts_label\",\n\t\t\ticon: () => BootIcons.Contacts,\n\t\t\thref: () => m.route.get(),\n\t\t\tdisableHoverBackground: true,\n\t\t}\n\t\treturn m(\".folders.mlr-button.border-radius-small.state-bg\", { style: { background: isNavButtonSelected(button) ? stateBgHover : \"\" } }, [\n\t\t\tm(\".folder-row.flex-space-between.plr-button.row-selected\", [m(NavButton, button), this.renderFolderMoreButton()]),\n\t\t])\n\t}\n\n\tprivate renderFolderMoreButton(): Children {\n\t\treturn m(\n\t\t\tIconButton,\n\t\t\tattachDropdown({\n\t\t\t\tmainButtonAttrs: {\n\t\t\t\t\ttitle: \"more_label\",\n\t\t\t\t\ticon: Icons.More,\n\t\t\t\t\tsize: ButtonSize.Compact,\n\t\t\t\t\tcolors: ButtonColor.Nav,\n\t\t\t\t},\n\t\t\t\tchildAttrs: () => {\n\t\t\t\t\tconst vcardButtons: Array<DropdownButtonAttrs> = isApp()\n\t\t\t\t\t\t? []\n\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: \"importVCard_action\",\n\t\t\t\t\t\t\t\t\tclick: () => importAsVCard(),\n\t\t\t\t\t\t\t\t\ticon: Icons.ContactImport,\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: \"exportVCard_action\",\n\t\t\t\t\t\t\t\t\tclick: () => exportAsVCard(locator.contactModel),\n\t\t\t\t\t\t\t\t\ticon: Icons.Export,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t  ]\n\n\t\t\t\t\treturn vcardButtons.concat([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"merge_action\",\n\t\t\t\t\t\t\ticon: Icons.People,\n\t\t\t\t\t\t\tclick: () => this._mergeAction(),\n\t\t\t\t\t\t},\n\t\t\t\t\t])\n\t\t\t\t},\n\t\t\t\twidth: 250,\n\t\t\t}),\n\t\t)\n\t}\n\n\t_mergeAction(): Promise<void> {\n\t\treturn showProgressDialog(\n\t\t\t\"pleaseWait_msg\",\n\t\t\tlocator.contactModel.contactListId().then((contactListId) => {\n\t\t\t\treturn contactListId ? locator.entityClient.loadAll(ContactTypeRef, contactListId) : []\n\t\t\t}),\n\t\t).then((allContacts) => {\n\t\t\tif (allContacts.length === 0) {\n\t\t\t\tDialog.message(\"noContacts_msg\")\n\t\t\t} else {\n\t\t\t\tlet mergeableAndDuplicates = getMergeableContacts(allContacts)\n\t\t\t\tlet deletePromise = Promise.resolve()\n\n\t\t\t\tif (mergeableAndDuplicates.deletable.length > 0) {\n\t\t\t\t\tdeletePromise = Dialog.confirm(() =>\n\t\t\t\t\t\tlang.get(\"duplicatesNotification_msg\", {\n\t\t\t\t\t\t\t\"{1}\": mergeableAndDuplicates.deletable.length,\n\t\t\t\t\t\t}),\n\t\t\t\t\t).then((confirmed) => {\n\t\t\t\t\t\tif (confirmed) {\n\t\t\t\t\t\t\t// delete async in the background\n\t\t\t\t\t\t\tmergeableAndDuplicates.deletable.forEach((dc) => {\n\t\t\t\t\t\t\t\tlocator.entityClient.erase(dc)\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\n\t\t\t\tdeletePromise.then(() => {\n\t\t\t\t\tif (mergeableAndDuplicates.mergeable.length === 0) {\n\t\t\t\t\t\tDialog.message(() => lang.get(\"noSimilarContacts_msg\"))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis._showMergeDialogs(mergeableAndDuplicates.mergeable).then((canceled) => {\n\t\t\t\t\t\t\tif (!canceled) {\n\t\t\t\t\t\t\t\tDialog.message(() => lang.get(\"noMoreSimilarContacts_msg\"))\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})\n\t}\n\n\t/**\n\t * @returns True if the merging was canceled by the user, false otherwise\n\t */\n\t_showMergeDialogs(mergable: Contact[][]): Promise<boolean> {\n\t\tlet canceled = false\n\n\t\tif (mergable.length > 0) {\n\t\t\tlet contact1 = mergable[0][0]\n\t\t\tlet contact2 = mergable[0][1]\n\t\t\tlet mergeDialog = new ContactMergeView(contact1, contact2)\n\t\t\treturn mergeDialog\n\t\t\t\t.show()\n\t\t\t\t.then((action) => {\n\t\t\t\t\t// execute action here and update mergable\n\t\t\t\t\tif (action === ContactMergeAction.Merge) {\n\t\t\t\t\t\tthis._removeFromMergableContacts(mergable, contact2)\n\n\t\t\t\t\t\tmergeContacts(contact1, contact2)\n\t\t\t\t\t\treturn showProgressDialog(\n\t\t\t\t\t\t\t\"pleaseWait_msg\",\n\t\t\t\t\t\t\tlocator.entityClient.update(contact1).then(() => locator.entityClient.erase(contact2)),\n\t\t\t\t\t\t).catch(ofClass(NotFoundError, noOp))\n\t\t\t\t\t} else if (action === ContactMergeAction.DeleteFirst) {\n\t\t\t\t\t\tthis._removeFromMergableContacts(mergable, contact1)\n\n\t\t\t\t\t\treturn locator.entityClient.erase(contact1)\n\t\t\t\t\t} else if (action === ContactMergeAction.DeleteSecond) {\n\t\t\t\t\t\tthis._removeFromMergableContacts(mergable, contact2)\n\n\t\t\t\t\t\treturn locator.entityClient.erase(contact2)\n\t\t\t\t\t} else if (action === ContactMergeAction.Skip) {\n\t\t\t\t\t\tthis._removeFromMergableContacts(mergable, contact2)\n\t\t\t\t\t} else if (action === ContactMergeAction.Cancel) {\n\t\t\t\t\t\tclear(mergable)\n\t\t\t\t\t\tcanceled = true\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.then(() => {\n\t\t\t\t\tif (!canceled && mergable.length > 0) {\n\t\t\t\t\t\treturn this._showMergeDialogs(mergable)\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn canceled\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t} else {\n\t\t\treturn Promise.resolve(canceled)\n\t\t}\n\t}\n\n\t/**\n\t * removes the given contact from the given mergable arrays first entry (first or second element)\n\t */\n\t_removeFromMergableContacts(mergable: Contact[][], contact: Contact) {\n\t\tif (mergable[0][0] === contact) {\n\t\t\tmergable[0].splice(0, 1) // remove contact1\n\t\t} else if (mergable[0][1] === contact) {\n\t\t\tmergable[0].splice(1, 1) // remove contact2\n\t\t}\n\n\t\t// remove the first entry if there is only one contact left in the first entry\n\t\tif (mergable[0].length <= 1) {\n\t\t\tmergable.splice(0, 1)\n\t\t}\n\t}\n\n\tonNewUrl(args: Record<string, any>) {\n\t\tthis.contactViewModel.init(args.listId, args.contactId)\n\t}\n\n\t_deleteSelected(): Promise<void> {\n\t\treturn deleteContacts(this.getSelectedContacts())\n\t}\n\n\tgetViewSlider(): ViewSlider | null {\n\t\treturn this.viewSlider\n\t}\n\n\thandleBackButton(): boolean {\n\t\t// only handle back button if viewing contact\n\t\tif (this.viewSlider.focusedColumn === this.contactColumn) {\n\t\t\tthis.viewSlider.focus(this.listColumn)\n\t\t\treturn true\n\t\t} else if (this.showingListView()) {\n\t\t\tthis.contactViewModel.listModel.selectNone()\n\n\t\t\treturn true\n\t\t}\n\n\t\treturn false\n\t}\n\n\tprivate renderListToolbar() {\n\t\treturn m(DesktopListToolbar, m(SelectAllCheckbox, selectionAttrsForList(this.contactViewModel.listModel)), this.renderSortByButton())\n\t}\n\n\tprivate renderSortByButton() {\n\t\treturn m(IconButton, {\n\t\t\ttitle: \"sortBy_label\",\n\t\t\ticon: Icons.ListOrdered,\n\t\t\tclick: (e: MouseEvent, dom: HTMLElement) => {\n\t\t\t\tcreateDropdown({\n\t\t\t\t\tlazyButtons: () => [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"firstName_placeholder\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tthis.contactViewModel.setSortByFirstName(true)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"lastName_placeholder\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tthis.contactViewModel.setSortByFirstName(false)\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})(e, dom)\n\t\t\t},\n\t\t})\n\t}\n}\n\nexport function writeMail(to: PartialRecipient, subject: string = \"\"): Promise<unknown> {\n\treturn locator.mailModel.getUserMailboxDetails().then((mailboxDetails) => {\n\t\treturn newMailEditorFromTemplate(\n\t\t\tmailboxDetails,\n\t\t\t{\n\t\t\t\tto: [to],\n\t\t\t},\n\t\t\tsubject,\n\t\t\tappendEmailSignature(\"\", locator.logins.getUserController().props),\n\t\t).then((editor) => editor.show())\n\t})\n}\n\nexport function deleteContacts(contactList: Contact[]): Promise<void> {\n\treturn Dialog.confirm(\"deleteContacts_msg\").then((confirmed) => {\n\t\tif (confirmed) {\n\t\t\tcontactList.forEach((contact) => {\n\t\t\t\tlocator.entityClient.erase(contact).catch(ofClass(NotFoundError, noOp)).catch(ofClass(LockedError, noOp))\n\t\t\t})\n\t\t}\n\t})\n}\n\nexport function confirmMerge(keptContact: Contact, goodbyeContact: Contact): Promise<void> {\n\tif (!keptContact.presharedPassword || !goodbyeContact.presharedPassword || keptContact.presharedPassword === goodbyeContact.presharedPassword) {\n\t\treturn Dialog.confirm(\"mergeAllSelectedContacts_msg\").then((confirmed) => {\n\t\t\tif (confirmed) {\n\t\t\t\tmergeContacts(keptContact, goodbyeContact)\n\t\t\t\treturn showProgressDialog(\n\t\t\t\t\t\"pleaseWait_msg\",\n\t\t\t\t\tlocator.entityClient.update(keptContact).then(() => locator.entityClient.erase(goodbyeContact)),\n\t\t\t\t).catch(ofClass(NotFoundError, noOp))\n\t\t\t}\n\t\t})\n\t} else {\n\t\treturn Dialog.message(\"presharedPasswordsUnequal_msg\")\n\t}\n}\n","import { ContactModel } from \"../model/ContactModel.js\"\nimport { EntityClient } from \"../../api/common/EntityClient.js\"\nimport { EntityEventsListener, EventController, isUpdateForTypeRef } from \"../../api/main/EventController.js\"\nimport { ListModel } from \"../../misc/ListModel.js\"\nimport { Contact, ContactTypeRef } from \"../../api/entities/tutanota/TypeRefs.js\"\nimport { compareContacts } from \"./ContactGuiUtils.js\"\nimport { ListState } from \"../../gui/base/List.js\"\nimport { assertNotNull } from \"@tutao/tutanota-utils\"\nimport { GENERATED_MAX_ID, getElementId } from \"../../api/common/utils/EntityUtils.js\"\nimport Stream from \"mithril/stream\"\nimport { Router } from \"../../gui/ScopedRouter.js\"\n\n/** ViewModel for the overall contact view. */\nexport class ContactViewModel {\n\tcontactListId!: Id\n\t/** id of the contact we are trying to load based on the url */\n\tprivate targetContactId: Id | null = null\n\tsortByFirstName: boolean = true\n\tprivate listModelStateStream: Stream<unknown> | null = null\n\n\tconstructor(\n\t\tprivate readonly contactModel: ContactModel,\n\t\tprivate readonly entityClient: EntityClient,\n\t\tprivate readonly eventController: EventController,\n\t\tprivate readonly router: Router,\n\t\tprivate readonly updateUi: () => unknown,\n\t) {}\n\n\treadonly listModel: ListModel<Contact> = new ListModel<Contact>({\n\t\ttopId: GENERATED_MAX_ID,\n\t\tfetch: async () => {\n\t\t\tconst items = await this.entityClient.loadAll(ContactTypeRef, this.contactListId)\n\t\t\treturn { items, complete: true }\n\t\t},\n\t\tloadSingle: async (elementId: Id) => {\n\t\t\tconst listId = await this.contactModel.contactListId()\n\t\t\tif (listId == null) return null\n\t\t\treturn this.entityClient.load(ContactTypeRef, [listId, elementId])\n\t\t},\n\t\tsortCompare: (c1, c2) => compareContacts(c1, c2, this.sortByFirstName),\n\t})\n\n\tasync init(contactListId?: Id, contactId?: Id) {\n\t\t// update url if the view was just opened\n\t\tif (contactListId == null && contactId == null) this.updateUrl()\n\t\tif (this.contactListId) return\n\n\t\tthis.contactListId = assertNotNull(await this.contactModel.contactListId(), \"not available for external users\")\n\t\tthis.targetContactId = contactId ?? null\n\n\t\tthis.listModel.loadInitial().then(() => {\n\t\t\t// we are loading all contacts at once anyway so we are not worried about starting parallel loads for target\n\t\t\ttypeof contactId === \"string\" &&\n\t\t\t\tthis.loadAndSelect(contactId).finally(() => {\n\t\t\t\t\tthis.targetContactId = null\n\t\t\t\t})\n\t\t})\n\n\t\tthis.eventController.addEntityListener(this.entityListener)\n\t\tthis.listModelStateStream = this.listModel.stateStream.map(() => {\n\t\t\tthis.updateUi()\n\t\t\tthis.updateUrl()\n\t\t})\n\t}\n\n\tprivate updateUrl() {\n\t\tconst contactId =\n\t\t\tthis.targetContactId != null\n\t\t\t\t? this.targetContactId\n\t\t\t\t: !this.listModel.state.inMultiselect && this.listModel.getSelectedAsArray().length === 1\n\t\t\t\t? getElementId(this.listModel.getSelectedAsArray()[0])\n\t\t\t\t: null\n\t\tif (contactId) {\n\t\t\tthis.router.routeTo(`/contact/:listId/:contactId`, { listId: this.contactListId, contactId: contactId })\n\t\t} else {\n\t\t\tthis.router.routeTo(`/contact/:listId`, { listId: this.contactListId })\n\t\t}\n\t}\n\n\tprivate readonly entityListener: EntityEventsListener = async (updates) => {\n\t\tfor (const update of updates) {\n\t\t\tif (isUpdateForTypeRef(ContactTypeRef, update) && update.instanceListId === this.contactListId) {\n\t\t\t\tawait this.listModel.entityEventReceived(update.instanceId, update.operation)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync loadAndSelect(contactId: Id) {\n\t\tconst listId = this.contactListId\n\t\tawait this.listModel.loadAndSelect(contactId, () => this.contactListId !== listId)\n\t}\n\n\tsetSortByFirstName(sorting: boolean) {\n\t\tthis.sortByFirstName = sorting\n\t\tthis.listModel.sort()\n\t}\n\n\tlistState(): ListState<Contact> {\n\t\treturn this.listModel.state\n\t}\n\n\tdispose() {\n\t\tthis.eventController.removeEntityListener(this.entityListener)\n\t\tthis.listModelStateStream?.end(true)\n\t\tthis.listModelStateStream = null\n\t}\n}\n"],"names":["ContactMailAddressTypeToLabel","getContactAddressTypeLabel","type","custom","lang","get","ContactPhoneNumberTypeToLabel","getContactPhoneNumberTypeLabel","ContactSocialTypeToLabel","getContactSocialTypeLabel","compareContacts","contact1","contact2","sortByFirstName","c1First","firstName","trim","c2First","c1Last","lastName","c2Last","c1MailLength","mailAddresses","length","c2MailLength","c1Primary","c1Secondary","c2Primary","c2Secondary","company","result","localeCompare","sortCompareByReverseId","address","ContactAggregateEditor","oncreate","vnode","attrs","animateCreate","this","animate","dom","async","view","m","TextField","value","label","fieldType","helpLabel","injectionsRight","_moreButtonFor","oninput","onUpdate","_cancelButtonFor","_doesAllowCancel","allowCancel","IconButton","title","click","cancelAction","icon","attachDropdown","mainButtonAttrs","size","childAttrs","typeLabels","map","key","onTypeSelected","domElement","fadein","childHeight","offsetHeight","style","opacity","opacityP","animations","add","heightP","height","then","Promise","all","assertMainOrNode","ContactEditor","constructor","entityClient","contact","listId","newContactIdReceiver","isPasswordRevealed","saving","clone","createContact","isNewContact","_id","ProgrammingError","assertNotNull","id","entity","newId","push","newMailAddress","phoneNumbers","phoneNumber","newPhoneNumber","addresses","newAddress","socialIds","socialId","newSocialId","hasInvalidBirthday","birthday","formatBirthdayOfContact","dialog","createDialog","windowCloseUnsubscribe","noOp","windowFacade","addWindowCloseListener","onremove","renderFirstNameField","renderLastNameField","renderTitleField","renderBirthdayField","renderRoleField","renderCompanyField","renderNickNameField","renderCommentField","index","lastEditor","lastIndex","renderMailAddressesEditor","renderPhonesEditor","renderAddressesEditor","renderSocialsEditor","renderPresharedPasswordField","show","close","Dialog","message","e","filter","number","saveNewContact","updateExistingContact","PayloadTooLargeError","LockedError","update","NotFoundError","console","log","_area","autoTransmitPassword","_owner","locator","logins","getUserController","user","_ownerGroup","memberships","find","groupType","GroupType","Contact","group","contactId","setup","mailAddress","isMailAddress","typedEntries","downcast","customTypeName","findAndRemove","t","lastThrow","StandaloneField","comment","nickname","bday","createBirthday","day","month","year","formatBirthdayNumeric","birthdayIso","parseBirthday","birthdayToIsoDate","role","presharedPassword","autocompleteAs","renderRevealIcon","createCloseButtonAttrs","createContactPhoneNumber","createContactMailAddress","createContactAddress","createContactSocialId","timestampToGeneratedId","Date","now","isCustom","aggregate","setTimeout","showTextInputDialog","name","DefaultAnimationTime","ToggleButton","toggled","onToggled","_","stopPropagation","headerBarAttrs","left","middle","right","validateAndSave","largeDialog","addShortcut","Keys","ESC","exec","help","S","ctrl","setCloseHandler","exports","shiftByForCheckbox","px","checkbox_size","hpad","translateXShow","translateXHide","ContactRow","onSelected","checkboxWasVisible","shouldAlwaysShowMultiselectCheckbox","top","selected","isInMultiSelect","selectionUpdater","showCheckboxAnimated","checkboxOpacity","checkboxDom","checked","domName","textContent","getContactListName","domAddress","NBSP","render","SelectableRowContainer","resolve","showCheckbox","onSelectedChangeRef","updater","transformOrigin","onclick","onchange","paddingRight","nameAnim","transform","selectableRowAnimParams","addressAnim","checkboxAnim","scaleXHide","scaleXShow","finished","cancel","translate","scale","padding","ContactListView","contactViewModel","renderConfig","itemHeight","list_row_height","multiselectionAllowed","swipe","createElement","KindaContactRow","item","_a","listModel","onSingleExclusiveSelection","onSingleSelection","ListColumnWrapper","headerContent","isEmptyAndDone","ColumnEmptyMessageBox","color","theme","list_message_bg","List","state","listState","onLoadMore","onRetryLoading","retryLoading","onSingleTogglingMultiselection","onSingleInclusiveSelection","onRangeSelectionTowards","selectRangeTowards","onStopLoading","stopLoading","onToggleSelection","cr","multiselect","MultiContactViewer","getContactSelectionMessage","selectedEntities","content_message_bg","bottomContent","Button","selectNone","undefined","backgroundColor","navigation_bg","ContactMergeView","resolveFunction","_close","mailAddresses1","phones","phones1","addresses1","socials","socials1","_createContactFields","mailAddresses2","phones2","addresses2","socials2","emptyFieldPlaceholder","disabled","emptyHTMLFieldPlaceholder","HtmlEditor","showBorders","setValue","setEnabled","setMode","HtmlEditorMode","HTML","setHtmlMonospace","titleFields","_createTextFields","firstNameFields","lastNameFields","nicknameFields","companyFields","roleFields","birthdayFields","presharedPasswordFields","commentField1","commentField2","TextDisplayArea","call","_createDeleteContactButton","element","getContactSocialType","value1","value2","labelTextId","action","confirm","confirmed","d","defer","promise","delay","mergeContacts","keptContact","eliminatedContact","_getMergedNameField","neverNull","_getMergedOtherField","birthday1","birthday2","b1","_convertIsoBirthday","b2","_getMergedBirthdays","filteredMailAddresses2","ma2","ma1","toLowerCase","concat","_getMergedEmailAddresses","phoneNumbers1","phoneNumbers2","filteredNumbers2","replace","_getMergedPhoneNumbers","socialIds1","socialIds2","filteredSocialIds2","_getMergedSocialIds","filteredAddresses2","_getMergedAddresses","_compareContactsForMerge","nameResult","_compareFullName","mailResult","contact1MailAddresses","contact2MailAddresses","_compareValues","phoneResult","contact1PhoneNumbers","contact2PhoneNumbers","birthdayResult","_compareBirthdays","residualContactFieldsEqual","_isEqualOtherField","contact1SocialIds","contact2SocialIds","_areSocialIdsEqual","contact1Addresses","contact2Addresses","_areAddressesEqual","_areResidualContactFieldsEqual","name1","name2","isoBirthday","isoDateToBirthday","values1","values2","equalAddresses","c2","c1","otherAttribute1","otherAttribute2","separator","exportContacts","contacts","vCardFile","forEach","contactToVCardString","fnString","_getVCardEscaped","_getFoldedString","nString","startsWith","_vCardFormatArrayToString","_addressesToVCardAddresses","numbers","num","kind","KIND","CONTENT","_phoneNumbersToVCardPhoneNumbers","sId","getSocialUrl","_socialIdsToVCardSocialUrls","_contactToVCard","contactsToVCard","data","stringToUtf8Uint8Array","tmpFile","createFile","mimeType","String","byteLength","fileController","saveDataFile","convertToDataFile","ad","typeAndContentArray","tagContent","reduce","elem","text","separateLinesArray","substring","join","content","ContactViewer","contactAppellation","memoized","formattedBirthday","hasBirthday","onWriteMail","insertBetween","fontWeight","renderMailAddressesAndPhones","renderAddressesAndSocialIds","renderComment","renderAddress","renderSocialId","renderMailAddress","renderPhoneNumber","contactSocialId","showButton","newMailButton","phone","callButton","prepAddress","indexOf","encodeURIComponent","split","array","spacer","ret","ContactCardViewer","class","responsiveCardHMargin","content_bg","MobileContactActionBar","justifyContent","deleteAction","editAction","ContactViewerActions","shortcuts","onDelete","onEdit","onMerge","onExport","actionButtons","canEdit","canMerge","canExport","canDelete","onupdate","keyManager","unregisterShortcuts","E","DELETE","M","registerShortcuts","vCardEscapingSplit","details","vCardReescapingArray","a","_decodeTag","encoding","charset","decoder","cs","l","decodeQuotedPrintable","decodeBase64","line","importAsVCard","showFileChooser","contactFiles","numberOfContacts","vCardsList","contactFile","vCards","vCardFileData","B","vCardFileToVCards","utf8Uint8ArrayToString","Error","showProgressDialog","contactList","vCardList","ownerGroupId","i","vCardLines","j","indexAfterTag","tagAndTypeString","toUpperCase","tagName","tagValue","encodingObj","includes","charsetObj","nameDetails","fullName","indexOfT","bDayDetails","match","bDay","isValidBirthday","ParsingError","orgDetails","note","_addAddress","_addMailAddress","_addPhoneNumber","website","nick","vCardAddressValue","addressDetails","vCardEscapingSplitAdr","vCardPhoneNumberValue","vCardMailAddressValue","email","vCardListToContacts","flat","contactModel","contactListId","setupMultipleEntities","SetupMultipleError","failedInstances","writeMail","to","subject","mailModel","getUserMailboxDetails","mailboxDetails","newMailEditorFromTemplate","appendEmailSignature","props","editor","deleteContacts","erase","catch","ofClass","confirmMerge","goodbyeContact","BaseTopLevelView","super","folderColumn","ViewColumn","FolderColumnView","drawer","drawerAttrs","button","styles","isUsingBottomNavigation","createNewContact","SidebarSection","getGroupInfoDisplayName","userGroupInfo","createContactFoldersExpanderChildren","ariaLabel","first_col_min_width","first_col_max_width","listColumn","BackgroundColumnLayout","columnLayout","viewSlider","focus","contactColumn","desktopToolbar","renderListToolbar","mobileHeader","inMultiselect","MultiselectMobileHeader","selectionAttrsForList","getSelectedContacts","MobileHeader","header","backAction","focusPreviousColumn","columnType","getTitle","actions","renderSortByButton","EnterMultiselectIconButton","clickAction","enterMultiselect","primaryAction","renderHeaderRightView","second_col_min_width","second_col_max_width","DesktopViewerToolbar","contactViewerActions","multicolumnActions","showingListView","third_col_min_width","third_col_max_width","ViewSlider","getShortcuts","c","editContact","isSingleColumnLayout","Header","searchBar","LazySearchBar","placeholder","bottomNav","focusedColumn","editSelectedContact","_deleteSelected","MobileBottomActionBar","BottomNav","getSelectedAsArray","firstSelected","listSelectionKeyboardShortcuts","N","href","route","disableHoverBackground","background","isNavButtonSelected","stateBgHover","NavButton","renderFolderMoreButton","colors","isApp","exportAsVCard","loadAll","ContactTypeRef","allContacts","nbrOfContacts","_mergeAction","width","mergeableAndDuplicates","inputContacts","mergableContacts","duplicateContacts","slice","firstContactIndex","currentMergableContacts","firstContact","secondContactIndex","secondContact","overallResult","splice","mergeable","deletable","getMergeableContacts","deletePromise","dc","_showMergeDialogs","canceled","mergable","_removeFromMergableContacts","clear","onNewUrl","args","init","getViewSlider","handleBackButton","DesktopListToolbar","SelectAllCheckbox","createDropdown","lazyButtons","setSortByFirstName","eventController","router","updateUi","targetContactId","listModelStateStream","ListModel","topId","GENERATED_MAX_ID","fetch","items","complete","loadSingle","elementId","load","sortCompare","entityListener","updates","isUpdateForTypeRef","instanceListId","entityEventReceived","instanceId","operation","updateUrl","loadInitial","loadAndSelect","finally","addEntityListener","stateStream","getElementId","routeTo","sorting","sort","dispose","removeEntityListener","end"],"mappings":"w4CAMO,MAAMA,GAA4E,CACxF,EAA8B,gBAC9B,EAA2B,aAC3B,EAA4B,cAC5B,EAA6B,gBAGd,SAAAC,GAA2BC,EAA0BC,GACpE,YAAID,EACIC,EAEAC,EAAKC,IAAIL,GAA8BE,GAEhD,CAEO,MAAMI,GAAgF,CAC5F,EAAkC,gBAClC,EAA+B,aAC/B,EAAiC,eACjC,EAA8B,YAC9B,EAAgC,cAChC,EAAiC,gBAGlB,SAAAC,GAA+BL,EAA8BC,GAC5E,YAAID,EACIC,EAEAC,EAAKC,IAAIC,GAA8BJ,GAEhD,CAEO,MAAMM,GAAsE,CAClF,EAA6B,gBAC7B,EAA8B,iBAC9B,EAA0B,aAC1B,EAA+B,iBAC/B,EAA2B,cAC3B,EAA4B,gBAGb,SAAAC,GAA0BP,EAAyBC,GAClE,YAAID,EACIC,EAEAC,EAAKC,IAAIG,GAAyBN,GAE3C,CAYM,SAAUQ,GAAgBC,EAAmBC,EAAmBC,GAA2B,GAChG,IAAIC,EAAUH,EAASI,UAAUC,OAC7BC,EAAUL,EAASG,UAAUC,OAC7BE,EAASP,EAASQ,SAASH,OAC3BI,EAASR,EAASO,SAASH,OAC3BK,EAAeV,EAASW,cAAcC,OACtCC,EAAeZ,EAASU,cAAcC,QACrCE,EAAWC,GAAeb,EAAkB,CAACC,EAASI,GAAU,CAACA,EAAQJ,IACzEa,EAAWC,GAAef,EAAkB,CAACI,EAASG,GAAU,CAACA,EAAQH,GAY9E,GARKQ,GAAcC,IAClBD,EAAYd,EAASkB,SAGjBF,GAAcC,IAClBD,EAAYf,EAASiB,SAGlBJ,IAAcE,EACjB,OAAQ,EACF,GAAIA,IAAcF,EACxB,OAAO,EACD,CACN,IAAIK,EAASL,EAAUM,cAAcJ,GAErC,GAAe,IAAXG,EAAc,CACjB,GAAIJ,IAAgBE,EACnB,OAAQ,EACF,GAAIA,IAAgBF,EAC1B,OAAO,EAEPI,EAASJ,EAAYK,cAAcH,EAEpC,CAED,OAAe,IAAXE,EAECT,EAAe,GAAsB,IAAjBG,GACf,EACEA,EAAe,GAAsB,IAAjBH,EACvB,EACoB,IAAjBA,GAAuC,IAAjBG,EAEzBQ,GAAuBrB,EAAUC,IAExCkB,EAASnB,EAASW,cAAc,GAAGW,QAAQjB,OAAOe,cAAcnB,EAASU,cAAc,GAAGW,QAAQjB,QAEnF,IAAXc,EAEIE,GAAuBrB,EAAUC,GAEjCkB,GAIFA,CAER,CACF,OClGaI,GACZC,SAASC,IAC6C,kBAA9BA,EAAMC,MAAMC,eAA8BF,EAAMC,MAAMC,gBAChEC,KAAKC,QAAQJ,EAAMK,KAAoB,EACpD,CAEDC,qBAAqBN,SACdG,KAAKC,QAAQJ,EAAMK,KAAoB,EAC7C,CAEDE,KAAKP,GACJ,MAAMC,EAAQD,EAAMC,MACpB,OAAOO,EAAE,gCAAiC,CACzCA,EAAEC,EAAW,CACZC,MAAOT,EAAMS,MACbC,MAAO,IAAMV,EAAMU,MACnB7C,KAAMmC,EAAMW,UACZC,UAAW,IAAM7C,EAAKC,IAAIgC,EAAMY,WAChCC,gBAAiB,IAAMX,KAAKY,eAAed,GAC3Ce,QAAUN,GAAUT,EAAMgB,SAASP,KAEpCP,KAAKe,iBAAiBjB,IAEvB,CAEDkB,iBAAiBlB,GAChB,MAAoC,kBAAtBA,EAAMmB,aAA4BnB,EAAMmB,WACtD,CAEDF,iBAAiBjB,GAChB,OAAIE,KAAKgB,iBAAiBlB,GAClBO,EAAEa,EAAY,CACpBC,MAAO,gBACPC,MAAO,IAAMtB,EAAMuB,eACnBC,KAAkB,WAIZjB,EAAE,eAEV,CAEDO,eAAed,GACd,OAAOO,EACNa,EACAK,EAAe,CACdC,gBAAiB,CAChBL,MAAO,aACPG,KAAsB,SACtBG,KAAwB,GAEzBC,WAAY,IACX5B,EAAM6B,WAAWC,KAAI,EAAEC,EAAKtB,MACpB,CACNC,MAAOD,EACPa,MAAO,IAAMtB,EAAMgC,eAAeD,SAKvC,CAED5B,QAAQ8B,EAAyBC,GAChC,IAAIC,EAAcF,EAAWG,aAEzBF,IACHD,EAAWI,MAAMC,QAAU,KAG5B,MAAMC,EAAWC,EAAWC,IAAIR,EAAYC,EAASI,EAAQ,EAAG,GAAG,GAAQA,EAAQ,EAAG,GAAG,IACnFI,EAAUF,EAAWC,IAAIR,EAAYC,EAASS,EAAO,EAAGR,GAAeQ,EAAOR,EAAa,IAIjG,OAHAO,EAAQE,MAAK,KACZX,EAAWI,MAAMM,OAAS,EAAE,IAEtBE,QAAQC,IAAI,CAACP,EAAUG,GAC9B,ECzDFK,WAIaC,GAuBZC,YACkBC,EACjBC,EACAC,EACiBC,EAA4D,MAK7E,GARiBnD,KAAYgD,aAAZA,EAGAhD,KAAoBmD,qBAApBA,EAnBVnD,KAAkBoD,oBAAY,EAM9BpD,KAAMqD,QAAY,EAezBrD,KAAKiD,QAAUA,EAAUK,GAAML,GAAWM,KAC1CvD,KAAKwD,aAA+B,OAAhBP,aAAO,EAAPA,EAASQ,KAEzBzD,KAAKwD,cAA0B,MAAVN,EACxB,MAAM,IAAIQ,GAAiB,sEAE3B1D,KAAKkD,OAASA,GAAkBS,GAAcV,EAAS,sCAAsCQ,IAAI,GAGlG,MAAMG,EAAMC,GAAwBA,EAAOJ,KAAOzD,KAAK8D,QAEvD9D,KAAKjB,cAAgBiB,KAAKiD,QAAQlE,cAAc6C,KAAKlC,GAAY,CAACA,EAASkE,EAAGlE,MAC9EM,KAAKjB,cAAcgF,KAAK/D,KAAKgE,kBAC7BhE,KAAKiE,aAAejE,KAAKiD,QAAQgB,aAAarC,KAAKsC,GAAgB,CAACA,EAAaN,EAAGM,MACpFlE,KAAKiE,aAAaF,KAAK/D,KAAKmE,kBAC5BnE,KAAKoE,UAAYpE,KAAKiD,QAAQmB,UAAUxC,KAAKlC,GAAY,CAACA,EAASkE,EAAGlE,MACtEM,KAAKoE,UAAUL,KAAK/D,KAAKqE,cACzBrE,KAAKsE,UAAYtE,KAAKiD,QAAQqB,UAAU1C,KAAK2C,GAAa,CAACA,EAAUX,EAAGW,MACxEvE,KAAKsE,UAAUP,KAAK/D,KAAKwE,eACzBxE,KAAKyE,oBAAqB,EAC1BzE,KAAK0E,SAAWC,EAAwB3E,KAAKiD,UAAY,GACzDjD,KAAK4E,OAAS5E,KAAK6E,eACnB7E,KAAK8E,uBAAyBC,EAC9B,CAEDnF,WACCI,KAAK8E,uBAAyBE,EAAaC,wBAAuB,QAClE,CAEDC,WACClF,KAAK8E,wBACL,CAED1E,OACC,OAAOC,EAAE,kBAAmB,CAC3BA,EAAE,gBAAiB,CAACL,KAAKmF,uBAAwBnF,KAAKoF,wBACtD/E,EAAE,gBAAiB,CAACL,KAAKqF,mBAAoBrF,KAAKsF,wBAClDjF,EAAE,gBAAiB,CAACL,KAAKuF,kBAAmBvF,KAAKwF,qBAAsBxF,KAAKyF,sBAAuBzF,KAAK0F,uBACxGrF,EAAE,gBAAiB,CAClBA,EAAE,cAAe,CAChBA,EAAE,MAAOxC,EAAKC,IAAI,gBAClBuC,EAAE,oBAAqB,CACtBL,KAAKjB,cAAc6C,KAAI,EAAElC,EAASkE,GAAK+B,KACtC,MAAMC,EAAaD,IAAUE,GAAU7F,KAAKjB,eAC5C,OAAOiB,KAAK8F,0BAA0BlC,GAAKgC,EAAYlG,EAAQ,QAIlEW,EAAE,eAAgB,CACjBA,EAAE,MAAOxC,EAAKC,IAAI,gBAClBuC,EAAE,oBAAqB,CACtBL,KAAKiE,aAAarC,KAAI,EAAEsC,EAAaN,GAAK+B,KACzC,MAAMC,EAAaD,IAAUE,GAAU7F,KAAKiE,cAC5C,OAAOjE,KAAK+F,mBAAmBnC,GAAKgC,EAAY1B,EAAY,UAKhE7D,EAAE,gBAAiB,CAClBA,EAAE,iBAAkB,CACnBA,EAAE,MAAOxC,EAAKC,IAAI,kBAClBuC,EAAE,oBAAqB,CACtBL,KAAKoE,UAAUxC,KAAI,EAAElC,EAASkE,GAAK+B,KAClC,MAAMC,EAAaD,IAAUE,GAAU7F,KAAKoE,WAC5C,OAAOpE,KAAKgG,sBAAsBpC,GAAKgC,EAAYlG,EAAQ,QAI9DW,EAAE,gBAAiB,CAClBA,EAAE,MAAOxC,EAAKC,IAAI,iBAClBuC,EAAE,oBAAqB,CACtBL,KAAKsE,UAAU1C,KAAI,EAAE2C,EAAUX,GAAK+B,KACnC,MAAMC,EAAaD,IAAUE,GAAU7F,KAAKsE,WAC5C,OAAOtE,KAAKiG,oBAAoBrC,GAAKgC,EAAYrB,EAAS,UAK9DvE,KAAKkG,+BACL7F,EAAE,QAEH,CAED8F,OACCnG,KAAK4E,OAAOuB,MACZ,CAEOC,QACPpG,KAAK4E,OAAOwB,OACZ,CAUOjG,wBACP,GAAIH,KAAKyE,mBACR,OAAO4B,EAAOC,QAAQ,uBAGvB,IAAItG,KAAKqD,OAAT,CAIArD,KAAKqD,QAAS,EAEdrD,KAAKiD,QAAQlE,cAAgBiB,KAAKjB,cAAc6C,KAAK2E,GAAMA,EAAE,KAAIC,QAAQD,GAAMA,EAAE7G,QAAQjB,OAAOO,OAAS,IACzGgB,KAAKiD,QAAQgB,aAAejE,KAAKiE,aAAarC,KAAK2E,GAAMA,EAAE,KAAIC,QAAQD,GAAMA,EAAEE,OAAOhI,OAAOO,OAAS,IACtGgB,KAAKiD,QAAQmB,UAAYpE,KAAKoE,UAAUxC,KAAK2E,GAAMA,EAAE,KAAIC,QAAQD,GAAMA,EAAE7G,QAAQjB,OAAOO,OAAS,IACjGgB,KAAKiD,QAAQqB,UAAYtE,KAAKsE,UAAU1C,KAAK2E,GAAMA,EAAE,KAAIC,QAAQD,GAAMA,EAAEhC,SAAS9F,OAAOO,OAAS,IAClG,IACKgB,KAAKwD,mBACFxD,KAAK0G,uBAEL1G,KAAK2G,wBAEZ3G,KAAKoG,OACL,CAAC,MAAOG,GAER,GADAvG,KAAKqD,QAAS,EACVkD,aAAaK,GAChB,OAAOP,EAAOC,QAAQ,uBAEvB,GAAIC,aAAaM,GAChB,OAAOR,EAAOC,QAAQ,2BAEvB,CAtBA,CAwBD,CAEOnG,8BACP,UACOH,KAAKgD,aAAa8D,OAAO9G,KAAKiD,QACpC,CAAC,MAAOsD,GACJA,aAAaQ,IAChBC,QAAQC,IAzKA,kBAyKS,4BAA4BjH,KAAKiD,QAAQQ,iBAE3D,CACD,CAEOtD,uBACPH,KAAKiD,QAAQiE,MAAQ,IACrBlH,KAAKiD,QAAQkE,qBAAuB,GACpCnH,KAAKiD,QAAQmE,OAASC,EAAQC,OAAOC,oBAAoBC,KAAK/D,IAC9DzD,KAAKiD,QAAQwE,YAAc9D,GAC1B0D,EAAQC,OAAOC,oBAAoBC,KAAKE,YAAYC,MAAMtH,GAAMA,EAAEuH,YAAcC,GAAUC,UAC1F,yCACCC,MACF,MAAMC,QAAkBhI,KAAKgD,aAAaiF,MAAMjI,KAAKkD,OAAQlD,KAAKiD,SAC9DjD,KAAKmD,sBACRnD,KAAKmD,qBAAqB6E,EAE3B,CAEOlC,0BAA0BlC,EAAQ3C,EAAsBiH,GAC/D,IAAIxH,EAGHA,EADGwH,EAAYxI,QAAQjB,OAAOO,OAAS,IAAMmJ,EAAcD,EAAYxI,QAAQjB,QAAQ,GAC3E,yBAEA,kBAGb,MAAMkD,EAA0DyG,GAAa3K,IAC7E,OAAO4C,EAAEV,GAAwB,CAChCY,MAAO2H,EAAYxI,QACnBe,UAA6B,OAC7BD,MAAO9C,GAA2B2K,GAASH,EAAYvK,MAAOuK,EAAYI,gBAC1E5H,YACAW,aAAc,KACbkH,GAAcvI,KAAKjB,eAAgByJ,GAAMA,EAAE,KAAO5E,GAAG,EAEtD9C,SAAWP,IACV2H,EAAYxI,QAAUa,EAClB2H,IAAgBO,GAAUzI,KAAKjB,eAAe,IAAIiB,KAAKjB,cAAcgF,KAAK/D,KAAKqE,aAAa,EAEjGtE,eAAgBmI,EAAYxI,QAC5BuB,cACAY,IAAK+B,EACLjC,aACAG,eAAiBnE,GAASqC,KAAK8B,eAAiD,MAAlCnE,EAAoCA,EAAMuK,IAEzF,CAEOnC,mBAAmBnC,EAAQ3C,EAAsBiD,GACxD,MAAMvC,EAAayG,GAAarK,IAChC,OAAOsC,EAAEV,GAAwB,CAChCY,MAAO2D,EAAYuC,OACnBhG,UAA6B,OAC7BD,MAAOxC,GAA+BqK,GAASnE,EAAYvG,MAAOuG,EAAYoE,gBAC9E5H,UAAW,kBACXW,aAAc,KACbkH,GAAcvI,KAAKiE,cAAeuE,GAAMA,EAAE,KAAO5E,GAAG,EAErD9C,SAAWP,IACV2D,EAAYuC,OAASlG,EACjB2D,IAAgBuE,GAAUzI,KAAKiE,cAAc,IAAIjE,KAAKiE,aAAaF,KAAK/D,KAAKmE,iBAAiB,EAEnGpE,eAAgBmE,EAAYuC,OAC5BxF,cACAY,IAAK+B,EACLjC,aACAG,eAAiBnE,GAASqC,KAAK8B,eAAqD,MAAtCnE,EAAwCA,EAAMuG,IAE7F,CAEO8B,sBAAsBpC,EAAQ3C,EAAsBvB,GAC3D,MAAMiC,EAAayG,GAAa3K,IAChC,OAAO4C,EAAEV,GAAwB,CAChCY,MAAOb,EAAQA,QACfe,UAA6B,OAC7BD,MAAO9C,GAA2B2K,GAAS3I,EAAQ/B,MAAO+B,EAAQ4I,gBAClE5H,UAAW,kBACXW,aAAc,KACbkH,GAAcvI,KAAKoE,WAAYoE,GAAMA,EAAE,KAAO5E,GAAG,EAElD9C,SAAWP,IACVb,EAAQA,QAAUa,EACdb,IAAY+I,GAAUzI,KAAKoE,WAAW,IAAIpE,KAAKoE,UAAUL,KAAK/D,KAAKqE,aAAa,EAErFtE,eAAgBL,EAAQA,QACxBuB,cACAY,IAAK+B,EACLjC,aACAG,eAAiBnE,GAASqC,KAAK8B,eAAiD,MAAlCnE,EAAoCA,EAAM+B,IAEzF,CAEOuG,oBAAoBrC,EAAQ3C,EAAsBsD,GACzD,MAAM5C,EAAayG,GAAanK,IAChC,OAAOoC,EAAEV,GAAwB,CAChCY,MAAOgE,EAASA,SAChB9D,UAA6B,OAC7BD,MAAOtC,GAA0BmK,GAA4B9D,EAAS5G,MAAO4G,EAAS+D,gBACtF5H,UAAW,kBACXW,aAAc,KACbkH,GAAcvI,KAAKsE,WAAYkE,GAAMA,EAAE,KAAO5E,GAAG,EAElD9C,SAAWP,IACVgE,EAASA,SAAWhE,EAChBgE,IAAakE,GAAUzI,KAAKsE,WAAW,IAAItE,KAAKsE,UAAUP,KAAK/D,KAAKwE,cAAc,EAEvFzE,eAAgBwE,EAASA,SACzBtD,cACAY,IAAK+B,EACLjC,aACAG,eAAiBnE,GAASqC,KAAK8B,eAAgD,MAAjCnE,EAAmCA,EAAM4G,IAExF,CAEOmB,qBACP,OAAOrF,EAAEqI,GAAiB,CACzBlI,MAAO,gBACPD,MAAOP,KAAKiD,QAAQ0F,QACpB9H,QAAUN,GAAWP,KAAKiD,QAAQ0F,QAAUpI,EAC5C5C,KAAwB,QAEzB,CAEOwH,uBACP,OAAO9E,EAAEqI,GAAiB,CACzBlI,MAAO,wBACPD,MAAOP,KAAKiD,QAAQzE,UACpBqC,QAAUN,GAAWP,KAAKiD,QAAQzE,UAAY+B,GAE/C,CAEOkF,4BACP,OAAOpF,EAAEqI,GAAiB,CACzBlI,MAAO,uBACPD,gBAAOP,KAAKiD,QAAQ2F,wBAAY,GAChC/H,QAAUN,GAAWP,KAAKiD,QAAQ2F,SAAWrI,GAE9C,CAEO6E,sBACP,OAAO/E,EAAEqI,GAAiB,CACzBlI,MAAO,uBACPD,MAAOP,KAAKiD,QAAQrE,SACpBiC,QAAUN,GAAWP,KAAKiD,QAAQrE,SAAW2B,GAE9C,CAEO+E,sBAaP,OAAOjF,EAAEqI,GAAiB,CACzBlI,MAAO,eACPD,MAAOP,KAAK0E,SACZhE,UAfsB,KACtB,IAAImI,EAAOC,KAIX,OAHAD,EAAKE,IAAM,KACXF,EAAKG,MAAQ,IACbH,EAAKI,KAAO,OACLjJ,KAAKyE,mBACT5G,EAAKC,IAAI,wBAAyB,CAClC,MAAOoL,EAAsBL,KAE7B,EAAE,EAOLhI,QAAUN,IAET,GADAP,KAAK0E,SAAWnE,EACY,IAAxBA,EAAM9B,OAAOO,OAChBgB,KAAKiD,QAAQkG,YAAc,KAC3BnJ,KAAKyE,oBAAqB,MACpB,CACN,IAAIC,EAAW0E,GAAc7I,GAE7B,GAAImE,EACH,IACC1E,KAAKiD,QAAQkG,YAAcE,GAAkB3E,GAC7C1E,KAAKyE,oBAAqB,CAC1B,CAAC,MAAO8B,GACRvG,KAAKyE,oBAAqB,CAC1B,MAEDzE,KAAKyE,oBAAqB,CAE3B,IAGH,CAEOe,qBACP,OAAOnF,EAAEqI,GAAiB,CACzBlI,MAAO,gBACPD,MAAOP,KAAKiD,QAAQ3D,QACpBuB,QAAUN,GAAWP,KAAKiD,QAAQ3D,QAAUiB,GAE7C,CAEOgF,kBACP,OAAOlF,EAAEqI,GAAiB,CACzBlI,MAAO,mBACPD,MAAOP,KAAKiD,QAAQqG,KACpBzI,QAAUN,GAAWP,KAAKiD,QAAQqG,KAAO/I,GAE1C,CAEO8E,mBACP,OAAOhF,EAAEqI,GAAiB,CACzBlI,MAAO,oBACPD,MAAOP,KAAKiD,QAAQ9B,OAAS,GAC7BN,QAAUN,GAAWP,KAAKiD,QAAQ9B,MAAQZ,GAE3C,CAEO2F,qCACP,OAAKlG,KAAKwD,cAAiBxD,KAAKiD,QAAQsG,kBAIjClJ,EAAE,gBAAiB,CACzBA,EAAE,mBAAoB,CACrBA,EAAE,MAAOxC,EAAKC,IAAI,4BAClBuC,EAAEC,EAAW,CACZ3C,KAAMqC,KAAKoD,mBAAoB,OAA4C,WAC3E5C,MAAO,iBACPD,gBAAOP,KAAKiD,QAAQsG,iCAAqB,GACzCC,eAAwC,eACxC3I,QAAUN,GAAWP,KAAKiD,QAAQsG,kBAAoBhJ,EACtDI,gBAAiB,IAAMX,KAAKyJ,uBAG9BpJ,EAAE,aAfK,IAiBR,CAEOqJ,yBACP,MAAO,CACNlJ,MAAO,YACPY,MAAO,CAACmF,EAAGrG,IAAQF,KAAKoG,QACxBzI,KAA0B,YAE3B,CAEOwG,iBAMP,MAAO,CALawF,GAAyB,CAC5ChM,KAAmC,IACnC2K,eAAgB,GAChB7B,OAAQ,KAEYzG,KAAK8D,QAC1B,CAEOE,iBAMP,MAAO,CALa4F,GAAyB,CAC5CjM,KAA6B,IAC7B2K,eAAgB,GAChB5I,QAAS,KAEWM,KAAK8D,QAC1B,CAEOO,aAMP,MAAO,CALSwF,GAAqB,CACpClM,KAA6B,IAC7B2K,eAAgB,GAChB5I,QAAS,KAEOM,KAAK8D,QACtB,CAEOU,cAMP,MAAO,CALUsF,GAAsB,CACtCnM,KAA+B,IAC/B2K,eAAgB,GAChB/D,SAAU,KAEOvE,KAAK8D,QACvB,CAEOA,QACP,OAAOiG,GAAuBC,KAAKC,MACnC,CAEOnI,eAAiEoI,EAAmBrI,EAAQsI,GAC/FD,EACHE,YAAW,KACV/D,EAAOgE,oBAAoB,oBAAqB,oBAAqB,KAAMF,EAAU7B,gBAAgB5F,MAAM4H,IAC1GH,EAAU7B,eAAiBgC,EAC3BH,EAAUxM,KAAOkE,CAAG,GACnB,GACA0I,GAEHJ,EAAUxM,KAAOkE,CAElB,CAEO4H,mBACP,OAAOpJ,EAAEmK,EAAc,CACtBrJ,MAAOnB,KAAKoD,mBAAqB,yBAA2B,wBAC5DqH,QAASzK,KAAKoD,mBACdsH,UAAW,CAACC,EAAGpE,KACdvG,KAAKoD,oBAAsBpD,KAAKoD,mBAChCmD,EAAEqE,iBAAiB,EAEpBtJ,KAAMtB,KAAKoD,mBAAoB,QAAwB,MACvD3B,KAAwB,GAEzB,CAEOoD,eACP,MAAMgG,EAAuC,CAC5CC,KAAM,CAAC9K,KAAK0J,0BACZqB,OAAQ,IAAM/K,KAAKiD,QAAQzE,UAAY,IAAMwB,KAAKiD,QAAQrE,SAC1DoM,MAAO,CACN,CACCxK,MAAO,cACPY,MAAO,IAAMpB,KAAKiL,kBAClBtN,KAAwB,aAI3B,OAAO0I,EAAO6E,YAAYL,EAAgB7K,MACxCmL,YAAY,CACZtJ,IAAKuJ,GAAKC,IACVC,KAAM,IAAMtL,KAAKoG,QACjBmF,KAAM,cAENJ,YAAY,CACZtJ,IAAKuJ,GAAKI,EACVC,MAAM,EACNH,KAAM,KAELtL,KAAKiL,iBAAiB,EAEvBM,KAAM,gBAENG,iBAAgB,IAAM1L,KAAKoG,SAC7B,EACDuF,EAAA,IAAA7I,IAGD,MAAM4F,GACLtI,MAAKN,MAAEA,IACN,OAAOO,EAAE,mBAAoB,CAACA,EAAEC,EAAWR,GAAQO,EAAE,iBACrD,0DC9hBF,MAAMuL,GAAqBC,EAAGpK,EAAKqK,cAAgBrK,EAAKsK,MAClDC,GAAiB,cAAcJ,MAC/BK,GAAiB,sBAEVC,GAWZnJ,YAA6BoJ,GAAAnM,KAAUmM,WAAVA,EAT7BnM,KAAA+B,WAAiC,KAOzB/B,KAAkBoM,mBAAGC,IAG5BrM,KAAKsM,IAAM,EACXtM,KAAK6D,OAAS,IACd,CAEDiD,OAAO7D,EAAkBsJ,EAAmBC,GAC3CxM,KAAK6D,OAASZ,EACdjD,KAAKyM,iBAAiBF,EAAUC,GAChCxM,KAAK0M,qBAAqBL,KAAyCG,GACnEG,EAAgB3M,KAAK4M,YAAaL,GAClCvM,KAAK4M,YAAYC,QAAUN,GAAYC,EAEvCxM,KAAK8M,QAAQC,YAAcC,EAAmB/J,GAC9CjD,KAAKiN,WAAWF,YAAc9J,EAAQlE,eAAiBkE,EAAQlE,cAAcC,OAAS,EAAIiE,EAAQlE,cAAc,GAAGW,QAAUwN,EAC7H,CAKDC,SACC,OAAO9M,EACN+M,EACA,CACCxN,SAAWC,IACV8C,QAAQ0K,UAAU3K,MAAK,IAAM1C,KAAKsN,aAAajB,MAAuC,EAEvFkB,oBAAsBC,GAAaxN,KAAKyM,iBAAmBe,GAE5DnN,EAAE,aAAc,CACfA,EAAE,+BAAgC,CACjC1C,KAAM,WACNwE,MAAO,CACNsL,gBAAiB,QAElBC,QAAUnH,IACTA,EAAEqE,iBAAiB,EAGpB+C,SAAU,KACT3N,KAAK6D,QAAU7D,KAAKmM,WAAWnM,KAAK6D,OAAQ7D,KAAK4M,YAAYC,QAAQ,EAEtEjN,SAAWC,IACVG,KAAK4M,YAAc/M,EAAMK,IACzByM,EAAgB3M,KAAK4M,aAAa,EAAM,MAI3CvM,EAAE,sCAAuC,CACxCA,EAAE,mCAAoC,CACrCT,SAAWC,GAAWG,KAAK8M,QAAUjN,EAAMK,MAE5CG,EAAE,gCAAiC,CAClCT,SAAWC,GAAWG,KAAKiN,WAAapN,EAAMK,QAIjD,CAEOwM,qBAAqBvG,GAC5B,GAAInG,KAAKoM,qBAAuBjG,EAAhC,CACA,GAAIA,EAAM,CACTnG,KAAK8M,QAAQ3K,MAAMyL,aAAehC,GAClC5L,KAAKiN,WAAW9K,MAAMyL,aAAehC,GAErC,MAAMiC,EAAW7N,KAAK8M,QAAQ7M,QAAQ,CAAE6N,UAAW,CAAC7B,GAAgBD,KAAmB+B,GACjFC,EAAchO,KAAKiN,WAAWhN,QAAQ,CAAE6N,UAAW,CAAC7B,GAAgBD,KAAmB+B,GACvFE,EAAejO,KAAK4M,YAAY3M,QAAQ,CAAE6N,UAAW,CAACI,EAAYC,IAAeJ,GAEvFpL,QAAQC,IAAI,CAACiL,EAASO,SAAUJ,EAAYI,SAAUH,EAAaG,WAAW1L,MAAK,KAClFmL,EAASQ,SACTL,EAAYK,SACZJ,EAAaI,SACbrO,KAAKsN,aAAanH,EAAK,GACrBpB,GACH,KAAM,CACN/E,KAAK8M,QAAQ3K,MAAMyL,aAAe,IAClC5N,KAAKiN,WAAW9K,MAAMyL,aAAe,IAErC,MAAMC,EAAW7N,KAAK8M,QAAQ7M,QAAQ,CAAE6N,UAAW,CAAC9B,GAAgBC,KAAmB8B,GACjFC,EAAchO,KAAKiN,WAAWhN,QAAQ,CAAE6N,UAAW,CAAC9B,GAAgBC,KAAmB8B,GACvFE,EAAejO,KAAK4M,YAAY3M,QAAQ,CAAE6N,UAAW,CAACK,EAAYD,IAAeH,GAEvFpL,QAAQC,IAAI,CAACiL,EAASO,SAAUJ,EAAYI,SAAUH,EAAaG,WAAW1L,MAAK,KAClFmL,EAASQ,SACTL,EAAYK,SACZJ,EAAaI,SACbrO,KAAKsN,aAAanH,EAAK,GACrBpB,GACH,CACD/E,KAAKoM,mBAAqBjG,CA9BkB,CA+B5C,CAEOmH,aAAanH,GACpB,IAAImI,EACAC,EACAC,EACArI,GACHmI,EAAYtC,GACZuC,EAAQJ,EACRK,EAAU5C,KAEV0C,EAAYrC,GACZsC,EAAQL,EACRM,EAAU,KAGXxO,KAAKiN,WAAW9K,MAAM2L,UAAYQ,EAClCtO,KAAK8M,QAAQ3K,MAAM2L,UAAYQ,EAC/BtO,KAAKiN,WAAW9K,MAAMyL,aAAeY,EACrCxO,KAAK8M,QAAQ3K,MAAMyL,aAAeY,EAClCxO,KAAK4M,YAAYzK,MAAM2L,UAAYS,CACnC,EClIF1L,WAOa4L,GAAb1L,cACS/C,KAAgB0O,iBAA4B,KAwCnC1O,KAAA2O,aAAuD,CACvEC,WAAYnN,EAAKoN,gBACjBC,sBAA8C,EAC9CC,MAAO,KACPC,cAAgB9O,GACR,IAAI+O,GAAgB/O,GAAMgP,IAAQ,IAAAC,EAAC,OAAuB,QAAvBA,EAAAnP,KAAK0O,wBAAkB,IAAAS,OAAA,EAAAA,EAAAC,UAAUC,2BAA2BH,EAAK,IAG7G,CA9CA9O,MAAON,OAAO4O,iBAAEA,EAAgBY,kBAAEA,KAEjC,OADAtP,KAAK0O,iBAAmBA,EACjBrO,EACNkP,EACA,CACCC,cAAe,MAEhBd,EAAiBU,UAAUK,iBACxBpP,EAAEqP,EAAuB,CACzBC,MAAOC,EAAMC,gBACbvJ,QAAS,iBACThF,KAAwB,aAExBjB,EAAEyP,GAAM,CACRnB,aAAc3O,KAAK2O,aACnBoB,MAAOrB,EAAiBsB,YAExBC,WAAY,OACZC,eAAgB,KACfxB,EAAiBU,UAAUe,cAAc,EAE1Cb,kBAAoBJ,IACnBR,EAAiBU,UAAUE,kBAAkBJ,GAC7CI,GAAmB,EAEpBc,+BAAiClB,IAChCR,EAAiBU,UAAUiB,2BAA2BnB,EAAK,EAE5DoB,wBAA0BpB,IACzBR,EAAiBU,UAAUmB,mBAAmBrB,EAAK,EAEpDsB,gBACC9B,EAAiBU,UAAUqB,aAC3B,IAGL,QAYWxB,GAKZlM,YAAY7C,EAAkBwQ,GAF9B1Q,KAAM6D,OAAmB,KAGxB7D,KAAK2Q,GAAK,IAAIzE,GAAWwE,GACzB1Q,KAAK+B,WAAa7B,EAClBG,EAAE8M,OAAOjN,EAAKF,KAAK2Q,GAAGxD,SACtB,CAEDrG,OAAOoI,EAAe3C,EAAmBqE,GACxC5Q,KAAK6D,OAASqL,EACdlP,KAAK2Q,GAAG7J,OAAOoI,EAAM3C,EAAUqE,EAC/B,CAEDzD,SACC,OAAOnN,KAAK2Q,GAAGxD,QACf,EACDxB,EAAA,IAAAsD,IChFDpM,WAUagO,GACZzQ,MAAKN,MAAEA,IACN,MAAO,CACNO,EAAEqP,EAAuB,CACxBpJ,QAAS,IAAMwK,GAA2BhR,EAAMiR,kBAChDzP,KAAwB,WACxBqO,MAAOC,EAAMoB,mBACbC,cACCnR,EAAMiR,iBAAiB/R,OAAS,EAC7BqB,EAAE6Q,GAAQ,CACV1Q,MAAO,gBACP7C,KAA0B,YAC1ByD,MAAO,IAAMtB,EAAMqR,oBAEnBC,EACJC,gBAAiBzB,EAAM0B,gBAGzB,EAGI,SAAUR,GAA2BC,GAC1C,OAAgC,IAA5BA,EAAiB/R,OACbnB,EAAKC,IAAI,iBAETD,EAAKC,IAAI,4BAA6B,CAC5C,MAAOiT,EAAiB/R,QAG3B,CAVC2M,EAAA,IAAAkF,UCpBYU,GAQZxO,YAAY3E,EAAmBC,GAJ/B2B,KAAAwR,gBAAwE,KAExExR,KAAsB8E,uBAAiB,KAGtC9E,KAAK5B,SAAWA,EAChB4B,KAAK3B,SAAWA,EAEhB,MAAMgD,EAAe,KACpBrB,KAAKyR,OAAM,SAA2B,EAGjC5G,EAAiB,CACtBC,KAAM,CACL,CACCtK,MAAO,gBACPY,MAAOC,EACP1D,KAA0B,cAG5BqN,MAAO,CACN,CACCxK,MAAO,cACPY,MAAO,IAAMpB,KAAKyR,OAA+B,QACjD9T,KAAwB,YAG1BoN,OAAQ,IAAMlN,EAAKC,IAAI,iBAExBkC,KAAK4E,OAASyB,EAAO6E,YAAYL,EAAwC7K,MACvE0L,gBAAgBrK,GAChB8J,YAAY,CACZtJ,IAAKuJ,GAAKC,IACVC,KAAM,KACLtL,KAAKyR,OAAM,WACJ,GAERlG,KAAM,aAER,CAEDnL,OACC,MAAQrB,cAAe2S,EAAgBC,OAAQC,EAASxN,UAAWyN,EAAYC,QAASC,GAAa/R,KAAKgS,qBAAqBhS,KAAK5B,WAE5HW,cAAekT,EAAgBN,OAAQO,EAAS9N,UAAW+N,EAAYL,QAASM,GAAapS,KAAKgS,qBAAqBhS,KAAK3B,UAGpI,IAAIgU,EAAwBhS,EAAEC,EAAW,CACxCE,MAAO,kBACPD,MAAO,GACP+R,UAAU,IAEPC,EAA4BlS,EAC/B,IAAImS,GAAW,mBAAmBC,cAAcC,SAAS,IAAIC,YAAW,GAAOC,QAAQC,GAAeC,MAAMC,kBAAiB,IAG1HC,EAAchT,KAAKiT,kBAAkBjT,KAAK5B,SAAS+C,MAAOnB,KAAK3B,SAAS8C,MAAO,qBAE/E+R,EAAkBlT,KAAKiT,kBAAkBjT,KAAK5B,SAASI,UAAWwB,KAAK3B,SAASG,UAAW,yBAE3F2U,EAAiBnT,KAAKiT,kBAAkBjT,KAAK5B,SAASQ,SAAUoB,KAAK3B,SAASO,SAAU,wBAExFwU,EAAiBpT,KAAKiT,kBAAkBjT,KAAK5B,SAASwK,SAAU5I,KAAK3B,SAASuK,SAAU,wBAExFyK,EAAgBrT,KAAKiT,kBAAkBjT,KAAK5B,SAASkB,QAASU,KAAK3B,SAASiB,QAAS,iBAErFgU,EAAatT,KAAKiT,kBAAkBjT,KAAK5B,SAASkL,KAAMtJ,KAAK3B,SAASiL,KAAM,oBAE5EiK,EAAiBvT,KAAKiT,kBAAkBtO,EAAwB3E,KAAK5B,UAAWuG,EAAwB3E,KAAK3B,UAAW,gBAExHmV,EAA0BxT,KAAKiT,kBAClCjT,KAAK5B,SAASmL,mBAAqBvJ,KAAK5B,SAASmL,kBAAkBvK,OAAS,EAAI,MAAQ,GACxFgB,KAAK3B,SAASkL,mBAAqBvJ,KAAK3B,SAASkL,kBAAkBvK,OAAS,EAAI,MAAQ,GACxF,2BAGGyU,EAA0B,KAC1BC,EAA0B,KAa9B,OAXI1T,KAAK5B,SAASuK,SAAW3I,KAAK3B,SAASsK,WAC1C8K,EAAgBpT,EAAEsT,GAAiB,CAClCnT,MAAO,gBACPD,MAAOP,KAAK5B,SAASuK,UAEtB+K,EAAgBrT,EAAEsT,GAAiB,CAClCnT,MAAO,gBACPD,MAAOP,KAAK3B,SAASsK,WAIhBtI,EACN,kBACA,CACCT,SAAU,IAAOI,KAAK8E,uBAAyBE,EAAaC,wBAAuB,SACnFC,SAAU,KAAM,IAAAiK,EAAA,OAA+B,QAA/BA,EAAAnP,KAAK8E,8BAA0B,IAAAqK,OAAA,EAAAA,EAAAyE,KAAA5T,KAAA,GAEhD,CACCK,EAAE,kBAAmB,CACpBA,EAAE,0BAA2B,CAC5BA,EAAE6Q,GAAQ,CACT1Q,MAAO,uBACPY,MAAO,IAAMpB,KAAKyR,OAAgC,SAClD9T,KAAsB,cAIzB0C,EAAE,oBAAqB,CACtBA,EACC,GAEA,CACCA,EAAE,gBAAiB,CAClBA,EAAE,iCAAkC,CACnCA,EAAE,WAAYxC,EAAKC,IAAI,4BACvBkC,KAAK6T,2BAA0D,qBAKnExT,EACC,GAEA,CACCA,EAAE,gBAAiB,CAClBA,EAAE,iCAAkC,CACnCA,EAAE,WAAYxC,EAAKC,IAAI,6BACvBkC,KAAK6T,2BAA2D,wBAMrEb,EAAc3S,EAAE,oBAAqB2S,GAAe,KACpDE,EAAkB7S,EAAE,oBAAqB6S,GAAmB,KAC5DC,EAAiB9S,EAAE,oBAAqB8S,GAAkB,KAC1DC,EAAiB/S,EAAE,oBAAqB+S,GAAkB,KAC1DC,EAAgBhT,EAAE,oBAAqBgT,GAAiB,KACxDE,EAAiBlT,EAAE,oBAAqBkT,GAAkB,KAC1DD,EAAajT,EAAE,oBAAqBiT,GAAc,KAClD5B,EAAe1S,OAAS,GAAKiT,EAAejT,OAAS,EAClDqB,EAAE,oBAAqB,CACvBA,EAAE,aAAc,CAACA,EAAE,GAAIxC,EAAKC,IAAI,gBAAiB4T,EAAe1S,OAAS,EAAI0S,EAAiBW,IAC9FhS,EAAE,aAAc,CAACA,EAAE,GAAIxC,EAAKC,IAAI,gBAAiBmU,EAAejT,OAAS,EAAIiT,EAAiBI,MAE9F,KACHT,EAAQ5S,OAAS,GAAKkT,EAAQlT,OAAS,EACpCqB,EAAE,oBAAqB,CACvBA,EAAE,cAAe,CAACA,EAAE,GAAIxC,EAAKC,IAAI,gBAAiBuC,EAAE,oBAAqB,CAACuR,EAAQ5S,OAAS,EAAI4S,EAAUS,MACzGhS,EAAE,cAAe,CAACA,EAAE,GAAIxC,EAAKC,IAAI,gBAAiBuC,EAAE,oBAAqB,CAAC6R,EAAQlT,OAAS,EAAIkT,EAAUG,QAEzG,KACHR,EAAW7S,OAAS,GAAKmT,EAAWnT,OAAS,EAC1CqB,EAAE,oBAAqB,CACvBA,EAAE,iCAAkC,CACnCA,EAAE,GAAIxC,EAAKC,IAAI,kBACfuC,EAAE,+CAAgD,CAACwR,EAAW7S,OAAS,EAAI6S,EAAaU,MAEzFlS,EAAE,gBAAiB,CAClBA,EAAE,GAAIxC,EAAKC,IAAI,kBACfuC,EAAE,+CAAgD,CAAC8R,EAAWnT,OAAS,EAAImT,EAAaI,QAGzF,KACHR,EAAS/S,OAAS,GAAKoT,EAASpT,OAAS,EACtCqB,EAAE,oBAAqB,CACvBA,EAAE,eAAgB,CACjBA,EAAE,GAAIxC,EAAKC,IAAI,iBACfuC,EAAE,oBAAqB0R,EAAS/S,OAAS,EAAI+S,EAAWM,KAEzDhS,EAAE,eAAgB,CACjBA,EAAE,GAAIxC,EAAKC,IAAI,iBACfuC,EAAE,oBAAqB+R,EAASpT,OAAS,EAAIoT,EAAWC,OAGzD,KACHoB,GAAiBC,EACdrT,EAAE,oBAAqB,CAACA,EAAE,yBAA0B,CAACoT,IAAiBpT,EAAE,yBAA0B,CAACqT,MACnG,KACHF,EAA0BnT,EAAE,oBAAqBmT,GAA2B,KAC5EnT,EACC,GACA,CACC8B,MAAO,CACNM,OAAQ,UAOb,CAEDuP,qBAAqB/O,GAkCpB,MAAO,CACNlE,cA7BqBkE,EAAQlE,cAAc6C,KAAKkS,GACzCzT,EAAEC,EAAW,CACnBE,MAAO,IAAM9C,GAA2BoW,EAAQnW,KAAamW,EAAQxL,gBACrE/H,MAAOuT,EAAQpU,QACf4S,UAAU,MA0BXX,OAvBc1O,EAAQgB,aAAarC,KAAKkS,GACjCzT,EAAEC,EAAW,CACnBE,MAAO,IAAMxC,GAA+B8V,EAAQnW,KAAamW,EAAQxL,gBACzE/H,MAAOuT,EAAQrN,OACf6L,UAAU,MAoBXlO,UAjBiBnB,EAAQmB,UAAUxC,KAAKkS,GAEjCzT,EAAEsT,GAAiB,CACzBpT,MAAOuT,EAAQpU,QACfc,MAAO,IAAM9C,GAA2B2K,GAA6ByL,EAAQnW,MAAOmW,EAAQxL,oBAc7FwJ,QAXe7O,EAAQqB,UAAU1C,KAAKkS,GAC/BzT,EAAEC,EAAW,CACnBE,MAAO,IAAMtC,GAA0B6V,GAAqBD,GAAUA,EAAQxL,gBAC9E/H,MAAOuT,EAAQvP,SACf+N,UAAU,MASZ,CAEDW,kBAAkBe,EAAuBC,EAAuBC,GAC/D,OAAIF,GAAUC,EACN,CACN5T,EAAEC,EAAW,CACZE,MAAO0T,EACP3T,MAAOyT,GAAU,GACjB1B,UAAU,IAEXjS,EAAEC,EAAW,CACZE,MAAO0T,EACP3T,MAAO0T,GAAU,GACjB3B,UAAU,KAIL,IAER,CAEDuB,2BAA2BM,GAC1B,OAAO9T,EAAEa,EAAY,CACpBC,MAAO,gBACPC,MAAO,KACNiF,EAAO+N,QAAQ,qBAAqB1R,MAAM2R,IACrCA,GACHrU,KAAKyR,OAAO0C,EACZ,GACA,EAEH7S,KAAiB,SAElB,CAED6E,OACCnG,KAAK4E,OAAOuB,OACZ,IAAImO,EAAIC,KAER,OADAvU,KAAKwR,gBAAkB8C,EAAEjH,QAClBiH,EAAEE,OACT,CAED/C,OAAO0C,GACNnU,KAAK4E,OAAOwB,QACZqO,GAAM,KAAK/R,MAAK,WACQ,QAAvByM,EAAAnP,KAAKwR,uBAAkB,IAAArC,GAAAA,EAAAyE,KAAA5T,KAAAmU,EAAO,GAE/B,EC5Nc,SAAAO,GAAcC,EAAsBC,GACnDD,EAAYnW,UAAYqW,GAAoBF,EAAYnW,UAAWoW,EAAkBpW,WACrFmW,EAAY/V,SAAWiW,GAAoBF,EAAY/V,SAAUgW,EAAkBhW,UACnF+V,EAAYxT,MAAQ2T,GAAUC,GAAqBJ,EAAYxT,MAAOyT,EAAkBzT,MAAO,OAC/FwT,EAAYhM,QAAUmM,GAAUC,GAAqBJ,EAAYhM,QAASiM,EAAkBjM,QAAS,SACrGgM,EAAYrV,QAAUwV,GAAUC,GAAqBJ,EAAYrV,QAASsV,EAAkBtV,QAAS,OACrGqV,EAAY/L,SAAWmM,GAAqBJ,EAAY/L,SAAUgM,EAAkBhM,SAAU,MAC9F+L,EAAYrL,KAAOwL,GAAUC,GAAqBJ,EAAYrL,KAAMsL,EAAkBtL,KAAM,OAC5FqL,EAAYxL,YAiTG,SAAoB6L,EAA0BC,GAC7D,MAAMC,EAAKC,GAAoBH,GAEzBI,EAAKD,GAAoBF,GAE/B,OAAIC,GAAME,EACLF,EAAGjM,KACC+L,EACGI,EAAGnM,KACNgM,EAEAD,EAEEA,IAEAC,GAGH,KAET,CArU2BI,CAAoBV,EAAYxL,YAAayL,EAAkBzL,aACzFwL,EAAY5V,cAuHG,SAAyB2S,EAAsCO,GAC9E,IAAIqD,EAAyBrD,EAAezL,QAAQ+O,IAC3C7D,EAAe/J,MAAM6N,GAAQA,EAAI9V,QAAQ+V,gBAAkBF,EAAI7V,QAAQ+V,kBAEhF,OAAO/D,EAAegE,OAAOJ,EAC9B,CA5H6BK,CAAyBhB,EAAY5V,cAAe6V,EAAkB7V,eAClG4V,EAAY1Q,aA6IG,SAAuB2R,EAAqCC,GAC3E,IAAIC,EAAmBD,EAAcrP,QAAQ+O,IACVK,EAAcjO,MAAM6N,GAAQA,EAAI/O,OAAOsP,QAAQ,MAAO,MAAQR,EAAI9O,OAAOsP,QAAQ,MAAO,QAG3H,OAAOH,EAAcF,OAAOI,EAC7B,CAnJ4BE,CAAuBrB,EAAY1Q,aAAc2Q,EAAkB3Q,cAC9F0Q,EAAYrQ,UAkLG,SAAoB2R,EAA+BC,GAClE,IAAIC,EAAqBD,EAAW1P,QAAQ+O,IACnCU,EAAWtO,MAAM6N,GAAQA,EAAIjR,WAAagR,EAAIhR,aAEvD,OAAO0R,EAAWP,OAAOS,EAC1B,CAvLyBC,CAAoBzB,EAAYrQ,UAAWsQ,EAAkBtQ,WACrFqQ,EAAYvQ,UAoMG,SAAoByN,EAA8BM,GACjE,IAAIkE,EAAqBlE,EAAW3L,QAAQ+O,IACnC1D,EAAWlK,MAAM6N,GAAQA,EAAI9V,UAAY6V,EAAI7V,YAEtD,OAAOmS,EAAW6D,OAAOW,EAC1B,CAzMyBC,CAAoB3B,EAAYvQ,UAAWwQ,EAAkBxQ,WACrFuQ,EAAYpL,kBAAoBuL,GAAUC,GAAqBJ,EAAYpL,kBAAmBqL,EAAkBrL,kBAAmB,IACpI,CAWgB,SAAAgN,GAAyBnY,EAAmBC,GAC3D,IAAImY,EAiDW,SAAiBpY,EAAmBC,GACnD,OAAID,EAASI,YAAcH,EAASG,WAAaJ,EAASQ,WAAaP,EAASO,WAAaR,EAASQ,UAAYR,EAASI,WACtF,QACzBJ,EAASI,WAAcJ,EAASQ,UAAaP,EAASG,WAAcH,EAASO,UAE5ER,EAASI,YAAcJ,EAASQ,WAAeP,EAASG,YAAcH,EAASO,SACzC,WAElDR,EAASI,UAAUiX,gBAAkBpX,EAASG,UAAUiX,eACxDrX,EAASQ,SAAS6W,gBAAkBpX,EAASO,SAAS6W,eACtDrX,EAASQ,SAE6B,UAC1BR,EAASI,WAAcH,EAASG,WAAcJ,EAASQ,SAAS6W,gBAAkBpX,EAASO,SAAS6W,gBAAiBrX,EAASQ,SAGrG,SAFC,UAVa,WAcrD,CAnEkB6X,CAAiBrY,EAAUC,GAExCqY,GAwFJC,EAxFuCvY,EAASW,cAyFhD6X,EAzF+DvY,EAASU,cA2FjE8X,GACNF,EAAsB/U,KAAKvB,GAAMA,EAAEX,UACnCkX,EAAsBhV,KAAKvB,GAAMA,EAAEX,YANrB,IACfiX,EACAC,EAvFA,IAAIE,GA6GJC,EA7GuC3Y,EAAS6F,aA8GhD+S,EA9G8D3Y,EAAS4F,aAgHhE4S,GACNE,EAAqBnV,KAAKvB,GAAMA,EAAEoG,SAClCuQ,EAAqBpV,KAAKvB,GAAMA,EAAEoG,WANpB,IACfsQ,EACAC,EA5GA,IAAIC,EA0LW,SAAkB7Y,EAAmBC,GACpD,MAAM6W,EAAKC,GAAoB/W,EAAS+K,aAElCiM,EAAKD,GAAoB9W,EAAS8K,aAExC,OAAI+L,GAAME,EACLF,EAAGnM,MAAQqM,EAAGrM,KAAOmM,EAAGlM,QAAUoM,EAAGpM,MACpCkM,EAAGjM,OAASmM,EAAGnM,KACkB,QAC1BiM,EAAGjM,MAAQmM,EAAGnM,MAAQiM,EAAGjM,OAASmM,EAAGnM,KAEV,SAEC,UAGF,SAE3B7K,EAAS+K,cAAgB9K,EAAS8K,cAAkB/K,EAAS+K,aAAe9K,EAAS8K,YAC9C,WAEC,WAErD,CAjNsB+N,CAAkB9Y,EAAUC,GAE7C8Y,EAkIW,SAA+B/Y,EAAmBC,GACjE,OACC+Y,GAAmBhZ,EAASuK,QAAStK,EAASsK,UAC9CyO,GAAmBhZ,EAASkB,QAASjB,EAASiB,UAC9C8X,GAAmBhZ,EAASwK,SAAUvK,EAASuK,WAC/CwO,GAAmBhZ,EAASkL,KAAMjL,EAASiL,OAC3C8N,GAAmBhZ,EAAS+C,MAAO9C,EAAS8C,QAC5CiW,GAAmBhZ,EAASmL,kBAAmBlL,EAASkL,oBAM1D,SAA4B8N,EAAsCC,GACjE,IAAI/X,EAASsX,GACZQ,EAAkBzV,KAAKvB,GAAMA,EAAEkE,WAC/B+S,EAAkB1V,KAAKvB,GAAMA,EAAEkE,YAGhC,MAAa,cAANhF,aAA2DA,CACnE,CAZEgY,CAAmBnZ,EAASkG,UAAWjG,EAASiG,YAwBlD,SAA4BkT,EAAqCC,GAChE,IAAIlY,EAASsX,GACZW,EAAkB5V,KAAKvB,GAAMA,EAAEX,UAC/B+X,EAAkB7V,KAAKvB,GAAMA,EAAEX,WAGhC,MAAa,cAANH,aAA2DA,CACnE,CA9BEmY,CAAmBtZ,EAASgG,UAAW/F,EAAS+F,UAElD,CA7IkCuT,CAA+BvZ,EAAUC,GAE1E,MACkD,WAAjD4Y,GACE7Y,EAASmL,mBAAsBlL,EAASkL,mBAAqBnL,EAASmL,oBAAsBlL,EAASkL,kBA2BlE,SAxBzB,UAAViN,iBAAgDA,GACtC,UAAVE,iBAAgDA,GACrC,UAAXI,iBAAiDA,IAClDK,EAOoB,UAAVX,eAAgDA,EACpB,UAE3B,cAAVA,gBAA+DA,GACjB,YAA9CE,GAC+C,YAA/CI,GAC4C,UAA5CJ,GACW,UAAXI,EAIoC,SAFC,UAdpB,cAAdG,aAAmEA,EAClC,QAEE,SAkB1C,CAgCgB,SAAApC,GAAoB+C,EAAeC,GAClD,OAAID,GAGIC,CAET,CA0IA,SAAS1C,GAAoB2C,GAC5B,IAAIA,EAQH,OAAO,KAPP,IACC,OAAOC,GAAkBD,EACzB,CAAC,MAAOvR,GAER,OADAS,QAAQC,IAAI,2BAA4BV,GACjC,IACP,CAIH,CAEA,SAASsQ,GAAemB,EAAmBC,GAC1C,GAAuB,IAAnBD,EAAQhZ,QAAmC,IAAnBiZ,EAAQjZ,OACnC,MAAmD,YAC7C,GAAuB,IAAnBgZ,EAAQhZ,QAAmC,IAAnBiZ,EAAQjZ,OAC1C,MAAkD,WAGnD,IAAIkZ,EAAiBD,EAAQzR,QAAQ2R,GAAOH,EAAQrQ,MAAMyQ,GAAOA,EAAG3Z,SAAW0Z,EAAG1Z,WAElF,OAAIuZ,EAAQhZ,SAAWiZ,EAAQjZ,QAAUgZ,EAAQhZ,SAAWkZ,EAAelZ,OACtC,QAGLiZ,EAAQzR,QAAQ2R,GAAOH,EAAQrQ,MAAMyQ,GAAOA,EAAG3Z,OAAOgX,gBAAkB0C,EAAG1Z,OAAOgX,kBAEpFzW,OAAS,EACA,UAGF,QACtC,CAKA,SAASoY,GAAmBiB,EAAgCC,GAU3D,OARuB,MAAnBD,IACHA,EAAkB,IAGI,MAAnBC,IACHA,EAAkB,IAGZD,IAAoBC,CAC5B,UAMgBvD,GAAqBsD,EAAgCC,EAAgCC,GACpG,OAAIF,IAAoBC,EAChBA,EACGD,GAAmBC,EACtBD,EAAkBE,EAAYD,GAC1BD,GAAmBC,EACvBA,EAEAD,CAET,CC1XM,SAAUG,GAAeC,GAC9B,IAAIC,EAeC,SAA0BD,GAC/B,IAAIC,EAAY,GAIhB,OAHAD,EAASE,SAAS1V,IACjByV,GAQI,SAA0BzV,GAC/B,IAAI2V,EAAuB,6BAGvBC,EAAW,MACfA,GAAY5V,EAAQ9B,MAAQ2X,GAAiB7V,EAAQ9B,OAAS,IAAM,GACpE0X,GAAY5V,EAAQzE,UAAYsa,GAAiB7V,EAAQzE,WAAa,IAAM,GAC5Eqa,GAAY5V,EAAQrE,SAAWka,GAAiB7V,EAAQrE,UAAY,GACpEga,GAAwBG,GAAiBF,EAASpa,QAAU,KAE5D,IAAIua,EAAU,KAQd,GAPAA,GAAW/V,EAAQrE,SAAWka,GAAiB7V,EAAQrE,UAAY,IAAM,IACzEoa,GAAW/V,EAAQzE,UAAYsa,GAAiB7V,EAAQzE,WAAa,KAAO,KAC5Ewa,GAAW/V,EAAQ9B,MAAQ2X,GAAiB7V,EAAQ9B,OAAS,IAAM,IACnEyX,GAAwBG,GAAiBC,GAAW,KACpDJ,GAAwB3V,EAAQ2F,SAAWmQ,GAAiB,YAAcD,GAAiB7V,EAAQ2F,WAAa,KAAO,GAGnH3F,EAAQkG,YAAa,CACxB,MAAMN,EAAO5F,EAAQkG,YAIrByP,GAAwB,SADH/P,EAAKoQ,WAAW,MAAQpQ,EAAKkN,QAAQ,KAAM,SAAWlN,GAC1B,IACjD,CAWD,OATA+P,GAAwBM,GAA0BC,GAA2BlW,EAAQmB,WAAY,OACjGwU,GAAwBM,GAA0BC,GAA2BlW,EAAQlE,eAAgB,SACrG6Z,GAAwBM,GA6CnB,SAA2CE,GAIhD,OAAOA,EAAQxX,KAAKyX,IACnB,IAAIC,EAAO,GAEX,OAAQD,EAAI1b,MACX,IAAA,IACC2b,EAAO,OACP,MAED,IAAA,IACCA,EAAO,OACP,MAED,IAAA,IACCA,EAAO,OACP,MAED,IAAA,IACCA,EAAO,MAMT,MAAO,CACNC,KAAMD,EACNE,QAASH,EAAI5S,OACb,GAEH,CA7EmDgT,CAAiCxW,EAAQgB,cAAe,OAC1G2U,GAAwBM,GAmFnB,SAAsC5U,GAI3C,OAAOA,EAAU1C,KAAK8X,IAEd,CACNH,KAAM,GACNC,QAASG,EAAaD,MAGzB,CA9FmDE,CAA4B3W,EAAQqB,WAAY,OAClGsU,GAAwB3V,EAAQqG,KAAOyP,GAAiB,QAAUD,GAAiB7V,EAAQqG,OAAS,KAAO,GAC3GsP,GAAwB3V,EAAQ3D,QAAUyZ,GAAiB,OAASD,GAAiB7V,EAAQ3D,UAAY,KAAO,GAChHsZ,GAAwB3V,EAAQ0F,QAAUoQ,GAAiB,QAAUD,GAAiB7V,EAAQ0F,UAAY,KAAO,GACjHiQ,GAAwB,gBAEjBA,CACR,CA5CeiB,CAAgB5W,EAAQ,IAE/ByV,CACR,CArBiBoB,CAAgBrB,GAC5BsB,EAAOC,GAAuBtB,GAC9BuB,EAAUC,KAId,OAHAD,EAAQ3P,KAAO,eACf2P,EAAQE,SAAW,gBACnBF,EAAQxY,KAAO2Y,OAAOL,EAAKM,YACpBhT,EAAQiT,eAAeC,aAAaC,GAAkBP,EAASF,GACvE,CA8DM,SAAUZ,GAA2B/U,GAI1C,OAAOA,EAAUxC,KAAK6Y,IACrB,IAAInB,EAAO,GAEX,OAAQmB,EAAG9c,MACV,IAAA,IACC2b,EAAO,OACP,MAED,IAAA,IACCA,EAAO,OAMT,MAAO,CACNC,KAAMD,EACNE,QAASiB,EAAG/a,QACZ,GAEH,CA8DgB,SAAAwZ,GACfwB,EAIAC,GAEA,OAAOD,EAAoBE,QAAO,CAACrb,EAAQsb,IACtCA,EAAKtB,KACDha,EAASwZ,GAAiB4B,EAAa,SAAWE,EAAKtB,KAAO,IAAMT,GAAiB+B,EAAKrB,UAAY,KAEtGja,EAASwZ,GAAiB4B,EAAa,IAAM7B,GAAiB+B,EAAKrB,UAAY,MAErF,GACJ,CAUA,SAAST,GAAiB+B,GACzB,IAAIC,EAA+B,GAEnC,KAAOD,EAAK9b,OAAS,IACpB+b,EAAmBhX,KAAK+W,EAAKE,UAAU,EAAG,KAC1CF,EAAOA,EAAKE,UAAU,GAAIF,EAAK9b,QAKhC,OAFA+b,EAAmBhX,KAAK+W,GACxBA,EAAOC,EAAmBE,KAAK,MAEhC,CAEA,SAASnC,GAAiBoC,GAIzB,OADAA,GADAA,GADAA,EAAUA,EAAQnF,QAAQ,MAAO,QACfA,QAAQ,KAAM,QACdA,QAAQ,KAAM,MAEjC,CAxMAlT,KCMAA,WAUasY,GAAbpY,cACkB/C,KAAAob,mBAAqBC,IAAUpY,KACjCA,EAAQ9B,MAAQ,GAAG8B,EAAQ9B,SAAW,IAEnC,GAAG8B,EAAQzE,aAAayE,EAAQrE,YACvBH,SAGVuB,KAAAsb,kBAAoBD,IAAUpY,GACvCjD,KAAKub,YAAYtY,GAAW0B,EAAwB1B,GAAW,MA4IvE,CAzIQsY,YAAYtY,GACnB,OAA8B,MAAvBA,EAAQkG,WACf,CAED/I,MAAKN,MAAEA,IACN,MAAMmD,QAAEA,EAAOuY,YAAEA,GAAgB1b,EACjC,OAAOO,EAAE,oCAAqC,CAC7CA,EAAE,GAAI,CACLA,EACC,qCACAA,EAAE,6BAA8B,CAC/BA,EAAE,4BAA6B,CAC9BL,KAAKob,mBAAmBnY,GACxBiK,KAEDjK,EAAQ2F,SAAWvI,EAAE,GAAI,IAAI4C,EAAQ2F,aAAe,KACpDvI,EACC,GACAob,GAAc,CAACxY,EAAQqG,KAAOjJ,EAAE,OAAQ4C,EAAQqG,MAAQ,KAAMrG,EAAQ3D,QAAUe,EAAE,OAAQ4C,EAAQ3D,SAAW,OAAO,IACnHe,EACC,aACA,CACC8B,MAAO,CACNuZ,WAAY,QAGd,UAIH1b,KAAKub,YAAYtY,GAAW5C,EAAE,GAAIL,KAAKsb,kBAAkBrY,IAAY,QAGvE5C,EAAE,iBAEHL,KAAK2b,6BAA6B1Y,EAASuY,GAC3Cxb,KAAK4b,4BAA4B3Y,GACjCjD,KAAK6b,cAAc5Y,IAEpB,CAEO2Y,4BAA4B3Y,GACnC,MAAMmB,EAAYnB,EAAQmB,UAAUxC,KAAKkS,GAAY9T,KAAK8b,cAAchI,KAClEhC,EAAU7O,EAAQqB,UAAU1C,KAAKkS,GAAY9T,KAAK+b,eAAejI,KACvE,OAAO1P,EAAUpF,OAAS,GAAK8S,EAAQ9S,OAAS,EAC7CqB,EAAE,gBAAiB,CACnBA,EAAE,gBAAiB+D,EAAUpF,OAAS,EAAI,CAACqB,EAAE,MAAOxC,EAAKC,IAAI,kBAAmBuC,EAAE,oBAAqB+D,IAAc,MACrH/D,EAAE,eAAgByR,EAAQ9S,OAAS,EAAI,CAACqB,EAAE,MAAOxC,EAAKC,IAAI,iBAAkBuC,EAAE,oBAAqByR,IAAY,QAE/G,IACH,CAEO6J,6BAA6B1Y,EAAkBuY,GACtD,MAAMzc,EAAgBkE,EAAQlE,cAAc6C,KAAKkS,GAAY9T,KAAKgc,kBAAkB/Y,EAAS6Q,EAAS0H,KAChG7J,EAAS1O,EAAQgB,aAAarC,KAAKkS,GAAY9T,KAAKic,kBAAkBnI,KAC5E,OAAO/U,EAAcC,OAAS,GAAK2S,EAAO3S,OAAS,EAChDqB,EAAE,gBAAiB,CACnBA,EAAE,aAActB,EAAcC,OAAS,EAAI,CAACqB,EAAE,MAAOxC,EAAKC,IAAI,gBAAiBuC,EAAE,oBAAqB,CAACtB,KAAmB,MAC1HsB,EAAE,cAAesR,EAAO3S,OAAS,EAAI,CAACqB,EAAE,MAAOxC,EAAKC,IAAI,gBAAiBuC,EAAE,oBAAqB,CAACsR,KAAY,QAE7G,IACH,CAEOkK,cAAc5Y,GACrB,OAAOA,EAAQ0F,SAAW1F,EAAQ0F,QAAQlK,OAAOO,OAAS,EACvD,CAACqB,EAAE,WAAYxC,EAAKC,IAAI,kBAAmBuC,EAAE,4CAA6C4C,EAAQ0F,UAClG,IACH,CAEOoT,eAAeG,GACtB,MAAMC,EAAa9b,EAAEa,EAAY,CAChCC,MAAO,cACPC,MAAO2D,GACPzD,KAAwB,eACxBG,KAAwB,IAEzB,OAAOpB,EAAEC,EAAW,CACnBE,MAAO,IAAMtC,GAA0B6V,GAAqBmI,GAAkBA,EAAgB5T,gBAC9F/H,MAAO2b,EAAgB3X,SACvB+N,UAAU,EACV3R,gBAAiB,IAAMN,EAAE,UAAUsZ,EAAauC,qBAAoCC,IAErF,CAEOH,kBAAkB/Y,EAAkBvD,EAAyB8b,GACpE,MAAMY,EAAgB/b,EAAEa,EAAY,CACnCC,MAAO,eACPC,MAAO,IAAMoa,EAAY,CAAElR,KAAM,GAAGrH,EAAQzE,aAAayE,EAAQrE,WAAWH,OAAQiB,QAASA,EAAQA,QAASuD,QAASA,IACvH3B,KAAwB,eACxBG,KAAwB,IAEzB,OAAOpB,EAAEC,EAAW,CACnBE,MAAO,IAAM9C,GAA2BgC,EAAQ/B,KAAa+B,EAAQ4I,gBACrE/H,MAAOb,EAAQA,QACf4S,UAAU,EACV3R,gBAAiB,IAAM,CAACyb,IAEzB,CAEOH,kBAAkBI,GACzB,MAAMC,EAAajc,EAAEa,EAAY,CAChCC,MAAO,iBACPC,MAAO,IAAM,KACbE,KAAgB,OAChBG,KAAwB,IAEzB,OAAOpB,EAAEC,EAAW,CACnBE,MAAO,IAAMxC,GAA+Bqe,EAAM1e,KAAgC0e,EAAM/T,gBACxF/H,MAAO8b,EAAM5V,OACb6L,UAAU,EACV3R,gBAAiB,IAAMN,EAAE,eAAegc,EAAM5V,0BAA2B6V,IAE1E,CAEOR,cAAcpc,GACrB,IAAI6c,EAGHA,GADsC,IAAnC7c,EAAQA,QAAQ8c,QAAQ,MACbC,mBAAmB/c,EAAQA,QAAQgd,MAAM,MAAMzB,KAAK,MAEpDwB,mBAAmB/c,EAAQA,SAG1C,MAAMyc,EAAa9b,EAAEa,EAAY,CAChCC,MAAO,kBACPC,MAAO,IAAM,KACbE,KAAe,MACfG,KAAwB,IAEzB,OAAOpB,EAAEC,EAAW,CACnBE,MAAO,IAAM9C,GAA2B2K,GAA6B3I,EAAQ/B,MAAO+B,EAAQ4I,gBAC5F/H,MAAOb,EAAQA,QACf4S,UAAU,EACV3U,KAAwB,OACxBgD,gBAAiB,IAAMN,EAAE,sDAAsDkc,qBAAgCJ,IAEhH,EAGF,SAASV,GAAckB,EAAmBC,GACzC,IAAIC,EAAgB,GAEpB,IAAK,IAAItW,KAAKoW,EACJ,MAALpW,IACCsW,EAAI7d,OAAS,GAChB6d,EAAI9Y,KAAK6Y,KAGVC,EAAI9Y,KAAKwC,IAIX,OAAOsW,CACR,OCjLaC,GACZ1c,MAAKN,MAAEA,IACN,MAAMmD,QAAEA,EAAOuY,YAAEA,GAAgB1b,EACjC,MAAO,CACNO,EACC,yBACA,CACC0c,MAAOC,IACP7a,MAAO,CACNkP,gBAAiBzB,EAAMqN,aAGzB5c,EAAE8a,GAAe,CAAElY,UAASuY,iBAE7Bnb,EAAE,SAEH,EACDsL,EAAA,IAAAmR,UCpBYI,GACZ9c,KAAKP,GACJ,MAAMC,MAAEA,GAAUD,EAElB,OAAOQ,EACN,wDACA,CACC8B,MAAO,CACNgb,eAAgB,iBAGlB,CACC9c,EAAEa,EAAY,CACbC,MAAO,gBACPG,KAAiB,QACjBF,MAAOtB,EAAMsd,eAEd/c,EAAEa,EAAY,CACbC,MAAO,cACPG,KAAgB,OAChBF,MAAOtB,EAAMud,cAIhB,EACD1R,EAAA,IAAAuR,UChBYI,GAAbva,cACS/C,KAASud,UAAoB,EA2GrC,CAzGAnd,MAAKN,MAAEA,IACN,MAAM2Y,SAAEA,EAAQ+E,SAAEA,EAAQC,OAAEA,EAAMC,QAAEA,EAAOC,SAAEA,GAAa7d,EACpD8d,EAA4B,GAqClC,OApCI5d,KAAK6d,QAAQpF,GAChBmF,EAAc7Z,KACb1D,EAAEa,EAAY,CACbC,MAAO,cACPC,MAAO,IAAMqc,EAAOhF,EAAS,IAC7BnX,KAAgB,UAGRtB,KAAK8d,SAASrF,IACxBmF,EAAc7Z,KACb1D,EAAEa,EAAY,CACbC,MAAO,eACPC,MAAO,IAAMsc,EAAQjF,EAAS,GAAIA,EAAS,IAC3CnX,KAAkB,YAKjBtB,KAAK+d,UAAUtF,IAClBmF,EAAc7Z,KACb1D,EAAEa,EAAY,CACbC,MAAO,gBACPC,MAAO,IAAMuc,EAASlF,GACtBnX,KAAkB,YAIjBtB,KAAKge,UAAUvF,IAClBmF,EAAc7Z,KACb1D,EAAEa,EAAY,CACbC,MAAO,gBACPC,MAAO,IAAMoc,EAAS/E,GACtBnX,KAAiB,WAIbsc,CACP,CAEDK,SAASpe,GACRqe,EAAWC,oBAAoBne,KAAKud,WACpCvd,KAAKud,UAAUve,OAAS,EACxB,MAAMyZ,SAAEA,EAAQgF,OAAEA,EAAMD,SAAEA,EAAQE,QAAEA,EAAOC,SAAEA,GAAa9d,EAAMC,MAC5DE,KAAK6d,QAAQpF,IAChBzY,KAAKud,UAAUxZ,KAAK,CACnBlC,IAAKuJ,GAAKgT,EACV9S,KAAM,KACLmS,EAAOhF,EAAS,GAAG,EAEpBlN,KAAM,gBAIJvL,KAAKge,UAAUvF,IAClBzY,KAAKud,UAAUxZ,KAAK,CACnBlC,IAAKuJ,GAAKiT,OACV/S,KAAM,KACLkS,EAAS/E,EAAS,EAEnBlN,KAAM,kBAIJvL,KAAK8d,SAASrF,IACjBzY,KAAKud,UAAUxZ,KAAK,CACnBlC,IAAKuJ,GAAKkT,EACV7S,MAAM,EACNH,KAAM,KACLoS,EAAQjF,EAAS,GAAIA,EAAS,GAAG,EAElClN,KAAM,iBAIJvL,KAAK+d,UAAUtF,IAClBzY,KAAKud,UAAUxZ,KAAK,CACnBlC,IAAKuJ,GAAKgT,EACV3S,MAAM,EACNH,KAAM,KACLqS,EAASlF,EAAS,EAEnBlN,KAAM,kBAGR2S,EAAWK,kBAAkBve,KAAKud,UAClC,CAEOQ,UAAUtF,GACjB,OAAOA,EAASzZ,OAAS,CACzB,CAEO8e,SAASrF,GAChB,OAA2B,IAApBA,EAASzZ,MAChB,CAEOgf,UAAUvF,GACjB,OAAOA,EAASzZ,OAAS,CACzB,CAEO6e,QAAQpF,GACf,OAA2B,IAApBA,EAASzZ,MAChB,EC5EI,SAAUwf,GAAmBC,GAIlC,IAAI9B,GADJ8B,GADAA,GADAA,EAAUA,EAAQ1I,QAAQ,QAAS,qBACjBA,QAAQ,OAAQ,2BAChBA,QAAQ,OAAQ,qBACd2G,MAAM,KAI1B,OAHAC,EAAQA,EAAM/a,KAAKiZ,GACXA,EAAKpc,SAENke,CACR,CAEM,SAAU+B,GAAqBD,GACpC,OAAOA,EAAQ7c,KAAK+c,GAKnBA,GADAA,GADAA,GADAA,GADAA,EAAIA,EAAE5I,QAAQ,wBAAyB,OACjCA,QAAQ,8BAA+B,MACvCA,QAAQ,wBAAyB,MACjCA,QAAQ,OAAQ,OAChBA,QAAQ,OAAQ,MAGxB,CAgBA,SAAS6I,GAAWC,EAAkBC,EAAiBhE,GACtD,IAAIiE,EAAU,CAACC,EAAYC,IAAcA,EAEzC,OAAQJ,EAASpJ,eAChB,IAAK,oBACJsJ,EAAUG,GACV,MAED,IAAK,UACJH,EAAUI,GAGZ,OAAOrE,EACL4B,MAAM,KACN9a,KAAKwd,GAASL,EAAQD,EAASM,KAC/BnE,KAAK,IACR,UCzFgBoE,KACfC,GAAgB,EAAM,CAAC,QAAQ5c,MAAM6c,IACpC,IAAIC,EAEJ,IACC,GAAID,EAAavgB,OAAS,EAAG,CAC5B,IAAIygB,EAAaF,EAAa3d,KAAK8d,IAClC,IACIC,EDCH,SAA4BC,GACjC,IAGIC,EAAI,gBAMR,OAFAD,GADAA,GADAA,EAAgBA,EAAc7J,QAAQ,eAAgB,gBACxBA,QAAQ,aAAc,cACtBA,QAAQ,eAAgB,gBAGvCyG,QAAQ,gBAAkB,GACxCoD,EAAcpD,QAPP,cAOqB,IAC3BoD,EAAcpD,QAZP,kBAYsB,GAAKoD,EAAcpD,QAXzC,kBAWwD,GAAKoD,EAAcpD,QAV3E,kBAU0F,IAQlGoD,GADAA,GADAA,GADAA,GAFAA,GADAA,EAAgBA,EAAc7J,QAAQ,MAAO,KACfA,QAAQ,OAAQ,KAEhBA,QAAQ,mBAAoB,KAC5BA,QAAQ,iBAAkB,KAC1BA,QAAQ,eAAgB,KACxBiF,UAAU4E,EAAcpD,QAAQqD,GAAKA,KAC9CnD,MAAMmD,GAEpB,IAET,CC3BkBC,CADOC,GAAuBL,EAAY3F,OAGvD,GAAc,MAAV4F,EACH,MAAM,IAAIK,MAAM,mBAEhB,OAAOL,CACP,IAEF,OAAOM,EACN,iBACAtd,QAAQ0K,UAAU3K,MAAK,KACtB,MAIMwd,EDuEI,SAAoBC,EAAqBC,GACxD,IAAI3H,EAAsB,GAE1B,IAAK,IAAI4H,EAAI,EAAGA,EAAIF,EAAUnhB,OAAQqhB,IAAK,CAC1C,IAAIpd,EAAUM,KACdN,EAAQiE,MAAQ,IAEhBjE,EAAQmE,OAASgZ,EAEjBnd,EAAQkE,qBAAuB,GAC/BlE,EAAQwE,YAAc2Y,EACtB,IAAIE,EAAaH,EAAUE,GAAG3D,MAAM,MAEpC,IAAK,IAAI6D,EAAI,EAAGA,EAAID,EAAWthB,OAAQuhB,IAAK,CAC3C,IAAIC,EAAgBF,EAAWC,GAAG/D,QAAQ,KACtCiE,EAAmBH,EAAWC,GAAGvF,UAAU,EAAGwF,GAAeE,cAC7DC,EAAUF,EAAiB/D,MAAM,KAAK,GACtCkE,EAAWN,EAAWC,GAAGvF,UAAUwF,EAAgB,GACnDK,EAAcP,EAAWC,GAAG7D,MAAM,KAAK/U,MAAMyX,GAASA,EAAK0B,SAAS,eACpEjC,EAAWgC,EAAcA,EAAYnE,MAAM,KAAK,GAAK,GACrDqE,EAAaT,EAAWC,GAAG7D,MAAM,KAAK/U,MAAMyX,GAASA,EAAK0B,SAAS,cAIvE,OAFAF,EAAWhC,GAAWC,EADRkC,EAAaA,EAAWrE,MAAM,KAAK,GAAK,QACbkE,GAEjCD,GACP,IAAK,IACJ,IAAIK,EAActC,GAAqBF,GAAmBoC,IAE1D,IAAK,IAAIP,EAAIW,EAAYhiB,OAAQgiB,EAAYhiB,OAAS,EAAGqhB,IACxDW,EAAYjd,KAAK,IAGlBd,EAAQrE,SAAWoiB,EAAY,GAC/B/d,EAAQzE,WAAawiB,EAAY,GAAK,IAAMA,EAAY,IAAIviB,OAE5DwE,EAAQ9B,MAAQ6f,EAAY,GAC5B,MAED,IAAK,KAEJ,GAA0B,KAAtB/d,EAAQzE,WAAyC,KAArByE,EAAQrE,UAAoC,MAAjBqE,EAAQ9B,MAAe,CACjF,IAAI8f,EAAWvC,GAAqBF,GAAmBoC,IACvD3d,EAAQzE,UAAYyiB,EAAShG,KAAK,KAAKlF,QAAQ,KAAM,GACrD,CAED,MAED,IAAK,OACJ,IAAImL,EAAWN,EAASpE,QAAQ,KAC5B2E,EAA+B,KAEnC,GAAIP,EAASQ,MAAM,YAClBD,EAAcrY,KACdqY,EAAYnY,MAAQ4X,EAAS5F,UAAU,EAAG,GAC1CmG,EAAYpY,IAAM6X,EAAS5F,UAAU,EAAG,QAClC,GAAI4F,EAASQ,MAAM,sBAAuB,CAChD,IAAIC,EAAOT,EAAS5F,UAAU,GAAiB,IAAdkG,EAAkBA,EAAWN,EAAS5hB,QAAQ0d,MAAM,KACrFyE,EAAcrY,KACdqY,EAAYlY,KAAOoY,EAAK,GAAG5iB,OAC3B0iB,EAAYnY,MAAQqY,EAAK,GAAG5iB,OAC5B0iB,EAAYpY,IAAMsY,EAAK,GAAG5iB,MAC1B,MAAUmiB,EAASQ,MAAM,YACzBD,EAAcrY,KACdqY,EAAYlY,KAAO2X,EAAS5F,UAAU,EAAG,GACzCmG,EAAYnY,MAAQ4X,EAAS5F,UAAU,EAAG,GAC1CmG,EAAYpY,IAAM6X,EAAS5F,UAAU,EAAG,IAGrCmG,GAAoC,SAArBA,EAAYlY,OAE9BkY,EAAYlY,KAAO,MAGpB,IACChG,EAAQkG,YAAcgY,GAAeG,GAAgBH,GAAe9X,GAAkB8X,GAAe,IACrG,CAAC,MAAO5a,GACR,KAAIA,aAAagb,IAGhB,MAAMhb,EAFNS,QAAQC,IAAI,2BAA4BV,EAIzC,CAED,MAED,IAAK,MACJ,IAAIib,EAAa9C,GAAqBF,GAAmBoC,IACzD3d,EAAQ3D,QAAUkiB,EAAWvG,KAAK,KAClC,MAED,IAAK,OACJ,IAAIwG,EAAO/C,GAAqBF,GAAmBoC,IACnD3d,EAAQ0F,QAAU8Y,EAAKxG,KAAK,KAC5B,MAED,IAAK,MACL,IAAK,YAEL,IAAK,YAEAwF,EAAiBjE,QAAQ,SAAW,EACvCkF,EAAYd,EAAU3d,OACZwd,EAAiBjE,QAAQ,SAAW,EAC9CkF,EAAYd,EAAU3d,OAEtBye,EAAYd,EAAU3d,OAGvB,MAED,IAAK,QACL,IAAK,cAEL,IAAK,cAEAwd,EAAiBjE,QAAQ,SAAW,EACvCmF,EAAgBf,EAAU3d,OAChBwd,EAAiBjE,QAAQ,SAAW,EAC9CmF,EAAgBf,EAAU3d,OAE1B0e,EAAgBf,EAAU3d,OAG3B,MAED,IAAK,MACL,IAAK,YAEL,IAAK,YAEJ2d,EAAWA,EAAS7K,QAAQ,mBAAoB,IAE5C0K,EAAiBjE,QAAQ,SAAW,EACvCoF,EAAgBhB,EAAU3d,OAChBwd,EAAiBjE,QAAQ,SAAW,EAC9CoF,EAAgBhB,EAAU3d,OAChBwd,EAAiBjE,QAAQ,QAAU,EAC7CoF,EAAgBhB,EAAU3d,OAChBwd,EAAiBjE,QAAQ,SAAW,EAC9CoF,EAAgBhB,EAAU3d,OAE1B2e,EAAgBhB,EAAU3d,OAG3B,MAED,IAAK,MACL,IAAK,YAEL,IAAK,YAEJ,IAAI4e,EAAU/X,KACd+X,EAAQlkB,KAAI,IACZkkB,EAAQtd,SAAWma,GAAqBF,GAAmBoC,IAAW3F,KAAK,IAC3E4G,EAAQvZ,eAAiB,GACzBrF,EAAQqB,UAAUP,KAAK8d,GACvB,MAED,IAAK,WACJ,IAAIC,EAAOpD,GAAqBF,GAAmBoC,IACnD3d,EAAQ2F,SAAWkZ,EAAK7G,KAAK,KAC7B,MAED,IAAK,QAKJ,MAED,IAAK,OACL,IAAK,QACJ,IAAI3R,EAAOoV,GAAqBF,GAAmBoC,IACnD3d,EAAQqG,OAAS,IAAMA,EAAK2R,KAAK,MAAMxc,OAKzC,CAEDga,EAAS4H,GAAKpd,CACd,CAED,SAASye,EAAYK,EAA2B9e,EAAkBtF,GACjE,IAAI+B,EAAUmK,KACdnK,EAAQ/B,KAAOA,EACf,IAAIqkB,EAAiBtD,GA7NjB,SAAgCsD,GAIrC,OAFAA,GADAA,EAAiBA,EAAejM,QAAQ,QAAS,qBACjBA,QAAQ,OAAQ,2BACrB2G,MAAM,KACpB9a,KAAKiZ,GACbA,EAAKpc,OAAOO,OAAS,EACjB6b,EAAKpc,OAAOiX,OAAO,MAGnB,IAGV,CAiN4CuM,CAAsBF,IAChEriB,EAAQA,QAAUsiB,EAAe/G,KAAK,IAAIxc,OAC1CiB,EAAQ4I,eAAiB,GACzBrF,EAAQmB,UAAUL,KAAKrE,EACvB,CAED,SAASkiB,EAAgBM,EAA+Bjf,EAAkBtF,GACzE,IAAIuG,EAAcyF,KAClBzF,EAAYvG,KAAOA,EACnBuG,EAAYuC,OAASyb,EACrBhe,EAAYoE,eAAiB,GAC7BrF,EAAQgB,aAAaF,KAAKG,EAC1B,CAED,SAASyd,EAAgBQ,EAA+Blf,EAAkBtF,GACzE,IAAIykB,EAAQxY,KACZwY,EAAMzkB,KAAOA,EACbykB,EAAM1iB,QAAUyiB,EAChBC,EAAM9Z,eAAiB,GACvBrF,EAAQlE,cAAcgF,KAAKqe,EAC3B,CAED,OAAO3J,CACR,CCxR0B4J,CAJDC,GAAK7C,GACE9b,GACzB0D,EAAQC,OAAOC,oBAAoBC,KAAKE,YAAYC,MAAMtH,GAAMA,EAAEuH,YAAcC,GAAUC,WAErBC,OAEtE,OADAyX,EAAmBU,EAAYlhB,OACxBqI,EAAQkb,aAAaC,gBAAgB9f,MAAM8f,GACjDnb,EAAQrE,aAAayf,sBAAsBD,EAAetC,GAAaxd,MAAK,KAE3E2D,EAAOC,SAAQ,IACdzI,EAAKC,IAAI,yBAA0B,CAClC,MAAO0hB,KAER,KAEF,IAGH,CACD,CAAC,MAAOjZ,GACRS,QAAQC,IAAIV,GAERA,aAAamc,GAChBrc,EAAOC,SAAQ,IACdzI,EAAKC,IAAI,0BAA2B,CACnC,WAAYyI,EAAEoc,gBAAgB3jB,OAAS,GACvC,UAAWwgB,EAAmB,OAIhCnZ,EAAOC,QAAQ,uBAEhB,IAEH,CF6DCqF,EAAA,IAAA2R,IC/GDza,KE4CAA,cAocgB+f,GAAUC,EAAsBC,EAAkB,IACjE,OAAOzb,EAAQ0b,UAAUC,wBAAwBtgB,MAAMugB,GAC/CC,GACND,EACA,CACCJ,GAAI,CAACA,IAENC,EACAK,GAAqB,GAAI9b,EAAQC,OAAOC,oBAAoB6b,QAC3D1gB,MAAM2gB,GAAWA,EAAOld,UAE5B,CAEM,SAAUmd,GAAepD,GAC9B,OAAO7Z,EAAO+N,QAAQ,sBAAsB1R,MAAM2R,IAC7CA,GACH6L,EAAYvH,SAAS1V,IACpBoE,EAAQrE,aAAaugB,MAAMtgB,GAASugB,MAAMC,GAAQ1c,GAAehC,KAAOye,MAAMC,GAAQ5c,GAAa9B,IAAM,GAE1G,GAEH,CAEgB,SAAA2e,GAAa/O,EAAsBgP,GAClD,OAAKhP,EAAYpL,mBAAsBoa,EAAepa,mBAAqBoL,EAAYpL,oBAAsBoa,EAAepa,kBAWpHlD,EAAOC,QAAQ,iCAVfD,EAAO+N,QAAQ,gCAAgC1R,MAAM2R,IAC3D,GAAIA,EAEH,OADAK,GAAcC,EAAagP,GACpB1D,EACN,iBACA5Y,EAAQrE,aAAa8D,OAAO6N,GAAajS,MAAK,IAAM2E,EAAQrE,aAAaugB,MAAMI,MAC9EH,MAAMC,GAAQ1c,GAAehC,IAC/B,GAKJ,iDAjeM,cAA2B6e,EAUhC7gB,YAAYlD,GACXgkB,QACA7jB,KAAK0O,iBAAmB7O,EAAMC,MAAM4O,iBAEpC1O,KAAK8jB,aAAe,IAAIC,GACvB,CACC3jB,KAAM,IACLC,EAAE2jB,EAAkB,CACnBC,OAAQpkB,EAAMC,MAAMokB,YACpBC,OAAQC,EAAOC,0BACZ,KACA,CACA1mB,KAAmC,qBACnC6C,MAAO,oBACPY,MAAO,IAAMpB,KAAKskB,oBAErBpJ,QAAS,CACR7a,EACCkkB,EACA,CACCja,KAAM,IAAMka,GAAwBnd,EAAQC,OAAOC,oBAAoBkd,gBAExEzkB,KAAK0kB,yCAGPC,UAAW,uBAEb,EAEDljB,EAAKmjB,oBACLnjB,EAAKojB,qBACL,IAAMhnB,EAAKC,IAAI,uBAEhBkC,KAAK8kB,WAAa,IAAIf,GACrB,CACC3jB,KAAM,IACLC,EAAE0kB,EAAwB,CACzB1T,gBAAiBzB,EAAM0B,cACvB0T,aAAc3kB,EAAEoO,GAAiB,CAChCC,iBAAkB1O,KAAK0O,iBACvBY,kBAAmB,KAClBtP,KAAKilB,WAAWC,MAAMllB,KAAKmlB,cAAc,IAG3CC,eAAgB,IAAMplB,KAAKqlB,oBAC3BC,aAAc,IACbtlB,KAAK0O,iBAAiBU,UAAUW,MAAMwV,cACnCllB,EAAEmlB,EAAyB,IACxBC,EAAsBzlB,KAAK0O,iBAAiBU,WAC/C9I,QAASwK,GAA2B9Q,KAAK0lB,yBAEzCrlB,EAAEslB,EAAc,IACb9lB,EAAMC,MAAM8lB,OACfC,WAAY,IAAM7lB,KAAKilB,WAAWa,sBAClCC,WAAY,QACZ5kB,MAAOnB,KAAK8kB,WAAWkB,WACvBC,QAAS5lB,EAAE,QAAS,CACnBL,KAAKkmB,qBACL7lB,EAAE8lB,EAA4B,CAC7BC,YAAa,KACZpmB,KAAK0O,iBAAiBU,UAAUiX,kBAAkB,MAIrDC,cAAe,IAAMtmB,KAAKumB,6BAGhC,EAED9kB,EAAK+kB,qBACL/kB,EAAKglB,sBACL,IAAM5oB,EAAKC,IAAI,oBAGhBkC,KAAKmlB,cAAgB,IAAIpB,GACxB,CACC3jB,KAAM,KACL,MAAMqY,EAAWzY,KAAK0lB,sBACtB,OAAOrlB,EAAE0kB,EAAwB,CAChC1T,gBAAiBzB,EAAM0B,cACvB8T,eAAgB,IAAM/kB,EAAEqmB,EAAsB1mB,KAAK2mB,qBAAqBlO,IACxE6M,aAAc,IACbjlB,EAAEslB,EAAc,IACZ9lB,EAAMC,MAAM8lB,OACfC,WAAY,IAAM7lB,KAAKilB,WAAWa,sBAClCG,QAAS,KACTW,mBAAoB,IAAM5mB,KAAK2mB,qBAAqBlO,GACpD6N,cAAe,IAAMtmB,KAAKumB,wBAC1BplB,MAAOtD,EAAKC,IAAI,kBAChBioB,WAAY,UAEdf,aAEC3kB,EACC,4CACAL,KAAK6mB,kBACFxmB,EAAEwQ,GAAoB,CACtBE,iBAAkB0H,EAClBtH,WAAY,IAAMnR,KAAK0O,iBAAiBU,UAAU+B,eAElD9Q,EAAEyc,GAAmB,CACrB7Z,QAASwV,EAAS,GAClB+C,YAAaoH,OAGjB,GAIJ,EAAAnhB,EAAKqlB,oBACLrlB,EAAKslB,yBACL3V,GACA,IAAMvT,EAAKC,IAAI,oBAEhBkC,KAAKilB,WAAa,IAAI+B,EAAW,CAAChnB,KAAK8jB,aAAc9jB,KAAK8kB,WAAY9kB,KAAKmlB,eAAgB,eAE3F,MAAM5H,EAAYvd,KAAKinB,eACvBjnB,KAAKJ,SAAYC,IAChBqe,EAAWK,kBAAkBhB,EAAU,EAGxCvd,KAAKkF,SAAW,KACfgZ,EAAWC,oBAAoBZ,EAAU,CAE1C,CAEOoJ,qBAAqBlO,GAC5B,OAAOpY,EAAEid,GAAsB,CAC9B7E,WACAgF,OAASyJ,GAAMlnB,KAAKmnB,YAAYD,GAChCvJ,SAAUnF,GACVgF,SAAU8F,GACV5F,QAASgG,IAEV,CAEOmD,kBACP,OAA6C,IAAtC7mB,KAAK0lB,sBAAsB1mB,QAAgBgB,KAAK0O,iBAAiBU,UAAUW,MAAMwV,aACxF,CAEDnlB,MAAKN,MAAEA,IACN,OAAOO,EACN,qBACAA,EAAEL,KAAKilB,WAAY,CAClBW,OAAQxB,EAAOgD,uBACZ,KACA/mB,EAAEgnB,EAAQ,CACVC,UAAW,IACVjnB,EAAEknB,EAAe,CAChBC,YAAa3pB,EAAKC,IAAI,mCAErBgC,EAAM8lB,SAEZ6B,UACCrD,EAAOgD,wBAA0BpnB,KAAKilB,WAAWyC,gBAAkB1nB,KAAKmlB,gBAAkBnlB,KAAK6mB,kBAC5FxmB,EAAE6c,GAAwB,CAC1BG,WAAY,IAAMrd,KAAK2nB,sBACvBvK,aAAc,IAAMpd,KAAK4nB,oBAEzBxD,EAAOgD,wBACPpnB,KAAKilB,WAAWyC,gBAAkB1nB,KAAK8kB,YAEvC9kB,KAAK0O,iBAAiBU,UAAUW,MAAMwV,cACtCllB,EAAEwnB,EAAuB7nB,KAAK2mB,qBAAqB3mB,KAAK0lB,wBACxDrlB,EAAEynB,KAGR,CAEOpC,sBACP,OAAO1lB,KAAK0O,iBAAiBU,UAAU2Y,oBACvC,CAEDzD,mBACC,IAAIxhB,GAAcuE,EAAQrE,aAAc,KAAMhD,KAAK0O,iBAAiB8T,eAAgBxa,IAAD,IAGhF7B,MACH,CAEOwhB,sBACP,MAAMK,EAAgBhoB,KAAK0lB,sBAAsB,GAC5CsC,GACLhoB,KAAKmnB,YAAYa,EACjB,CAEOb,YAAYlkB,GACnB,IAAIH,GAAcuE,EAAQrE,aAAcC,GAASkD,MACjD,CAEOogB,wBACP,OAAOlmB,EAAEa,EAAY,CACpBC,MAAO,oBACPC,MAAO,IAAMpB,KAAKskB,mBAClBhjB,KAAe,OAEhB,CAEO2lB,eAkBP,MAjB4B,IACxBgB,GAAwD,GAAA,IAAMjoB,KAAK0O,iBAAiBU,YACvF,CACCvN,IAAKuJ,GAAKiT,OACV/S,KAAM,KACLtL,KAAK4nB,mBACE,GAERrc,KAAM,yBAEP,CACC1J,IAAKuJ,GAAK8c,EACV5c,KAAM,IAAMtL,KAAKskB,mBACjB/Y,KAAM,qBAKR,CAEDmZ,uCACC,MAAMP,EAAyB,CAC9B3jB,MAAO,qBACPc,KAAM,IAAwB,WAC9B6mB,KAAM,IAAM9nB,EAAE+nB,MAAMtqB,MACpBuqB,wBAAwB,GAEzB,OAAOhoB,EAAE,mDAAoD,CAAE8B,MAAO,CAAEmmB,WAAYC,GAAoBpE,GAAUqE,EAAe,KAAQ,CACxInoB,EAAE,yDAA0D,CAACA,EAAEooB,GAAWtE,GAASnkB,KAAK0oB,4BAEzF,CAEOA,yBACP,OAAOroB,EACNa,EACAK,EAAe,CACdC,gBAAiB,CAChBL,MAAO,aACPG,KAAgB,OAChBG,KAAwB,EACxBknB,OAAuB,OAExBjnB,WAAY,KACsCknB,KAC9C,GACA,CACA,CACCpoB,MAAO,qBACPY,MAAO,IAAMie,KACb/d,KAAyB,iBAE1B,CACCd,MAAO,qBACPY,MAAO,KAAMynB,ODnQQtG,ECmQMlb,EAAQkb,aDlQpCtC,EACN,iBACAsC,EAAaC,gBAAgB9f,MAAM8f,GAC7BA,EACEnb,EAAQrE,aAAa8lB,QAAQC,GAAgBvG,GAAe9f,MAAMsmB,GAC7C,IAAvBA,EAAYhqB,OACR,EAEAwZ,GAAewQ,GAAatmB,MAAK,IAAMsmB,EAAYhqB,WALjC,KAS3B0D,MAAMumB,IACe,IAAlBA,GACH5iB,EAAOC,QAAQ,iBACf,IAhBG,IAAwBic,CCmQ2B,EAChDjhB,KAAkB,YAIFoU,OAAO,CAC1B,CACClV,MAAO,eACPc,KAAkB,SAClBF,MAAO,IAAMpB,KAAKkpB,kBAIrBC,MAAO,MAGT,CAEDD,eACC,OAAOjJ,EACN,iBACA5Y,EAAQkb,aAAaC,gBAAgB9f,MAAM8f,GACnCA,EAAgBnb,EAAQrE,aAAa8lB,QAAQC,GAAgBvG,GAAiB,MAErF9f,MAAMsmB,IACP,GAA2B,IAAvBA,EAAYhqB,OACfqH,EAAOC,QAAQ,sBACT,CACN,IAAI8iB,ERvVF,SAA+BC,GAIpC,IAAIC,EAAgC,GAChCC,EAA+B,GAC/B9Q,EAAW4Q,EAAcG,QACzBC,EAAoB,EAExB,KAAOA,EAAoBhR,EAASzZ,OAAS,GAAG,CAC/C,IAAI0qB,EAAqC,GACrCC,EAAelR,EAASgR,GAC5BC,EAAwB3lB,KAAK4lB,GAC7B,IAAIC,EAAqBH,EAAoB,EAG7C,KAAOG,EAAqBnR,EAASzZ,QAAQ,CAC5C,IAAI6qB,EAAgBpR,EAASmR,GAE7B,GAAID,EAAalmB,IAAI,KAAOomB,EAAcpmB,IAAI,GAAI,CAEjD,IAAIqmB,WAGJ,IAAK,IAAIzJ,EAAI,EAAGA,EAAIqJ,EAAwB1qB,OAAQqhB,IAAK,CACxD,IAAI9gB,EAASgX,GAAyBmT,EAAwBrJ,GAAIwJ,GAElE,aAAItqB,EAA0C,CAC7CuqB,UACA,KACA,CAAM,eAAIvqB,EAIV,MAHAuqB,EAAa,SAKd,WAEGA,GACHP,EAAkBxlB,KAAK8lB,GACvBpR,EAASsR,OAAOH,EAAoB,gBAC1BE,GACVJ,EAAwB3lB,KAAK8lB,GAC7BpR,EAASsR,OAAOH,EAAoB,IAEpCA,GAED,CACD,CAEGF,EAAwB1qB,OAAS,GACpCsqB,EAAiBvlB,KAAK2lB,GAGvBD,GACA,CAED,MAAO,CACNO,UAAWV,EACXW,UAAWV,EAEb,CQ0RiCW,CAAqBlB,GAC9CmB,EAAgBxnB,QAAQ0K,UAExB+b,EAAuBa,UAAUjrB,OAAS,IAC7CmrB,EAAgB9jB,EAAO+N,SAAQ,IAC9BvW,EAAKC,IAAI,6BAA8B,CACtC,MAAOsrB,EAAuBa,UAAUjrB,WAExC0D,MAAM2R,IACHA,GAEH+U,EAAuBa,UAAUtR,SAASyR,IACzC/iB,EAAQrE,aAAaugB,MAAM6G,EAAG,GAE/B,KAIHD,EAAcznB,MAAK,KAC8B,IAA5C0mB,EAAuBY,UAAUhrB,OACpCqH,EAAOC,SAAQ,IAAMzI,EAAKC,IAAI,2BAE9BkC,KAAKqqB,kBAAkBjB,EAAuBY,WAAWtnB,MAAM4nB,IACzDA,GACJjkB,EAAOC,SAAQ,IAAMzI,EAAKC,IAAI,8BAC9B,GAEF,GAEF,IAEF,CAKDusB,kBAAkBE,GACjB,IAAID,GAAW,EAEf,GAAIC,EAASvrB,OAAS,EAAG,CACxB,IAAIZ,EAAWmsB,EAAS,GAAG,GACvBlsB,EAAWksB,EAAS,GAAG,GAE3B,OADkB,IAAIhZ,GAAiBnT,EAAUC,GAE/C8H,OACAzD,MAAMyR,aAEFA,GACHnU,KAAKwqB,4BAA4BD,EAAUlsB,GAE3CqW,GAActW,EAAUC,GACjB4hB,EACN,iBACA5Y,EAAQrE,aAAa8D,OAAO1I,GAAUsE,MAAK,IAAM2E,EAAQrE,aAAaugB,MAAMllB,MAC3EmlB,MAAMC,GAAQ1c,GAAehC,sBACrBoP,GACVnU,KAAKwqB,4BAA4BD,EAAUnsB,GAEpCiJ,EAAQrE,aAAaugB,MAAMnlB,qBACxB+V,GACVnU,KAAKwqB,4BAA4BD,EAAUlsB,GAEpCgJ,EAAQrE,aAAaugB,MAAMllB,kBACxB8V,EACVnU,KAAKwqB,4BAA4BD,EAAUlsB,cACjC8V,IACVsW,GAAMF,GACND,GAAW,MAGZ5nB,MAAK,KACA4nB,GAAYC,EAASvrB,OAAS,EAC3BgB,KAAKqqB,kBAAkBE,GAEvBD,GAGV,CACA,OAAO3nB,QAAQ0K,QAAQid,EAExB,CAKDE,4BAA4BD,EAAuBtnB,GAC9CsnB,EAAS,GAAG,KAAOtnB,EACtBsnB,EAAS,GAAGR,OAAO,EAAG,GACZQ,EAAS,GAAG,KAAOtnB,GAC7BsnB,EAAS,GAAGR,OAAO,EAAG,GAInBQ,EAAS,GAAGvrB,QAAU,GACzBurB,EAASR,OAAO,EAAG,EAEpB,CAEDW,SAASC,GACR3qB,KAAK0O,iBAAiBkc,KAAKD,EAAKznB,OAAQynB,EAAK3iB,UAC7C,CAED4f,kBACC,OAAOtE,GAAetjB,KAAK0lB,sBAC3B,CAEDmF,gBACC,OAAO7qB,KAAKilB,UACZ,CAED6F,mBAEC,OAAI9qB,KAAKilB,WAAWyC,gBAAkB1nB,KAAKmlB,eAC1CnlB,KAAKilB,WAAWC,MAAMllB,KAAK8kB,aACpB,KACG9kB,KAAK6mB,oBACf7mB,KAAK0O,iBAAiBU,UAAU+B,cAEzB,EAIR,CAEOkU,oBACP,OAAOhlB,EAAE0qB,EAAoB1qB,EAAE2qB,EAAmBvF,EAAsBzlB,KAAK0O,iBAAiBU,YAAapP,KAAKkmB,qBAChH,CAEOA,qBACP,OAAO7lB,EAAEa,EAAY,CACpBC,MAAO,eACPG,KAAuB,cACvBF,MAAO,CAACmF,EAAerG,KACtB+qB,GAAe,CACdC,YAAa,IAAM,CAClB,CACC1qB,MAAO,wBACPY,MAAO,KACNpB,KAAK0O,iBAAiByc,oBAAmB,EAAK,GAGhD,CACC3qB,MAAO,uBACPY,MAAO,KACNpB,KAAK0O,iBAAiByc,oBAAmB,EAAM,KAXnDF,CAeG1kB,EAAGrG,EAAI,GAGZ,gHCzeD6C,YACkBwf,EACAvf,EACAooB,EACAC,EACAC,GAJAtrB,KAAYuiB,aAAZA,EACAviB,KAAYgD,aAAZA,EACAhD,KAAeorB,gBAAfA,EACAprB,KAAMqrB,OAANA,EACArrB,KAAQsrB,SAARA,EATVtrB,KAAeurB,gBAAc,KACrCvrB,KAAe1B,iBAAY,EACnB0B,KAAoBwrB,qBAA2B,KAU9CxrB,KAASoP,UAAuB,IAAIqc,EAAmB,CAC/DC,MAAOC,GACPC,MAAOzrB,UAEC,CAAE0rB,YADW7rB,KAAKgD,aAAa8lB,QAAQC,GAAgB/oB,KAAKwiB,eACnDsJ,UAAU,IAE3BC,WAAY5rB,MAAO6rB,IAClB,MAAM9oB,QAAelD,KAAKuiB,aAAaC,gBACvC,OAAc,MAAVtf,EAAuB,KACpBlD,KAAKgD,aAAaipB,KAAKlD,GAAgB,CAAC7lB,EAAQ8oB,GAAW,EAEnEE,YAAa,CAAC9T,EAAID,IAAOha,GAAgBia,EAAID,EAAInY,KAAK1B,mBAwCtC0B,KAAAmsB,eAAuChsB,MAAOisB,IAC9D,IAAK,MAAMtlB,KAAUslB,EAChBC,EAAmBtD,GAAgBjiB,IAAWA,EAAOwlB,iBAAmBtsB,KAAKwiB,qBAC1ExiB,KAAKoP,UAAUmd,oBAAoBzlB,EAAO0lB,WAAY1lB,EAAO2lB,UAEpE,CA1DE,CAgBJtsB,WAAWqiB,EAAoBxa,GAET,MAAjBwa,GAAsC,MAAbxa,GAAmBhI,KAAK0sB,YACjD1sB,KAAKwiB,gBAETxiB,KAAKwiB,cAAgB7e,SAAoB3D,KAAKuiB,aAAaC,gBAAiB,oCAC5ExiB,KAAKurB,gBAAkBvjB,QAAAA,EAAa,KAEpChI,KAAKoP,UAAUud,cAAcjqB,MAAK,KAEZ,iBAAdsF,GACNhI,KAAK4sB,cAAc5kB,GAAW6kB,SAAQ,KACrC7sB,KAAKurB,gBAAkB,IAAI,GAC1B,IAGJvrB,KAAKorB,gBAAgB0B,kBAAkB9sB,KAAKmsB,gBAC5CnsB,KAAKwrB,qBAAuBxrB,KAAKoP,UAAU2d,YAAYnrB,KAAI,KAC1D5B,KAAKsrB,WACLtrB,KAAK0sB,WAAW,IAEjB,CAEOA,YACP,MAAM1kB,EACmB,MAAxBhI,KAAKurB,gBACFvrB,KAAKurB,gBACJvrB,KAAKoP,UAAUW,MAAMwV,eAAgE,IAA/CvlB,KAAKoP,UAAU2Y,qBAAqB/oB,OAE3E,KADAguB,GAAahtB,KAAKoP,UAAU2Y,qBAAqB,IAEjD/f,EACHhI,KAAKqrB,OAAO4B,QAAQ,8BAA+B,CAAE/pB,OAAQlD,KAAKwiB,cAAexa,UAAWA,IAE5FhI,KAAKqrB,OAAO4B,QAAQ,mBAAoB,CAAE/pB,OAAQlD,KAAKwiB,eAExD,CAUDriB,oBAAoB6H,GACnB,MAAM9E,EAASlD,KAAKwiB,oBACdxiB,KAAKoP,UAAUwd,cAAc5kB,GAAW,IAAMhI,KAAKwiB,gBAAkBtf,GAC3E,CAEDioB,mBAAmB+B,GAClBltB,KAAK1B,gBAAkB4uB,EACvBltB,KAAKoP,UAAU+d,MACf,CAEDnd,YACC,OAAOhQ,KAAKoP,UAAUW,KACtB,CAEDqd,gBACCptB,KAAKorB,gBAAgBiC,qBAAqBrtB,KAAKmsB,gBACtB,QAAzBhd,EAAAnP,KAAKwrB,4BAAoB,IAAArc,GAAAA,EAAEme,KAAI,GAC/BttB,KAAKwrB,qBAAuB,IAC5B"}