Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Share redesign split into internal external sections #49653

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3ebab0d
refactor(files_sharing): move deprecated projects component
thlehmann-ionos Nov 7, 2024
e63c9c0
refactor(files_sharing): split internal/external share (relayout)
thlehmann-ionos Aug 7, 2024
5df6fb2
refactor(files_sharing): add another user search input in external sh…
thlehmann-ionos Nov 7, 2024
0a721a3
refactor(files_sharing): dissolve component SharingEntryLink into Sha…
thlehmann-ionos Nov 7, 2024
2fb95df
[DROP] Recompile for files_sharing changes
thlehmann-ionos Dec 4, 2024
642d623
[POC] [WIP] refactor(files_sharing): move getSuggestions(), getRecomm…
thlehmann-ionos Dec 5, 2024
a9507ea
[DROP] Recompile for files_sharing changes
thlehmann-ionos Dec 5, 2024
b4f6b1d
Revert "[DROP] Recompile for files_sharing changes"
thlehmann-ionos Dec 6, 2024
c17261e
Revert "[POC] [WIP] refactor(files_sharing): move getSuggestions(), g…
thlehmann-ionos Dec 6, 2024
818af87
AMEND TO: 'refactor(files_sharing): dissolve component SharingEntryLi…
thlehmann-ionos Dec 6, 2024
e623446
chore(files_sharing): fix inconsistent trim() of shareWith
thlehmann-ionos Dec 6, 2024
4ac858c
chore(files_sharing): replace reduce() by flat()
thlehmann-ionos Dec 6, 2024
ecd31c2
refactor(files_sharing): add ShareTypes module
thlehmann-ionos Dec 6, 2024
0ba6f7b
[WIP] refactor(files_sharing): extract formatForMultiselect() from Sh…
thlehmann-ionos Dec 6, 2024
8d66704
[WIP] feat(files_sharing): add ExternalShareeSearch
thlehmann-ionos Dec 6, 2024
ac8a857
[WIP] feat(files_sharing): use external sharee search
thlehmann-ionos Dec 6, 2024
0c71e8b
[WIP] feat(files_sharing): remove lookup, limit to internal shares
thlehmann-ionos Dec 6, 2024
e42d673
[DROP] Recompile for files_sharing changes
thlehmann-ionos Dec 6, 2024
01ec6d0
[WIP] Note: work in progress
thlehmann-ionos Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
376 changes: 376 additions & 0 deletions apps/files_sharing/src/components/ExternalShareeSearch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,376 @@
<!--
- SPDX-FileLicenseText: 2019, 2024 Nextcloud GmbH and Nextcloud contributors, STRATO AG
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<!--
TODO: Initially copied from ShareInput.vue for differentiation of sharee seatch by internal/external

- externalResults (OCA.Sharing.ShareSearch.state) already dropped (not needed here)
- shareType is passed with the request
- inconsistent use of trim() on shareWith in reducer alreay fixed here
- Needs more refactoring esp. with filterOutExistingShares(), formatForMultiselect()
- filterOutExistingShares() should be solved with filter(predicateFunctions) instead
- formatForMultiselect() could/shoul be common to both components
- shareTypeToIcon() does not need some types here, if not refactored out, drop them
-->

<!--
Search field to look up internal sharees (shares with other internal Nextcloud users)
-->

<template>
<div class="sharing-search">
<NcSelect ref="select"
v-model="value"
input-id="sharing-search-external-input"
class="sharing-search-external__input"
:disabled="!canReshare"
:loading="loading"
:filterable="false"
:placeholder="inputPlaceholder"
:clear-search-on-blur="() => false"
:user-select="true"
:options="options"
:label-outside="true"
@search="asyncFind"
@option:selected="onSelected">
<template #no-options="{ search }">
{{ search ? noResultText : t('files_sharing', 'No recommendations. Start typing.') }}
</template>
</NcSelect>
</div>
</template>

<script>
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'

Check failure on line 47 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'getCurrentUser' is defined but never used
import { getCapabilities } from '@nextcloud/capabilities'
import axios from '@nextcloud/axios'
import debounce from 'debounce'
import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'

