summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2021-04-20 17:04:35 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2021-05-30 10:28:56 +0200
commitbb5f38e9231b659f348fbd83422af0d65194037b (patch)
tree54406f474575bf3aea9d7c95f2767941ab399450
parentc948ec1aa78690e9a4902d66fc1365d469cc4565 (diff)
Circle details
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
-rw-r--r--package.json8
-rw-r--r--src/components/AppNavigation/CircleNavigationItem.vue3
-rw-r--r--src/components/CircleDetails.vue141
-rw-r--r--src/components/CircleDetails/CircleConfigs.vue110
-rw-r--r--src/components/CircleDetails/ContentHeading.vue39
-rw-r--r--src/components/ContactDetails.vue359
-rw-r--r--src/components/ContactDetails/ContactDetailsAvatar.vue138
-rw-r--r--src/components/DetailsHeader.vue165
-rw-r--r--src/components/MemberList.vue47
-rw-r--r--src/components/MembersList/MembersListItem.vue32
-rw-r--r--src/models/circle.d.ts8
-rw-r--r--src/models/circle.ts13
-rw-r--r--src/models/constants.d.ts10
-rw-r--r--src/models/constants.ts42
-rw-r--r--src/models/member.d.ts10
-rw-r--r--src/models/member.ts16
-rw-r--r--src/services/circles.d.ts16
-rw-r--r--src/services/circles.ts22
-rw-r--r--src/store/circles.js10
19 files changed, 829 insertions, 360 deletions
diff --git a/package.json b/package.json
index d96d2dae..dee4be15 100644
--- a/package.json
+++ b/package.json
@@ -44,8 +44,7 @@
"@nextcloud/moment": "^1.1.1",
"@nextcloud/paths": "^1.1.2",
"@nextcloud/router": "^2.0.0",
- "@nextcloud/vue": "^3.9.0",
- "axios": "^0.21.1",
+ "@nextcloud/vue": "^4.0.0-alpha.1",
"b64-to-blob": "^1.2.19",
"camelcase": "^5.3.1",
"cdav-library": "git+https://github.com/nextcloud/cdav-library.git",
@@ -83,12 +82,15 @@
"@nextcloud/browserslist-config": "^2.1.0",
"@nextcloud/eslint-config": "^5.1.0",
"@nextcloud/eslint-plugin": "^2.0.0",
+ "@nextcloud/typings": "^1.0.0",
"@nextcloud/webpack-vue-config": "^4.0.3",
+ "@typescript-eslint/parser": "^4.22.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"css-loader": "^4.3.0",
"eslint": "^7.27.0",
"eslint-config-standard": "^16.0.3",
+ "eslint-import-resolver-typescript": "^2.4.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.23.3",
"eslint-plugin-node": "^11.1.0",
@@ -105,6 +107,8 @@
"stylelint-config-recommended-scss": "^4.2.0",
"stylelint-scss": "^3.19.0",
"stylelint-webpack-plugin": "^2.1.1",
+ "ts-loader": "^8.1.0",
+ "typescript": "^4.2.4",
"url-loader": "^4.1.1",
"vue-loader": "^15.9.7",
"vue-template-compiler": "^2.6.12",
diff --git a/src/components/AppNavigation/CircleNavigationItem.vue b/src/components/AppNavigation/CircleNavigationItem.vue
index 26838678..eadea9a7 100644
--- a/src/components/AppNavigation/CircleNavigationItem.vue
+++ b/src/components/AppNavigation/CircleNavigationItem.vue
@@ -93,7 +93,7 @@ import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
import ExitToApp from 'vue-material-design-icons/ExitToApp'
import LocationEnter from 'vue-material-design-icons/LocationEnter'
-import { deleteCircle, joinCircle } from '../../services/circles.ts'
+import { joinCircle } from '../../services/circles.ts'
import { showError } from '@nextcloud/dialogs'
import Circle from '../../models/circle.ts'
import CopyToClipboardMixin from '../../mixins/CopyToClipboardMixin'
@@ -194,7 +194,6 @@ export default {
this.loading = true
try {
- await deleteCircle(this.circle.id)
this.$store.dispatch('deleteCircle', this.circle.id)
} catch (error) {
showError(t('contacts', 'Unable to delete the circle'))
diff --git a/src/components/CircleDetails.vue b/src/components/CircleDetails.vue
index ebe173f7..98e4870c 100644
--- a/src/components/CircleDetails.vue
+++ b/src/components/CircleDetails.vue
@@ -1,39 +1,101 @@
<!--
- - @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
- -
- - @author John Molakvoæ <skjnldsv@protonmail.com>
- -
- - @license GNU AGPL version 3 or any later version
- -
- - This program is free software: you can redistribute it and/or modify
- - it under the terms of the GNU Affero General Public License as
- - published by the Free Software Foundation, either version 3 of the
- - License, or (at your option) any later version.
- -
- - This program is distributed in the hope that it will be useful,
- - but WITHOUT ANY WARRANTY; without even the implied warranty of
- - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- - GNU Affero General Public License for more details.
- -
- - You should have received a copy of the GNU Affero General Public License
- - along with this program. If not, see <http://www.gnu.org/licenses/>.
- -
- -->
+ - @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
+ -
+ - @author John Molakvoæ <skjnldsv@protonmail.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
<template>
<AppContentDetails>
- {{ circleId }}
+ <!-- contact header -->
+ <DetailsHeader>
+ <!-- avatar and upload photo -->
+ <template #avatar="{avatarSize}">
+ <Avatar
+ :disable-tooltip="true"
+ :display-name="circle.displayName"
+ :is-no-user="true"
+ :size="avatarSize" />
+ </template>
+
+ <!-- display name -->
+ <input
+ slot="title"
+ v-model="circle.displayName"
+ :readonly="!circle.isOwner"
+ :placeholder="t('contacts', 'Circle name')"
+ type="text"
+ autocomplete="off"
+ autocorrect="off"
+ spellcheck="false"
+ name="displayname"
+ @input="debounceUpdateCircle">
+
+ <!-- org, title -->
+ <template #subtitle>
+ </template>
+
+ <!-- actions -->
+ <template #actions>
+ </template>
+
+ <!-- menu actions -->
+ <template #actions-menu>
+ </template>
+ </DetailsHeader>
+
+ <section class="circle-details-section">
+ <ContentHeading>{{ t('contacts', 'Description') }}</ContentHeading>
+
+ <RichContenteditable class="circle-details-section__description"
+ :value="circle.description"
+ :auto-complete="onAutocomplete"
+ :maxlength="1024"
+ :multiline="true"
+ :disabled="loading"
+ :placeholder="t('contacts', 'Enter a description for the circle')"
+ @submit="onDescriptionSubmit" />
+ </section>
+
+ <section class="circle-details-section">
+ <CircleConfigs class="circle-details-section__configs" :circle="circle" />
+ </section>
</AppContentDetails>
</template>
<script>
import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails'
+import Avatar from '@nextcloud/vue/dist/Components/Avatar'
+import RichContenteditable from '@nextcloud/vue/dist/Components/RichContenteditable'
+import DetailsHeader from './DetailsHeader'
+import CircleConfigs from './CircleDetails/CircleConfigs'
+import ContentHeading from './CircleDetails/ContentHeading'
export default {
name: 'CircleDetails',
components: {
AppContentDetails,
+ Avatar,
+ CircleConfigs,
+ ContentHeading,
+ DetailsHeader,
+ RichContenteditable,
},
props: {
@@ -42,9 +104,46 @@ export default {
required: true,
},
},
+
+ computed: {
+ circle() {
+ return this.$store.getters.getCircle(this.circleId)
+ },
+ },
+ methods: {
+ /**
+ * Autocomplete @mentions on the description
+ * @param {string} search the search term
+ * @param {Function} callback callback to be called with results array
+ */
+ onAutocomplete(search, callback) {
+ // TODO: implement autocompletion. Disabled for now
+ // eslint-disable-next-line node/no-callback-literal
+ callback([])
+ },
+
+ onDescriptionSubmit() {
+ console.info(...arguments)
+ },
+ },
}
</script>
<style lang="scss" scoped>
+.app-content-details {
+ flex: 1 1 100%;
+ min-width: 0;
+}
+
+.circle-details-section {
+ padding: 0 80px;
+ &:not(:first-of-type) {
+ margin-top: 24px;
+ }
+
+ &__description {
+ max-width: 400px;
+ }
+}
</style>
diff --git a/src/components/CircleDetails/CircleConfigs.vue b/src/components/CircleDetails/CircleConfigs.vue
new file mode 100644
index 00000000..bd7b27a8
--- /dev/null
+++ b/src/components/CircleDetails/CircleConfigs.vue
@@ -0,0 +1,110 @@
+<!--
+ - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ -
+ - @author John Molakvoæ <skjnldsv@protonmail.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <ul>
+ <li v-for="(configs, title) in PUBLIC_CIRCLE_CONFIG" :key="title" class="circle-config">
+ <ContentHeading class="circle-config__title">
+ {{ title }}
+ </ContentHeading>
+
+ <ul class="circle-config__list">
+ <CheckboxRadio v-for="(label, config) in configs"
+ :key="'circle-config' + config"
+ :checked="isChecked(config)"
+ wrapper-element="li"
+ @update:checked="onChange(config, $event)">
+ {{ label }}
+ </CheckboxRadio>
+ </ul>
+ </li>
+ </ul>
+</template>
+
+<script>
+import CheckboxRadio from '@nextcloud/vue/dist/Components/CheckboxRadio'
+import ContentHeading from './ContentHeading'
+
+import { PUBLIC_CIRCLE_CONFIG } from '../../models/constants.ts'
+import Circle from '../../models/circle.ts'
+import { CircleEdit, editCircle } from '../../services/circles'
+
+export default {
+ name: 'CircleConfigs',
+
+ components: {
+ CheckboxRadio,
+ ContentHeading,
+ },
+
+ props: {
+ circle: {
+ type: Circle,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ PUBLIC_CIRCLE_CONFIG,
+ }
+ },
+
+ methods: {
+ isChecked(config) {
+ return (this.circle.config & config) !== 0
+ },
+
+ /**
+ * On toggle, add or remove the config bitwise
+ * @param {CircleConfig} config the circle config to manage
+ * @param {boolean} checked checked or not
+ */
+ async onChange(config, checked) {
+ console.debug('Circle config', `'${PUBLIC_CIRCLE_CONFIG[config]}'`, 'is set to', checked)
+
+ const prevConfig = this.circle.config
+
+ if (checked) {
+ // eslint-disable-next-line vue/no-mutating-props
+ this.circle.config = prevConfig | config
+ } else {
+ // eslint-disable-next-line vue/no-mutating-props
+ this.circle.config = prevConfig & ~config
+ }
+
+ const data = await editCircle(this.circle.id, CircleEdit.Config, this.circle.config)
+ console.info(data)
+
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.circle-config {
+ &__title {
+ user-select: none;
+ margin-top: 22px;
+ }
+}
+</style>
diff --git a/src/components/CircleDetails/ContentHeading.vue b/src/components/CircleDetails/ContentHeading.vue
new file mode 100644
index 00000000..2f435a0f
--- /dev/null
+++ b/src/components/CircleDetails/ContentHeading.vue
@@ -0,0 +1,39 @@
+<!--
+ - @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
+ -
+ - @author John Molakvoæ <skjnldsv@protonmail.com>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - This program is free software: you can redistribute it and/or modify
+ - it under the terms of the GNU Affero General Public License as
+ - published by the Free Software Foundation, either version 3 of the
+ - License, or (at your option) any later version.
+ -
+ - This program is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU Affero General Public License for more details.
+ -
+ - You should have received a copy of the GNU Affero General Public License
+ - along with this program. If not, see <http://www.gnu.org/licenses/>.
+ -
+ -->
+
+<template>
+ <h3 class="app-content-heading">
+ <slot />
+ </h3>
+</template>
+
+<script>
+export default {
+ name: 'ContentHeading',
+}
+</script>
+
+<style lang="scss" scoped>
+.app-content-heading {
+ font-weight: bold;
+}
+</style>
diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue
index 0dbdfd20..f4e53ab4 100644
--- a/src/components/ContactDetails.vue
+++ b/src/components/ContactDetails.vue
@@ -32,56 +32,54 @@
<template v-else>
<!-- contact header -->
- <header class="contact-header">
+ <DetailsHeader>
<!-- avatar and upload photo -->
<ContactAvatar
+ slot="avatar"
:contact="contact"
@update-local-contact="updateLocalContact" />
- <!-- QUESTION: is it better to pass contact as a prop or get it from the store inside
- contact-avatar ? :avatar="contact.photo"-->
-
- <!-- fullname, org, title -->
- <div class="contact-header__infos">
- <h2>
- <input id="contact-fullname"
- ref="fullname"
- v-model="contact.fullName"
- :readonly="contact.addressbook.readOnly"
- :placeholder="t('contacts', 'Name')"
- type="text"
- autocomplete="off"
- autocorrect="off"
- spellcheck="false"
- name="fullname"
- @input="debounceUpdateContact"
- @click="selectInput">
- </h2>
- <div id="details-org-container">
- <input id="contact-org"
- v-model="contact.org"
- :readonly="contact.addressbook.readOnly"
- :placeholder="t('contacts', 'Company')"
- type="text"
- autocomplete="off"
- autocorrect="off"
- spellcheck="false"
- name="org"
- @input="debounceUpdateContact">
- <input id="contact-title"
- v-model="contact.title"
- :readonly="contact.addressbook.readOnly"
- :placeholder="t('contacts', 'Title')"
- type="text"
- autocomplete="off"
- autocorrect="off"
- spellcheck="false"
- name="title"
- @input="debounceUpdateContact">
- </div>
- </div>
+
+ <!-- fullname -->
+ <input id="contact-fullname"
+ slot="title"
+ ref="fullname"
+ v-model="contact.fullName"
+ :readonly="contact.addressbook.readOnly"
+ :placeholder="t('contacts', 'Name')"
+ type="text"
+ autocomplete="off"
+ autocorrect="off"
+ spellcheck="false"
+ name="fullname"
+ @input="debounceUpdateContact"
+ @click="selectInput">
+
+ <!-- org, title -->
+ <template #subtitle>
+ <input id="contact-org"
+ v-model="contact.org"
+ :readonly="contact.addressbook.readOnly"
+ :placeholder="t('contacts', 'Company')"
+ type="text"
+ autocomplete="off"
+ autocorrect="off"
+ spellcheck="false"
+ name="org"
+ @input="debounceUpdateContact">
+ <input id="contact-title"
+ v-model="contact.title"
+ :readonly="contact.addressbook.readOnly"
+ :placeholder="t('contacts', 'Title')"
+ type="text"
+ autocomplete="off"
+ autocorrect="off"
+ spellcheck="false"
+ name="title"
+ @input="debounceUpdateContact">
+ </template>
<!-- actions -->
- <div class="contact-header__actions">
+ <template #actions>
<!-- warning message -->
<a v-if="loadingUpdate || warning"
v-tooltip.bottom="{
@@ -112,67 +110,64 @@
}"
class="header-icon header-icon--pulse icon-up"
@click="updateContact" />
-
- <!-- menu actions -->
- <Actions ref="actions"
- class="header-menu"
- menu-align="right"
- :open.sync="openedMenu">
- <ActionLink :href="contact.url"
- :download="`${contact.displayName}.vcf`"
- icon="icon-download">
- {{ t('contacts', 'Download') }}
- </ActionLink>
- <!-- user can clone if there is at least one option available -->
- <ActionButton v-if="isReadOnly && addressbooksOptions.length > 0"
- ref="cloneAction"
- :close-after-click="true"
- icon="icon-clone"
- @click="cloneContact">
- {{ t('contacts', 'Clone contact') }}
- </ActionButton>
- <ActionButton icon="icon-qrcode" @click="showQRcode">
- {{ t('contacts', 'Generate QR code') }}
- </ActionButton>
- <ActionButton v-if="!isReadOnly" icon="icon-delete" @click="deleteContact">
- {{ t('contacts', 'Delete') }}
- </ActionButton>
- </Actions>
- </div>
-
- <!-- qrcode -->
- <Modal v-if="qrcode"
- id="qrcode-modal"
- :clear-view-delay="-1"
- :title="contact.displayName"
- @close="closeQrModal">
- <img :src="`data:image/svg+xml;base64,${qrcode}`"
- :alt="t('contacts', 'Contact vCard as QR code')"
- class="qrcode"
- width="400">
- </Modal>
-
- <!-- pick addressbook when cloning contact -->
- <Modal v-if="showPickAddressbookModal"
- id="pick-addressbook-modal"
- :clear-view-delay="-1"
- :title="t('contacts', 'Pick an address book')"
- @close="closePickAddressbookModal">
- <Multiselect ref="pickAddressbook"
- v-model="pickedAddressbook"
- :allow-empty="false"
- :options="addressbooksOptions"
- :placeholder="t('contacts', 'Select address book')"
- track-by="id"
- label="name" />
- <button @click="closePickAddressbookModal">
- {{ t('contacts', 'Cancel') }}
- </button>
- <button class="primary" @click="cloneContact">
+ </template>
+
+ <!-- menu actions -->
+ <template #actions-menu>
+ <ActionLink :href="contact.url"
+ :download="`${contact.displayName}.vcf`"
+ icon="icon-download">
+ {{ t('contacts', 'Download') }}
+ </ActionLink>
+ <!-- user can clone if there is at least one option available -->
+ <ActionButton v-if="isReadOnly && addressbooksOptions.length > 0"
+ ref="cloneAction"
+ :close-after-click="true"
+ icon="icon-clone"
+ @click="cloneContact">
{{ t('contacts', 'Clone contact') }}
- </button>
- </Modal>
- </header>
+ </ActionButton>
+ <ActionButton icon="icon-qrcode" @click="showQRcode">
+ {{ t('contacts', 'Generate QR Code') }}
+ </ActionButton>
+ <ActionButton v-if="!isReadOnly" icon="icon-delete" @click="deleteContact">
+ {{ t('contacts', 'Delete') }}
+ </ActionButton>
+ </template>
+ </DetailsHeader>
+
+ <!-- qrcode -->
+ <Modal v-if="qrcode"
+ id="qrcode-modal"
+ :clear-view-delay="-1"
+ :title="contact.displayName"
+ @close="closeQrModal">
+ <img :src="`data:image/svg+xml;base64,${qrcode}`"
+ :alt="t('contacts', 'Contact vCard as QR code')"
+ class="qrcode"
+ width="400">
+ </Modal>
+
+ <!-- pick addressbook when cloning contact -->
+ <Modal v-if="showPickAddressbookModal"
+ id="pick-addressbook-modal"
+ :clear-view-delay="-1"
+ :title="t('contacts', 'Pick an address book')"
+ @close="closePickAddressbookModal">
+ <Multiselect ref="pickAddressbook"
+ v-model="pickedAddressbook"
+ :allow-empty="false"
+ :options="addressbooksOptions"
+ :placeholder="t('contacts', 'Select address book')"
+ track-by="id"
+ label="name" />
+ <button @click="closePickAddressbookModal">
+ {{ t('contacts', 'Cancel') }}
+ </button>
+ <button class="primary" @click="cloneContact">
+ {{ t('contacts', 'Clone contact') }}
+ </button>
+ </Modal>
<!-- contact details loading -->
<section v-if="loadingData" class="icon-loading contact-details" />
@@ -247,7 +242,6 @@ import { VueMasonryPlugin } from 'vue-masonry'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
-import Actions from '@nextcloud/vue/dist/Components/Actions'
import AppContentDetails from '@nextcloud/vue/dist/Components/AppContentDetails'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
@@ -258,6 +252,7 @@ import validate from '../services/validate'
import AddNewProp from './ContactDetails/ContactDetailsAddNewProp'
import ContactAvatar from './ContactDetails/ContactDetailsAvatar'
import ContactProperty from './ContactDetails/ContactDetailsProperty'
+import DetailsHeader from './DetailsHeader'
import EmptyContent from './EmptyContent'
import PropertyGroups from './Properties/PropertyGroups'
import PropertyRev from './Properties/PropertyRev'
@@ -272,11 +267,11 @@ export default {
components: {
ActionButton,
ActionLink,
- Actions,
AddNewProp,
AppContentDetails,
ContactAvatar,
ContactProperty,
+ DetailsHeader,
EmptyContent,
Modal,
Multiselect,
@@ -532,14 +527,6 @@ export default {
updateQueue.add(this.updateContact)
}, 500),
- // menu handling
- closeMenu() {
- this.openedMenu = false
- },
- toggleMenu() {
- this.openedMenu = !this.openedMenu
- },
-
/**
* Generate a qrcode for the contact
*/
@@ -780,126 +767,58 @@ export default {
}
</script>
-<style lang="scss">
+<style lang="scss" scoped>
.app-content-details {
flex: 1 1 100%;
min-width: 0;
+}
- // Header with avatar, name, position, actions...
- header {
- display: flex;
- align-items: center;
- padding: 50px 0 20px;
- font-weight: bold;
-
- // ORG-TITLE-NAME
- .contact-header__infos {
- display: flex;
- flex: 1 1 auto; // shrink avatar before this one
- flex-direction: column;
- h2,
- #details-org-container {
- display: flex;
- flex-wrap: wrap;
- margin: 0;
- }
- input {
- overflow: hidden;
- flex: 1 1;
- min-width: 100px;
- max-width: 100%;
- margin: 0;
- padding: 4px 5px;
- white-space: nowrap;
- text-overflow: ellipsis;
- border: none;
- background: transparent;
- font-size: inherit;
- &#contact-fullname {
- font-weight: bold;
- }
- }
- #contact-org:placeholder-shown {
- max-width: 20%;
- }
- }
+// List of all properties
+section.contact-details {
+ margin: 0 auto;
+ // Relative positioning for masonry
+ position: relative;
- // ACTIONS
- .contact-header__actions {
- position: relative;
- display: flex;
- .header-menu {
- margin-right: 10px;
- }
- .header-icon {
- width: 44px;
- height: 44px;
- padding: 14px;
- cursor: pointer;
- opacity: .7;
- border-radius: 22px;
- background-size: 16px;
- &:hover,
- &:focus {
- opacity: 1;
- }
- &.header-icon--pulse {
- width: 16px;
- height: 16px;
- margin: 8px;
- }
- }
- }
+ ::v-deep .property-masonry {
+ width: 350px;
}
- // List of all properties
- section.contact-details {
- margin: 0 auto;
- // Relative positioning for masonry
- position: relative;
-
- .property-masonry {
- width: 350px;
- }
-
- .property--rev {
- position: fixed;
- right: 22px;
- bottom: 0;
- height: 44px;
- opacity: .5;
- color: var(--color-text-lighter);
- line-height: 44px;
- }
+ .property--rev {
+ position: fixed;
+ right: 22px;
+ bottom: 0;
+ height: 44px;
+ opacity: .5;
+ color: var(--color-text-lighter);
+ line-height: 44px;
}
+}
- #qrcode-modal {
- .modal-container {
- display: flex;
- padding: 10px;
- background-color: #fff;
- .qrcode {
- max-width: 100%;
- }
+#qrcode-modal {
+ ::v-deep .modal-container {
+ display: flex;
+ padding: 10px;
+ background-color: #fff;
+ .qrcode {
+ max-width: 100%;
}
}
+}
- #pick-addressbook-modal {
- .modal-container {
- display: flex;
- overflow: visible;
- flex-wrap: wrap;
- justify-content: space-evenly;
+#pick-addressbook-modal {
+ ::v-deep .modal-container {
+ display: flex;
+ overflow: visible;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ margin-bottom: 20px;
+ padding: 10px;
+ background-color: #fff;
+ .multiselect {
+ flex: 1 1 100%;
+ width: 100%;
margin-bottom: 20px;
- padding: 10px;
- background-color: #fff;
- .multiselect {
- flex: 1 1 100%;
- width: 100%;
- margin-bottom: 20px;
- }
}
}
}
-
</style>
diff --git a/src/components/ContactDetails/ContactDetailsAvatar.vue b/src/components/ContactDetails/ContactDetailsAvatar.vue
index 9d6208d6..e8708743 100644
--- a/