Skip to content

Commit 0fee7b8

Browse files
authored
Merge pull request nextcloud#787 from nextcloud/enh/simple-link-input
2 parents ee0ffa4 + ef6ea7e commit 0fee7b8

File tree

5 files changed

+133
-30
lines changed

5 files changed

+133
-30
lines changed

cypress/e2e/column-text-link.cy.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ describe('Test column text-link', () => {
3737

3838
cy.loadTable('Test text-link')
3939
cy.get('.NcTable').contains('Create row').click({ force: true })
40-
cy.get('.modal__content .slot input').first().type('https://nextcloud.com').tick(500)
41-
cy.get('.icon-label-container .labels').contains('https://nextcloud.com').click()
40+
cy.get('.modal__content .slot input').first().type('https://nextcloud.com')
4241

4342
cy.intercept({ method: 'GET', url: '**/search/providers/files/*' }).as('filesResults')
4443
cy.get('.modal__content .slot input').eq(1).type('pdf').tick(500)
@@ -58,8 +57,7 @@ describe('Test column text-link', () => {
5857
cy.loadTable('Test text-link')
5958
cy.get('.NcTable tr td button').click({ force: true })
6059

61-
cy.get('.modal__content .slot input').first().clear().type('https://github.com').tick(500)
62-
cy.get('[data-cy*="github"]').click()
60+
cy.get('.modal__content .slot input').first().clear().type('https://github.com')
6361

6462
cy.get('.modal__content .slot input').eq(1).type('photo-test').tick(500)
6563
cy.get('[data-cy*="photo-test"]').first().click()

cypress/support/commands.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@ Cypress.Commands.add('createTextLinkColumn', (title, ressourceProvider, isFirstC
156156
cy.get('.typeSelection span').contains('Url', { matchCase: false }).click()
157157
cy.get('.typeSelection span').contains('Files').click()
158158
// TODO is the contacts search provider deactivated by default beginning with nc28
159-
// cy.get('.typeSelection span label').contains('Contacts').click()
159+
if (['stable27'].includes(Cypress.env('ncVersion'))) {
160+
cy.get('.typeSelection span').contains('Contacts').click()
161+
}
160162

161163
ressourceProvider.forEach(provider =>
162164
cy.get('.typeSelection span').contains(provider, { matchCase: false }).click(),

src/modules/sidebar/sections/SidebarIntegration.vue

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,18 @@
4848
import { mapGetters, mapState } from 'vuex'
4949
import { generateUrl } from '@nextcloud/router'
5050
import permissionsMixin from '../../../shared/components/ncTable/mixins/permissionsMixin.js'
51+
import copyToClipboard from '../../../shared/mixins/copyToClipboard.js'
52+
5153
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
5254
import ContentCopy from 'vue-material-design-icons/ContentCopy.vue'
53-
import { showError, showSuccess } from '@nextcloud/dialogs'
5455
5556
export default {
5657
components: {
5758
NcInputField,
5859
ContentCopy,
5960
},
6061
61-
mixins: [permissionsMixin],
62+
mixins: [permissionsMixin, copyToClipboard],
6263
6364
data() {
6465
return {
@@ -80,22 +81,7 @@ export default {
8081
},
8182
methods: {
8283
copyUrl() {
83-
if (navigator?.clipboard) {
84-
navigator.clipboard.writeText(this.apiEndpointUrl).then(function() {
85-
showSuccess(t('files', 'Integration URL copied to clipboard'))
86-
}, function(err) {
87-
showError(t('files', 'Clipboard is not available'))
88-
console.error('Async: Could not copy text: ', err)
89-
})
90-
} else {
91-
try {
92-
document.querySelector('input#urlTextField').select()
93-
document.execCommand('copy')
94-
showSuccess(t('files', 'Integration URL copied to clipboard'))
95-
} catch (e) {
96-
showError(t('files', 'Clipboard is not available'))
97-
}
98-
}
84+
this.copyToClipboard(this.apiEndpointUrl, false)
9985
},
10086
},
10187
}

src/shared/components/ncTable/partials/rowTypePartials/TextLinkForm.vue

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
{{ t('tables', 'You can not insert any links in this field. Please configure at least one link provider in the column configuration.') }}
1111
</NcNoteCard>
1212
</div>
13-
<div class="col-4">
14-
<NcSelect v-model="localValue"
13+
<div class="link-input">
14+
<NcTextField v-if="isPlainUrl" :value.sync="plainLink" :placeholder="t('tables', 'URL')" />
15+
<NcSelect v-else v-model="localValue"
1516
:options="results"
1617
:clearable="true"
1718
label="title"
@@ -22,35 +23,58 @@
2223
<LinkWidget :thumbnail-url="props.thumbnailUrl" :icon-url="props.icon" :title="props.title" :subline="props.subline" :icon-size="40" />
2324
</template>
2425
<template #selected-option="props">
25-
<LinkWidget :thumbnail-url="props.thumbnailUrl" :icon-url="props.icon" :title="props.title" :subline="props.subline" :icon-size="40" />
26+
<LinkWidget :thumbnail-url="props.thumbnailUrl" :icon-url="props.icon" :title="props.title" :icon-size="24" />
2627
</template>
2728
</NcSelect>
29+
<NcButton type="tertiary" :disabled="!localValue" :title="t('tables', 'Copy link')" @click="copyLink">
30+
<template #icon>
31+
<ContentCopy v-if="!copied" :size="20" />
32+
<ClipboardCheckMultipleOutline v-else :size="20" />
33+
</template>
34+
</NcButton>
35+
<NcButton type="tertiary" :disabled="!localValue" :title="t('tables', 'Open link')" @click="openLink">
36+
<template #icon>
37+
<OpenInNew :size="20" />
38+
</template>
39+
</NcButton>
2840
</div>
41+
<NcReferenceList :text="localValue?.value" :limit="1" :interactive="false" />
2942
</div>
3043
</RowFormWrapper>
3144
</template>
3245

3346
<script>
47+
import ContentCopy from 'vue-material-design-icons/ContentCopy.vue'
48+
import ClipboardCheckMultipleOutline from 'vue-material-design-icons/ClipboardCheckMultipleOutline.vue'
49+
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
50+
import { NcReferenceList } from '@nextcloud/vue/dist/Components/NcRichText.js'
3451
import RowFormWrapper from './RowFormWrapper.vue'
3552
import axios from '@nextcloud/axios'
3653
import { generateOcsUrl } from '@nextcloud/router'
3754
import displayError from '../../../../utils/displayError.js'
38-
import { NcNoteCard, NcSelect } from '@nextcloud/vue'
55+
import { NcButton, NcTextField, NcNoteCard, NcSelect } from '@nextcloud/vue'
3956
import debounce from 'debounce'
4057
import generalHelper from '../../../../mixins/generalHelper.js'
58+
import copyToClipboard from '../../../../mixins/copyToClipboard.js'
4159
import LinkWidget from '../LinkWidget.vue'
4260
import { translate as t } from '@nextcloud/l10n'
4361
4462
export default {
4563
4664
components: {
65+
ContentCopy,
66+
ClipboardCheckMultipleOutline,
67+
OpenInNew,
68+
NcButton,
4769
NcNoteCard,
48-
RowFormWrapper,
4970
NcSelect,
71+
NcReferenceList,
72+
NcTextField,
73+
RowFormWrapper,
5074
LinkWidget,
5175
},
5276
53-
mixins: [generalHelper],
77+
mixins: [generalHelper, copyToClipboard],
5478
5579
props: {
5680
column: {
@@ -69,10 +93,24 @@ export default {
6993
results: [],
7094
term: '',
7195
providerLoading: {},
96+
copied: false,
7297
}
7398
},
7499
75100
computed: {
101+
plainLink: {
102+
get() {
103+
return this.localValue?.value ?? ''
104+
},
105+
set(v) {
106+
this.$emit('update:value', JSON.stringify({
107+
title: v,
108+
subline: t('tables', 'URL'),
109+
providerId: 'url',
110+
value: v,
111+
}))
112+
},
113+
},
76114
localValue: {
77115
get() {
78116
// if we got an old value (string not object as json)
@@ -95,6 +133,9 @@ export default {
95133
this.$emit('update:value', value)
96134
},
97135
},
136+
isPlainUrl() {
137+
return this.providers?.length === 1 && this.providers[0] === 'url'
138+
},
98139
isLoadingResults() {
99140
for (const [key, value] of Object.entries(this.providerLoading)) {
100141
console.debug('is still loading results at least for: ' + key, value)
@@ -155,7 +196,9 @@ export default {
155196
this.removeResultsByProviderId(providerId)
156197
157198
if (providerId === 'url') {
158-
this.addUrlResult(term)
199+
if (this.isValidUrl(term)) {
200+
this.addUrlResult(term)
201+
}
159202
this.setProviderLoading(providerId, false)
160203
return
161204
}
@@ -186,6 +229,14 @@ export default {
186229
this.setProviderLoading(providerId, false)
187230
},
188231
232+
isValidUrl(string) {
233+
try {
234+
return new URL(string)
235+
} catch (err) {
236+
return false
237+
}
238+
},
239+
189240
addUrlResult(term) {
190241
this.results.push({
191242
title: term,
@@ -198,6 +249,42 @@ export default {
198249
removeResultsByProviderId(providerId) {
199250
this.results = this.results.filter(item => item.providerId !== providerId)
200251
},
252+
253+
copyLink() {
254+
if (this.localValue) {
255+
if (this.copied !== false) {
256+
clearTimeout(this.copied)
257+
}
258+
259+
this.copied = this.copyToClipboard(this.localValue.value)
260+
? setTimeout(() => {
261+
this.copied = false
262+
}, 3000)
263+
: false
264+
}
265+
},
266+
267+
openLink() {
268+
if (this.localValue) {
269+
window.open(this.localValue.value, '_blank')
270+
}
271+
},
201272
},
202273
}
203274
</script>
275+
<style lang="scss" scoped>
276+
.link-input {
277+
display: flex;
278+
align-items: center;
279+
margin-bottom: var(--default-grid-baseline);
280+
281+
:deep(.v-select:not(.vs--open) .vs__search) {
282+
position: absolute;
283+
}
284+
285+
:deep(.vs__selected) {
286+
flex-grow: 1;
287+
height: auto !important;
288+
}
289+
}
290+
</style>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { showError, showSuccess } from '@nextcloud/dialogs'
7+
8+
export default {
9+
methods: {
10+
async copyToClipboard(content, silent = true) {
11+
try {
12+
if (navigator?.clipboard) {
13+
await navigator.clipboard.writeText(content)
14+
} else {
15+
throw new Error('Clipboard is not available')
16+
}
17+
18+
if (!silent) {
19+
showSuccess(t('files', 'Copied to clipboard.'))
20+
}
21+
return true
22+
} catch (e) {
23+
console.error('Error copying to clipboard', e)
24+
if (!silent) {
25+
showError(t('files', 'Clipboard is not available'))
26+
}
27+
}
28+
},
29+
},
30+
}

0 commit comments

Comments
 (0)