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
2 changes: 1 addition & 1 deletion src/css/common/_kebab-menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
inline-size: 224px;
box-sizing: border-box;
margin: 0;
padding: 0;
padding: 6px 0;
list-style: none;
background: #fff;
border: 1px solid #e2e2e4;
Expand Down
3 changes: 1 addition & 2 deletions src/css/edit/_form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ $sidebar-gap: 30px;
gap: 8px;
inline-size: 100%;
border-color: #ccc;
padding-block: 6px;
padding-inline: 12px;
padding: 6px 12px;
}

&.no-condition .cond-badge {
Expand Down
4 changes: 4 additions & 0 deletions src/css/manage/_cloud-community.scss
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ $status-colors: (
.banner {
justify-content: center;
}

.cloud-search-filters select {
inline-size: 245px;
}
}

.bundle-share-code-form {
Expand Down
2 changes: 1 addition & 1 deletion src/js/components/EditMenu/SnippetForm/SnippetForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const EditForm: React.FC<EditFormProps> = ({ children, className }) => {
.replace(__('Create New Condition', 'code-snippets'), __('Edit Condition', 'code-snippets'))

const newUrl = buildUrl(window.CODE_SNIPPETS.urls.edit, { id: response.id })
window.history.pushState({}, document.title, newUrl)
window.history.replaceState({}, document.title, newUrl)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why use replaceState instead of pushState? pushState feels more user-friendly given we are changing the page URL itself.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

With pushState, pressing back after the first save returns to the add-snippet URL, which renders an empty editor for a snippet that now exists — saving again from there creates a duplicate. replaceState lets the edit URL take over the history entry, so back leads to the previous page instead of the stale form. Happy to switch it back if keeping the extra history entry is preferred.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah fair enough – I suppose that makes sense. Just looking for the best user behaviour here.

}
}
})
Expand Down
6 changes: 3 additions & 3 deletions src/js/components/ImportMenu/MigrateForm/MigrateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const StatusMessages: React.FC = () => {
</StatusDisplay>)}

