fix(files): do nothing if `view local` dialog was just closed

We try to open a file in the Nextcloud client.
If this fails a dialog is shown with 3 options:

1. Retry: If it fails no further dialog is shown.
2. Open online: The viewer is used to open the file.
3. Close the dialog and nothing happens (abort).

This correctly implements 3 and also adds some comments + order file in
reading order (using `function` instead of arrow functions allows this
easily).

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/53176/head
Ferdinand Thiessen 2025-05-28 16:50:35 +07:00
parent 44dd42870e
commit 6ef37924bf
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
3 changed files with 78 additions and 68 deletions

@ -122,6 +122,7 @@ describe('Edit locally action execute tests', () => {
jest.spyOn(axios, 'post').mockImplementation(async () => ({
data: { ocs: { data: { token: 'foobar' } } },
}))
const windowOpenSpy = jest.spyOn(window, 'open')
const mockedShowError = jest.mocked(showError)
const spyDialogBuilder = jest.spyOn(dialogBuilder, 'build')
@ -142,7 +143,7 @@ describe('Edit locally action execute tests', () => {
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(mockedShowError).toBeCalledTimes(0)
expect(window.location.href).toBe('nc://open/test@localhost/foobar.txt?token=foobar')
expect(windowOpenSpy).toBeCalledWith('nc://open/test@localhost/foobar.txt?token=foobar', '_self')
})
test('Edit locally fails and shows error', async () => {

@ -12,71 +12,6 @@ import axios from '@nextcloud/axios'
import IconWeb from '@mdi/svg/svg/web.svg?raw'
import LaptopSvg from '@mdi/svg/svg/laptop.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 on your device. If it doesn\'t, please check that you have the desktop app installed.'))
.setButtons([
{
label: t('files', 'Retry and close'),
type: 'secondary',
callback: () => {
callbackCalled = true
localEditCallback(true)
},
},
{
label: t('files', 'Edit online'),
icon: IconWeb,
type: 'primary',
callback: () => {
callbackCalled = true
localEditCallback(false)
},
},
])
.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'
try {
const result = await axios.post(link, { path })
const uid = getCurrentUser()?.uid
let url = `nc://open/${uid}@` + window.location.host + encodePath(path)
url += '?token=' + result.data.ocs.data.token
window.location.href = url
} catch (error) {
showError(t('files', 'Failed to redirect to client'))
}
}
export const action = new FileAction({
id: 'edit-locally',
displayName: () => t('files', 'Edit locally'),
@ -93,9 +28,81 @@ export const action = new FileAction({
},
async exec(node: Node) {
attemptOpenLocalClient(node.path)
await attemptOpenLocalClient(node.path)
return null
},
order: 25,
})
/**
* Try to open the path in the Nextcloud client.
*
* If this fails a dialog is shown with 3 options:
* 1. Retry: If it fails no further dialog is shown.
* 2. Open online: The viewer is used to open the file.
* 3. Close the dialog and nothing happens (abort).
*
* @param path - The path to open
*/
async function attemptOpenLocalClient(path: string) {
await openLocalClient(path)
const result = await confirmLocalEditDialog()
if (result === 'local') {
await openLocalClient(path)
} else if (result === 'online') {
window.OCA.Viewer.open({ path })
}
}
/**
* Try to open a file in the Nextcloud client.
* There is no way to get notified if this action was successfull.
*
* @param path - Path to open
*/
async function openLocalClient(path: string): Promise<void> {
const link = generateOcsUrl('apps/files/api/v1') + '/openlocaleditor?format=json'
try {
const result = await axios.post(link, { path })
const uid = getCurrentUser()?.uid
let url = `nc://open/${uid}@` + window.location.host + encodePath(path)
url += '?token=' + result.data.ocs.data.token
window.open(url, '_self')
} catch (error) {
showError(t('files', 'Failed to redirect to client'))
}
}
/**
* Open the confirmation dialog.
*/
async function confirmLocalEditDialog(): Promise<'online'|'local'|false> {
let result: 'online'|'local'|false = false
const dialog = (new DialogBuilder())
.setName(t('files', 'Open file locally'))
.setText(t('files', 'The file should now open on your device. If it doesn\'t, please check that you have the desktop app installed.'))
.setButtons([
{
label: t('files', 'Retry and close'),
type: 'secondary',
callback: () => {
result = 'local'
},
},
{
label: t('files', 'Open online'),
icon: IconWeb,
type: 'primary',
callback: () => {
result = 'online'
},
},
])
.build()
await dialog.show()
return result
}

@ -169,7 +169,9 @@ const config = {
plugins: [
new VueLoaderPlugin(),
new NodePolyfillPlugin(),
new NodePolyfillPlugin({
additionalAliases: ['process'],
}),
new webpack.ProvidePlugin({
// Provide jQuery to jquery plugins as some are loaded before $ is exposed globally.
// We need to provide the path to node_moduels as otherwise npm link will fail due