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 && (() =>