aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorF. E Noel Nfebe <fenn25.fn@gmail.com>2024-07-31 14:21:22 +0100
committerGitHub <noreply@github.com>2024-07-31 14:21:22 +0100
commit656828a8bb65bf9c3234fcc3533e54a6a7630837 (patch)
tree76ca6ab449542937cb10734a07165e00f291383f /apps
parent24d5c22ba523ff812e8828fdaca98b92778de269 (diff)
parent52ff995a445c131c2b9607b0c9f0335cfc26ba3d (diff)
downloadnextcloud-server-656828a8bb65bf9c3234fcc3533e54a6a7630837.tar.gz
nextcloud-server-656828a8bb65bf9c3234fcc3533e54a6a7630837.zip
Merge pull request #46909 from nextcloud/backport/46452/stable28
[stable28] feat(editLocallyAction): Handle possible no local client scenario
Diffstat (limited to 'apps')
-rw-r--r--apps/files/src/actions/editLocallyAction.spec.ts47
-rw-r--r--apps/files/src/actions/editLocallyAction.ts57
2 files changed, 92 insertions, 12 deletions
diff --git a/apps/files/src/actions/editLocallyAction.spec.ts b/apps/files/src/actions/editLocallyAction.spec.ts
index e7102b8defb..3d32930c273 100644
--- a/apps/files/src/actions/editLocallyAction.spec.ts
+++ b/apps/files/src/actions/editLocallyAction.spec.ts
@@ -22,14 +22,35 @@
import { action } from './editLocallyAction'
import { expect } from '@jest/globals'
import { File, Permission, View, FileAction } from '@nextcloud/files'
-import * as ncDialogs from '@nextcloud/dialogs'
+import { DialogBuilder, showError } from '@nextcloud/dialogs'
import axios from '@nextcloud/axios'
+const dialogBuilder = {
+ setName: jest.fn().mockReturnThis(),
+ setText: jest.fn().mockReturnThis(),
+ setButtons: jest.fn().mockReturnThis(),
+ build: jest.fn().mockReturnValue({
+ show: jest.fn().mockResolvedValue(true),
+ }),
+} as unknown as DialogBuilder
+
+jest.mock('@nextcloud/dialogs', () => ({
+ DialogBuilder: jest.fn(() => dialogBuilder),
+ showError: jest.fn(),
+}))
+
const view = {
id: 'files',
name: 'Files',
} as View
+// Mock webroot variable
+beforeAll(() => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (window as any)._oc_webroot = '';
+ (window as any).OCA = { Viewer: { open: jest.fn() } }
+})
+
describe('Edit locally action conditions tests', () => {
test('Default values', () => {
expect(action).toBeInstanceOf(FileAction)
@@ -55,7 +76,7 @@ describe('Edit locally action enabled tests', () => {
expect(action.enabled!([file], view)).toBe(true)
})
- test('Disabled for non-dav ressources', () => {
+ test('Disabled for non-dav resources', () => {
const file = new File({
id: 1,
source: 'https://domain.com/data/foobar.txt',
@@ -115,8 +136,11 @@ describe('Edit locally action enabled tests', () => {
describe('Edit locally action execute tests', () => {
test('Edit locally opens proper URL', async () => {
- jest.spyOn(axios, 'post').mockImplementation(async () => ({ data: { ocs: { data: { token: 'foobar' } } } }))
- jest.spyOn(ncDialogs, 'showError')
+ jest.spyOn(axios, 'post').mockImplementation(async () => ({
+ data: { ocs: { data: { token: 'foobar' } } }
+ }))
+ const mockedShowError = jest.mocked(showError)
+ const spyDialogBuilder = jest.spyOn(dialogBuilder, 'build')
const file = new File({
id: 1,
@@ -128,17 +152,20 @@ describe('Edit locally action execute tests', () => {
const exec = await action.exec(file, view, '/')
+ expect(spyDialogBuilder).toBeCalled()
+
// Silent action
expect(exec).toBe(null)
expect(axios.post).toBeCalledTimes(1)
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files/api/v1/openlocaleditor?format=json', { path: '/foobar.txt' })
- expect(ncDialogs.showError).toBeCalledTimes(0)
+ expect(mockedShowError).toBeCalledTimes(0)
expect(window.location.href).toBe('nc://open/test@localhost/foobar.txt?token=foobar')
})
- test('Edit locally fails and show error', async () => {
+ test('Edit locally fails and shows error', async () => {
jest.spyOn(axios, 'post').mockImplementation(async () => ({}))
- jest.spyOn(ncDialogs, 'showError')
+ const mockedShowError = jest.mocked(showError)
+ const spyDialogBuilder = jest.spyOn(dialogBuilder, 'build')
const file = new File({
id: 1,
@@ -150,12 +177,14 @@ describe('Edit locally action execute tests', () => {
const exec = await action.exec(file, view, '/')
+ expect(spyDialogBuilder).toBeCalled()
+
// Silent action
expect(exec).toBe(null)
expect(axios.post).toBeCalledTimes(1)
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files/api/v1/openlocaleditor?format=json', { path: '/foobar.txt' })
- expect(ncDialogs.showError).toBeCalledTimes(1)
- expect(ncDialogs.showError).toBeCalledWith('Failed to redirect to client')
+ expect(mockedShowError).toBeCalledTimes(1)
+ expect(mockedShowError).toBeCalledWith('Failed to redirect to client')
expect(window.location.href).toBe('http://localhost/')
})
})
diff --git a/apps/files/src/actions/editLocallyAction.ts b/apps/files/src/actions/editLocallyAction.ts
index f52f8191df8..38393b919d5 100644
--- a/apps/files/src/actions/editLocallyAction.ts
+++ b/apps/files/src/actions/editLocallyAction.ts
@@ -23,11 +23,62 @@ import { encodePath } from '@nextcloud/paths'
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { FileAction, Permission, type Node } from '@nextcloud/files'
-import { showError } from '@nextcloud/dialogs'
+import { showError, DialogBuilder } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
-
import LaptopSvg from '@mdi/svg/svg/laptop.svg?raw'
+import IconCancel from '@mdi/svg/svg/cancel.svg?raw'
+import IconCheck from '@mdi/svg/svg/check.svg?raw'
+
+const confirmLocalEditDialog = (
+ localEditCallback: (openingLocally: boolean) => void = () => {},
+) => {
+ let callbackCalled = false
+
+ return (new DialogBuilder())
+ .setName(t('files', 'Edit file locally'))
+ .setText(t('files', 'The file should now open locally. If you don\'t see this happening, make sure that the desktop client is installed on your system.'))
+ .setButtons([
+ {
+ label: t('files', 'Retry local edit'),
+ icon: IconCancel,
+ callback: () => {
+ callbackCalled = true
+ localEditCallback(false)
+ },
+ },
+ {
+ label: t('files', 'Edit online'),
+ icon: IconCheck,
+ type: 'primary',
+ callback: () => {
+ callbackCalled = true
+ localEditCallback(true)
+ },
+ },
+ ])
+ .build()
+ .show()
+ .then(() => {
+ // Ensure the callback is called even if the dialog is dismissed in other ways
+ if (!callbackCalled) {
+ localEditCallback(false)
+ }
+ })
+}
+
+const attemptOpenLocalClient = async (path: string) => {
+ openLocalClient(path)
+ confirmLocalEditDialog(
+ (openLocally: boolean) => {
+ if (!openLocally) {
+ window.OCA.Viewer.open({ path })
+ return
+ }
+ openLocalClient(path)
+ },
+ )
+}
const openLocalClient = async function(path: string) {
const link = generateOcsUrl('apps/files/api/v1') + '/openlocaleditor?format=json'
@@ -60,7 +111,7 @@ export const action = new FileAction({
},
async exec(node: Node) {
- openLocalClient(node.path)
+ attemptOpenLocalClient(node.path)
return null
},