Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat(actions): standardize contexts
Signed-off-by: skjnldsv <[email protected]>
  • Loading branch information
skjnldsv committed Nov 26, 2025
commit d9c2e53d5726615ae41765db59ca3ae294096432
26 changes: 15 additions & 11 deletions __tests__/actions/fileAction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Node } from '../../lib/node/index.ts'
import type { Folder, Node } from '../../lib/node/index.ts'
import type { View } from '../../lib/navigation/view.ts'

import { beforeEach, describe, expect, test, vi } from 'vitest'
import { getFileActions, registerFileAction, FileAction, DefaultType, FileActionData } from '../../lib/actions/index.ts'
import logger from '../../lib/utils/logger.ts'

const folder = {} as Folder
const view = {} as View
describe('FileActions init', () => {

beforeEach(() => {
Expand All @@ -34,8 +36,8 @@ describe('FileActions init', () => {
})

expect(action.id).toBe('test')
expect(action.displayName([], {} as unknown as View)).toBe('Test')
expect(action.iconSvgInline([], {} as unknown as View)).toBe('<svg></svg>')
expect(action.displayName({ view, folder, nodes: [], content: [] })).toBe('Test')
expect(action.iconSvgInline({ view, folder, nodes: [], content: [] })).toBe('<svg></svg>')

registerFileAction(action)

Expand Down Expand Up @@ -244,18 +246,20 @@ describe('FileActions creation', () => {
},
})

const node = {} as Node

expect(action.id).toBe('test')
expect(action.displayName([], {} as unknown as View)).toBe('Test')
expect(action.title?.([], {} as unknown as View)).toBe('Test title')
expect(action.iconSvgInline([], {} as unknown as View)).toBe('<svg></svg>')
await expect(action.exec({} as unknown as Node, {} as unknown as View, '/')).resolves.toBe(true)
await expect(action.execBatch?.([], {} as unknown as View, '/')).resolves.toStrictEqual([true])
expect(action.enabled?.([], {} as unknown as View)).toBe(true)
expect(action.displayName({ view, folder, nodes: [], content: [] })).toBe('Test')
expect(action.title?.({ view, folder, nodes: [], content: [] })).toBe('Test title')
expect(action.iconSvgInline({ view, folder, nodes: [], content: [] })).toBe('<svg></svg>')
await expect(action.exec({ view, folder, nodes: [node], content: [] })).resolves.toBe(true)
await expect(action.execBatch?.({ view, folder, nodes: [], content: [] })).resolves.toStrictEqual([true])
expect(action.enabled?.({ view, folder, nodes: [], content: [] })).toBe(true)
expect(action.order).toBe(100)
expect(action.parent).toBe('123')
expect(action.destructive).toBe(true)
expect(action.default).toBe(DefaultType.DEFAULT)
expect(action.inline?.({} as unknown as Node, {} as unknown as View)).toBe(true)
expect((await action.renderInline?.({} as unknown as Node, {} as unknown as View))?.outerHTML).toBe('<span>test</span>')
expect(action.inline?.({ view, folder, nodes: [], content: [] })).toBe(true)
expect((await action.renderInline?.({ view, folder, nodes: [], content: [] }))?.outerHTML).toBe('<span>test</span>')
})
})
15 changes: 9 additions & 6 deletions __tests__/actions/fileListAction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { getFileListActions, registerFileListAction, FileListAction } from '../.
import { Folder } from '../../lib/node/index.ts'
import logger from '../../lib/utils/logger.ts'

const folder = {} as Folder
const view = {} as View

const mockAction = (id: string) => new FileListAction({
id,
displayName: () => 'Test',
Expand Down Expand Up @@ -37,8 +40,8 @@ describe('FileListActions init', () => {
const testAction = mockAction('test')

expect(testAction.id).toBe('test')
expect(testAction.displayName({} as unknown as View)).toBe('Test')
expect(testAction.iconSvgInline!({} as unknown as View)).toBe('<svg></svg>')
expect(testAction.displayName({ view, folder })).toBe('Test')
expect(testAction.iconSvgInline!({ view, folder })).toBe('<svg></svg>')

registerFileListAction(testAction)
expect(actions).toHaveLength(1)
Expand Down Expand Up @@ -155,10 +158,10 @@ describe('FileListAction creation', () => {
})

expect(testAction.id).toBe('test')
expect(testAction.displayName({} as unknown as View)).toBe('Test')
expect(testAction.iconSvgInline!({} as unknown as View)).toBe('<svg></svg>')
expect(testAction.displayName({ view, folder })).toBe('Test')
expect(testAction.iconSvgInline!({ view, folder })).toBe('<svg></svg>')
expect(testAction.order).toBe(0)
expect(testAction.enabled?.({} as unknown as View, [], {} as Folder)).toBe(true)
await expect(testAction.exec({} as unknown as View, [], {} as Folder)).resolves.toBe(undefined)
expect(testAction.enabled?.({ view, folder })).toBe(true)
await expect(testAction.exec({ view, folder, nodes: [], content: [] })).resolves.toBe(undefined)
})
})
20 changes: 9 additions & 11 deletions lib/actions/fileAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Node } from '../node/node.ts'
import type { View } from '../navigation/view.ts'
import type { ActionContext, ActionContextSingle } from '../types'