import Config from '../services/ConfigService.ts'
import formatForMultiselect from '../utils/formatForMultiselect.js'
import Share from '../models/Share.ts'
import ShareRequests from '../mixins/ShareRequests.js'
import ShareDetails from '../mixins/ShareDetails.js'
import { ShareType } from '@nextcloud/sharing'
import { external as externalShareTypes, externalAllowed } from '../utils/ShareTypes.js';

Check failure on line 59 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Extra semicolon

export default {
name: 'InternalShareeSearch',

Check failure on line 62 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Component name `InternalShareeSearch` should match file name `ExternalShareeSearch`

components: {
NcSelect,
},

mixins: [ShareRequests, ShareDetails],

props: {
shares: {
type: Array,
default: () => [],
required: true,
},
linkShares: {
type: Array,
default: () => [],
required: true,
},
fileInfo: {
type: Object,
default: () => {},
required: true,
},
reshare: {
type: Share,
default: null,
},
canReshare: {
type: Boolean,
required: true,
},
},

data() {
return {
config: new Config(),
loading: false,
query: '',
recommendations: [],
ShareSearch: OCA.Sharing.ShareSearch.state,
suggestions: [],
value: null,
}
},

computed: {
inputPlaceholder() {
if (!this.canReshare) {
return t('files_sharing', 'Resharing is not allowed')
}

if (getCapabilities().files_sharing.public.enabled !== true) {
return t('files_sharing', 'Federated Cloud ID …')
}

return t('files_sharing', 'Email, or Federated Cloud ID …')
},

isValidQuery() {
return this.query && this.query.trim() !== '' && this.query.length > this.config.minSearchStringLength
},

options() {
if (this.isValidQuery) {
return this.suggestions
}
return this.recommendations
},

noResultText() {
if (this.loading) {
return t('files_sharing', 'Searching …')
}
return t('files_sharing', 'No elements found.')
},
},

mounted() {
this.getRecommendations()
},

methods: {
onSelected(option) {
this.value = null // Reset selected option
this.openSharingDetails(option)
},

async asyncFind(query) {
// save current query to check if we display
// recommendations or search results
this.query = query.trim()
if (this.isValidQuery) {
// start loading now to have proper ux feedback
// during the debounce
this.loading = true
await this.debounceGetSuggestions(query)
}
},

/**
* Get suggestions
*
* @param {string} search the search query
*/
async getSuggestions(search) {
this.loading = true

const lookup = getCapabilities().files_sharing.sharee.query_lookup_default === true;

Check failure on line 170 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Extra semicolon

let request = null
try {
request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees'), {
params: {
format: 'json',
itemType: this.fileInfo.type === 'dir' ? 'folder' : 'file',
search,
lookup,
perPage: this.config.maxAutocompleteResults,
shareType: externalAllowed,
},
})
} catch (error) {
console.error('Error fetching suggestions', error)
return
}

const data = request.data.ocs.data
const exact = request.data.ocs.data.exact
data.exact = [] // removing exact from general results

// flatten array of arrays
const rawExactSuggestions = Object.values(exact).flat()
const rawSuggestions = Object.values(data).flat()

const shouldAlwaysShowUnique = this.config.shouldAlwaysShowUnique

// remove invalid data and format to user-select layout
const exactSuggestions = this.filterOutExistingShares(rawExactSuggestions)
.map(share => formatForMultiselect(share, shouldAlwaysShowUnique))
// sort by type so we can get user&groups first...
.sort((a, b) => a.shareType - b.shareType)
const suggestions = this.filterOutExistingShares(rawSuggestions)
.map(share => formatForMultiselect(share, shouldAlwaysShowUnique))
// sort by type so we can get user&groups first...
.sort((a, b) => a.shareType - b.shareType)

// lookup clickable entry
// show if enabled and not already requested
const lookupEntry = []
if (data.lookupEnabled && !lookup) {
lookupEntry.push({
id: 'global-lookup',
isNoUser: true,
displayName: t('files_sharing', 'Search globally'),
lookup: true,
})
}

const allSuggestions = exactSuggestions.concat(suggestions).concat(lookupEntry)

// Count occurrences of display names in order to provide a distinguishable description if needed
const nameCounts = allSuggestions.reduce((nameCounts, result) => {
if (!result.displayName) {
return nameCounts
}
if (!nameCounts[result.displayName]) {
nameCounts[result.displayName] = 0
}
nameCounts[result.displayName]++
return nameCounts
}, {})

this.suggestions = allSuggestions.map(item => {
// Make sure that items with duplicate displayName get the shareWith applied as a description
if (nameCounts[item.displayName] > 1 && !item.desc) {
return { ...item, desc: item.shareWithDisplayNameUnique }
}
return item
})

this.loading = false
console.info('suggestions', this.suggestions)
},

