diff --git a/changelog/unreleased/enhancement-clipboard-copy b/changelog/unreleased/enhancement-clipboard-copy
new file mode 100644
index 00000000000..a4687ed2752
--- /dev/null
+++ b/changelog/unreleased/enhancement-clipboard-copy
@@ -0,0 +1,8 @@
+Enhancement: Make clipboard copy available to more browsers
+
+We have added more functionality for copying (e.g. links) to the user's clipboard.
+By switching libraries we now use the standard browser API (if available) with a
+fallback and only offer copy-to-clipboard buttons if the browser supports it.
+
+https://github.com/owncloud/web/pull/8136
+https://github.com/owncloud/web/issues/8134
diff --git a/packages/web-app-files/package.json b/packages/web-app-files/package.json
index b52eb8965f5..adab7e78705 100644
--- a/packages/web-app-files/package.json
+++ b/packages/web-app-files/package.json
@@ -5,7 +5,6 @@
"description": "ownCloud web files",
"license": "AGPL-3.0",
"dependencies": {
- "copy-to-clipboard": "^3.3.1",
"mark.js": "^8.11.1"
},
"devDependencies": {
diff --git a/packages/web-app-files/src/components/SideBar/Details/FileDetails.vue b/packages/web-app-files/src/components/SideBar/Details/FileDetails.vue
index 40cc97a04bf..904e1fb6c09 100644
--- a/packages/web-app-files/src/components/SideBar/Details/FileDetails.vue
+++ b/packages/web-app-files/src/components/SideBar/Details/FileDetails.vue
@@ -95,6 +95,7 @@
v-text="file.path"
/>
diff --git a/packages/web-app-files/src/components/SideBar/Shares/Links/NameAndCopy.vue b/packages/web-app-files/src/components/SideBar/Shares/Links/NameAndCopy.vue
index e937731e7ed..e20ac4494d7 100644
--- a/packages/web-app-files/src/components/SideBar/Shares/Links/NameAndCopy.vue
+++ b/packages/web-app-files/src/components/SideBar/Shares/Links/NameAndCopy.vue
@@ -15,6 +15,7 @@
/>
-
diff --git a/packages/web-app-files/src/helpers/share/link.ts b/packages/web-app-files/src/helpers/share/link.ts
index dbfc82c7ee4..74be344658e 100644
--- a/packages/web-app-files/src/helpers/share/link.ts
+++ b/packages/web-app-files/src/helpers/share/link.ts
@@ -2,7 +2,7 @@ import { DateTime } from 'luxon'
import { Share } from 'web-client/src/helpers/share'
import { Store } from 'vuex'
import { clientService } from 'web-pkg/src/services'
-import copyToClipboard from 'copy-to-clipboard'
+import { useClipboard } from '@vueuse/core'
interface CreateQuicklink {
store: Store
@@ -45,7 +45,8 @@ export const createQuicklink = async (args: CreateQuicklink): Promise =>
storageId: resource.fileId || resource.id
})
- copyToClipboard(link.url)
+ const { copy } = useClipboard({ legacy: true })
+ copy(link.url)
await store.dispatch('showMessage', {
title: $gettext('The quicklink has been copied to your clipboard.')
diff --git a/packages/web-app-files/tests/unit/components/SideBar/PrivateLinkItem.spec.ts b/packages/web-app-files/tests/unit/components/SideBar/PrivateLinkItem.spec.ts
index d62c72a3b23..606d2286f2f 100644
--- a/packages/web-app-files/tests/unit/components/SideBar/PrivateLinkItem.spec.ts
+++ b/packages/web-app-files/tests/unit/components/SideBar/PrivateLinkItem.spec.ts
@@ -1,23 +1,39 @@
-import PrivateLinkItem from 'web-app-files/src/components/SideBar/PrivateLinkItem.vue'
-import { mockDeep } from 'jest-mock-extended'
+import { mock } from 'jest-mock-extended'
import { Resource } from 'web-client'
import { createStore, defaultPlugins, mount, defaultStoreMockOptions } from 'web-test-helpers'
+import PrivateLinkItem from 'web-app-files/src/components/SideBar/PrivateLinkItem.vue'
jest.useFakeTimers()
+const folder = mock({
+ type: 'folder',
+ ownerId: 'marie',
+ ownerDisplayName: 'Marie',
+ mdate: 'Wed, 21 Oct 2015 07:28:00 GMT',
+ size: '740',
+ name: 'lorem.txt',
+ privateLink: 'https://example.com/fake-private-link'
+})
+
describe('PrivateLinkItem', () => {
it('should render a button', () => {
const { wrapper } = getWrapper()
expect(wrapper.html()).toMatchSnapshot()
})
it('upon clicking it should copy the private link to the clipboard button, render a success message and change icon for half a second', async () => {
- jest.spyOn(window, 'prompt').mockImplementation()
+ Object.assign(window.navigator, {
+ clipboard: {
+ writeText: jest.fn().mockImplementation(() => Promise.resolve())
+ }
+ })
+
const { wrapper } = getWrapper()
const spyShowMessage = jest.spyOn(wrapper.vm, 'showMessage')
expect(spyShowMessage).not.toHaveBeenCalled()
await wrapper.trigger('click')
expect(wrapper.html()).toMatchSnapshot()
+ expect(window.navigator.clipboard.writeText).toHaveBeenCalledWith(folder.privateLink)
expect(spyShowMessage).toHaveBeenCalledTimes(1)
jest.advanceTimersByTime(550)
@@ -29,15 +45,6 @@ describe('PrivateLinkItem', () => {
})
function getWrapper() {
- const folder = mockDeep({
- type: 'folder',
- ownerId: 'marie',
- ownerDisplayName: 'Marie',
- mdate: 'Wed, 21 Oct 2015 07:28:00 GMT',
- size: '740',
- name: 'lorem.txt',
- privateLink: 'https://example.com/fake-private-link'
- })
const storeOptions = { ...defaultStoreMockOptions }
storeOptions.getters.capabilities.mockImplementation(() => ({ files: { privateLinks: true } }))
storeOptions.modules.Files.getters.highlightedFile.mockImplementation(() => folder)
diff --git a/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/NameAndCopy.spec.ts b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/NameAndCopy.spec.ts
index 384f4ef0cfd..2ac38ef16e2 100644
--- a/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/NameAndCopy.spec.ts
+++ b/packages/web-app-files/tests/unit/components/SideBar/Shares/Links/NameAndCopy.spec.ts
@@ -16,17 +16,20 @@ describe('NameAndCopy', () => {
expect(wrapper.html()).toMatchSnapshot()
})
it('upon clicking it should copy the private link to the clipboard button, render a success message and change icon for half a second', async () => {
- const windowSpy = jest.spyOn(window, 'prompt').mockImplementation()
+ Object.assign(window.navigator, {
+ clipboard: {
+ writeText: jest.fn().mockImplementation(() => Promise.resolve())
+ }
+ })
+
const { wrapper } = getWrapper()
const spyShowMessage = jest.spyOn(wrapper.vm, 'showMessage')
expect(spyShowMessage).not.toHaveBeenCalled()
- expect(windowSpy).not.toHaveBeenCalled()
await wrapper.find('.oc-files-public-link-copy-url').trigger('click')
+ expect(window.navigator.clipboard.writeText).toHaveBeenCalledWith(exampleLink.url)
expect(wrapper.html()).toMatchSnapshot()
expect(spyShowMessage).toHaveBeenCalledTimes(1)
- expect(windowSpy).toHaveBeenCalledTimes(1)
- expect(windowSpy).toHaveBeenCalledWith('Copy to clipboard: Ctrl+C, Enter', exampleLink.url)
jest.advanceTimersByTime(550)
diff --git a/packages/web-runtime/src/defaults/vue.js b/packages/web-runtime/src/defaults/vue.js
index 2f12418b766..41aa4c58d49 100644
--- a/packages/web-runtime/src/defaults/vue.js
+++ b/packages/web-runtime/src/defaults/vue.js
@@ -1,12 +1,9 @@
-import 'vue-resize/dist/vue-resize.css'
import Vue from 'vue'
import WebPlugin from '../plugins/web'
import Avatar from '../components/Avatar.vue'
import focusMixin from '../mixins/focusMixin'
import lifecycleMixin from '../mixins/lifecycleMixin'
-import VueEvents from 'vue-events'
import VueScrollTo from 'vue-scrollto'
-import VueResize from 'vue-resize'
import VueMeta from 'vue-meta'
import PortalVue from 'portal-vue'
import AsyncComputed from 'vue-async-computed'
@@ -15,10 +12,8 @@ import Vuex from 'vuex'
Vue.use(Vuex)
Vue.use(VueRouter)
-Vue.use(VueEvents)
Vue.use(VueScrollTo)
Vue.use(WebPlugin)
-Vue.use(VueResize)
Vue.use(VueMeta, {
refreshOnceOnNavigation: true
})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 22866f35661..de5a9e35a80 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -424,7 +424,6 @@ importers:
specifiers:
'@jest/globals': 29.3.1
'@vueuse/core': ^9.8.2
- copy-to-clipboard: ^3.3.1
filesize: ^9.0.11
fuse.js: ^6.5.3
lodash-es: 4.17.21
@@ -449,7 +448,6 @@ importers:
web-pkg: npm:@ownclouders/web-pkg
web-runtime: workspace:*
dependencies:
- copy-to-clipboard: 3.3.1
filesize: 9.0.11
fuse.js: 6.5.3
lodash-es: 4.17.21
@@ -9763,12 +9761,6 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /copy-to-clipboard/3.3.1:
- resolution: {integrity: sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==}
- dependencies:
- toggle-selection: 1.0.6
- dev: false
-
/copy-webpack-plugin/5.1.2_webpack@4.46.0:
resolution: {integrity: sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==}
engines: {node: '>= 6.9.0'}
@@ -21504,10 +21496,6 @@ packages:
safe-regex: 1.1.0
dev: true
- /toggle-selection/1.0.6:
- resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
- dev: false
-
/toidentifier/1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}