summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2021-05-06 16:43:56 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2021-05-30 10:28:57 +0200
commite8a26bcf0a84dc3880ede70998c75e4d85b72d60 (patch)
tree9a646f238d5a9e0c4a25346828843472d054d7b9 /src
parent5fec07abf282c01ede1a5fcbf2eff89367b39c62 (diff)
Fix copylink, and member picker
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/AppNavigation/CircleNavigationItem.vue2
-rw-r--r--src/components/AppNavigation/Settings/SettingsAddressbook.vue2
-rw-r--r--src/components/CircleDetails.vue23
-rw-r--r--src/components/EntityPicker/ContactsPicker.vue4
-rw-r--r--src/components/EntityPicker/EntityPicker.vue36
-rw-r--r--src/components/EntityPicker/EntitySearchResult.vue9
-rw-r--r--src/components/EntityPicker/MembersPicker.vue185
-rw-r--r--src/components/MemberList.vue13
-rw-r--r--src/mixins/CopyToClipboardMixin.js12
-rw-r--r--src/models/circle.d.ts4
-rw-r--r--src/models/circle.ts7
-rw-r--r--src/models/constants.d.ts5
-rw-r--r--src/models/constants.ts69
-rw-r--r--src/services/collaborationAutocompletion.js19
-rw-r--r--src/store/circles.js2
15 files changed, 130 insertions, 262 deletions
diff --git a/src/components/AppNavigation/CircleNavigationItem.vue b/src/components/AppNavigation/CircleNavigationItem.vue
index d6787a6e..4af059af 100644
--- a/src/components/AppNavigation/CircleNavigationItem.vue
+++ b/src/components/AppNavigation/CircleNavigationItem.vue
@@ -42,7 +42,7 @@
<!-- copy circle link -->
<ActionLink
:href="circleUrl"
- :icon="copyLoading ? 'icon-loading-small' : 'icon-public'"
+ :icon="copyLinkIcon"
@click.stop.prevent="copyToClipboard(circleUrl)">
{{ copyButtonText }}
</ActionLink>
diff --git a/src/components/AppNavigation/Settings/SettingsAddressbook.vue b/src/components/AppNavigation/Settings/SettingsAddressbook.vue
index c4dac9fd..16625bfe 100644
--- a/src/components/AppNavigation/Settings/SettingsAddressbook.vue
+++ b/src/components/AppNavigation/Settings/SettingsAddressbook.vue
@@ -41,7 +41,7 @@
<!-- copy addressbook link -->
<ActionLink
:href="addressbook.url"
- :icon="copyLoading ? 'icon-loading-small' : 'icon-public'"
+ :icon="copyLinkIcon"
@click.stop.prevent="copyToClipboard(addressbookUrl)">
{{ copyButtonText }}
</ActionLink>
diff --git a/src/components/CircleDetails.vue b/src/components/CircleDetails.vue
index 71a4e5dd..79cac2b2 100644
--- a/src/components/CircleDetails.vue
+++ b/src/components/CircleDetails.vue
@@ -54,6 +54,16 @@
<!-- actions -->
<template #actions>
<Actions>
+ <!-- copy circle link -->
+ <ActionLink
+ :href="circleUrl"
+ :icon="copyLinkIcon"
+ @click.stop.prevent="copyToClipboard(circleUrl)">
+ {{ copyButtonText }}
+ </ActionLink>
+ </Actions>
+
+ <Actions>
<!-- leave circle -->
<ActionButton
v-if="circle.canLeave"
@@ -74,15 +84,6 @@
decorative />
</ActionButton>
</Actions>
- <Actions>
- <!-- copy circle link -->
- <ActionLink
- :href="circleUrl"
- :icon="copyLoading ? 'icon-loading-small' : 'icon-public'"
- @click.stop.prevent="copyToClipboard(circleUrl)">
- {{ copyButtonText }}
- </ActionLink>
- </Actions>
</template>
<!-- menu actions -->
@@ -204,8 +205,8 @@ export default {
}
},
- onDisplayNameChangeDebounce: debounce(function() {
- this.onDisplayNameChange(...arguments)
+ onDisplayNameChangeDebounce: debounce(function(event) {
+ this.onDisplayNameChange(event.target.value)
}, 500),
async onDisplayNameChange(description) {
this.loadingDescription = true
diff --git a/src/components/EntityPicker/ContactsPicker.vue b/src/components/EntityPicker/ContactsPicker.vue
index f388298c..35b842f1 100644
--- a/src/components/EntityPicker/ContactsPicker.vue
+++ b/src/components/EntityPicker/ContactsPicker.vue
@@ -181,7 +181,3 @@ export default {
},
}
</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/components/EntityPicker/EntityPicker.vue b/src/components/EntityPicker/EntityPicker.vue
index 878d9fb7..0ad48a76 100644
--- a/src/components/EntityPicker/EntityPicker.vue
+++ b/src/components/EntityPicker/EntityPicker.vue
@@ -24,8 +24,7 @@
size="full"
@close="onCancel">
<!-- Wrapper for content & navigation -->
- <div
- class="entity-picker">
+ <div class="entity-picker">
<!-- Search -->
<div class="entity-picker__search">
<div class="entity-picker__search-icon icon-search" />
@@ -68,7 +67,7 @@
:data-sources="availableEntities"
:data-component="EntitySearchResult"
:estimate-size="44"
- :extra-props="{selection: selectionSet, onClick: onPick}" />
+ :extra-props="{ selection: selectionSet, onClick }" />
<EmptyContent v-else-if="searchQuery" icon="icon-search">
{{ t('contacts', 'No results') }}
@@ -166,6 +165,15 @@ export default {
},
/**
+ * The input will also filter the dataSet based on the label.
+ * If you are using the search event to inject a different dataSet, you can disable this
+ */
+ internalSearch: {
+ type: Boolean,
+ default: true,
+ },
+
+ /**
* Override the local management of selection
* You MUST use a sync modifier or the selection will be locked
*/
@@ -236,7 +244,8 @@ export default {
* @returns {Object[]}
*/
searchSet() {
- if (this.searchQuery && this.searchQuery.trim !== '') {
+ // If internal search is enabled and we have a search query, filter data set
+ if (this.internalSearch && this.searchQuery && this.searchQuery.trim !== '') {
return this.dataSet.filter(entity => {
return entity.label.indexOf(this.searchQuery) > -1
})
@@ -316,10 +325,16 @@ export default {
},
/**
- * Add entity from selection
+ * Add/remove entity from selection
* @param {Object} entity the entity to add
*/
- onPick(entity) {
+ onClick(entity) {
+ if (entity.id in this.selectionSet) {
+ this.$delete(this.selectionSet, entity.id)
+ console.debug('Removed entity to selection', entity)
+ return
+ }
+
this.$set(this.selectionSet, entity.id, entity)
console.debug('Added entity to selection', entity)
},
@@ -400,7 +415,7 @@ $icon-margin: ($clickable-area - $icon-size) / 2;
display: flex;
overflow-y: auto;
align-content: flex-start;
- justify-content: space-between;
+ justify-content: flex-start;
flex: 1 0 auto;
flex-wrap: wrap;
// half a line height to know there is more lines
@@ -411,8 +426,9 @@ $icon-margin: ($clickable-area - $icon-size) / 2;
// Allows 2 per line
.entity-picker__bubble {
- flex: 0 1 50%;
max-width: calc(50% - #{$entity-spacing});
+ margin-right: $entity-spacing;
+ margin-bottom: $entity-spacing;
}
}
@@ -435,10 +451,6 @@ $icon-margin: ($clickable-area - $icon-size) / 2;
margin-left: auto;
}
}
-
- &::v-deep &__bubble {
- margin-bottom: $entity-spacing;
- }
}
// Properly center Entity Picker empty content
diff --git a/src/components/EntityPicker/EntitySearchResult.vue b/src/components/EntityPicker/EntitySearchResult.vue
index 1def9bb4..8e94ee4e 100644
--- a/src/components/EntityPicker/EntitySearchResult.vue
+++ b/src/components/EntityPicker/EntitySearchResult.vue
@@ -118,12 +118,15 @@ $icon-margin: ($clickable-area - $icon-size) / 2;
opacity: 0;
}
+ // Show checkmark on selected
+ &--selected .entity-picker__bubble-checkmark {
+ opacity: 1;
+ }
+
+ // Show primary bg on hovering entities
&--selected,
&:hover,
&:focus {
- .entity-picker__bubble-checkmark {
- opacity: 1;
- }
::v-deep .user-bubble__content {
// better visual with light default tint
background-color: var(--color-primary-light);
diff --git a/src/components/EntityPicker/MembersPicker.vue b/src/components/EntityPicker/MembersPicker.vue
deleted file mode 100644
index 882a7946..00000000
--- a/src/components/EntityPicker/MembersPicker.vue
+++ /dev/null
@@ -1,185 +0,0 @@
-<template>
- <!-- Bulk contacts edit modal -->
- <Modal v-if="isProcessing || isProcessDone"
- :clear-view-delay="-1"
- :can-close="isProcessDone"
- @close="closeProcess">
- <AddToGroupView v-bind="processStatus" @close="closeProcess" />
- </Modal>
-
- <!-- contacts picker -->
- <EntityPicker v-else-if="showPicker"
- :confirm-label="t('contacts', 'Add to circle {circle}', { circle: pickerforCircle.name})"
- :data-types="pickerTypes"
- :data-set="pickerData"
- @close="onContactPickerClose"
- @submit="onContactPickerPick" />
-</template>
-
-<script>
-import { subscribe } from '@nextcloud/event-bus'
-import pLimit from 'p-limit'
-
-import Modal from '@nextcloud/vue/dist/Components/Modal'
-
-import AddToGroupView from '../../views/Processing/AddToGroupView'
-import appendContactToGroup from '../../services/appendContactToGroup'
-import EntityPicker from './EntityPicker'
-
-export default {
- name: 'MembersPicker',
-
- components: {
- AddToGroupView,
- EntityPicker,
- Modal,
-
- },
-
- data() {
- return {
- // Entity picker
- showPicker: false,
- pickerforCircle: null,
- pickerData: [],
- pickerTypes: [{
- id: 'contact',
- label: t('contacts', 'Contacts'),
- }],
-
- // Bulk processing
- isProcessing: false,
- isProcessDone: false,
- processStatus: {
- failed: 0,
- progress: 0,
- success: 0,
- total: 0,
- name: '',
- },
- }
- },
- computed: {
- contacts() {
- return this.$store.getters.getContacts
- },
- groups() {
- return this.$store.getters.getGroups
- },
- sortedContacts() {
- return this.$store.getters.getSortedContacts
- },
- },
-
- mounted() {
- // Watch for a add-to-group event
- subscribe('contacts:circles:append', this.addMemberToCircle)
- },
-
- methods: {
- // Bulk contacts group management handlers
- addMemberToCircle(circleId) {
- const circle = this.$store.getters.getCircle(circleId)
- console.debug('Member picker opened for circle', circleId)
-
- // Get the full group if we provided the group name only
- if (circle?.id !== circleId) {
- console.error('Cannot add member to an undefined circle', circle, circleId)
- return
- }
-
- // Init data set
- this.pickerData = this.sortedContacts
- .map(({ key }) => {
- const contact = this.contacts[key]
- return {
- id: contact.key,
- label: contact.displayName,
- type: 'contact',
- readOnly: contact.addressbook.readOnly,
- groups: contact.groups,
- }
- })
- // No read only contacts
- .filter(contact => !contact.readOnly)
- // No contacts already present in group
- .filter(contact => contact.groups.indexOf(circle.name) === -1)
-
- this.showPicker = true
- this.pickerforCircle = circle
- },
-
- onContactPickerClose() {
- this.pickerData = []
- this.showPicker = false
- },
-
- onContactPickerPick(selection) {
- console.debug('Adding', selection, 'to circle', this.pickerforCircle)
- const groupName = this.pickerforCircle.name
-
- this.isProcessing = true
- this.showPicker = false
-
- this.processStatus.total = selection.length
- this.processStatus.name = this.pickerforCircle.name
- this.processStatus.progress = 0
- this.processStatus.failed = 0
-
- // max simultaneous requests
- const limit = pLimit(3)
- const requests = []
-
- // create the array of requests to send
- selection.map(async entity => {
- try {
- // Get contact
- const contact = this.contacts[entity.id]
-
- // push contact to server and use limit
- requests.push(limit(() => appendContactToGroup(contact, groupName)
- .then((response) => {
- this.$store.dispatch('addContactToGroup', { contact, groupName })
- this.processStatus.progress++
- this.processStatus.success++
- })
- .catch((error) => {
- this.processStatus.progress++
- this.processStatus.error++
- console.error(error)
- })
- ))
- } catch (e) {
- console.error(e)
- }
- })
-
- Promise.all(requests).then(() => {
- this.isProcessDone = true
- this.showPicker = false
-
- // Auto close after 3 seconds if no errors
- if (this.processStatus.failed === 0) {
- setTimeout(this.closeProcess, 3000)
- }
- })
- },
-
- closeProcess() {
- this.pickerforCircle = null
- this.isProcessing = false
- this.isProcessDone = false
-
- // Reset
- this.processStatus.failed = 0
- this.processStatus.progress = 0
- this.processStatus.success = 0
- this.processStatus.total = 0
- },
- },
-}
-</script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/src/components/MemberList.vue b/src/components/MemberList.vue
index 7b924712..2aa21ce0 100644
--- a/src/components/MemberList.vue
+++ b/src/components/MemberList.vue
@@ -39,6 +39,7 @@
:confirm-label="t('contacts', 'Add to {circle}', { circle: circle.displayName})"
:data-types="pickerTypes"
:data-set="pickerData"
+ :internal-search="false"
:loading="pickerLoading"
:selection.sync="pickerSelection"
@close="resetPicker"
@@ -82,6 +83,7 @@ export default {
MembersListItem,
pickerLoading: false,
showPicker: false,
+ showPickerIntro: true,
recommendations: [],
pickerCircle: null,
@@ -113,9 +115,11 @@ export default {
return r
}, Object.create(null))
- return CIRCLES_MEMBER_GROUPING
- // Filter unpopulated types
- .filter(group => groupedList[group.type])
+ return Object.keys(groupedList)
+ // Object.keys returns string
+ .map(type => parseInt(type, 10))
+ // Map populated types to the group entry
+ .map(type => CIRCLES_MEMBER_GROUPING.find(group => group.type === type))
// Injecting headings
.map(group => [{
heading: true,
@@ -200,7 +204,7 @@ export default {
if (members.length !== selection.length) {
showWarning(t('contacts', 'Some members could not be added'))
// TODO filter successful members and edit selection
- this.selection = []
+ this.pickerSelection = []
return
}
@@ -220,6 +224,7 @@ export default {
this.showPicker = false
this.pickerCircle = null
this.pickerData = []
+ this.pickerSelection = []
},
},
}
diff --git a/src/mixins/CopyToClipboardMixin.js b/src/mixins/CopyToClipboardMixin.js
index 54e76536..3093d7dd 100644
--- a/src/mixins/CopyToClipboardMixin.js
+++ b/src/mixins/CopyToClipboardMixin.js
@@ -35,6 +35,18 @@ export default {
}
},
+ computed: {
+ copyLinkIcon() {
+ if (this.copySuccess) {
+ return 'icon-checkmark'
+ }
+ if (this.copyLoading) {
+ return 'icon-loading-small'
+ }
+ return 'icon-public'
+ },
+ },
+
methods: {
async copyToClipboard(url) {
// change to loading status
diff --git a/src/models/circle.d.ts b/src/models/circle.d.ts
index 2e98fb0c..a51a56a8 100644
--- a/src/models/circle.d.ts
+++ b/src/models/circle.d.ts
@@ -43,6 +43,10 @@ export default class Circle {
*/
get displayName(): string;
/**
+ * Set the display name
+ */
+ set displayName(text: string);
+ /**
* Circle creation date
*/
get creation(): number;
diff --git a/src/models/circle.ts b/src/models/circle.ts
index 8c0012a3..3a5880e3 100644
--- a/src/models/circle.ts
+++ b/src/models/circle.ts
@@ -78,6 +78,13 @@ export default class Circle {
}
/**
+ * Set the display name
+ */
+ set displayName(text: string) {
+ this._data.displayName = text
+ }
+
+ /**
* Circle creation date
*/
get creation(): number {
diff --git a/src/models/constants.d.ts b/src/models/constants.d.ts
index e4a258fb..219de065 100644
--- a/src/models/constants.d.ts
+++ b/src/models/constants.d.ts
@@ -42,11 +42,10 @@ export declare const PUBLIC_CIRCLE_CONFIG: {
export declare const CIRCLES_MEMBER_GROUPING: {
id: string;
label: string;
+ share: any;
type: number;
}[];
-export declare const SHARES_TYPES_MEMBER_MAP: {
- [x: number]: number;
-};
+export declare const SHARES_TYPES_MEMBER_MAP: {};
export declare enum MemberLevels {
NONE,
MEMBER,
diff --git a/src/models/constants.ts b/src/models/constants.ts
index 520e4b63..20a990de 100644
--- a/src/models/constants.ts
+++ b/src/models/constants.ts
@@ -116,31 +116,50 @@ export const PUBLIC_CIRCLE_CONFIG = {
// Represents the picker options but also the
// sorting of the members list
-export const CIRCLES_MEMBER_GROUPING = [{
- id: `picker-${OC.Share.SHARE_TYPE_USER}`,
- label: t('contacts', 'Users'),
- type: MEMBER_TYPE_USER
-}, {
- id: `picker-${OC.Share.SHARE_TYPE_EMAIL}`,
- label: t('contacts', 'Emails'),
- type: MEMBER_TYPE_MAIL
-}, {
- id: `picker-${OC.Share.SHARE_TYPE_GROUP}`,
- label: t('contacts', 'Groups'),
- type: MEMBER_TYPE_GROUP
-}, {
- id: `picker-${OC.Share.SHARE_TYPE_CIRCLE}`,
- label: t('contacts', 'Circles'),
- type: MEMBER_TYPE_CIRCLE
-}]
-
-export const SHARES_TYPES_MEMBER_MAP = {
- [OC.Share.SHARE_TYPE_CIRCLE]: MEMBER_TYPE_SINGLEID,
- [OC.Share.SHARE_TYPE_USER]: MEMBER_TYPE_USER,
- [OC.Share.SHARE_TYPE_GROUP]: MEMBER_TYPE_GROUP,
- [OC.Share.SHARE_TYPE_EMAIL]: MEMBER_TYPE_MAIL,
- // []: MEMBER_TYPE_CONTACT,
-}
+export const CIRCLES_MEMBER_GROUPING = [
+ {
+ id: `picker-${OC.Share.SHARE_TYPE_USER}`,
+ label: t('contacts', 'Users'),
+ share: OC.Share.SHARE_TYPE_USER,
+ type: MEMBER_TYPE_USER
+ },
+ {
+ id: `picker-${OC.Share.SHARE_TYPE_GROUP}`,
+ label: t('contacts', 'Groups'),
+ share: OC.Share.SHARE_TYPE_GROUP,
+ type: MEMBER_TYPE_GROUP
+ },
+ {
+ id: `picker-${OC.Share.SHARE_TYPE_REMOTE}`,
+ label: t('contacts', 'Federated users'),
+ share: OC.Share.SHARE_TYPE_REMOTE,
+ type: MEMBER_TYPE_USER
+ },
+ {
+ id: `picker-${OC.Share.SHARE_TYPE_REMOTE_GROUP}`,
+ label: t('contacts', 'Federated groups'),
+ share: OC.Share.SHARE_TYPE_REMOTE_GROUP,
+ type: MEMBER_TYPE_GROUP
+ },
+ {
+ id: `picker-${OC.Share.SHARE_TYPE_CIRCLE}`,
+ label: t('contacts', 'Circles'),
+ share: OC.Share.SHARE_TYPE_CIRCLE,
+ type: MEMBER_TYPE_CIRCLE
+ },
+ {
+ id: `picker-${OC.Share.SHARE_TYPE_EMAIL}`,
+ label: t('contacts', 'Emails'),
+ share: OC.Share.SHARE_TYPE_EMAIL,
+ type: MEMBER_TYPE_MAIL
+ },
+]
+
+// Generating a map between share types and circle member types
+export const SHARES_TYPES_MEMBER_MAP = CIRCLES_MEMBER_GROUPING.reduce((list, entry) => {
+ list[entry.share] = entry.type
+ return list
+}, {})
export enum MemberLevels {
NONE = MEMBER_LEVEL_NONE,
diff --git a/src/services/collaborationAutocompletion.js b/src/services/collaborationAutocompletion.js
index f7200387..331e4129 100644
--- a/src/services/collaborationAutocompletion.js
+++ b/src/services/collaborationAutocompletion.js
@@ -24,19 +24,11 @@
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
-const maxAutocompleteResults = parseInt(OC.config['sharing.maxAutocompleteResults'], 10) || 25
+import { SHARES_TYPES_MEMBER_MAP } from '../models/constants.ts'
-export const shareType = [
- OC.Share.SHARE_TYPE_USER,
- OC.Share.SHARE_TYPE_GROUP,
- // OC.Share.SHARE_TYPE_REMOTE,
- // OC.Share.SHARE_TYPE_REMOTE_GROUP,
- OC.Share.SHARE_TYPE_CIRCLE,
- // OC.Share.SHARE_TYPE_ROOM,
- // OC.Share.SHARE_TYPE_GUEST,
- // OC.Share.SHARE_TYPE_DECK,
- OC.Share.SHARE_TYPE_EMAIL,
-]
+// generate allowed shareType from SHARES_TYPES_MEMBER_MAP
+const shareType = Object.keys(SHARES_TYPES_MEMBER_MAP)
+const maxAutocompleteResults = parseInt(OC.config['sharing.maxAutocompleteResults'], 10) || 25
/**
* Get suggestions
@@ -51,6 +43,7 @@ export const getSuggestions = async function(search) {
search,
perPage: maxAutocompleteResults,
shareType,
+ lookup: false,
},
})
@@ -132,7 +125,7 @@ const formatResults = function(result) {
label: result.label,
id: `${type}-${result.value.shareWith}`,
// If this is a user, set as user for avatar display by UserBubble
- user: result.value.shareType === OC.Share.SHARE_TYPE_USER
+ user: [OC.Share.SHARE_TYPE_USER, OC.Share.SHARE_TYPE_REMOTE].indexOf(result.value.shareType) > -1
? result.value.shareWith
: null,
type,
diff --git a/src/store/circles.js b/src/store/circles.js
index 72d8cb60..ea056f36 100644
--- a/src/store/circles.js
+++ b/src/store/circles.js
@@ -192,8 +192,10 @@ const actions = {
* @param {Circle} circleId the circle to delete
*/
async deleteCircle(context, circleId) {
+ const circle = context.getters.getCircle(circleId)
try {
await deleteCircle(circleId)
+ context.commit('deleteCircle', circle)
console.debug('Deleted circle', circleId)
} catch (error) {
console.error(error)