mirror of
https://github.com/nextcloud/server.git
synced 2026-02-19 02:38:40 -05:00
refactor(files): adjust for files library interfaces
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
a023b5b2d5
commit
aac91a8df9
31 changed files with 395 additions and 379 deletions
|
|
@ -1,10 +1,13 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import AutoRenewSvg from '@mdi/svg/svg/autorenew.svg?raw'
|
||||
import { getCapabilities } from '@nextcloud/capabilities'
|
||||
import { FileAction, registerFileAction } from '@nextcloud/files'
|
||||
import { registerFileAction } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { convertFile, convertFiles } from './convertUtils.ts'
|
||||
|
|
@ -18,47 +21,45 @@ type ConversionsProvider = {
|
|||
export const ACTION_CONVERT = 'convert'
|
||||
|
||||
/**
|
||||
*
|
||||
* Registers the convert actions based on the capabilities provided by the server.
|
||||
*/
|
||||
export function registerConvertActions() {
|
||||
// Generate sub actions
|
||||
const convertProviders = getCapabilities()?.files?.file_conversions as ConversionsProvider[] ?? []
|
||||
const actions = convertProviders.map(({ to, from, displayName }) => {
|
||||
return new FileAction({
|
||||
id: `convert-${from}-${to}`,
|
||||
displayName: () => t('files', 'Save as {displayName}', { displayName }),
|
||||
iconSvgInline: () => generateIconSvg(to),
|
||||
enabled: ({ nodes }) => {
|
||||
// Check that all nodes have the same mime type
|
||||
return nodes.every((node) => from === node.mime)
|
||||
},
|
||||
const actions = convertProviders.map(({ to, from, displayName }) => ({
|
||||
id: `convert-${from}-${to}`,
|
||||
displayName: () => t('files', 'Save as {displayName}', { displayName }),
|
||||
iconSvgInline: () => generateIconSvg(to),
|
||||
enabled: ({ nodes }) => {
|
||||
// Check that all nodes have the same mime type
|
||||
return nodes.every((node) => from === node.mime)
|
||||
},
|
||||
|
||||
async exec({ nodes }) {
|
||||
if (!nodes[0]) {
|
||||
return false
|
||||
}
|
||||
async exec({ nodes }) {
|
||||
if (!nodes[0]) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we're here, we know that the node has a fileid
|
||||
convertFile(nodes[0].fileid as number, to)
|
||||
// If we're here, we know that the node has a fileid
|
||||
convertFile(nodes[0].fileid as number, to)
|
||||
|
||||
// Silently terminate, we'll handle the UI in the background
|
||||
return null
|
||||
},
|
||||
// Silently terminate, we'll handle the UI in the background
|
||||
return null
|
||||
},
|
||||
|
||||
async execBatch({ nodes }) {
|
||||
const fileIds = nodes.map((node) => node.fileid).filter(Boolean) as number[]
|
||||
convertFiles(fileIds, to)
|
||||
async execBatch({ nodes }) {
|
||||
const fileIds = nodes.map((node) => node.fileid).filter(Boolean) as number[]
|
||||
convertFiles(fileIds, to)
|
||||
|
||||
// Silently terminate, we'll handle the UI in the background
|
||||
return Array(nodes.length).fill(null)
|
||||
},
|
||||
// Silently terminate, we'll handle the UI in the background
|
||||
return Array(nodes.length).fill(null)
|
||||
},
|
||||
|
||||
parent: ACTION_CONVERT,
|
||||
})
|
||||
})
|
||||
parent: ACTION_CONVERT,
|
||||
} satisfies IFileAction))
|
||||
|
||||
// Register main action
|
||||
registerFileAction(new FileAction({
|
||||
registerFileAction({
|
||||
id: ACTION_CONVERT,
|
||||
displayName: () => t('files', 'Save as …'),
|
||||
iconSvgInline: () => AutoRenewSvg,
|
||||
|
|
@ -69,15 +70,16 @@ export function registerConvertActions() {
|
|||
return null
|
||||
},
|
||||
order: 25,
|
||||
}))
|
||||
} satisfies IFileAction)
|
||||
|
||||
// Register sub actions
|
||||
actions.forEach(registerFileAction)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an SVG icon for a given mime type by using the server's mime icon endpoint.
|
||||
*
|
||||
* @param mime
|
||||
* @param mime - The mime type to generate the icon for
|
||||
*/
|
||||
export function generateIconSvg(mime: string) {
|
||||
// Generate icon based on mime type
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import type { View } from '@nextcloud/files'
|
||||
|
||||
import type { IView } from '@nextcloud/files'
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import * as capabilities from '@nextcloud/capabilities'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import { File, FileAction, Folder, Permission } from '@nextcloud/files'
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import logger from '../logger.ts'
|
||||
import { action } from './deleteAction.ts'
|
||||
|
|
@ -20,12 +21,12 @@ vi.mock('@nextcloud/capabilities')
|
|||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
const trashbinView = {
|
||||
id: 'trashbin',
|
||||
name: 'Trashbin',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
describe('Delete action conditions tests', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -90,7 +91,6 @@ describe('Delete action conditions tests', () => {
|
|||
})
|
||||
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('delete')
|
||||
expect(action.displayName({
|
||||
nodes: [file],
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import CloseSvg from '@mdi/svg/svg/close.svg?raw'
|
||||
import NetworkOffSvg from '@mdi/svg/svg/network-off.svg?raw'
|
||||
import TrashCanSvg from '@mdi/svg/svg/trash-can-outline.svg?raw'
|
||||
import { FileAction, Permission } from '@nextcloud/files'
|
||||
import { Permission } from '@nextcloud/files'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import PQueue from 'p-queue'
|
||||
|
|
@ -20,7 +23,7 @@ const queue = new PQueue({ concurrency: 5 })
|
|||
|
||||
export const ACTION_DELETE = 'delete'
|
||||
|
||||
export const action = new FileAction({
|
||||
export const action: IFileAction = {
|
||||
id: ACTION_DELETE,
|
||||
displayName,
|
||||
iconSvgInline: ({ nodes }) => {
|
||||
|
|
@ -117,4 +120,4 @@ export const action = new FileAction({
|
|||
description: t('files', 'Delete'),
|
||||
key: 'Delete',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { View } from '@nextcloud/files'
|
||||
import type { IView } from '@nextcloud/files'
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import * as dialogs from '@nextcloud/dialogs'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import { DefaultType, File, FileAction, Folder, Permission } from '@nextcloud/files'
|
||||
import { DefaultType, File, Folder, Permission } from '@nextcloud/files'
|
||||
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import { action } from './downloadAction.ts'
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ vi.mock('@nextcloud/event-bus')
|
|||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
// Mock webroot variable
|
||||
beforeAll(() => {
|
||||
|
|
@ -28,7 +28,6 @@ beforeAll(() => {
|
|||
|
||||
describe('Download action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('download')
|
||||
expect(action.displayName({
|
||||
nodes: [],
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Node, View } from '@nextcloud/files'
|
||||
import type { IFileAction, INode, IView } from '@nextcloud/files'
|
||||
|
||||
import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { DefaultType, FileAction, FileType } from '@nextcloud/files'
|
||||
import { DefaultType, FileType } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import logger from '../logger.ts'
|
||||
import { useFilesStore } from '../store/files.ts'
|
||||
|
|
@ -17,112 +17,7 @@ import { getPinia } from '../store/index.ts'
|
|||
import { usePathsStore } from '../store/paths.ts'
|
||||
import { isDownloadable } from '../utils/permissions.ts'
|
||||
|
||||
/**
|
||||
* Trigger downloading a file.
|
||||
*
|
||||
* @param url The url of the asset to download
|
||||
* @param name Optionally the recommended name of the download (browsers might ignore it)
|
||||
*/
|
||||
async function triggerDownload(url: string, name?: string) {
|
||||
// try to see if the resource is still available
|
||||
await axios.head(url)
|
||||
|
||||
const hiddenElement = document.createElement('a')
|
||||
hiddenElement.download = name ?? ''
|
||||
hiddenElement.href = url
|
||||
hiddenElement.click()
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the longest common path prefix of both input paths
|
||||
*
|
||||
* @param first The first path
|
||||
* @param second The second path
|
||||
*/
|
||||
function longestCommonPath(first: string, second: string): string {
|
||||
const firstSegments = first.split('/').filter(Boolean)
|
||||
const secondSegments = second.split('/').filter(Boolean)
|
||||
let base = ''
|
||||
for (const [index, segment] of firstSegments.entries()) {
|
||||
if (index >= second.length) {
|
||||
break
|
||||
}
|
||||
if (segment !== secondSegments[index]) {
|
||||
break
|
||||
}
|
||||
const sep = base === '' ? '' : '/'
|
||||
base = `${base}${sep}${segment}`
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the given nodes.
|
||||
*
|
||||
* If only one node is given, it will be downloaded directly.
|
||||
* If multiple nodes are given, they will be zipped and downloaded.
|
||||
*
|
||||
* @param nodes The node(s) to download
|
||||
*/
|
||||
async function downloadNodes(nodes: Node[]) {
|
||||
let url: URL
|
||||
|
||||
if (!nodes[0]) {
|
||||
throw new Error('No nodes to download')
|
||||
}
|
||||
|
||||
if (nodes.length === 1) {
|
||||
if (nodes[0].type === FileType.File) {
|
||||
await triggerDownload(nodes[0].encodedSource, nodes[0].displayname)
|
||||
return
|
||||
} else {
|
||||
url = new URL(nodes[0].encodedSource)
|
||||
url.searchParams.append('accept', 'zip')
|
||||
}
|
||||
} else {
|
||||
url = new URL(nodes[0].encodedSource)
|
||||
let base = url.pathname
|
||||
for (const node of nodes.slice(1)) {
|
||||
base = longestCommonPath(base, (new URL(node.encodedSource).pathname))
|
||||
}
|
||||
url.pathname = base
|
||||
|
||||
// The URL contains the path encoded so we need to decode as the query.append will re-encode it
|
||||
const filenames = nodes.map((node) => decodeURIComponent(node.encodedSource.slice(url.href.length + 1)))
|
||||
url.searchParams.append('accept', 'zip')
|
||||
url.searchParams.append('files', JSON.stringify(filenames))
|
||||
}
|
||||
|
||||
if (url.pathname.at(-1) !== '/') {
|
||||
url.pathname = `${url.pathname}/`
|
||||
}
|
||||
|
||||
await triggerDownload(url.href)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current directory node for the given view and path.
|
||||
* TODO: ideally the folder would directly be passed as exec params
|
||||
*
|
||||
* @param view The current view
|
||||
* @param directory The directory path
|
||||
* @return The current directory node or null if not found
|
||||
*/
|
||||
function getCurrentDirectory(view: View, directory: string): Node | null {
|
||||
const filesStore = useFilesStore(getPinia())
|
||||
const pathsStore = usePathsStore(getPinia())
|
||||
if (!view?.id) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (directory === '/') {
|
||||
return filesStore.getRoot(view.id) || null
|
||||
}
|
||||
const fileId = pathsStore.getPath(view.id, directory)!
|
||||
return filesStore.getNode(fileId) || null
|
||||
}
|
||||
|
||||
export const action = new FileAction({
|
||||
export const action: IFileAction = {
|
||||
id: 'download',
|
||||
default: DefaultType.DEFAULT,
|
||||
|
||||
|
|
@ -172,4 +67,109 @@ export const action = new FileAction({
|
|||
},
|
||||
|
||||
order: 30,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger downloading a file.
|
||||
*
|
||||
* @param url The url of the asset to download
|
||||
* @param name Optionally the recommended name of the download (browsers might ignore it)
|
||||
*/
|
||||
async function triggerDownload(url: string, name?: string) {
|
||||
// try to see if the resource is still available
|
||||
await axios.head(url)
|
||||
|
||||
const hiddenElement = document.createElement('a')
|
||||
hiddenElement.download = name ?? ''
|
||||
hiddenElement.href = url
|
||||
hiddenElement.click()
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the longest common path prefix of both input paths
|
||||
*
|
||||
* @param first The first path
|
||||
* @param second The second path
|
||||
*/
|
||||
function longestCommonPath(first: string, second: string): string {
|
||||
const firstSegments = first.split('/').filter(Boolean)
|
||||
const secondSegments = second.split('/').filter(Boolean)
|
||||
let base = ''
|
||||
for (const [index, segment] of firstSegments.entries()) {
|
||||
if (index >= second.length) {
|
||||
break
|
||||
}
|
||||
if (segment !== secondSegments[index]) {
|
||||
break
|
||||
}
|
||||
const sep = base === '' ? '' : '/'
|
||||
base = `${base}${sep}${segment}`
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the given nodes.
|
||||
*
|
||||
* If only one node is given, it will be downloaded directly.
|
||||
* If multiple nodes are given, they will be zipped and downloaded.
|
||||
*
|
||||
* @param nodes The node(s) to download
|
||||
*/
|
||||
async function downloadNodes(nodes: INode[]) {
|
||||
let url: URL
|
||||
|
||||
if (!nodes[0]) {
|
||||
throw new Error('No nodes to download')
|
||||
}
|
||||
|
||||
if (nodes.length === 1) {
|
||||
if (nodes[0].type === FileType.File) {
|
||||
await triggerDownload(nodes[0].encodedSource, nodes[0].displayname)
|
||||
return
|
||||
} else {
|
||||
url = new URL(nodes[0].encodedSource)
|
||||
url.searchParams.append('accept', 'zip')
|
||||
}
|
||||
} else {
|
||||
url = new URL(nodes[0].encodedSource)
|
||||
let base = url.pathname
|
||||
for (const node of nodes.slice(1)) {
|
||||
base = longestCommonPath(base, (new URL(node.encodedSource).pathname))
|
||||
}
|
||||
url.pathname = base
|
||||
|
||||
// The URL contains the path encoded so we need to decode as the query.append will re-encode it
|
||||
const filenames = nodes.map((node) => decodeURIComponent(node.encodedSource.slice(url.href.length + 1)))
|
||||
url.searchParams.append('accept', 'zip')
|
||||
url.searchParams.append('files', JSON.stringify(filenames))
|
||||
}
|
||||
|
||||
if (url.pathname.at(-1) !== '/') {
|
||||
url.pathname = `${url.pathname}/`
|
||||
}
|
||||
|
||||
await triggerDownload(url.href)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current directory node for the given view and path.
|
||||
* TODO: ideally the folder would directly be passed as exec params
|
||||
*
|
||||
* @param view The current view
|
||||
* @param directory The directory path
|
||||
* @return The current directory node or null if not found
|
||||
*/
|
||||
function getCurrentDirectory(view: IView, directory: string): INode | null {
|
||||
const filesStore = useFilesStore(getPinia())
|
||||
const pathsStore = usePathsStore(getPinia())
|
||||
if (!view?.id) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (directory === '/') {
|
||||
return filesStore.getRoot(view.id) || null
|
||||
}
|
||||
const fileId = pathsStore.getPath(view.id, directory)!
|
||||
return filesStore.getNode(fileId) || null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Folder, View } from '@nextcloud/files'
|
||||
import type { IFolder, IView } from '@nextcloud/files'
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import { File, FileAction, Permission } from '@nextcloud/files'
|
||||
import { File, Permission } from '@nextcloud/files'
|
||||
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import logger from '../logger.ts'
|
||||
import { action } from './favoriteAction.ts'
|
||||
|
|
@ -19,12 +19,12 @@ vi.mock('@nextcloud/axios')
|
|||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
const favoriteView = {
|
||||
id: 'favorites',
|
||||
name: 'Favorites',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
// Mock webroot variable
|
||||
beforeAll(() => {
|
||||
|
|
@ -46,18 +46,17 @@ describe('Favorite action conditions tests', () => {
|
|||
root: '/files/admin',
|
||||
})
|
||||
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('favorite')
|
||||
expect(action.displayName({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe('Add to favorites')
|
||||
expect(action.iconSvgInline({
|
||||
nodes: [],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toMatch(/<svg.+<\/svg>/)
|
||||
expect(action.default).toBeUndefined()
|
||||
|
|
@ -79,7 +78,7 @@ describe('Favorite action conditions tests', () => {
|
|||
expect(action.displayName({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe('Remove from favorites')
|
||||
})
|
||||
|
|
@ -122,25 +121,25 @@ describe('Favorite action conditions tests', () => {
|
|||
expect(action.displayName({
|
||||
nodes: [file1, file2, file3],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe('Add to favorites')
|
||||
expect(action.displayName({
|
||||
nodes: [file2, file3],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe('Add to favorites')
|
||||
expect(action.displayName({
|
||||
nodes: [file2, file3],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe('Add to favorites')
|
||||
expect(action.displayName({
|
||||
nodes: [file1, file3],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe('Remove from favorites')
|
||||
})
|
||||
|
|
@ -161,7 +160,7 @@ describe('Favorite action enabled tests', () => {
|
|||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe(true)
|
||||
})
|
||||
|
|
@ -179,7 +178,7 @@ describe('Favorite action enabled tests', () => {
|
|||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe(false)
|
||||
})
|
||||
|
|
@ -205,7 +204,7 @@ describe('Favorite action execute tests', () => {
|
|||
const exec = await action.exec({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
|
|
@ -239,7 +238,7 @@ describe('Favorite action execute tests', () => {
|
|||
const exec = await action.exec({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
|
|
@ -273,7 +272,7 @@ describe('Favorite action execute tests', () => {
|
|||
const exec = await action.exec({
|
||||
nodes: [file],
|
||||
view: favoriteView,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
|
|
@ -308,7 +307,7 @@ describe('Favorite action execute tests', () => {
|
|||
const exec = await action.exec({
|
||||
nodes: [file],
|
||||
view: favoriteView,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
|
|
@ -345,7 +344,7 @@ describe('Favorite action execute tests', () => {
|
|||
const exec = await action.exec({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
|
|
@ -383,7 +382,7 @@ describe('Favorite action execute tests', () => {
|
|||
const exec = await action.exec({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
|
|
@ -437,7 +436,7 @@ describe('Favorite action batch execute tests', () => {
|
|||
const exec = await action.execBatch!({
|
||||
nodes: [file1, file2],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
expect(exec).toStrictEqual([true, true])
|
||||
|
|
@ -479,7 +478,7 @@ describe('Favorite action batch execute tests', () => {
|
|||
const exec = await action.execBatch!({
|
||||
nodes: [file1, file2],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
expect(exec).toStrictEqual([true, true])
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { INode, IView } from '@nextcloud/files'
|
||||
import type { IFileAction, INode, IView } from '@nextcloud/files'
|
||||
|
||||
import StarOutlineSvg from '@mdi/svg/svg/star-outline.svg?raw'
|
||||
import StarSvg from '@mdi/svg/svg/star.svg?raw'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { FileAction, Permission } from '@nextcloud/files'
|
||||
import { Permission } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { encodePath } from '@nextcloud/paths'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
|
@ -18,63 +18,11 @@ import PQueue from 'p-queue'
|
|||
import Vue from 'vue'
|
||||
import logger from '../logger.ts'
|
||||
|
||||
export const ACTION_FAVORITE = 'favorite'
|
||||
|
||||
const queue = new PQueue({ concurrency: 5 })
|
||||
|
||||
/**
|
||||
* If any of the nodes is not favorited, we display the favorite action.
|
||||
*
|
||||
* @param nodes - The nodes to check
|
||||
*/
|
||||
function shouldFavorite(nodes: INode[]): boolean {
|
||||
return nodes.some((node) => node.attributes.favorite !== 1)
|
||||
}
|
||||
export const ACTION_FAVORITE = 'favorite'
|
||||
|
||||
/**
|
||||
* Favorite or unfavorite a node
|
||||
*
|
||||
* @param node - The node to favorite/unfavorite
|
||||
* @param view - The current view
|
||||
* @param willFavorite - Whether to favorite or unfavorite the node
|
||||
*/
|
||||
export async function favoriteNode(node: INode, view: IView, willFavorite: boolean): Promise<boolean> {
|
||||
try {
|
||||
// TODO: migrate to webdav tags plugin
|
||||
const url = generateUrl('/apps/files/api/v1/files') + encodePath(node.path)
|
||||
await axios.post(url, {
|
||||
tags: willFavorite
|
||||
? [window.OC.TAG_FAVORITE]
|
||||
: [],
|
||||
})
|
||||
|
||||
// Let's delete if we are in the favourites view
|
||||
// AND if it is removed from the user favorites
|
||||
// AND it's in the root of the favorites view
|
||||
if (view.id === 'favorites' && !willFavorite && node.dirname === '/') {
|
||||
emit('files:node:deleted', node)
|
||||
}
|
||||
|
||||
// Update the node webdav attribute
|
||||
Vue.set(node.attributes, 'favorite', willFavorite ? 1 : 0)
|
||||
emit('files:node:updated', node)
|
||||
|
||||
// Dispatch event to whoever is interested
|
||||
if (willFavorite) {
|
||||
emit('files:favorites:added', node)
|
||||
} else {
|
||||
emit('files:favorites:removed', node)
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
const action = willFavorite ? 'adding a file to favourites' : 'removing a file from favourites'
|
||||
logger.error('Error while ' + action, { error, source: node.source, node })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const action = new FileAction({
|
||||
export const action: IFileAction = {
|
||||
id: ACTION_FAVORITE,
|
||||
displayName({ nodes }) {
|
||||
return shouldFavorite(nodes)
|
||||
|
|
@ -132,4 +80,56 @@ export const action = new FileAction({
|
|||
description: t('files', 'Add or remove favorite'),
|
||||
key: 'S',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Favorite or unfavorite a node
|
||||
*
|
||||
* @param node - The node to favorite/unfavorite
|
||||
* @param view - The current view
|
||||
* @param willFavorite - Whether to favorite or unfavorite the node
|
||||
*/
|
||||
export async function favoriteNode(node: INode, view: IView, willFavorite: boolean): Promise<boolean> {
|
||||
try {
|
||||
// TODO: migrate to webdav tags plugin
|
||||
const url = generateUrl('/apps/files/api/v1/files') + encodePath(node.path)
|
||||
await axios.post(url, {
|
||||
tags: willFavorite
|
||||
? [window.OC.TAG_FAVORITE]
|
||||
: [],
|
||||
})
|
||||
|
||||
// Let's delete if we are in the favourites view
|
||||
// AND if it is removed from the user favorites
|
||||
// AND it's in the root of the favorites view
|
||||
if (view.id === 'favorites' && !willFavorite && node.dirname === '/') {
|
||||
emit('files:node:deleted', node)
|
||||
}
|
||||
|
||||
// Update the node webdav attribute
|
||||
Vue.set(node.attributes, 'favorite', willFavorite ? 1 : 0)
|
||||
emit('files:node:updated', node)
|
||||
|
||||
// Dispatch event to whoever is interested
|
||||
if (willFavorite) {
|
||||
emit('files:favorites:added', node)
|
||||
} else {
|
||||
emit('files:favorites:removed', node)
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
const action = willFavorite ? 'adding a file to favourites' : 'removing a file from favourites'
|
||||
logger.error('Error while ' + action, { error, source: node.source, node })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If any of the nodes is not favored, we display the favorite action.
|
||||
*
|
||||
* @param nodes - The nodes to check
|
||||
*/
|
||||
function shouldFavorite(nodes: INode[]): boolean {
|
||||
return nodes.some((node) => node.attributes.favorite !== 1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import type { IFilePickerButton } from '@nextcloud/dialogs'
|
||||
import type { IFolder, INode } from '@nextcloud/files'
|
||||
import type { IFileAction, IFolder, INode } from '@nextcloud/files'
|
||||
import type { FileStat, ResponseDataDetailed, WebDAVClientError } from 'webdav'
|
||||
import type { MoveCopyResult } from './moveOrCopyActionUtils.ts'
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ import CopyIconSvg from '@mdi/svg/svg/folder-multiple-outline.svg?raw'
|
|||
import { isAxiosError } from '@nextcloud/axios'
|
||||
import { FilePickerClosed, getFilePickerBuilder, openConflictPicker, showError, showLoading } from '@nextcloud/dialogs'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { FileAction, FileType, getUniqueName, NodeStatus, Permission } from '@nextcloud/files'
|
||||
import { FileType, getUniqueName, NodeStatus, Permission } from '@nextcloud/files'
|
||||
import { defaultRootPath, getClient, getDefaultPropfind, resultToNode } from '@nextcloud/files/dav'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { getConflicts } from '@nextcloud/upload'
|
||||
|
|
@ -31,7 +31,7 @@ export class HintException extends Error {}
|
|||
|
||||
export const ACTION_COPY_MOVE = 'move-copy'
|
||||
|
||||
export const action = new FileAction({
|
||||
export const action: IFileAction = {
|
||||
id: ACTION_COPY_MOVE,
|
||||
order: 15,
|
||||
displayName({ nodes }) {
|
||||
|
|
@ -84,7 +84,7 @@ export const action = new FileAction({
|
|||
return nodes.map(() => false)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the copy/move of a node to a destination
|
||||
|
|
@ -248,11 +248,11 @@ function getActionForNodes(nodes: INode[]): MoveCopyAction {
|
|||
function createLoadingNotification(mode: MoveCopyAction, sources: string[], destination: string): () => void {
|
||||
const text = mode === MoveCopyAction.MOVE
|
||||
? (sources.length === 1
|
||||
? t('files', 'Moving "{source}" to "{destination}" …', { source: sources[0], destination })
|
||||
? t('files', 'Moving "{source}" to "{destination}" …', { source: sources[0]!, destination })
|
||||
: t('files', 'Moving {count} files to "{destination}" …', { count: sources.length, destination })
|
||||
)
|
||||
: (sources.length === 1
|
||||
? t('files', 'Copying "{source}" to "{destination}" …', { source: sources[0], destination })
|
||||
? t('files', 'Copying "{source}" to "{destination}" …', { source: sources[0]!, destination })
|
||||
: t('files', 'Copying {count} files to "{destination}" …', { count: sources.length, destination })
|
||||
)
|
||||
|
||||
|
|
@ -277,7 +277,7 @@ async function openFilePickerForAction(
|
|||
const fileIDs = nodes.map((node) => node.fileid).filter(Boolean)
|
||||
const filePicker = getFilePickerBuilder(t('files', 'Choose destination'))
|
||||
.allowDirectories(true)
|
||||
.setFilter((n: INode) => {
|
||||
.setFilter((n) => {
|
||||
// We don't want to show the current nodes in the file picker
|
||||
return !fileIDs.includes(n.fileid)
|
||||
})
|
||||
|
|
@ -288,7 +288,7 @@ async function openFilePickerForAction(
|
|||
.setMimeTypeFilter([])
|
||||
.setMultiSelect(false)
|
||||
.startAt(dir)
|
||||
.setButtonFactory((selection: INode[], path: string) => {
|
||||
.setButtonFactory((selection, path) => {
|
||||
const buttons: IFilePickerButton[] = []
|
||||
const target = basename(path)
|
||||
|
||||
|
|
@ -300,9 +300,9 @@ async function openFilePickerForAction(
|
|||
label: target ? t('files', 'Copy to {target}', { target }, { escape: false, sanitize: false }) : t('files', 'Copy'),
|
||||
variant: 'primary',
|
||||
icon: CopyIconSvg,
|
||||
async callback(destination: INode[]) {
|
||||
async callback(destination) {
|
||||
resolve({
|
||||
destination: destination[0] as IFolder,
|
||||
destination: destination[0] as unknown as IFolder,
|
||||
action: MoveCopyAction.COPY,
|
||||
} as MoveCopyResult)
|
||||
},
|
||||
|
|
@ -330,9 +330,9 @@ async function openFilePickerForAction(
|
|||
label: target ? t('files', 'Move to {target}', { target }, undefined, { escape: false, sanitize: false }) : t('files', 'Move'),
|
||||
variant: action === MoveCopyAction.MOVE ? 'primary' : 'secondary',
|
||||
icon: FolderMoveSvg,
|
||||
async callback(destination: INode[]) {
|
||||
async callback(destination) {
|
||||
resolve({
|
||||
destination: destination[0] as IFolder,
|
||||
destination: destination[0] as unknown as IFolder,
|
||||
action: MoveCopyAction.MOVE,
|
||||
} as MoveCopyResult)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { View } from '@nextcloud/files'
|
||||
import type { IView } from '@nextcloud/files'
|
||||
|
||||
import { DefaultType, File, FileAction, Folder, Permission } from '@nextcloud/files'
|
||||
import { DefaultType, File, Folder, Permission } from '@nextcloud/files'
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
import { action } from './openFolderAction.ts'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
describe('Open folder action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
|
|
@ -24,7 +24,6 @@ describe('Open folder action conditions tests', () => {
|
|||
root: '/files/admin',
|
||||
})
|
||||
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('open-folder')
|
||||
expect(action.displayName({
|
||||
nodes: [folder],
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
|
||||
import { DefaultType, FileAction, FileType, Permission } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
|
||||
export const action = new FileAction({
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
|
||||
import { DefaultType, FileType, Permission } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
export const action: IFileAction = {
|
||||
id: 'open-folder',
|
||||
displayName({ nodes }) {
|
||||
if (nodes.length !== 1 || !nodes[0]) {
|
||||
|
|
@ -51,4 +54,4 @@ export const action = new FileAction({
|
|||
// Main action if enabled, meaning folders only
|
||||
default: DefaultType.HIDDEN,
|
||||
order: -100,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { View } from '@nextcloud/files'
|
||||
import type { IView } from '@nextcloud/files'
|
||||
|
||||
import { DefaultType, File, FileAction, Folder, Permission } from '@nextcloud/files'
|
||||
import { DefaultType, File, Folder, Permission } from '@nextcloud/files'
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
import { action } from './openInFilesAction.ts'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
const recentView = {
|
||||
id: 'recent',
|
||||
name: 'Recent',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
describe('Open in files action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('open-in-files')
|
||||
expect(action.displayName({
|
||||
nodes: [],
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { DefaultType, FileAction, FileType } from '@nextcloud/files'
|
||||
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import { DefaultType, FileType } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { VIEW_ID as SEARCH_VIEW_ID } from '../views/search.ts'
|
||||
|
||||
export const action = new FileAction({
|
||||
export const action: IFileAction = {
|
||||
id: 'open-in-files',
|
||||
displayName: () => t('files', 'Open in Files'),
|
||||
iconSvgInline: () => '',
|
||||
|
|
@ -36,4 +39,4 @@ export const action = new FileAction({
|
|||
// Before openFolderAction
|
||||
order: -1000,
|
||||
default: DefaultType.HIDDEN,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Folder, View } from '@nextcloud/files'
|
||||
import type { IFolder, IView } from '@nextcloud/files'
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import * as nextcloudDialogs from '@nextcloud/dialogs'
|
||||
import { File, FileAction, Permission } from '@nextcloud/files'
|
||||
import { File, Permission } from '@nextcloud/files'
|
||||
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import { action } from './openLocallyAction.ts'
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ vi.mock('@nextcloud/axios')
|
|||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
// Mock web root variable
|
||||
beforeAll(() => {
|
||||
|
|
@ -28,18 +28,17 @@ beforeAll(() => {
|
|||
|
||||
describe('Open locally action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('edit-locally')
|
||||
expect(action.displayName({
|
||||
nodes: [],
|
||||
view,
|
||||
folder: {} as any,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe('Open locally')
|
||||
expect(action.iconSvgInline({
|
||||
nodes: [],
|
||||
view,
|
||||
folder: {} as any,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toMatch(/<svg.+<\/svg>/)
|
||||
expect(action.default).toBeUndefined()
|
||||
|
|
@ -62,7 +61,7 @@ describe('Open locally action enabled tests', () => {
|
|||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as any,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe(true)
|
||||
})
|
||||
|
|
@ -80,7 +79,7 @@ describe('Open locally action enabled tests', () => {
|
|||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as any,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe(false)
|
||||
})
|
||||
|
|
@ -107,7 +106,7 @@ describe('Open locally action enabled tests', () => {
|
|||
expect(action.enabled!({
|
||||
nodes: [file1, file2],
|
||||
view,
|
||||
folder: {} as any,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe(false)
|
||||
})
|
||||
|
|
@ -125,7 +124,7 @@ describe('Open locally action enabled tests', () => {
|
|||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as any,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe(false)
|
||||
})
|
||||
|
|
@ -144,7 +143,7 @@ describe('Open locally action enabled tests', () => {
|
|||
expect(action.enabled!({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as any,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})).toBe(false)
|
||||
})
|
||||
|
|
@ -177,7 +176,7 @@ describe('Open locally action execute tests', () => {
|
|||
const exec = await action.exec({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
|
|
@ -207,7 +206,7 @@ describe('Open locally action execute tests', () => {
|
|||
const exec = await action.exec({
|
||||
nodes: [file],
|
||||
view,
|
||||
folder: {} as Folder,
|
||||
folder: {} as IFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import LaptopSvg from '@mdi/svg/svg/laptop.svg?raw'
|
||||
import IconWeb from '@mdi/svg/svg/web.svg?raw'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { DialogBuilder, showError } from '@nextcloud/dialogs'
|
||||
import { FileAction } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { encodePath } from '@nextcloud/paths'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
|
|
@ -15,7 +17,7 @@ import { isPublicShare } from '@nextcloud/sharing/public'
|
|||
import logger from '../logger.ts'
|
||||
import { isSyncable } from '../utils/permissions.ts'
|
||||
|
||||
export const action = new FileAction({
|
||||
export const action: IFileAction = {
|
||||
id: 'edit-locally',
|
||||
displayName: () => t('files', 'Open locally'),
|
||||
iconSvgInline: () => LaptopSvg,
|
||||
|
|
@ -32,7 +34,7 @@ export const action = new FileAction({
|
|||
return false
|
||||
}
|
||||
|
||||
return isSyncable(nodes[0])
|
||||
return isSyncable(nodes[0]!)
|
||||
},
|
||||
|
||||
async exec({ nodes }) {
|
||||
|
|
@ -41,7 +43,7 @@ export const action = new FileAction({
|
|||
},
|
||||
|
||||
order: 25,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open the path in the Nextcloud client.
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { View } from '@nextcloud/files'
|
||||
import type { IView } from '@nextcloud/files'
|
||||
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import { File, FileAction, Folder, Permission } from '@nextcloud/files'
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import { useFilesStore } from '../store/files.ts'
|
||||
import { getPinia } from '../store/index.ts'
|
||||
|
|
@ -15,7 +15,7 @@ import { action } from './renameAction.ts'
|
|||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
beforeEach(() => {
|
||||
const root = new Folder({
|
||||
|
|
@ -31,7 +31,6 @@ beforeEach(() => {
|
|||
|
||||
describe('Rename action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('rename')
|
||||
expect(action.displayName({
|
||||
nodes: [],
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import PencilSvg from '@mdi/svg/svg/pencil-outline.svg?raw'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { FileAction, Permission } from '@nextcloud/files'
|
||||
import { Permission } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { dirname } from 'path'
|
||||
import { useFilesStore } from '../store/files.ts'
|
||||
|
|
@ -12,7 +15,7 @@ import { getPinia } from '../store/index.ts'
|
|||
|
||||
export const ACTION_RENAME = 'rename'
|
||||
|
||||
export const action = new FileAction({
|
||||
export const action: IFileAction = {
|
||||
id: ACTION_RENAME,
|
||||
displayName: () => t('files', 'Rename'),
|
||||
iconSvgInline: () => PencilSvg,
|
||||
|
|
@ -52,4 +55,4 @@ export const action = new FileAction({
|
|||
description: t('files', 'Rename'),
|
||||
key: 'F2',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { IView } from '@nextcloud/files'
|
||||
|
||||
import { File, FileAction, Folder, Permission } from '@nextcloud/files'
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import logger from '../logger.ts'
|
||||
import { action } from './sidebarAction.ts'
|
||||
|
|
@ -32,7 +32,6 @@ beforeEach(() => {
|
|||
|
||||
describe('Open sidebar action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('details')
|
||||
expect(action.displayName({
|
||||
nodes: [],
|
||||
|
|
|
|||
|
|
@ -3,15 +3,17 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import InformationSvg from '@mdi/svg/svg/information-outline.svg?raw'
|
||||
import { FileAction, getSidebar, Permission } from '@nextcloud/files'
|
||||
import { getSidebar, Permission } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { isPublicShare } from '@nextcloud/sharing/public'
|
||||
import logger from '../logger.ts'
|
||||
|
||||
export const ACTION_DETAILS = 'details'
|
||||
|
||||
export const action = new FileAction({
|
||||
export const action: IFileAction = {
|
||||
id: ACTION_DETAILS,
|
||||
displayName: () => t('files', 'Details'),
|
||||
iconSvgInline: () => InformationSvg,
|
||||
|
|
@ -59,4 +61,4 @@ export const action = new FileAction({
|
|||
key: 'D',
|
||||
description: t('files', 'Open the details sidebar'),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import type { View } from '@nextcloud/files'
|
||||
|
||||
import { File, FileAction, Folder, Permission } from '@nextcloud/files'
|
||||
import type { IView } from '@nextcloud/files'
|
||||
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
import { action } from './viewInFolderAction.ts'
|
||||
|
||||
const view = {
|
||||
id: 'trashbin',
|
||||
name: 'Trashbin',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
const viewFiles = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as View
|
||||
} as IView
|
||||
|
||||
describe('View in folder action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('view-in-folder')
|
||||
expect(action.displayName({
|
||||
nodes: [],
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import FolderMoveSvg from '@mdi/svg/svg/folder-move-outline.svg?raw'
|
||||
import { FileAction, FileType, Permission } from '@nextcloud/files'
|
||||
import { FileType, Permission } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { isPublicShare } from '@nextcloud/sharing/public'
|
||||
|
||||
export const action = new FileAction({
|
||||
export const action: IFileAction = {
|
||||
id: 'view-in-folder',
|
||||
displayName() {
|
||||
return t('files', 'View in folder')
|
||||
|
|
@ -61,4 +64,4 @@ export const action = new FileAction({
|
|||
},
|
||||
|
||||
order: 80,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { FileAction, Folder, Node, View } from '@nextcloud/files'
|
||||
import type { IFileAction, IFolder, INode, IView } from '@nextcloud/files'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
type RenderFunction = typeof FileAction.prototype.renderInline
|
||||
type RenderFunction = IFileAction['renderInline']
|
||||
|
||||
/**
|
||||
* This component is used to render custom
|
||||
|
|
@ -22,17 +22,17 @@ export default {
|
|||
name: 'CustomElementRender',
|
||||
props: {
|
||||
source: {
|
||||
type: Object as PropType<Node>,
|
||||
type: Object as PropType<INode>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
activeView: {
|
||||
type: Object as PropType<View>,
|
||||
type: Object as PropType<IView>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
activeFolder: {
|
||||
type: Object as PropType<Folder>,
|
||||
type: Object as PropType<IFolder>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { ActionContextSingle, FileAction, Node } from '@nextcloud/files'
|
||||
import type { ActionContextSingle, IFileAction, Node } from '@nextcloud/files'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import { DefaultType, NodeStatus } from '@nextcloud/files'
|
||||
|
|
@ -179,7 +179,7 @@ export default defineComponent({
|
|||
// The file list is guaranteed to be shown with active view - thus we can set the `loaded` flag
|
||||
const activeStore = useActiveStore()
|
||||
const { isNarrow } = useFileListWidth()
|
||||
const enabledFileActions = inject<FileAction[]>('enabledFileActions', [])
|
||||
const enabledFileActions = inject<IFileAction[]>('enabledFileActions', [])
|
||||
return {
|
||||
activeStore,
|
||||
enabledFileActions,
|
||||
|
|
@ -302,7 +302,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
methods: {
|
||||
actionDisplayName(action: FileAction) {
|
||||
actionDisplayName(action: IFileAction) {
|
||||
try {
|
||||
if ((this.gridMode || (this.isNarrow && action.inline)) && typeof action.title === 'function') {
|
||||
// if an inline action is rendered in the menu for
|
||||
|
|
@ -320,7 +320,7 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
|
||||
isLoadingAction(action: FileAction) {
|
||||
isLoadingAction(action: IFileAction) {
|
||||
if (!this.isActive) {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { FileAction, Node, TFileType } from '@nextcloud/files'
|
||||
import type { IFileAction, Node, TFileType } from '@nextcloud/files'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
|
|
@ -92,7 +92,7 @@ export default defineComponent({
|
|||
const userConfigStore = useUserConfigStore()
|
||||
const { activeFolder, activeView } = useActiveStore()
|
||||
|
||||
const defaultFileAction = inject<FileAction | undefined>('defaultFileAction')
|
||||
const defaultFileAction = inject<IFileAction | undefined>('defaultFileAction')
|
||||
|
||||
return {
|
||||
activeFolder,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { FileAction } from '@nextcloud/files'
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
import type { PropType } from 'vue'
|
||||
import type { FileSource } from '../types.ts'
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
return actions
|
||||
.filter((action: FileAction) => {
|
||||
.filter((action: IFileAction) => {
|
||||
if (!action.enabled) {
|
||||
return true
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
defaultFileAction() {
|
||||
return this.enabledFileActions.find((action: FileAction) => action.default !== undefined)
|
||||
return this.enabledFileActions.find((action: IFileAction) => action.default !== undefined)
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { ActionContext, FileAction, Node, View } from '@nextcloud/files'
|
||||
import type { ActionContext, IFileAction, Node, View } from '@nextcloud/files'
|
||||
import type { PropType } from 'vue'
|
||||
import type { FileSource } from '../types.ts'
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
computed: {
|
||||
enabledFileActions(): FileAction[] {
|
||||
enabledFileActions(): IFileAction[] {
|
||||
return actions
|
||||
// We don't handle renderInline actions in this component
|
||||
.filter((action) => !action.renderInline)
|
||||
|
|
@ -178,7 +178,7 @@ export default defineComponent({
|
|||
* This means that they are not within a menu, nor
|
||||
* being the parent of submenu actions.
|
||||
*/
|
||||
enabledInlineActions(): FileAction[] {
|
||||
enabledInlineActions(): IFileAction[] {
|
||||
return this.enabledFileActions
|
||||
// Remove all actions that are not top-level actions
|
||||
.filter((action) => action.parent === undefined)
|
||||
|
|
@ -194,7 +194,7 @@ export default defineComponent({
|
|||
* Return the rest of enabled actions that are not
|
||||
* rendered inlined.
|
||||
*/
|
||||
enabledMenuActions(): FileAction[] {
|
||||
enabledMenuActions(): IFileAction[] {
|
||||
// If we're in a submenu, only render the inline
|
||||
// actions before the filtered submenu
|
||||
if (this.openedSubmenu) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { FileAction } from '@nextcloud/files'
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
|
|
@ -11,23 +11,23 @@ export default defineComponent({
|
|||
|
||||
data() {
|
||||
return {
|
||||
openedSubmenu: null as FileAction | null,
|
||||
openedSubmenu: null as IFileAction | null,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
enabledSubmenuActions(): Record<string, FileAction[]> {
|
||||
return (this.enabledFileActions as FileAction[])
|
||||
enabledSubmenuActions(): Record<string, IFileAction[]> {
|
||||
return (this.enabledFileActions as IFileAction[])
|
||||
.reduce((record, action) => {
|
||||
if (action.parent !== undefined) {
|
||||
if (!record[action.parent]) {
|
||||
record[action.parent] = []
|
||||
}
|
||||
|
||||
record[action.parent].push(action)
|
||||
record[action.parent]!.push(action)
|
||||
}
|
||||
return record
|
||||
}, {} as Record<string, FileAction[]>)
|
||||
}, {} as Record<string, IFileAction[]>)
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -38,11 +38,11 @@ export default defineComponent({
|
|||
*
|
||||
* @param action The action to check
|
||||
*/
|
||||
isValidMenu(action: FileAction): boolean {
|
||||
isValidMenu(action: IFileAction): boolean {
|
||||
return this.enabledSubmenuActions[action.id]?.length > 0
|
||||
},
|
||||
|
||||
async onBackToMenuClick(action: FileAction | null) {
|
||||
async onBackToMenuClick(action: IFileAction | null) {
|
||||
if (!action) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { FileAction, IFolder, INode, IView } from '@nextcloud/files'
|
||||
import type { IFileAction, IFolder, INode, IView } from '@nextcloud/files'
|
||||
|
||||
import { subscribe } from '@nextcloud/event-bus'
|
||||
import { getNavigation } from '@nextcloud/files'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, shallowRef, watch } from 'vue'
|
||||
import logger from '../logger.ts'
|
||||
|
||||
export const useActiveStore = defineStore('active', () => {
|
||||
/**
|
||||
* The currently active action
|
||||
*/
|
||||
const activeAction = ref<FileAction>()
|
||||
const activeAction = shallowRef<IFileAction>()
|
||||
|
||||
/**
|
||||
* The currently active folder
|
||||
|
|
@ -30,7 +30,7 @@ export const useActiveStore = defineStore('active', () => {
|
|||
/**
|
||||
* The current active view
|
||||
*/
|
||||
const activeView = ref<IView>()
|
||||
const activeView = shallowRef<IView>()
|
||||
|
||||
// Set the active node on the router params
|
||||
watch(activeNode, () => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
/**
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import type { FileAction, Folder, Node, View } from '@nextcloud/files'
|
||||
|
||||
import type { IFileAction, IFolder, INode, IView } from '@nextcloud/files'
|
||||
import type { Upload } from '@nextcloud/upload'
|
||||
|
||||
// Global definitions
|
||||
|
|
@ -12,11 +13,11 @@ export type ViewId = string
|
|||
|
||||
// Files store
|
||||
export type FilesStore = {
|
||||
[source: FileSource]: Node
|
||||
[source: FileSource]: INode
|
||||
}
|
||||
|
||||
export type RootsStore = {
|
||||
[service: Service]: Folder
|
||||
[service: Service]: IFolder
|
||||
}
|
||||
|
||||
export type FilesState = {
|
||||
|
|
@ -25,7 +26,7 @@ export type FilesState = {
|
|||
}
|
||||
|
||||
export interface RootOptions {
|
||||
root: Folder
|
||||
root: IFolder
|
||||
service: Service
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +96,7 @@ export interface ViewConfigStore {
|
|||
|
||||
// Renaming store
|
||||
export interface RenamingStore {
|
||||
renamingNode?: Node
|
||||
renamingNode?: INode
|
||||
newName: string
|
||||
}
|
||||
|
||||
|
|
@ -111,10 +112,10 @@ export interface DragAndDropStore {
|
|||
|
||||
// Active node store
|
||||
export interface ActiveStore {
|
||||
activeAction: FileAction | null
|
||||
activeFolder: Folder | null
|
||||
activeNode: Node | null
|
||||
activeView: View | null
|
||||
activeAction: IFileAction | null
|
||||
activeFolder: IFolder | null
|
||||
activeNode: INode | null
|
||||
activeView: IView | null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import type { ActionContextSingle, FileAction } from '@nextcloud/files'
|
||||
import type { ActionContextSingle, IFileAction } from '@nextcloud/files'
|
||||
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { NodeStatus } from '@nextcloud/files'
|
||||
|
|
@ -16,7 +16,7 @@ import { useActiveStore } from '../store/active.ts'
|
|||
*
|
||||
* @param action The action to execute
|
||||
*/
|
||||
export async function executeAction(action: FileAction) {
|
||||
export async function executeAction(action: IFileAction) {
|
||||
const activeStore = useActiveStore()
|
||||
const currentFolder = activeStore.activeFolder
|
||||
const currentNode = activeStore.activeNode
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import type { Node } from '@nextcloud/files'
|
||||
|
||||
import type { INode } from '@nextcloud/files'
|
||||
import type { ShareAttribute } from '../../../files_sharing/src/sharing.ts'
|
||||
|
||||
import { Permission } from '@nextcloud/files'
|
||||
|
|
@ -13,7 +14,7 @@ import { Permission } from '@nextcloud/files'
|
|||
* @param node The node to check
|
||||
* @return True if downloadable, false otherwise
|
||||
*/
|
||||
export function isDownloadable(node: Node): boolean {
|
||||
export function isDownloadable(node: INode): boolean {
|
||||
if ((node.permissions & Permission.READ) === 0) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -43,7 +44,7 @@ export function isDownloadable(node: Node): boolean {
|
|||
* @param node The node to check
|
||||
* @return True if syncable, false otherwise
|
||||
*/
|
||||
export function isSyncable(node: Node): boolean {
|
||||
export function isSyncable(node: INode): boolean {
|
||||
if (!node.isDavResource) {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
*/
|
||||
|
||||
import type { User } from '@nextcloud/e2e-test-server/cypress'
|
||||
import type { IFileAction } from '@nextcloud/files'
|
||||
|
||||
import { FileAction } from '@nextcloud/files'
|
||||
import { getActionButtonForFileId, getActionEntryForFileId, getRowForFile, getSelectionActionButton, getSelectionActionEntry, selectRowForFile } from './FilesUtils.ts'
|
||||
|
||||
const ACTION_DELETE = 'delete'
|
||||
|
|
@ -14,7 +14,7 @@ const ACTION_DETAILS = 'details'
|
|||
|
||||
declare global {
|
||||
interface Window {
|
||||
_nc_fileactions: FileAction[]
|
||||
_nc_fileactions: IFileAction[]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,28 +58,28 @@ describe('Files: Actions', { testIsolation: true }, () => {
|
|||
})
|
||||
|
||||
it('Show some nested actions', () => {
|
||||
const parent = new FileAction({
|
||||
const parent: IFileAction = {
|
||||
id: 'nested-action',
|
||||
displayName: () => 'Nested Action',
|
||||
exec: cy.spy(),
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
})
|
||||
}
|
||||
|
||||
const child1 = new FileAction({
|
||||
const child1: IFileAction = {
|
||||
id: 'nested-child-1',
|
||||
displayName: () => 'Nested Child 1',
|
||||
exec: cy.spy(),
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
parent: 'nested-action',
|
||||
})
|
||||
}
|
||||
|
||||
const child2 = new FileAction({
|
||||
const child2: IFileAction = {
|
||||
id: 'nested-child-2',
|
||||
displayName: () => 'Nested Child 2',
|
||||
exec: cy.spy(),
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
parent: 'nested-action',
|
||||
})
|
||||
}
|
||||
|
||||
cy.visit('/apps/files', {
|
||||
// Cannot use registerFileAction here
|
||||
|
|
@ -147,30 +147,30 @@ describe('Files: Actions', { testIsolation: true }, () => {
|
|||
})
|
||||
|
||||
it('Show some nested actions for a selection', () => {
|
||||
const parent = new FileAction({
|
||||
const parent: IFileAction = {
|
||||
id: 'nested-action',
|
||||
displayName: () => 'Nested Action',
|
||||
exec: cy.spy(),
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
})
|
||||
}
|
||||
|
||||
const child1 = new FileAction({
|
||||
const child1: IFileAction = {
|
||||
id: 'nested-child-1',
|
||||
displayName: () => 'Nested Child 1',
|
||||
exec: cy.spy(),
|
||||
execBatch: cy.spy(),
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
parent: 'nested-action',
|
||||
})
|
||||
}
|
||||
|
||||
const child2 = new FileAction({
|
||||
const child2: IFileAction = {
|
||||
id: 'nested-child-2',
|
||||
displayName: () => 'Nested Child 2',
|
||||
exec: cy.spy(),
|
||||
execBatch: cy.spy(),
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
parent: 'nested-action',
|
||||
})
|
||||
}
|
||||
|
||||
cy.visit('/apps/files', {
|
||||
// Cannot use registerFileAction here
|
||||
|
|
@ -220,28 +220,28 @@ describe('Files: Actions', { testIsolation: true }, () => {
|
|||
})
|
||||
|
||||
it('Do not show parent if nested action has no batch support', () => {
|
||||
const parent = new FileAction({
|
||||
const parent: IFileAction = {
|
||||
id: 'nested-action',
|
||||
displayName: () => 'Nested Action',
|
||||
exec: cy.spy(),
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
})
|
||||
}
|
||||
|
||||
const child1 = new FileAction({
|
||||
const child1: IFileAction = {
|
||||
id: 'nested-child-1',
|
||||
displayName: () => 'Nested Child 1',
|
||||
exec: cy.spy(),
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
parent: 'nested-action',
|
||||
})
|
||||
}
|
||||
|
||||
const child2 = new FileAction({
|
||||
const child2: IFileAction = {
|
||||
id: 'nested-child-2',
|
||||
displayName: () => 'Nested Child 2',
|
||||
exec: cy.spy(),
|
||||
iconSvgInline: () => '<svg></svg>',
|
||||
parent: 'nested-action',
|
||||
})
|
||||
}
|
||||
|
||||
cy.visit('/apps/files', {
|
||||
// Cannot use registerFileAction here
|
||||
|
|
|
|||
Loading…
Reference in a new issue