{0 < importedIds.length && (
// translators: %d: number of imported snippets.
// translators: %d: number of imported snippets.
<StatusDisplay type="success" title={sprintf(__('%d snippets imported!', 'code-snippets'), importedIds.length)}>
{createInterpolateElement(
__('Selected snippets have been successfully imported to your <a>Code Snippets library</a>.', 'code-snippets'),
Expand All @@ -59,8 +59,8 @@ const StatusMessages: React.FC = () => {
</StatusDisplay>)}

{selectedImporter &&
isWorking !== MigrationStep.FetchSnippets && error?.step !== MigrationStep.FetchSnippets &&
0 === snippetSelection.availableItems.length && 0 === importedIds.length && (
isWorking !== MigrationStep.FetchSnippets && error?.step !== MigrationStep.FetchSnippets &&
0 === snippetSelection.availableItems.length && 0 === importedIds.length && (
<ImportCard className="no-snippets-card" role="status" aria-live="polite">
<div className="card-inner">
<div className="card-icon" aria-hidden="true">📭</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,8 @@ const TableNav: React.FC<TableNavProps> = ({ which, onImport, isImporting, selec
>
{isImporting
? __('Importing…', 'code-snippets')
: sprintf(
// translators: %d: number of selected snippets.
__('Import Selected (%d)', 'code-snippets'),
selection.selectedItems.size
)}
// translators: %d: number of selected snippets.
: sprintf(__('Import Selected (%d)', 'code-snippets'), selection.selectedItems.size)}
</Button>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,8 @@ export interface SelectedFilesListProps {
export const SelectedFilesList: React.FC<SelectedFilesListProps> = ({ files, onRemoveFile }) =>
<div className="selected-files">
<h3>
{sprintf(
// translators: %d: number of selected files.
__('Selected files: (%d)', 'code-snippets'),
files.length
)}
{// translators: %d: number of selected files.
sprintf(__('Selected files: (%d)', 'code-snippets'), files.length)}
</h3>

<div className="selected-files-list">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Button } from '../../../common/Button'
import { ImportCard } from '../../common/ImportCard'
import { useSelection } from '../../../../hooks/useSelection'
import { SnippetSelectionTable } from './SnippetSelectionTable'
import type { FormEventHandler, ReactNode} from 'react'
import type { FormEventHandler, ReactNode } from 'react'
import type { UseSelection } from '../../../../hooks/useSelection'
import type { ImportResult } from './ImportResultDisplay'
import type { ImportableSnippetSchema } from '../../../../types/schema/ImportableSnippetSchema'
Expand Down Expand Up @@ -67,11 +67,8 @@ const SelectSnippetsForm: React.FC<SelectSnippetsFormProps> = ({ availableSnippe
<>
<div className="tablenav top">
<div>
<h2>{sprintf(
// translators: %d: number of available snippets.
__('Available snippets (%d)', 'code-snippets'),
availableSnippets.length
)}</h2>
<h2>{// translators: %d: number of available snippets.
sprintf(__('Available snippets (%d)', 'code-snippets'), availableSnippets.length)}</h2>
<p>{__('Select the snippets you would like to import.', 'code-snippets')}</p>
</div>
<div className="table-actions">
Expand Down
42 changes: 22 additions & 20 deletions src/js/components/ManageMenu/CommunityCloud/CloudSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import classnames from 'classnames'
import { Spinner } from '@wordpress/components'
import { useRestAPI } from '../../../hooks/useRestAPI'
import { REST_BASES } from '../../../utils/restAPI'
import { isLicensed } from '../../../utils/screen'
import { isProSnippet } from '../../../utils/snippets/snippets'
import { isCloudSnippetDownloadable } from '../../../utils/snippets/snippets'
import { TableNav } from '../../common/ListTable/TableNavigation'
import { LoadingStatusNotices } from '../../common/LoadingStatusNotices'
import { SnippetViewToggle } from '../../common/SnippetViewToggle'
Expand Down Expand Up @@ -67,9 +66,6 @@ const SearchBox = () => {
)
}

const isSnippetDownloadable = (snippet: CloudSnippetSchema): boolean =>
!snippet.local_id && (isLicensed() || !isProSnippet(snippet))

interface SearchResultsGridProps {
snippets: CloudSnippetSchema[]
selected: Set<CloudSnippetSchema['id']>
Expand All @@ -88,19 +84,21 @@ const SearchResultsGrid: React.FC<SearchResultsGridProps> = ({ snippets, selecte
snippet={result}
author={<CloudSnippetAuthor codevaultSlug={result.codevault} />}
isSelected={selected.has(result.id)}
onSelectedChange={isSelected => {
setSelected(previous => {
const updated = new Set(previous)

if (isSelected) {
updated.add(result.id)
} else {
updated.delete(result.id)
}

return updated
})
}}
onSelectedChange={isCloudSnippetDownloadable(result)
? isSelected => {
setSelected(previous => {
const updated = new Set(previous)

if (isSelected) {
updated.add(result.id)
} else {
updated.delete(result.id)
}

return updated
})
}
: undefined}
/>)}
</ul>

Expand Down Expand Up @@ -130,7 +128,7 @@ const useSearchResultsSelection = () => {
): Promise<void> => {
if ('download' === action) {
await Promise.all((searchResults?.snippets ?? [])
.filter(snippet => selectedIds.has(snippet.id) && isSnippetDownloadable(snippet))
.filter(snippet => selectedIds.has(snippet.id) && isCloudSnippetDownloadable(snippet))
.map(({ id }) => api.post(`${REST_BASES.cloud.snippets}/${id}/download`)))

doSearch()
Expand Down Expand Up @@ -166,7 +164,11 @@ const SearchResultsTable: React.FC<SearchResultsViewProps> = ({ snippetView, set
which="top"
actions={CLOUD_BULK_ACTIONS}
doAction={doAction}
selectAllKeys={searchResults.snippets.map(snippet => snippet.id)}
selectAllKeys={'card' === snippetView
? searchResults.snippets
.filter(snippet => isCloudSnippetDownloadable(snippet))
.map(snippet => snippet.id)
: undefined}
extraTableNav={() => <SearchFilters />}
endTableNav={which =>
'top' === which
Expand Down
76 changes: 44 additions & 32 deletions src/js/components/ManageMenu/CommunityCloud/CloudSnippetsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { __, sprintf } from '@wordpress/i18n'
import React, { useState } from 'react'
import { getSnippetType } from '../../../utils/snippets/snippets'
import { getSnippetType , isCloudSnippetDownloadable } from '../../../utils/snippets/snippets'
import { stripTags, truncateChars } from '../../../utils/text'
import { Badge } from '../../common/Badge'
import { Button } from '../../common/Button'
Expand All @@ -24,8 +24,8 @@ const updateSelection = (setSelected: SetSelected, ids: CloudSnippetId[], isSele

interface CloudSnippetRowProps {
snippet: CloudSnippetSchema
selected: Set<CloudSnippetId>
setSelected: SetSelected
selected?: Set<CloudSnippetId>
setSelected?: SetSelected
}

interface CloudSnippetActionsProps extends Pick<CloudSnippetRowProps, 'snippet'> {
Expand Down Expand Up @@ -65,20 +65,22 @@ const CloudSnippetRow: React.FC<CloudSnippetRowProps> = ({ snippet, selected, se

return (
<tr>
<th scope="row" className="check-column">
<input
id={`cb-select-${snippet.id}`}
type="checkbox"
name="checked[]"
checked={selected.has(snippet.id)}
aria-label={sprintf(
// translators: %s: snippet name.
__('Select %s', 'code-snippets'),
snippet.name
)}
onChange={event => updateSelection(setSelected, [snippet.id], event.target.checked)}
/>
</th>
{selected && setSelected
? <th scope="row" className="check-column">
{isCloudSnippetDownloadable(snippet) && <input
id={`cb-select-${snippet.id}`}
type="checkbox"
name="checked[]"
checked={selected.has(snippet.id)}
aria-label={sprintf(
// translators: %s: snippet name.
__('Select %s', 'code-snippets'),
snippet.name
)}
onChange={event => updateSelection(setSelected, [snippet.id], event.target.checked)}
/>}
</th>
: null}

<td className="column-name column-primary">
<strong>
Expand Down Expand Up @@ -110,29 +112,38 @@ const CloudSnippetRow: React.FC<CloudSnippetRowProps> = ({ snippet, selected, se

export interface CloudSnippetsTableProps {
snippets: CloudSnippetSchema[]
selected: Set<CloudSnippetId>
setSelected: SetSelected
selected?: Set<CloudSnippetId>
setSelected?: SetSelected
}

/**
* Table view for lists of cloud snippets (community search results and
* bundle contents), mirroring the card actions. Descriptions are clamped
* so all rows stay the same height.
* so all rows stay the same height. Selection is only offered when the
* containing view provides selection state, as bundle contents have no
* bulk actions.
*/
export const CloudSnippetsTable: React.FC<CloudSnippetsTableProps> = ({ snippets, selected, setSelected }) =>
<table className="wp-list-table widefat fixed striped cloud-snippets-table">
export const CloudSnippetsTable: React.FC<CloudSnippetsTableProps> = ({
snippets,
selected,
setSelected
}) => {
const downloadable = snippets.filter(snippet => isCloudSnippetDownloadable(snippet))

return <table className="wp-list-table widefat fixed striped cloud-snippets-table">
<thead>
<tr>
<td className="column-cb check-column">
<input
id="cb-select-all-cloud-snippets"
type="checkbox"
checked={0 < snippets.length && snippets.every(snippet => selected.has(snippet.id))}
aria-label={__('Select all snippets', 'code-snippets')}
onChange={event =>
updateSelection(setSelected, snippets.map(snippet => snippet.id), event.target.checked)}
/>
</td>
{selected && setSelected && (
<td className="column-cb check-column">
<input
id="cb-select-all-cloud-snippets"
type="checkbox"
checked={0 < downloadable.length && downloadable.every(snippet => selected.has(snippet.id))}
aria-label={__('Select all snippets', 'code-snippets')}
onChange={event =>
updateSelection(setSelected, downloadable.map(snippet => snippet.id), event.target.checked)}
/>
</td>)}
<th scope="col" className="column-name column-primary">{__('Name', 'code-snippets')}</th>
<th scope="col" className="column-type">{__('Type', 'code-snippets')}</th>
<th scope="col" className="column-status">{__('Status', 'code-snippets')}</th>
Expand All @@ -147,3 +158,4 @@ export const CloudSnippetsTable: React.FC<CloudSnippetsTableProps> = ({ snippets
<CloudSnippetRow key={snippet.id} {...{ snippet, selected, setSelected }} />)}
</tbody>
</table>
}
37 changes: 24 additions & 13 deletions src/js/components/ManageMenu/CommunityCloud/SearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,33 @@ const CloudSnippetDetails: React.FC<CloudSnippetDetailsProps> = ({
</h3>
</div>

{0 < snippet.tags.length || snippet.updated && (
<div className="snippet-card-meta">
{0 < snippet.tags.length && (
<span className="cloud-snippet-tags">{snippet.tags.join(', ')}</span>)}
{0 < snippet.tags.length || snippet.updated
? <div className="snippet-card-meta">
{0 < snippet.tags.length
? <span className="cloud-snippet-tags">
<span className="snippet-card-tags-label">
{__('Tags:', 'code-snippets')}
</span> {snippet.tags.join(', ')}
</span>
: null}

{snippet.updated && (
<time className="snippet-card-modified" dateTime={snippet.updated} title={snippet.updated}>
{/* translators: %s: human-readable time difference, including "ago" suffix. */
sprintf(__('Modified %s', 'code-snippets'), humanTimeDiff(snippet.updated, undefined))}
</time>)}
</div>)}
{snippet.updated
? <time className="snippet-card-modified" dateTime={snippet.updated} title={snippet.updated}>
{sprintf(
/* translators: %s: human-readable time difference. */
__('Modified %s', 'code-snippets'),
humanTimeDiff(snippet.updated, undefined)
)}
</time>
: null}
</div>
: null}

{snippet.description && (
<p className="snippet-description-content">
{snippet.description
? <p className="snippet-description-content">
{truncateChars(stripTags(snippet.description))}
</p>)}
</p>
: null}

{author}
</div>
Expand Down
Loading
Loading