Merge pull request #58108 from nextcloud/chore/update-files

chore(deps): update `@nextcloud/files` to v4.0.0-rc.1
This commit is contained in:
Ferdinand Thiessen 2026-02-06 12:42:24 +01:00 committed by GitHub
commit 9def7a8ba7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
196 changed files with 970 additions and 1141 deletions

View file

@ -1,11 +1,11 @@
/**
/*!
* 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 { File, FileAction, Permission } from '@nextcloud/files'
import { File, Permission } from '@nextcloud/files'
import { describe, expect, test, vi } from 'vitest'
import logger from '../logger.js'
import { action } from './inlineUnreadCommentsAction.ts'
@ -13,7 +13,7 @@ import { action } from './inlineUnreadCommentsAction.ts'
const view = {
id: 'files',
name: 'Files',
} as View
} as IView
describe('Inline unread comments action display name tests', () => {
test('Default values', () => {
@ -29,36 +29,35 @@ describe('Inline unread comments action display name tests', () => {
root: '/files/admin',
})
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('comments-unread')
expect(action.displayName({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('')
expect(action.title!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('1 new comment')
expect(action.iconSvgInline({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toMatch(/<svg.+<\/svg>/)
expect(action.enabled!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
expect(action.inline!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
expect(action.default).toBeUndefined()
@ -81,13 +80,13 @@ describe('Inline unread comments action display name tests', () => {
expect(action.displayName({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('')
expect(action.title!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('2 new comments')
})
@ -108,7 +107,7 @@ describe('Inline unread comments action enabled tests', () => {
expect(action.enabled!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
})
@ -129,7 +128,7 @@ describe('Inline unread comments action enabled tests', () => {
expect(action.enabled!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
})
@ -150,7 +149,7 @@ describe('Inline unread comments action enabled tests', () => {
expect(action.enabled!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -171,7 +170,7 @@ describe('Inline unread comments action enabled tests', () => {
expect(action.enabled!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -204,7 +203,7 @@ describe('Inline unread comments action execute tests', () => {
const result = await action.exec!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
@ -241,7 +240,7 @@ describe('Inline unread comments action execute tests', () => {
const result = await action.exec!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})

View file

@ -3,12 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction } from '@nextcloud/files'
import CommentProcessingSvg from '@mdi/svg/svg/comment-processing.svg?raw'
import { FileAction, getSidebar } from '@nextcloud/files'
import { getSidebar } from '@nextcloud/files'
import { n, t } from '@nextcloud/l10n'
import logger from '../logger.js'
export const action = new FileAction({
export const action: IFileAction = {
id: 'comments-unread',
title({ nodes }) {
@ -47,4 +49,4 @@ export const action = new FileAction({
inline: () => true,
order: -140,
})
}

View file

@ -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

View file

@ -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],

View 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',
},
})
}

View file

@ -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: [],

View file

@ -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
}

View file

@ -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])

View file

@ -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)
}

View file

@ -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)
},

View file

@ -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],

View file

@ -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,
})
}

View file

@ -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: [],

View file

@ -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,
})
}

View file

@ -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: [],
})

View file

@ -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.

View file

@ -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: [],

View file

@ -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',
},
})
}

View file

@ -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: [],

View file

@ -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'),
},
})
}

View file

@ -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: [],

View file

@ -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,
})
}

View file

@ -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,
},

View file

@ -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
}

View file

@ -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,

View file

@ -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)
},
},

View file

@ -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) {

View file

@ -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
}

View file

@ -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, () => {

View file

@ -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
}
/**

View file

@ -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

View file

@ -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
}

View file

@ -167,7 +167,6 @@ export default defineComponent({
const viewsToLoad: View[] = (Object.entries(this.viewConfigStore.viewConfigs) as Array<[string, ViewConfig]>)
.filter(([, config]) => config.expanded === true)
.map(([viewId]) => this.views.find((view) => view.id === viewId))
.filter(Boolean as unknown as ((u: unknown) => u is View))
.filter((view) => view.loadChildViews && !view.loaded)
for (const view of viewsToLoad) {

View file

@ -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 type { IStorage } from '../types.ts'
import { DefaultType, File, FileAction, Folder, Permission } from '@nextcloud/files'
import { DefaultType, File, Folder, Permission } from '@nextcloud/files'
import { describe, expect, test } from 'vitest'
import { StorageStatus } from '../types.ts'
import { action } from './enterCredentialsAction.ts'
@ -14,12 +14,12 @@ import { action } from './enterCredentialsAction.ts'
const view = {
id: 'files',
name: 'Files',
} as View
} as IView
const externalStorageView = {
id: 'extstoragemounts',
name: 'External storage',
} as View
} as IView
describe('Enter credentials action conditions tests', () => {
test('Default values', () => {
@ -36,7 +36,6 @@ describe('Enter credentials action conditions tests', () => {
},
})
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('credentials-external-storage')
expect(action.displayName({
view: externalStorageView,

View file

@ -4,14 +4,14 @@
*/
import type { AxiosResponse } from '@nextcloud/axios'
import type { INode } from '@nextcloud/files'
import type { IFileAction, INode } from '@nextcloud/files'
import type { IStorage } from '../types.ts'
import LoginSvg from '@mdi/svg/svg/login.svg?raw'
import axios from '@nextcloud/axios'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { DefaultType, FileAction } from '@nextcloud/files'
import { DefaultType } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'
import { generateUrl } from '@nextcloud/router'
@ -60,7 +60,7 @@ async function setCredentials(node: INode, login: string, password: string): Pro
export const ACTION_CREDENTIALS_EXTERNAL_STORAGE = 'credentials-external-storage'
export const action = new FileAction({
export const action: IFileAction = {
id: ACTION_CREDENTIALS_EXTERNAL_STORAGE,
displayName: () => t('files', 'Enter missing credentials'),
iconSvgInline: () => LoginSvg,
@ -104,4 +104,4 @@ export const action = new FileAction({
order: -1000,
default: DefaultType.DEFAULT,
inline: () => true,
})
}

View file

@ -4,12 +4,12 @@
*/
import type { AxiosError } from '@nextcloud/axios'
import type { IFileAction } from '@nextcloud/files'
import type { IStorage } from '../types.ts'
import AlertSvg from '@mdi/svg/svg/alert-circle.svg?raw'
import { showWarning } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { FileAction } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { getStatus } from '../services/externalStorage.ts'
import { StorageStatus } from '../types.ts'
@ -18,7 +18,7 @@ import { isNodeExternalStorage } from '../utils/externalStorageUtils.ts'
import '../css/fileEntryStatus.scss'
export const action = new FileAction({
export const action: IFileAction = {
id: 'check-external-storage',
displayName: () => '',
iconSvgInline: () => '',
@ -88,4 +88,4 @@ export const action = new FileAction({
},
order: 10,
})
}

View file

@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { View } from '@nextcloud/files'
import type { IView } from '@nextcloud/files'
import type { IStorage } from '../types.ts'
import * as dialogs from '@nextcloud/dialogs'
import { DefaultType, FileAction, Folder, Permission } from '@nextcloud/files'
import { DefaultType, Folder, Permission } from '@nextcloud/files'
import { describe, expect, test, vi } from 'vitest'
import { StorageStatus } from '../types.ts'
import { action } from './openInFilesAction.ts'
@ -17,12 +17,12 @@ vi.mock('@nextcloud/dialogs', { spy: true })
const view = {
id: 'files',
name: 'Files',
} as View
} as IView
const externalStorageView = {
id: 'extstoragemounts',
name: 'External storage',
} as View
} as IView
describe('Open in files action conditions tests', () => {
test('Default values', () => {
@ -39,7 +39,6 @@ describe('Open in files action conditions tests', () => {
},
})
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('open-in-files-external-storage')
expect(action.displayName({
nodes: [storage],

View file

@ -3,16 +3,17 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction } from '@nextcloud/files'
import type { IStorage } from '../types.ts'
import { getCurrentUser } from '@nextcloud/auth'
import { showConfirmation } from '@nextcloud/dialogs'
import { DefaultType, FileAction } from '@nextcloud/files'
import { DefaultType } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { StorageStatus } from '../types.ts'
export const action = new FileAction({
export const action: IFileAction = {
id: 'open-in-files-external-storage',
displayName: ({ nodes }) => {
const config = nodes?.[0]?.attributes?.config as IStorage || { status: StorageStatus.Indeterminate }
@ -54,4 +55,4 @@ export const action = new FileAction({
// Before openFolderAction
order: -1000,
default: DefaultType.HIDDEN,
})
}

View file

@ -1,4 +1,4 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@ -9,7 +9,7 @@ import { action as clearAction } from './files_actions/clearReminderAction.ts'
import { action as statusAction } from './files_actions/reminderStatusAction.ts'
import { action as customAction } from './files_actions/setReminderCustomAction.ts'
import { action as menuAction } from './files_actions/setReminderMenuAction.ts'
import { actions as suggestionActions } from './files_actions/setReminderSuggestionActions.ts'
import { getSetReminderSuggestionActions } from './files_actions/setReminderSuggestionActions.ts'
registerDavProperty('nc:reminder-due-date', { nc: 'http://nextcloud.org/ns' })
@ -17,4 +17,5 @@ registerFileAction(statusAction)
registerFileAction(clearAction)
registerFileAction(menuAction)
registerFileAction(customAction)
suggestionActions.forEach((action) => registerFileAction(action))
getSetReminderSuggestionActions()
.forEach(registerFileAction)

View file

@ -1,15 +1,15 @@
/**
/*!
* SPDX-FileCopyrightText: 2025 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 { Folder } from '@nextcloud/files'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { action } from './clearReminderAction.ts'
const view = {} as unknown as View
const view = {} as unknown as IView
const root = new Folder({
owner: 'user',

View file

@ -1,15 +1,17 @@
/**
/*!
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction } from '@nextcloud/files'
import AlarmOffSvg from '@mdi/svg/svg/alarm-off.svg?raw'
import { emit } from '@nextcloud/event-bus'
import { FileAction } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { clearReminder } from '../services/reminderService.ts'
import { getVerboseDateString } from '../shared/utils.ts'
export const action = new FileAction({
export const action: IFileAction = {
id: 'clear-reminder',
displayName: () => t('files_reminders', 'Clear reminder'),
@ -48,4 +50,4 @@ export const action = new FileAction({
},
order: 19,
})
}

View file

@ -1,15 +1,15 @@
/**
/*!
* SPDX-FileCopyrightText: 2025 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 { Folder } from '@nextcloud/files'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { action } from './reminderStatusAction.ts'
const view = {} as unknown as View
const view = {} as unknown as IView
const root = new Folder({
owner: 'user',

View file

@ -1,14 +1,16 @@
/**
/*!
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction } from '@nextcloud/files'
import AlarmSvg from '@mdi/svg/svg/alarm.svg?raw'
import { FileAction } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { pickCustomDate } from '../services/customPicker.ts'
import { getVerboseDateString } from '../shared/utils.ts'
export const action = new FileAction({
export const action: IFileAction = {
id: 'reminder-status',
inline: () => true,
@ -41,4 +43,4 @@ export const action = new FileAction({
},
order: -15,
})
}

View file

@ -1,14 +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 CalendarClockSvg from '@mdi/svg/svg/calendar-clock.svg?raw'
import { FileAction } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { pickCustomDate } from '../services/customPicker.ts'
import { SET_REMINDER_MENU_ID } from './setReminderMenuAction.ts'
export const action = new FileAction({
export const action: IFileAction = {
id: 'set-reminder-custom',
displayName: () => t('files_reminders', 'Custom reminder'),
title: () => t('files_reminders', 'Reminder at custom date & time'),
@ -37,4 +39,4 @@ export const action = new FileAction({
// After presets
order: 22,
})
}

View file

@ -1,14 +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 AlarmSvg from '@mdi/svg/svg/alarm.svg?raw'
import { FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
export const SET_REMINDER_MENU_ID = 'set-reminder-menu'
export const action = new FileAction({
export const action: IFileAction = {
id: SET_REMINDER_MENU_ID,
displayName: () => t('files_reminders', 'Set reminder'),
iconSvgInline: () => AlarmSvg,
@ -31,4 +33,4 @@ export const action = new FileAction({
},
order: 20,
})
}

View file

@ -1,10 +1,12 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction } from '@nextcloud/files'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { FileAction } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { setReminder } from '../services/reminderService.ts'
import { logger } from '../shared/logger.ts'
@ -54,14 +56,45 @@ const nextWeek: ReminderOption = {
verboseDateString: '',
}
/**
* Get all set reminder suggestion actions from presets
*/
export function getSetReminderSuggestionActions() {
[laterToday, tomorrow, thisWeekend, nextWeek].forEach((option) => {
// Generate the initial date string
const dateTime = getDateTime(option.dateTimePreset)
if (!dateTime) {
return
}
option.dateString = getDateString(dateTime)
option.verboseDateString = getVerboseDateString(dateTime)
// Update the date string every 30 minutes
setInterval(() => {
const dateTime = getDateTime(option.dateTimePreset)
if (!dateTime) {
return
}
// update the submenu remind options strings
option.dateString = getDateString(dateTime)
option.verboseDateString = getVerboseDateString(dateTime)
}, 1000 * 30 * 60)
})
// Generate the default preset actions
return [laterToday, tomorrow, thisWeekend, nextWeek]
.map(generateFileAction)
}
/**
* Generate a file action for the given option
*
* @param option The option to generate the action for
* @return The file action or null if the option should not be shown
*/
function generateFileAction(option: ReminderOption): FileAction | null {
return new FileAction({
function generateFileAction(option: ReminderOption): IFileAction {
return {
id: `set-reminder-${option.dateTimePreset}`,
displayName: () => `${option.label} ${option.dateString}`,
title: () => `${option.ariaLabel} ${option.verboseDateString}`,
@ -109,31 +142,5 @@ function generateFileAction(option: ReminderOption): FileAction | null {
},
order: 21,
})
}
[laterToday, tomorrow, thisWeekend, nextWeek].forEach((option) => {
// Generate the initial date string
const dateTime = getDateTime(option.dateTimePreset)
if (!dateTime) {
return
}
option.dateString = getDateString(dateTime)
option.verboseDateString = getVerboseDateString(dateTime)
// Update the date string every 30 minutes
setInterval(() => {
const dateTime = getDateTime(option.dateTimePreset)
if (!dateTime) {
return
}
// update the submenu remind options strings
option.dateString = getDateString(dateTime)
option.verboseDateString = getVerboseDateString(dateTime)
}, 1000 * 30 * 60)
})
// Generate the default preset actions
export const actions = [laterToday, tomorrow, thisWeekend, nextWeek]
.map(generateFileAction) as FileAction[]
}

View file

@ -1,20 +0,0 @@
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { registerFileAction } from '@nextcloud/files'
import { registerDavProperty } from '@nextcloud/files/dav'
import { action as clearAction } from './files_actions/clearReminderAction.ts'
import { action as statusAction } from './files_actions/reminderStatusAction.ts'
import { action as customAction } from './files_actions/setReminderCustomAction.ts'
import { action as menuAction } from './files_actions/setReminderMenuAction.ts'
import { actions as suggestionActions } from './files_actions/setReminderSuggestionActions.ts'
registerDavProperty('nc:reminder-due-date', { nc: 'http://nextcloud.org/ns' })
registerFileAction(statusAction)
registerFileAction(clearAction)
registerFileAction(menuAction)
registerFileAction(customAction)
suggestionActions.forEach((action) => registerFileAction(action))

View file

@ -1,13 +1,14 @@
import type { Folder, View } from '@nextcloud/files'
import axios from '@nextcloud/axios'
import * as eventBus from '@nextcloud/event-bus'
import { File, FileAction, Permission } from '@nextcloud/files'
import { ShareType } from '@nextcloud/sharing'
/**
/*
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFolder, IView } from '@nextcloud/files'
import axios from '@nextcloud/axios'
import * as eventBus from '@nextcloud/event-bus'
import { File, Permission } from '@nextcloud/files'
import { ShareType } from '@nextcloud/sharing'
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'
import { action } from './acceptShareAction.ts'
@ -18,12 +19,12 @@ vi.mock('@nextcloud/axios')
const view = {
id: 'files',
name: 'Files',
} as View
} as IView
const pendingShareView = {
id: 'pendingshares',
name: 'Pending shares',
} as View
} as IView
// Mock webroot variable
beforeAll(() => {
@ -41,18 +42,17 @@ describe('Accept share action conditions tests', () => {
root: '/files/admin',
})
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('accept-share')
expect(action.displayName({
nodes: [file],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('Accept share')
expect(action.iconSvgInline({
nodes: [file],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toMatch(/<svg.+<\/svg>/)
expect(action.default).toBeUndefined()
@ -61,7 +61,7 @@ describe('Accept share action conditions tests', () => {
expect(action.inline!({
nodes: [file],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -87,7 +87,7 @@ describe('Accept share action conditions tests', () => {
expect(action.displayName({
nodes: [file1, file2],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('Accept shares')
})
@ -108,7 +108,7 @@ describe('Accept share action enabled tests', () => {
expect(action.enabled!({
nodes: [file],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -118,7 +118,7 @@ describe('Accept share action enabled tests', () => {
expect(action.enabled!({
nodes: [],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
})
@ -128,7 +128,7 @@ describe('Accept share action enabled tests', () => {
expect(action.enabled!({
nodes: [],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
})
@ -159,7 +159,7 @@ describe('Accept share action execute tests', () => {
const exec = await action.exec({
nodes: [file],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
@ -192,7 +192,7 @@ describe('Accept share action execute tests', () => {
const exec = await action.exec({
nodes: [file],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
@ -237,7 +237,7 @@ describe('Accept share action execute tests', () => {
const exec = await action.execBatch!({
nodes: [file1, file2],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
@ -272,7 +272,7 @@ describe('Accept share action execute tests', () => {
const exec = await action.exec({
nodes: [file],
view: pendingShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})

View file

@ -1,16 +1,18 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction } from '@nextcloud/files'
import CheckSvg from '@mdi/svg/svg/check.svg?raw'
import axios from '@nextcloud/axios'
import { emit } from '@nextcloud/event-bus'
import { FileAction, registerFileAction } from '@nextcloud/files'
import { translatePlural as n } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { pendingSharesViewId } from '../files_views/shares.ts'
export const action = new FileAction({
export const action: IFileAction = {
id: 'accept-share',
displayName: ({ nodes }) => n('files_sharing', 'Accept share', 'Accept shares', nodes.length),
iconSvgInline: () => CheckSvg,
@ -46,6 +48,4 @@ export const action = new FileAction({
order: 1,
inline: () => true,
})
registerFileAction(action)
}

View file

@ -1,11 +1,11 @@
/**
/*!
* 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 { DefaultType, File, FileAction, Permission } from '@nextcloud/files'
import { DefaultType, File, Permission } from '@nextcloud/files'
import { describe, expect, test, vi } from 'vitest'
import { deletedSharesViewId, pendingSharesViewId, sharedWithOthersViewId, sharedWithYouViewId, sharesViewId, sharingByLinksViewId } from '../files_views/shares.ts'
import { action } from './openInFilesAction.ts'
@ -15,34 +15,33 @@ import '../main.ts'
const view = {
id: 'files',
name: 'Files',
} as View
} as IView
const validViews = [
sharesViewId,
sharedWithYouViewId,
sharedWithOthersViewId,
sharingByLinksViewId,
].map((id) => ({ id, name: id })) as View[]
].map((id) => ({ id, name: id })) as IView[]
const invalidViews = [
deletedSharesViewId,
pendingSharesViewId,
].map((id) => ({ id, name: id })) as View[]
].map((id) => ({ id, name: id })) as IView[]
describe('Open in files action conditions tests', () => {
test('Default values', () => {
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('files_sharing:open-in-files')
expect(action.displayName({
nodes: [],
view: validViews[0]!,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('Open in Files')
expect(action.iconSvgInline({
nodes: [],
view: validViews[0]!,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('')
expect(action.default).toBe(DefaultType.HIDDEN)
@ -58,7 +57,7 @@ describe('Open in files action enabled tests', () => {
expect(action.enabled!({
nodes: [],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -70,7 +69,7 @@ describe('Open in files action enabled tests', () => {
expect(action.enabled!({
nodes: [],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
})
@ -80,6 +79,7 @@ describe('Open in files action enabled tests', () => {
describe('Open in files action execute tests', () => {
test('Open in files', async () => {
const goToRouteMock = vi.fn()
// @ts-expect-error -- mocking for tests
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
const file = new File({
@ -94,7 +94,7 @@ describe('Open in files action execute tests', () => {
const exec = await action.exec({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
// Silent action

View file

@ -1,12 +1,15 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { DefaultType, FileAction, FileType, registerFileAction } from '@nextcloud/files'
import type { IFileAction } from '@nextcloud/files'
import { DefaultType, FileType } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { sharedWithOthersViewId, sharedWithYouViewId, sharesViewId, sharingByLinksViewId } from '../files_views/shares.ts'
export const action = new FileAction({
export const action: IFileAction = {
id: 'files_sharing:open-in-files',
displayName: () => t('files_sharing', 'Open in Files'),
iconSvgInline: () => '',
@ -42,6 +45,4 @@ export const action = new FileAction({
// Before openFolderAction
order: -1000,
default: DefaultType.HIDDEN,
})
registerFileAction(action)
}

View file

@ -1,13 +1,13 @@
/**
/*
* 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 eventBus from '@nextcloud/event-bus'
import { File, FileAction, Folder, Permission } from '@nextcloud/files'
import { File, Folder, Permission } from '@nextcloud/files'
import { ShareType } from '@nextcloud/sharing'
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'
import { action } from './rejectShareAction.ts'
@ -19,12 +19,12 @@ vi.mock('@nextcloud/axios')
const view = {
id: 'files',
name: 'Files',
} as View
} as IView
const pendingShareView = {
id: 'pendingshares',
name: 'Pending shares',
} as View
} as IView
// Mock webroot variable
beforeAll(() => {
@ -42,7 +42,6 @@ describe('Reject share action conditions tests', () => {
root: '/files/admin',
})
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('reject-share')
expect(action.displayName({
nodes: [file],

View file

@ -1,17 +1,19 @@
/**
/*!
* 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 axios from '@nextcloud/axios'
import { emit } from '@nextcloud/event-bus'
import { FileAction, registerFileAction } from '@nextcloud/files'
import { translatePlural as n } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { ShareType } from '@nextcloud/sharing'
import { pendingSharesViewId } from '../files_views/shares.ts'
export const action = new FileAction({
export const action: IFileAction = {
id: 'reject-share',
displayName: ({ nodes }) => n('files_sharing', 'Reject share', 'Reject shares', nodes.length),
iconSvgInline: () => CloseSvg,
@ -69,6 +71,4 @@ export const action = new FileAction({
order: 2,
inline: () => true,
})
registerFileAction(action)
}

View file

@ -1,12 +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 { ShareType } from '@nextcloud/sharing'
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'
import { action } from './restoreShareAction.ts'
@ -19,12 +20,12 @@ vi.mock('@nextcloud/axios')
const view = {
id: 'files',
name: 'Files',
} as View
} as IView
const deletedShareView = {
id: 'deletedshares',
name: 'Deleted shares',
} as View
} as IView
// Mock webroot variable
beforeAll(() => {
@ -42,18 +43,17 @@ describe('Restore share action conditions tests', () => {
root: '/files/admin',
})
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('restore-share')
expect(action.displayName({
view: deletedShareView,
nodes: [file],
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('Restore share')
expect(action.iconSvgInline({
view: deletedShareView,
nodes: [file],
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toMatch(/<svg.+<\/svg>/)
expect(action.default).toBeUndefined()
@ -62,7 +62,7 @@ describe('Restore share action conditions tests', () => {
expect(action.inline!({
view: deletedShareView,
nodes: [file],
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -88,7 +88,7 @@ describe('Restore share action conditions tests', () => {
expect(action.displayName({
view: deletedShareView,
nodes: [file1, file2],
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('Restore shares')
})
@ -109,7 +109,7 @@ describe('Restore share action enabled tests', () => {
expect(action.enabled!({
nodes: [file],
view: deletedShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -119,7 +119,7 @@ describe('Restore share action enabled tests', () => {
expect(action.enabled!({
nodes: [],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
})
@ -129,7 +129,7 @@ describe('Restore share action enabled tests', () => {
expect(action.enabled!({
nodes: [],
view: deletedShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
})
@ -160,7 +160,7 @@ describe('Restore share action execute tests', () => {
const exec = await action.exec({
nodes: [file],
view: deletedShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
@ -205,7 +205,7 @@ describe('Restore share action execute tests', () => {
const exec = await action.execBatch!({
nodes: [file1, file2],
view: deletedShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
@ -239,7 +239,7 @@ describe('Restore share action execute tests', () => {
const exec = await action.exec({
nodes: [file],
view: deletedShareView,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})

View file

@ -1,16 +1,18 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction } from '@nextcloud/files'
import ArrowULeftTopSvg from '@mdi/svg/svg/arrow-u-left-top.svg?raw'
import axios from '@nextcloud/axios'
import { emit } from '@nextcloud/event-bus'
import { FileAction, registerFileAction } from '@nextcloud/files'
import { translatePlural as n } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { deletedSharesViewId } from '../files_views/shares.ts'
export const action = new FileAction({
export const action: IFileAction = {
id: 'restore-share',
displayName: ({ nodes }) => n('files_sharing', 'Restore share', 'Restore shares', nodes.length),
@ -40,6 +42,4 @@ export const action = new FileAction({
order: 1,
inline: () => true,
})
registerFileAction(action)
}

View file

@ -1,7 +1,8 @@
/**
/*
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
// Only when rendered inline, when not enough space, this is put in the menu
.action-items > .files-list__row-action-sharing-status {
// align icons with text-less inline actions

View file

@ -1,16 +1,16 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { INode } from '@nextcloud/files'
import type { IFileAction, INode } from '@nextcloud/files'
import AccountGroupSvg from '@mdi/svg/svg/account-group-outline.svg?raw'
import AccountPlusSvg from '@mdi/svg/svg/account-plus-outline.svg?raw'
import LinkSvg from '@mdi/svg/svg/link.svg?raw'
import { getCurrentUser } from '@nextcloud/auth'
import { showError } from '@nextcloud/dialogs'
import { FileAction, getSidebar, Permission, registerFileAction } from '@nextcloud/files'
import { getSidebar, Permission } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { ShareType } from '@nextcloud/sharing'
import { isPublicShare } from '@nextcloud/sharing/public'
@ -29,7 +29,7 @@ function isExternal(node: INode) {
}
export const ACTION_SHARING_STATUS = 'sharing-status'
export const action = new FileAction({
export const action: IFileAction = {
id: ACTION_SHARING_STATUS,
displayName({ nodes }) {
const node = nodes[0]!
@ -153,6 +153,4 @@ export const action = new FileAction({
inline: () => true,
})
registerFileAction(action)
}

View file

@ -1,21 +1,20 @@
/**
/*!
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { addNewFileMenuEntry } from '@nextcloud/files'
import { addNewFileMenuEntry, registerFileAction } from '@nextcloud/files'
import { registerDavProperty } from '@nextcloud/files/dav'
import { action as acceptShareAction } from './files_actions/acceptShareAction.ts'
import { action as openInFilesAction } from './files_actions/openInFilesAction.ts'
import { action as rejectShareAction } from './files_actions/rejectShareAction.ts'
import { action as restoreShareAction } from './files_actions/restoreShareAction.ts'
import { action as sharingStatusAction } from './files_actions/sharingStatusAction.ts'
import { registerAccountFilter } from './files_filters/AccountFilter.ts'
import registerNoteToRecipient from './files_headers/noteToRecipient.ts'
import { entry as newFileRequest } from './files_newMenu/newFileRequest.ts'
import registerSharingViews from './files_views/shares.ts'
import './files_actions/acceptShareAction.ts'
import './files_actions/openInFilesAction.ts'
import './files_actions/rejectShareAction.ts'
import './files_actions/restoreShareAction.ts'
import './files_actions/sharingStatusAction.ts'
registerSharingViews()
addNewFileMenuEntry(newFileRequest)
@ -27,6 +26,12 @@ registerDavProperty('nc:share-attributes', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('oc:share-types', { oc: 'http://owncloud.org/ns' })
registerDavProperty('ocs:share-permissions', { ocs: 'http://open-collaboration-services.org/ns' })
registerFileAction(acceptShareAction)
registerFileAction(openInFilesAction)
registerFileAction(rejectShareAction)
registerFileAction(restoreShareAction)
registerFileAction(sharingStatusAction)
registerAccountFilter()
// Add "note to recipient" message

View file

@ -2,19 +2,22 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileAction } from '@nextcloud/files'
import svgHistory from '@mdi/svg/svg/history.svg?raw'
import { getCurrentUser } from '@nextcloud/auth'
import axios, { isAxiosError } from '@nextcloud/axios'
import { showError } from '@nextcloud/dialogs'
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 { generateRemoteUrl } from '@nextcloud/router'
import { TRASHBIN_VIEW_ID } from '../files_views/trashbinView.ts'
import { logger } from '../logger.ts'
export const restoreAction = new FileAction({
export const restoreAction: IFileAction = {
id: 'restore',
displayName() {
@ -68,4 +71,4 @@ export const restoreAction = new FileAction({
order: 1,
inline: () => true,
})
}

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node } from '@nextcloud/files'
import type { IFolder, Node } from '@nextcloud/files'
import * as ncDialogs from '@nextcloud/dialogs'
import * as ncEventBus from '@nextcloud/event-bus'
@ -34,7 +34,7 @@ describe('files_trashbin: file list actions - empty trashbin', () => {
})
it('has display name set', () => {
expect(emptyTrashAction.displayName({ view: trashbinView })).toBe('Empty deleted files')
expect(emptyTrashAction.displayName({ view: trashbinView, folder: {} as IFolder, contents: [] })).toBe('Empty deleted files')
})
it('has order set', () => {

View file

@ -1,10 +1,12 @@
/**
/*
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { IFileListAction } from '@nextcloud/files'
import { getDialogBuilder } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { FileListAction } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { TRASHBIN_VIEW_ID } from '../files_views/trashbinView.ts'
@ -14,7 +16,7 @@ export type FilesTrashbinConfigState = {
allow_delete: boolean
}
export const emptyTrashAction = new FileListAction({
export const emptyTrashAction: IFileListAction = {
id: 'empty-trash',
displayName: () => t('files_trashbin', 'Empty deleted files'),
@ -67,4 +69,4 @@ export const emptyTrashAction = new FileListAction({
return null
},
})
}

View file

@ -1,33 +1,32 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { View } from '@nextcloud/files'
import type { IFolder, IView } from '@nextcloud/files'
import { File, FileAction, Folder, Permission } from '@nextcloud/files'
import { File, Folder, Permission } from '@nextcloud/files'
import { describe, expect, test } from 'vitest'
import { action } from './bulkSystemTagsAction.ts'
const view = {
id: 'files',
name: 'Files',
} as View
} as IView
describe('Manage tags action conditions tests', () => {
test('Default values', () => {
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('systemtags:bulk')
expect(action.displayName({
nodes: [],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('Manage tags')
expect(action.iconSvgInline({
nodes: [],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toMatch(/<svg.+<\/svg>/)
expect(action.default).toBeUndefined()
@ -60,19 +59,19 @@ describe('Manage tags action enabled tests', () => {
expect(action.enabled!({
nodes: [file1, file2],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
expect(action.enabled!({
nodes: [file1],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
expect(action.enabled!({
nodes: [file2],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -91,7 +90,7 @@ describe('Manage tags action enabled tests', () => {
expect(action.enabled!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(false)
})

View file

@ -3,10 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { ActionContext, ActionContextSingle } from '@nextcloud/files'
import type { ActionContext, ActionContextSingle, IFileAction } from '@nextcloud/files'
import TagMultipleSvg from '@mdi/svg/svg/tag-multiple-outline.svg?raw'
import { FileAction, Permission } from '@nextcloud/files'
import { Permission } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { isPublicShare } from '@nextcloud/sharing/public'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
@ -29,7 +29,7 @@ async function execBatch({ nodes }: ActionContext | ActionContextSingle): Promis
.fill(response)
}
export const action = new FileAction({
export const action: IFileAction = {
id: 'systemtags:bulk',
displayName: () => t('systemtags', 'Manage tags'),
iconSvgInline: () => TagMultipleSvg,
@ -64,4 +64,4 @@ export const action = new FileAction({
description: t('systemtags', 'Manage tags'),
key: 't',
},
})
}

View file

@ -3,10 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Folder, View } from '@nextcloud/files'
import type { IFolder, IView } from '@nextcloud/files'
import { emit, subscribe } from '@nextcloud/event-bus'
import { File, FileAction, Permission } from '@nextcloud/files'
import { File, Permission } from '@nextcloud/files'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import * as serviceTagApi from '../services/api.ts'
import { setNodeSystemTags } from '../utils.ts'
@ -15,7 +15,7 @@ import { action } from './inlineSystemTagsAction.ts'
const view = {
id: 'files',
name: 'Files',
} as View
} as IView
describe('Inline system tags action conditions tests', () => {
test('Default values', () => {
@ -28,18 +28,17 @@ describe('Inline system tags action conditions tests', () => {
root: '/files/admin',
})
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('system-tags')
expect(action.displayName({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('')
expect(action.iconSvgInline({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe('')
expect(action.default).toBeUndefined()
@ -49,7 +48,7 @@ describe('Inline system tags action conditions tests', () => {
expect(action.enabled!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -72,7 +71,7 @@ describe('Inline system tags action conditions tests', () => {
expect(action.enabled!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})).toBe(true)
})
@ -98,7 +97,7 @@ describe('Inline system tags action render tests', () => {
const result = await action.renderInline!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
@ -123,7 +122,7 @@ describe('Inline system tags action render tests', () => {
const result = await action.renderInline!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
@ -148,7 +147,7 @@ describe('Inline system tags action render tests', () => {
const result = await action.renderInline!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
@ -178,7 +177,7 @@ describe('Inline system tags action render tests', () => {
const result = await action.renderInline!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
@ -208,7 +207,7 @@ describe('Inline system tags action render tests', () => {
const result = await action.renderInline!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
}) as HTMLElement
document.body.appendChild(result)
@ -270,7 +269,7 @@ describe('Inline system tags action colors', () => {
const result = await action.renderInline!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
@ -297,7 +296,7 @@ describe('Inline system tags action colors', () => {
const result = await action.renderInline!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
})
expect(result).toBeInstanceOf(HTMLElement)
@ -324,7 +323,7 @@ describe('Inline system tags action colors', () => {
const result = await action.renderInline!({
nodes: [file],
view,
folder: {} as Folder,
folder: {} as IFolder,
contents: [],
}) as HTMLElement
document.body.appendChild(result)

View file

@ -3,11 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { INode } from '@nextcloud/files'
import type { IFileAction, INode } from '@nextcloud/files'
import type { TagWithId } from '../types.ts'
import { subscribe } from '@nextcloud/event-bus'
import { FileAction } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import logger from '../logger.ts'
import { fetchTags } from '../services/api.ts'
@ -19,7 +18,7 @@ import '../css/fileEntryInlineSystemTags.scss'
// Init tag cache
const cache: TagWithId[] = []
export const action = new FileAction({
export const action: IFileAction = {
id: 'system-tags',
displayName: () => '',
iconSvgInline: () => '',
@ -44,7 +43,7 @@ export const action = new FileAction({
},
order: 0,
})
}
// Subscribe to the events
subscribe('systemtags:node:updated', updateSystemTagsHtml)
@ -167,11 +166,11 @@ async function renderInline(node: INode): Promise<HTMLElement> {
}
}
systemTagsElement.append(renderTag(tags[0]))
systemTagsElement.append(renderTag(tags[0]!))
if (tags.length === 2) {
// Special case only two tags:
// the overflow fake tag would take the same space as this, so render it
systemTagsElement.append(renderTag(tags[1]))
systemTagsElement.append(renderTag(tags[1]!))
} else if (tags.length > 1) {
// More tags than the one we're showing
// So we add a overflow element indicating there are more tags

View file

@ -3,21 +3,21 @@
* 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 systemTagsView = {
id: 'tags',
name: 'tags',
} as View
} as IView
const validNode = new Folder({
id: 1,
@ -43,7 +43,6 @@ const validTag = new Folder({
describe('Open in files action conditions tests', () => {
test('Default values', () => {
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('systemtags:open-in-files')
expect(action.displayName({
nodes: [],

View file

@ -1,13 +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 { systemTagsViewId } from '../files_views/systemtagsView.ts'
export const action = new FileAction({
export const action: IFileAction = {
id: 'systemtags:open-in-files',
displayName: () => t('systemtags', 'Open in Files'),
iconSvgInline: () => '',
@ -47,4 +49,4 @@ export const action = new FileAction({
// Before openFolderAction
order: -1000,
default: DefaultType.HIDDEN,
})
}

File diff suppressed because it is too large Load diff

View file

@ -35,7 +35,7 @@
"@nextcloud/capabilities": "^1.2.1",
"@nextcloud/dialogs": "^7.2.0",
"@nextcloud/event-bus": "^3.3.3",
"@nextcloud/files": "^4.0.0-rc.0",
"@nextcloud/files": "^4.0.0-rc.1",
"@nextcloud/initial-state": "^3.0.0",
"@nextcloud/l10n": "^3.4.1",
"@nextcloud/logger": "^3.0.3",

View file

@ -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

View file

@ -56,7 +56,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.0
- version: 4.0.0-rc.1
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

View file

@ -91,7 +91,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.0
- version: 4.0.0-rc.1
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

View file

@ -84,7 +84,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.0
- version: 4.0.0-rc.1
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

View file

@ -43,9 +43,6 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/capabilities
- version: 1.2.1
- license: GPL-3.0-or-later
- @nextcloud/paths
- version: 3.0.0
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 3.12.2
- license: AGPL-3.0-or-later

View file

@ -51,9 +51,6 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/capabilities
- version: 1.2.1
- license: GPL-3.0-or-later
- @nextcloud/paths
- version: 3.0.0
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 3.12.2
- license: AGPL-3.0-or-later

4
dist/7394-7394.js vendored

File diff suppressed because one or more lines are too long

View file

@ -60,9 +60,6 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/capabilities
- version: 1.2.1
- license: GPL-3.0-or-later
- @nextcloud/paths
- version: 3.0.0
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 3.12.2
- license: AGPL-3.0-or-later

File diff suppressed because one or more lines are too long

View file

@ -36,9 +36,6 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/capabilities
- version: 1.2.1
- license: GPL-3.0-or-later
- @nextcloud/paths
- version: 3.0.0
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 3.12.2
- license: AGPL-3.0-or-later
@ -69,6 +66,9 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/logger
- version: 3.0.3
- license: GPL-3.0-or-later
- @nextcloud/paths
- version: 3.0.0
- license: GPL-3.0-or-later
- @nextcloud/router
- version: 3.1.0
- license: GPL-3.0-or-later

View file

@ -108,7 +108,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.0
- version: 4.0.0-rc.1
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -73,7 +73,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.0
- version: 4.0.0-rc.1
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -41,7 +41,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.0
- version: 4.0.0-rc.1
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

4
dist/core-common.js vendored

File diff suppressed because one or more lines are too long

View file

@ -123,9 +123,6 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/capabilities
- version: 1.2.1
- license: GPL-3.0-or-later
- @nextcloud/paths
- version: 3.0.0
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 3.12.2
- license: AGPL-3.0-or-later
@ -157,7 +154,7 @@ This file is generated from multiple sources. Included packages:
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/files
- version: 4.0.0-rc.0
- version: 4.0.0-rc.1
- license: AGPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more