From 67508fdb4e951745c9782fcbbc306f2401d8686c Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Tue, 19 May 2026 22:05:36 +0200 Subject: [PATCH] Bulk and single action handlers for Trash tab --- .../SnippetsTable/SnippetsListTable.tsx | 195 +++++++++++------- .../ManageMenu/SnippetsTable/TableColumns.tsx | 4 +- .../Snippets/Snippets_REST_Controller.php | 15 +- 3 files changed, 134 insertions(+), 80 deletions(-) diff --git a/src/js/components/ManageMenu/SnippetsTable/SnippetsListTable.tsx b/src/js/components/ManageMenu/SnippetsTable/SnippetsListTable.tsx index 3dcb43ca..3730ec56 100644 --- a/src/js/components/ManageMenu/SnippetsTable/SnippetsListTable.tsx +++ b/src/js/components/ManageMenu/SnippetsTable/SnippetsListTable.tsx @@ -252,99 +252,141 @@ const NoItemsMessage = () => { } -const useBulkActions = (allSnippets: Snippet[]): ListTableBulkAction[] => { - const { activate, deactivate, delete: trashOrDelete, create } = useSnippetsAPI() +const useBulkActions = ( + currentSnippets: Snippet[], + currentStatus: SnippetStatus +): ListTableBulkAction[] => { + const { activate, deactivate, delete: trashOrDelete, restore, create } = useSnippetsAPI() const { refreshSnippetsList } = useSnippetsList() return useMemo( - () => [ - { - name: __('Activate', 'code-snippets'), - apply: async (selected: Set) => { - const targets = allSnippets.filter(snippet => selected.has(snippet.id) && !snippet.active) - - if (0 === targets.length) { - return + () => { + if ('trashed' === currentStatus) { + return [ + { + name: __('Restore', 'code-snippets'), + apply: async (selected: Set) => { + const targets = currentSnippets.filter(snippet => selected.has(snippet.id) && snippet.trashed) + + if (0 === targets.length) { + return + } + + for (const snippet of targets) { + await restore({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) + } + + await refreshSnippetsList() + } + }, + { + name: __('Delete Permanently', 'code-snippets'), + apply: async (selected: Set) => { + const targets = currentSnippets.filter(snippet => selected.has(snippet.id) && snippet.trashed) + + if (0 === targets.length) { + return + } + + for (const snippet of targets) { + await trashOrDelete({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) + } + + await refreshSnippetsList() + } } + ] + } - for (const snippet of targets) { - await activate({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) - } + return [ + { + name: __('Activate', 'code-snippets'), + apply: async (selected: Set) => { + const targets = currentSnippets.filter(snippet => selected.has(snippet.id) && !snippet.active) - await refreshSnippetsList() - } - }, - { - name: __('Deactivate', 'code-snippets'), - apply: async (selected: Set) => { - const targets = allSnippets.filter(snippet => selected.has(snippet.id) && snippet.active) + if (0 === targets.length) { + return + } - if (0 === targets.length) { - return - } + for (const snippet of targets) { + await activate({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) + } - for (const snippet of targets) { - await deactivate({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) + await refreshSnippetsList() } + }, + { + name: __('Deactivate', 'code-snippets'), + apply: async (selected: Set) => { + const targets = currentSnippets.filter(snippet => selected.has(snippet.id) && snippet.active) - await refreshSnippetsList() - } - }, - { - name: __('Clone', 'code-snippets'), - apply: async (selected: Set) => { - const targets = allSnippets.filter(snippet => selected.has(snippet.id) && !snippet.trashed) + if (0 === targets.length) { + return + } - if (0 === targets.length) { - return - } + for (const snippet of targets) { + await deactivate({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) + } - for (const snippet of targets) { - await create(cloneSnippetObject(snippet)).catch(handleUnknownError) + await refreshSnippetsList() } + }, + { + name: __('Clone', 'code-snippets'), + apply: async (selected: Set) => { + const targets = currentSnippets.filter(snippet => selected.has(snippet.id) && !snippet.trashed) - await refreshSnippetsList() - } - }, - { - name: __('Export', 'code-snippets'), - apply: (selected: Set) => { - downloadBulkSnippetExportFile( - allSnippets.filter(snippet => selected.has(snippet.id)) - ) - return Promise.resolve() - } - }, - { - name: __('Download', 'code-snippets'), - apply: (selected: Set) => { - const selectedSnippets = allSnippets.filter(snippet => selected.has(snippet.id)) + if (0 === targets.length) { + return + } + + for (const snippet of targets) { + await create(cloneSnippetObject(snippet)).catch(handleUnknownError) + } - if (1 < selectedSnippets.length && !window.CODE_SNIPPETS_MANAGE?.supportsZipDownloads) { - return submitBulkSnippetDownloadsIndividually(selectedSnippets) + await refreshSnippetsList() } + }, + { + name: __('Export', 'code-snippets'), + apply: (selected: Set) => { + downloadBulkSnippetExportFile( + currentSnippets.filter(snippet => selected.has(snippet.id)) + ) + return Promise.resolve() + } + }, + { + name: __('Download', 'code-snippets'), + apply: (selected: Set) => { + const selectedSnippets = currentSnippets.filter(snippet => selected.has(snippet.id)) - return submitBulkSnippetDownload(selectedSnippets) - } - }, - { - name: __('Trash', 'code-snippets'), - apply: async (selected: Set) => { - const targets = allSnippets.filter(snippet => selected.has(snippet.id)) + if (1 < selectedSnippets.length && !window.CODE_SNIPPETS_MANAGE?.supportsZipDownloads) { + return submitBulkSnippetDownloadsIndividually(selectedSnippets) + } - if (0 === targets.length) { - return + return submitBulkSnippetDownload(selectedSnippets) } + }, + { + name: __('Trash', 'code-snippets'), + apply: async (selected: Set) => { + const targets = currentSnippets.filter(snippet => selected.has(snippet.id)) - for (const snippet of targets) { - await trashOrDelete({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) - } + if (0 === targets.length) { + return + } + + for (const snippet of targets) { + await trashOrDelete({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) + } - await refreshSnippetsList() + await refreshSnippetsList() + } } - } - ], - [allSnippets, activate, deactivate, trashOrDelete, create, refreshSnippetsList] + ] + }, + [currentSnippets, currentStatus, activate, deactivate, trashOrDelete, restore, create, refreshSnippetsList] ) } @@ -353,12 +395,15 @@ export const SnippetsListTable: React.FC = () => { const { snippetsByStatus } = useFilteredSnippets() const { hiddenColumns, truncateRowValues } = useManageTableSettings() - const allSnippets = useMemo(() => snippetsByStatus.get('all') ?? [], [snippetsByStatus]) - const totalItems = snippetsByStatus.get(currentStatus)?.length ?? 0 + const currentSnippets = useMemo( + () => snippetsByStatus.get(currentStatus) ?? [], + [snippetsByStatus, currentStatus] + ) + const totalItems = currentSnippets.length const itemsPerPage = window.CODE_SNIPPETS_MANAGE?.snippetsPerPage const columns = useMemo(() => getTableColumns(hiddenColumns), [hiddenColumns]) - const actions = useBulkActions(allSnippets) + const actions = useBulkActions(currentSnippets, currentStatus) useEffect(() => { if (INDEX_STATUS !== currentStatus && !snippetsByStatus.has(currentStatus)) { @@ -380,7 +425,7 @@ export const SnippetsListTable: React.FC = () => {

snippet.id} ariaLabel={__('Snippets list', 'code-snippets')} getCheckboxAriaLabel={(snippet: Snippet) => sprintf( diff --git a/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx b/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx index bdfa470b..ec8f6824 100644 --- a/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx +++ b/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx @@ -107,14 +107,14 @@ const ActionLinks = ({ snippet }: { snippet: Snippet }) => { {__('Clone', 'code-snippets')} ) - const Export = () => + const Export = !snippet.trashed && (() => + ) const Restore = snippet.trashed && (() =>