import logger from '../utils/logger.ts'

Expand Down Expand Up @@ -48,28 +46,28 @@ export interface FileActionData {
/** Unique ID */
id: string
/** Translatable string displayed in the menu */
displayName: (files: Node[], view: View) => string
displayName: (context: ActionContext) => string
/** Translatable title for of the action */
title?: (files: Node[], view: View) => string
title?: (context: ActionContext) => string
/** Svg as inline string. <svg><path fill="..." /></svg> */
iconSvgInline: (files: Node[], view: View) => string
iconSvgInline: (context: ActionContext) => string
/** Condition wether this action is shown or not */
enabled?: (files: Node[], view: View) => boolean
enabled?: (context: ActionContext) => boolean
Comment on lines +49 to +55
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@susnux I'm second guessing myself here, do you think we also need the full context here?
nodes: Node[], view: View, folder: Folder, content: Node[],

Or would only nodes: Node[], view: View should be enough ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could probably stick closer to the original implementation

export type ActionContextBase = {
	nodes: Node[],
	view: View,
}

export type ActionContext = {
	folder: Folder,
	content: Node[],
} & ActionContextBase

export type ActionContextSingle = {
	nodes: [Node],
} & ActionContext

export type ViewActionContext = {
	view: View,
	folder: Folder,
}
export interface FileActionData {
	/** Unique ID */
	id: string
	/** Translatable string displayed in the menu */
	displayName: (context: ActionContextBase) => string
	/** Translatable title for of the action */
	title?: (context: ActionContextBase) => string
	/** Svg as inline string. <svg><path fill="..." /></svg> */
	iconSvgInline: (context: ActionContextBase) => string
	/** Condition wether this action is shown or not */
	enabled?: (context: ActionContextBase) => boolean

	/**
	 * Function executed on single file action
	 * @return true if the action was executed successfully,
	 * false otherwise and null if the action is silent/undefined.
	 * @throws Error if the action failed
	 */
	exec: (context: ActionContextSingle) => Promise<boolean|null>,
	/**
	 * Function executed on multiple files action
	 * @return true if the action was executed successfully,
	 * false otherwise and null if the action is silent/undefined.
	 * @throws Error if the action failed
	 */
	execBatch?: (context: ActionContext) => Promise<(boolean|null)[]>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Especially "enabled" should also have the folder, because some actions only work in specific parent folders

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, for enabled. But the other might not need it. Wdyt ?


/**
* Function executed on single file action
* @return true if the action was executed successfully,
* false otherwise and null if the action is silent/undefined.
* @throws Error if the action failed
*/
exec: (file: Node, view: View, dir: string) => Promise<boolean|null>,
exec: (context: ActionContextSingle) => Promise<boolean|null>,
/**
* Function executed on multiple files action
* @return true if the action was executed successfully,
* false otherwise and null if the action is silent/undefined.
* @throws Error if the action failed
*/
execBatch?: (files: Node[], view: View, dir: string) => Promise<(boolean|null)[]>
execBatch?: (context: ActionContext) => Promise<(boolean|null)[]>

/** This action order in the list */
order?: number
Expand Down Expand Up @@ -104,12 +102,12 @@ export interface FileActionData {
/**
* If true, the renderInline function will be called
*/
inline?: (file: Node, view: View) => boolean,
inline?: (context: ActionContext) => boolean,
/**
* If defined, the returned html element will be
* appended before the actions menu.
*/
renderInline?: (file: Node, view: View) => Promise<HTMLElement | null>,
renderInline?: (context: ActionContext) => Promise<HTMLElement | null>,
}

export class FileAction {
Expand Down
16 changes: 5 additions & 11 deletions lib/actions/fileListAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Folder } from '../node/folder.ts'
import type { Node } from '../node/node.ts'
import type { View } from '../navigation/view.ts'
import type { ActionContext, ViewActionContext } from '../types'

import logger from '../utils/logger.ts'

Expand All @@ -14,29 +11,26 @@ interface FileListActionData {
id: string

/** Translated name of the action */
displayName: (view: View) => string
displayName: (context: ViewActionContext) => string

/** Raw svg string */
iconSvgInline?: (view: View) => string
iconSvgInline?: (context: ViewActionContext) => string

/** Sort order */
order: number

/**
* Condition whether this action is shown or not
* @param view The current view
* @param nodes The nodes in the current directory
* @param folder The current folder
*/
enabled?: (view: View, nodes: Node[], folder: Folder) => boolean
enabled?: (context: ViewActionContext) => boolean

/**
* Function executed on single file action
* @return true if the action was executed successfully,
* false otherwise and null if the action is silent/undefined.
* @throws Error if the action failed
*/
exec: (view: View, nodes: Node[], folder: Folder) => Promise<boolean|null>,
exec: (context: ActionContext) => Promise<boolean|null>,
}

export class FileListAction {
Expand Down
26 changes: 26 additions & 0 deletions lib/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { Folder, Node } from './node/index.ts'
import { View } from './navigation/index.ts'

type ActionContextSingle = {
nodes: [Node],
view: View,
folder: Folder,
content: Node[],
}

type ActionContext = {
nodes: Node[],
view: View,
folder: Folder,
content: Node[],
}

type ViewActionContext = {
view: View,
folder: Folder,
}