diff --git a/src/js/components/ManageMenu/SnippetsTable/SnippetsListTable.tsx b/src/js/components/ManageMenu/SnippetsTable/SnippetsListTable.tsx index 757a3f05..a8c49af4 100644 --- a/src/js/components/ManageMenu/SnippetsTable/SnippetsListTable.tsx +++ b/src/js/components/ManageMenu/SnippetsTable/SnippetsListTable.tsx @@ -3,11 +3,12 @@ import React, { Fragment, useEffect, useMemo, useState } from 'react' import classnames from 'classnames' import { createInterpolateElement } from '@wordpress/element' import { useRestAPI } from '../../../hooks/useRestAPI' +import { useSnippetsAPI } from '../../../hooks/useSnippetsAPI' import { useSnippetsList } from '../../../hooks/useSnippetsList' import { handleUnknownError } from '../../../utils/errors' import { downloadBulkSnippetExportFile } from '../../../utils/files' import { REST_BASES } from '../../../utils/restAPI' -import { getSnippetDisplayName, getSnippetType } from '../../../utils/snippets/snippets' +import { cloneSnippetObject, getSnippetDisplayName, getSnippetType } from '../../../utils/snippets/snippets' import { buildUrl } from '../../../utils/urls' import { ListTable } from '../../common/ListTable' import { SubmitButton } from '../../common/SubmitButton' @@ -248,21 +249,59 @@ const NoItemsMessage = () => { } -const useBulkActions = (allSnippets: Snippet[]): ListTableBulkAction[] => -{ +const useBulkActions = (allSnippets: Snippet[]): ListTableBulkAction[] => { + const { activate, deactivate, delete: trashOrDelete, create } = useSnippetsAPI() + const { refreshSnippetsList } = useSnippetsList() + return useMemo( () => [ { name: __('Activate', 'code-snippets'), - apply: () => Promise.resolve() + apply: async (selected: Set) => { + const targets = allSnippets.filter(snippet => selected.has(snippet.id) && !snippet.active) + + if (0 === targets.length) { + return + } + + for (const snippet of targets) { + await activate({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) + } + + await refreshSnippetsList() + } }, { name: __('Deactivate', 'code-snippets'), - apply: () => Promise.resolve() + apply: async (selected: Set) => { + const targets = allSnippets.filter(snippet => selected.has(snippet.id) && snippet.active) + + if (0 === targets.length) { + return + } + + for (const snippet of targets) { + await deactivate({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) + } + + await refreshSnippetsList() + } }, { name: __('Clone', 'code-snippets'), - apply: () => Promise.resolve() + apply: async (selected: Set) => { + const targets = allSnippets.filter(snippet => selected.has(snippet.id) && !snippet.trashed) + + if (0 === targets.length) { + return + } + + for (const snippet of targets) { + await create(cloneSnippetObject(snippet)).catch(handleUnknownError) + } + + await refreshSnippetsList() + } }, { name: __('Export', 'code-snippets'), @@ -287,10 +326,22 @@ const useBulkActions = (allSnippets: Snippet[]): ListTableBulkAction Promise.resolve() + apply: async (selected: Set) => { + const targets = allSnippets.filter(snippet => selected.has(snippet.id)) + + if (0 === targets.length) { + return + } + + for (const snippet of targets) { + await trashOrDelete({ id: snippet.id, network: snippet.network }).catch(handleUnknownError) + } + + await refreshSnippetsList() + } } ], - [allSnippets] + [allSnippets, activate, deactivate, trashOrDelete, create, refreshSnippetsList] ) } diff --git a/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx b/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx index 7aada846..bdfa470b 100644 --- a/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx +++ b/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx @@ -1,6 +1,6 @@ import classnames from 'classnames' import React, { Fragment, useState } from 'react' -import { __, sprintf } from '@wordpress/i18n' +import { __ } from '@wordpress/i18n' import { humanTimeDiff } from '@wordpress/date' import { RawHTML } from '@wordpress/element' import { useSnippetsAPI } from '../../../hooks/useSnippetsAPI' @@ -8,7 +8,7 @@ import { useSnippetsList } from '../../../hooks/useSnippetsList' import { handleUnknownError } from '../../../utils/errors' import { downloadSnippetExportFile } from '../../../utils/files' import { isNetworkAdmin } from '../../../utils/screen' -import { createSnippetObject, getSnippetDisplayName, getSnippetEditUrl, getSnippetType } from '../../../utils/snippets/snippets' +import { cloneSnippetObject, getSnippetDisplayName, getSnippetEditUrl, getSnippetType } from '../../../utils/snippets/snippets' import { buildUrl } from '../../../utils/urls' import { Badge } from '../../common/Badge' import { Button } from '../../common/Button' @@ -89,15 +89,6 @@ const ActivateColumn: React.FC = ({ snippet }) => { } } -const cloneSnippetObject = (snippet: Snippet): Snippet => - createSnippetObject({ - ...snippet, - id: 0, - active: false, - // translators: %s: snippet title. - name: sprintf(__('%s [CLONE]', 'code-snippets'), snippet.name) - }) - const ActionLinks = ({ snippet }: { snippet: Snippet }) => { const api = useSnippetsAPI() const { refreshSnippetsList } = useSnippetsList() diff --git a/src/js/components/common/ListTable/ListTable.tsx b/src/js/components/common/ListTable/ListTable.tsx index d7b7621f..afe33218 100644 --- a/src/js/components/common/ListTable/ListTable.tsx +++ b/src/js/components/common/ListTable/ListTable.tsx @@ -131,7 +131,14 @@ const ListTableMarkup = ({ @@ -170,6 +177,7 @@ export const ListTable = ({ actions, extraTableNav, selected: getVisibleSelected(visibleItems, getKey, selected), + setSelected, disabled, currentPage, totalPages, @@ -178,7 +186,7 @@ export const ListTable = ({ } const tableHeadingsProps: Omit, 'which'> = - { items: visibleItems, setSelected, columns, getKey, sortColumn, setSortColumn, sortDirection, setSortDirection } + { items: visibleItems, selected, setSelected, columns, getKey, sortColumn, setSortColumn, sortDirection, setSortDirection } return extends Pick, 'columns' | 'getKey' | 'items'> { which: 'head' | 'foot' sortColumn: ListTableColumn | undefined + selected: Set setSelected: Dispatch>> sortDirection: ListTableSortDirection setSortColumn: Dispatch | undefined>> @@ -72,17 +73,21 @@ export const TableHeadings = ({ getKey, columns, sortColumn, + selected, setSelected, setSortColumn, sortDirection, setSortDirection -}: TableHeadingsProps) => - +}: TableHeadingsProps) => { + const allSelected = 0 < items.length && items.every(item => selected.has(getKey(item))) + + return { setSelected(new Set(event.target.checked ? items.map(getKey) : [])) }} @@ -113,3 +118,4 @@ export const TableHeadings = ({ /> })} +} diff --git a/src/js/components/common/ListTable/TableItems.tsx b/src/js/components/common/ListTable/TableItems.tsx index 5b8e67b1..8c3393eb 100644 --- a/src/js/components/common/ListTable/TableItems.tsx +++ b/src/js/components/common/ListTable/TableItems.tsx @@ -6,10 +6,11 @@ import type { ListTableColumn, ListTableItemsProps } from './ListTable' interface CheckboxCellProps extends Pick, 'getKey'> { item: T + selected: Set setSelected: Dispatch>> } -const CheckboxCell = ({ item, setSelected, getKey }: CheckboxCellProps) => +const CheckboxCell = ({ item, selected, setSelected, getKey }: CheckboxCellProps) =>