/**
* Debounce getSuggestions
*
* @param {...*} args the arguments
*/
debounceGetSuggestions: debounce(function(...args) {
this.getSuggestions(...args)
}, 300),

/**
* Get the sharing recommendations
*/
async getRecommendations() {
this.loading = true

let request = null
try {
request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees_recommended'), {
params: {
format: 'json',
itemType: this.fileInfo.type,
shareType: externalAllowed
},
})
} catch (error) {
console.error('Error fetching external share recommendations', error)
return
}

const shouldAlwaysShowUnique = this.config.shouldAlwaysShowUnique

const rawRecommendations = Object.values(request.data.ocs.data.exact).flat()

// remove invalid data and format to user-select layout
this.recommendations = this.filterOutExistingShares(rawRecommendations)
.map(share => formatForMultiselect(share, shouldAlwaysShowUnique));

Check failure on line 282 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Extra semicolon

this.loading = false
console.info('external recommendations', this.recommendations)
},

/**
* Filter out existing shares from
* the provided shares search results
*
* @param {object[]} shares the array of shares object
* @return {object[]}
*/
filterOutExistingShares(shares) {
console.log("external: filterOutExistingShares()", shares);

Check failure on line 296 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Unexpected console statement

Check failure on line 296 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Strings must use singlequote

Check failure on line 296 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Extra semicolon
return shares.reduce((arr, share) => {
// only check proper objects
if (typeof share !== 'object') {
return arr
}

const shareType = share.value.shareType

try {
// Here we care only about external share types
if (!externalShareTypes.includes(shareType)) {
return arr
}

// filter out existing mail shares
if (shareType === ShareType.Email) {
const emails = this.linkShares.map(elem => elem.shareWith)
if (emails.indexOf(share.value.shareWith.trim()) !== -1) {
return arr
}
} else { // filter out existing shares
console.log("non-email share with (shareWith): ", this.shares)

Check failure on line 318 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Unexpected console statement

Check failure on line 318 in apps/files_sharing/src/components/ExternalShareeSearch.vue

View workflow job for this annotation

GitHub Actions / NPM lint

Strings must use singlequote
// creating an object of uid => type
const sharesObj = this.shares.reduce((obj, elem) => {
obj[elem.shareWith.trim()] = elem.type
return obj
}, {})

// if shareWith is the same and the share type too, ignore it
const key = share.value.shareWith.trim()
if (key in sharesObj
&& sharesObj[key] === shareType) {
return arr
}
}

// ALL GOOD
// let's add the suggestion
arr.push(share)
} catch (e) {
return arr
}
return arr
}, [])
},
},
}
</script>

<style lang="scss">
.sharing-search {
display: flex;
flex-direction: column;
margin-bottom: 4px;

label[for="sharing-search-external-input"] {
margin-bottom: 2px;
}

&__input {
width: 100%;
margin: 10px 0;
}
}

.vs__dropdown-menu {
// properly style the lookup entry
span[lookup] {
.avatardiv {
background-image: var(--icon-search-white);
background-repeat: no-repeat;
background-position: center;
background-color: var(--color-text-maxcontrast) !important;
.avatardiv__initials-wrapper {
display: none;
}
}
}
}
</style>
Loading
Loading