1 line
14 KiB
Plaintext
1 line
14 KiB
Plaintext
{"version":3,"file":"contacts-oca.mjs","sources":["../src/views/ReadOnlyContactDetails.vue","../src/oca.ts"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n\n<template>\n\t<div class=\"display-contact-details\">\n\t\t<div v-if=\"loading\" class=\"recipient-details-loading\">\n\t\t\t<NcLoadingIcon />\n\t\t</div>\n\t\t<!-- nothing selected or contact not found -->\n\t\t<NcEmptyContent\n\t\t\tv-else-if=\"!contact\"\n\t\t\tclass=\"empty-content\"\n\t\t\t:name=\"t('mail', 'No data for this contact')\"\n\t\t\t:description=\"t('mail', 'No data for this contact on their profile')\">\n\t\t\t<template #icon>\n\t\t\t\t<IconContact :size=\"20\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\t\t<div\n\t\t\tv-else\n\t\t\tclass=\"recipient-details-content\">\n\t\t\t<div class=\"contact-title\">\n\t\t\t\t<h6>{{ contact.fullName }}</h6>\n\t\t\t\t<!-- Subtitle -->\n\t\t\t\t<span v-html=\"formattedSubtitle\" />\n\t\t\t</div>\n\t\t\t<div class=\"contact-details-wrapper\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(properties, name) in groupedProperties\"\n\t\t\t\t\t:key=\"name\">\n\t\t\t\t\t<ContactDetailsProperty\n\t\t\t\t\t\tv-for=\"(property, index) in properties\"\n\t\t\t\t\t\t:key=\"`${index}-${contact.key}-${property.name}`\"\n\t\t\t\t\t\t:is-first-property=\"index === 0\"\n\t\t\t\t\t\t:is-last-property=\"index === properties.length - 1\"\n\t\t\t\t\t\t:property=\"property\"\n\t\t\t\t\t\t:contact=\"contact\"\n\t\t\t\t\t\t:local-contact=\"localContact\"\n\t\t\t\t\t\t:contacts=\"[contact]\"\n\t\t\t\t\t\t:is-read-only=\"true\"\n\t\t\t\t\t\t:bus=\"bus\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { namespaces as NS } from '@nextcloud/cdav-library'\nimport { loadState } from '@nextcloud/initial-state'\nimport { NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport escape from 'lodash/fp/escape.js'\nimport mitt from 'mitt'\nimport IconContact from 'vue-material-design-icons/AccountMultipleOutline.vue'\nimport ContactDetailsProperty from '../components/ContactDetails/ContactDetailsProperty.vue'\nimport IsMobileMixin from '../mixins/IsMobileMixin.ts'\nimport Contact from '../models/contact.js'\nimport rfcProps from '../models/rfcProps.js'\nimport client from '../services/cdav.js'\nimport validate from '../services/validate.js'\nimport usePrincipalsStore from '../store/principals.js'\n\nconst { profileEnabled } = loadState('user_status', 'profileEnabled', false)\n\nexport default {\n\tname: 'ReadOnlyContactDetails',\n\n\tcomponents: {\n\t\tContactDetailsProperty,\n\t\tNcEmptyContent,\n\t\tIconContact,\n\t\tNcLoadingIcon,\n\t},\n\n\tmixins: [IsMobileMixin],\n\n\tprops: {\n\t\tcontactEmailAddress: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\tdesc: {\n\t\t\ttype: String,\n\t\t\trequired: false,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tcontactDetailsSelector: '.contact-details',\n\t\t\texcludeFromBirthdayKey: 'x-nc-exclude-from-birthday-calendar',\n\n\t\t\tbus: mitt(),\n\t\t\tshowMenuPopover: false,\n\t\t\tprofileEnabled,\n\t\t\tcontact: undefined,\n\t\t\tlocalContact: undefined,\n\t\t\tloading: true,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Read-only representation of the contact title and organization.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tformattedSubtitle() {\n\t\t\tconst title = this.contact.title\n\t\t\tconst organization = this.contact.org\n\n\t\t\tif (title && organization) {\n\t\t\t\treturn t('contacts', '{title} at {organization}', {\n\t\t\t\t\ttitle,\n\t\t\t\t\torganization,\n\t\t\t\t})\n\t\t\t} else if (title) {\n\t\t\t\treturn escape(title)\n\t\t\t} else if (organization) {\n\t\t\t\treturn escape(organization)\n\t\t\t}\n\n\t\t\treturn ''\n\t\t},\n\n\t\taddressbooks() {\n\t\t\treturn this.$store.getters.getAddressbooks\n\t\t},\n\n\t\t/**\n\t\t * Contact properties copied and sorted by rfcProps.fieldOrder\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tsortedProperties() {\n\t\t\tif (!this.localContact || !this.localContact.properties) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.localContact.properties\n\t\t\t\t.toSorted((a, b) => {\n\t\t\t\t\tconst nameA = a.name.split('.').pop()\n\t\t\t\t\tconst nameB = b.name.split('.').pop()\n\t\t\t\t\treturn rfcProps.fieldOrder.indexOf(nameA) - rfcProps.fieldOrder.indexOf(nameB)\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Contact properties filtered and grouped by rfcProps.fieldOrder\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tgroupedProperties() {\n\t\t\tif (!this.sortedProperties) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn this.sortedProperties.reduce((list, property) => {\n\t\t\t\tif (!this.canDisplay(property)) {\n\t\t\t\t\treturn list\n\t\t\t\t}\n\t\t\t\tif (!list[property.name]) {\n\t\t\t\t\tlist[property.name] = []\n\t\t\t\t}\n\t\t\t\tlist[property.name].push(property)\n\t\t\t\treturn list\n\t\t\t}, {})\n\t\t},\n\n\t\t/**\n\t\t * The address book is read-only (e.g. shared with me).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\taddressbookIsReadOnly() {\n\t\t\treturn this.contact.addressbook?.readOnly\n\t\t},\n\n\t\t/**\n\t\t * Usable addressbook object linked to the local contact\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\taddressbook() {\n\t\t\treturn this.contact.addressbook.id\n\t\t},\n\n\t\t/**\n\t\t * Fake model to use the propertyGroups component\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tgroupsModel() {\n\t\t\treturn {\n\t\t\t\treadableName: t('mail', 'Contact groups'),\n\t\t\t\ticon: 'icon-contacts-dark',\n\t\t\t}\n\t\t},\n\t},\n\n\twatch: {\n\t\tcontact: {\n\t\t\thandler(contact) {\n\t\t\t\tthis.updateLocalContact(contact)\n\t\t\t},\n\n\t\t\timmediate: true,\n\t\t},\n\t},\n\n\tasync beforeMount() {\n\t\t// Init client and stores\n\t\tawait client.connect({ enableCardDAV: true })\n\t\tconst principalsStore = usePrincipalsStore()\n\t\tprincipalsStore.setCurrentUserPrincipal(client)\n\t\tawait this.$store.dispatch('getAddressbooks')\n\n\t\t// Fetch contact\n\t\tawait this.fetchContact()\n\t},\n\n\tmethods: {\n\t\tasync fetchContact() {\n\t\t\ttry {\n\t\t\t\tconst email = this.contactEmailAddress\n\t\t\t\tconst result = await Promise.all(this.addressbooks.map(async (addressBook) => [\n\t\t\t\t\taddressBook.dav,\n\t\t\t\t\tawait addressBook.dav.addressbookQuery([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: [NS.IETF_CARDDAV, 'prop-filter'],\n\t\t\t\t\t\t\tattributes: [['name', 'EMAIL']],\n\t\t\t\t\t\t\tchildren: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tname: [NS.IETF_CARDDAV, 'text-match'],\n\t\t\t\t\t\t\t\t\tvalue: email,\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\n\t\t\t\tconst contacts = result.flatMap(([addressBook, vcards]) => vcards.map((vcard) => new Contact(vcard.data, addressBook)))\n\n\t\t\t\tthis.contact = contacts.find((contact) => contact.email === email)\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Error fetching contact:', error)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\tupdateGroups(value) {\n\t\t\tthis.newGroupsValue = value\n\t\t},\n\n\t\t/**\n\t\t * Update this.localContact\n\t\t *\n\t\t * @param {Contact} contact the contact to clone\n\t\t */\n\t\tasync updateLocalContact(contact) {\n\t\t\tif (!contact) {\n\t\t\t\tthis.localContact = undefined\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// create empty contact and copy inner data\n\t\t\tconst localContact = Object.assign(\n\t\t\t\tObject.create(Object.getPrototypeOf(contact)),\n\t\t\t\tcontact,\n\t\t\t)\n\t\t\tvalidate(localContact)\n\n\t\t\tthis.localContact = localContact\n\t\t\tthis.newGroupsValue = [...this.localContact.groups]\n\t\t},\n\n\t\t/**\n\t\t * Should display the property\n\t\t *\n\t\t * @param {Property} property the property to check\n\t\t * @return {boolean}\n\t\t */\n\t\tcanDisplay(property) {\n\t\t\t// Make sure we have some model for the property and check for ITEM.PROP custom label format\n\t\t\tconst propModel = rfcProps.properties[property.name.split('.').pop()]\n\n\t\t\tconst propType = propModel && propModel.force\n\t\t\t\t? propModel.force\n\t\t\t\t: property.getDefaultType()\n\n\t\t\treturn propModel && propType !== 'unknown'\n\t\t},\n\n\t},\n}\n</script>\n\n<style lang=\"scss\" scoped>\n\n.empty-content {\n\theight: 100%;\n}\n\n.contact-title {\n\tmargin-inline-start: 100px;\n\tmargin-top: 40px;\n}\n\n:deep(.property__value) {\n\tfont-size: medium !important;\n}\n\n.recipient-details-loading {\n\tmargin-top: 64px;\n}\n\n:deep(input) {\n\tbox-shadow: none !important;\n}\n</style>\n","/**\n * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { createPinia } from 'pinia'\nimport { createApp } from 'vue'\nimport ReadOnlyContactDetails from './views/ReadOnlyContactDetails.vue'\nimport LegacyGlobalMixin from './mixins/LegacyGlobalMixin.js'\nimport store from './store/index.js'\n\nimport 'vite/modulepreload-polyfill'\n// Global scss sheets\nimport './css/contacts.scss'\n// Dialogs css\nimport '@nextcloud/dialogs/style.css'\n\ndeclare global {\n\tinterface Window {\n\t\tOCA: {\n\t\t\tContacts?: {\n\t\t\t\t/**\n\t\t\t\t * Mount the contact details component at the given DOM element.\n\t\t\t\t *\n\t\t\t\t * @param el Html element to mount the component at.\n\t\t\t\t * @param contactEmailAddress Email address of the contact whose details to display.\n\t\t\t\t * @return Component handle with a basic API to control it.\n\t\t\t\t */\n\t\t\t\tmountContactDetails(\n\t\t\t\t\tel: HTMLElement,\n\t\t\t\t\tcontactEmailAddress: string,\n\t\t\t\t): Promise<{ $destroy(): void }>\n\t\t\t}\n\t\t}\n\t}\n}\n\nwindow.OCA ??= {}\nwindow.OCA.Contacts = {\n\tasync mountContactDetails(el, contactEmailAddress) {\n\t\tconst app = createApp(ReadOnlyContactDetails, {\n\t\t\tcontactEmailAddress,\n\t\t})\n\n\t\tconst pinia = createPinia()\n\t\tapp.use(pinia)\n\t\tapp.use(store)\n\n\t\tapp.mixin(LegacyGlobalMixin)\n\n\t\tapp.mount(el)\n\n\t\treturn {\n\t\t\t$destroy: () => app.unmount(),\n\t\t}\n\t},\n}\n"],"names":["profileEnabled","loadState","_sfc_main","ContactDetailsProperty","NcEmptyContent","IconContact","NcLoadingIcon","IsMobileMixin","mitt","title","organization","escape","a","b","nameA","nameB","rfcProps","list","property","contact","client","usePrincipalsStore","email","contacts","addressBook","NS","vcards","vcard","Contact","error","value","localContact","validate","propModel","propType","_hoisted_1","_hoisted_4","_hoisted_6","_openBlock","_createElementBlock","$data","_hoisted_2","_createVNode","_component_NcLoadingIcon","_hoisted_3","_createElementVNode","_toDisplayString","$options","_hoisted_5","_Fragment","_renderList","properties","name","index","_createBlock","_component_ContactDetailsProperty","_component_NcEmptyContent","_ctx","_component_IconContact","el","contactEmailAddress","app","createApp","ReadOnlyContactDetails","pinia","createPinia","store","LegacyGlobalMixin"],"mappings":"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"} |