Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,7 @@ const SnippetsView: React.FC<SnippetsViewProps> = ({
}) => {
const { activeByCondition } = useFilteredSnippets()
const columns = useMemo(() => getTableColumns(hiddenColumns), [hiddenColumns])
const itemsPerPage = window.CODE_SNIPPETS_MANAGE?.snippetsPerPage
const pageCount = itemsPerPage && Math.ceil(snippets.length / itemsPerPage)
const itemsPerPage = Number(window.CODE_SNIPPETS_MANAGE?.snippetsPerPage) || undefined
const endTableNav = (which: 'top' | 'bottom') =>
'top' === which
? <SnippetViewToggle snippetView={snippetView} setSnippetView={setSnippetView} />
Expand All @@ -148,7 +147,7 @@ const SnippetsView: React.FC<SnippetsViewProps> = ({
columns={columns}
actions={actions}
doAction={doAction}
totalPages={pageCount}
itemsPerPage={itemsPerPage}
extraTableNav={extraTableNav}
selectAllControl
endTableNav={endTableNav}
Expand Down
22 changes: 22 additions & 0 deletions src/js/components/ManageMenu/SnippetsTable/SnippetsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { isLicensed } from '../../../utils/screen'
import { SNIPPET_TYPE_LABELS, getSnippetAddNewUrl, getSnippetType, isProType } from '../../../utils/snippets/snippets'
import { buildUrl } from '../../../utils/urls'
import { Badge } from '../../common/Badge'
import { Button } from '../../common/Button'
import { Notice } from '../../common/Notice'
import { ScreenMetaSlot } from '../../common/ScreenMetaSlot'
import { UpsellPage } from '../../common/UpsellDialog'
Expand Down Expand Up @@ -99,6 +100,26 @@ const SafeModeNotice = () =>
</Notice>
: null

const SnippetsLoadErrorNotice = () => {
const { hasLoadError, refreshSnippetsList } = useSnippetsList()

return (
<>
{hasLoadError &&
<Notice type="error">
<p>
{__('Some of your snippets could not be loaded, so this list may be incomplete.', 'code-snippets')}{' '}
<Button link onClick={() => {
refreshSnippetsList().catch(() => undefined)
}}>
{__('Try again', 'code-snippets')}
</Button>
</p>
</Notice>}
</>
)
}

// Counts render immediately from localized values, then switch to live values.
const useSnippetTypeCounts = () => {
const { snippetsList } = useSnippetsList()
Expand Down Expand Up @@ -174,6 +195,7 @@ const SnippetsTableInner = () => {
</>)}

<SafeModeNotice />
<SnippetsLoadErrorNotice />

{isUpsell
? <UpsellPage />
Expand Down
28 changes: 13 additions & 15 deletions src/js/components/common/ListTable/ListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export interface ListTableRowsProps<T, K extends Key> {
}

export interface ListTablePaginationProps {
totalPages?: number
/** Items on each page; every item is on one page when omitted. */
itemsPerPage?: number
pageSearchParam?: string
}

Expand Down Expand Up @@ -78,17 +79,11 @@ export const sortTableItems = <T, >(

const pageItems = <T, >(
items: T[],
{ currentPage, totalPages }: { currentPage: number; totalPages?: number }
): T[] => {
if (totalPages) {
const itemsPerPage = Math.ceil(items.length / totalPages)
const start = (currentPage - 1) * itemsPerPage
const end = start + itemsPerPage
return items.slice(start, end)
} else {
return items
}
}
{ currentPage, itemsPerPage }: { currentPage: number; itemsPerPage?: number }
): T[] =>
itemsPerPage
? items.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
: items

const getVisibleSelected = <T, K extends Key>(
visibleItems: T[],
Expand Down Expand Up @@ -137,7 +132,7 @@ export interface ListTableProps<T, K extends Key, A extends string> extends List
export const ListTable = <T, K extends Key, A extends string = never>({
items,
getKey,
totalPages,
itemsPerPage,
pageSearchParam = 'paged',
initialSort,
...tableProps
Expand All @@ -154,9 +149,11 @@ export const ListTable = <T, K extends Key, A extends string = never>({
() => initialSort?.direction ?? 'asc'
)

const totalPages = itemsPerPage ? Math.ceil(items.length / itemsPerPage) : undefined

const visibleItems: T[] = useMemo(
() => pageItems(sortTableItems(items, sortColumn, sortDirection), { currentPage, totalPages }),
[items, sortColumn, sortDirection, currentPage, totalPages])
() => pageItems(sortTableItems(items, sortColumn, sortDirection), { currentPage, itemsPerPage }),
[items, sortColumn, sortDirection, currentPage, itemsPerPage])

return (
<PartialDataListTable
Expand All @@ -178,6 +175,7 @@ export interface PartialDataListTableProps<T, K extends Key, A extends string>
ListTableNavProps<K, A> {
sortColumn: ListTableColumn<T> | undefined
totalItems: number
totalPages?: number
currentPage: number
visibleItems: T[]
beforeTable?: ReactNode
Expand Down
6 changes: 6 additions & 0 deletions src/js/hooks/useSnippetsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { Snippet } from '../types/Snippet'

export interface SnippetsListContext {
snippetsList: readonly Snippet[] | undefined
/** Whether the last attempt to load the full list failed, leaving only the snippets sent with the page. */
hasLoadError: boolean
refreshSnippetsList: () => Promise<void>
}

Expand All @@ -18,14 +20,17 @@ export const WithSnippetsListContext: React.FC<PropsWithChildren> = ({ children
const [snippetsList, setSnippetsList] = useState<Snippet[] | undefined>(
() => window.CODE_SNIPPETS_MANAGE?.snippetsList?.map(parseSnippetObject)
)
const [hasLoadError, setHasLoadError] = useState(false)

const refreshSnippetsList = useCallback(async (): Promise<void> => {
try {
console.info('Fetching snippets list')
const response = await fetchAll(isNetworkAdmin())
setSnippetsList(response)
setHasLoadError(false)
} catch (error: unknown) {
console.error('Error fetching snippets list', error)
setHasLoadError(true)
}
}, [fetchAll])

Expand All @@ -36,6 +41,7 @@ export const WithSnippetsListContext: React.FC<PropsWithChildren> = ({ children

const value: SnippetsListContext = {
snippetsList,
hasLoadError,
refreshSnippetsList
}

Expand Down
2 changes: 1 addition & 1 deletion src/php/Admin/Menus/Manage/Manage_Menu_Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ function ( Snippet $snippet ) {
get_snippets()
);

$inline_limit = apply_filters( 'code_snippets/manage/inline_snippets_limit', min( $snippets_per_page, 100 ) );
$inline_limit = apply_filters( 'code_snippets/manage/inline_snippets_limit', $snippets_per_page );
$inline_limit = max( 0, intval( $inline_limit ) );

if ( $inline_limit > 0 ) {
Expand Down
29 changes: 26 additions & 3 deletions tests/unit/Admin/Menus/Manage/Manage_Menu_Assets_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,28 @@ public function test_enqueue_applies_disabled_inline_snippets_limit(): void {
$this->assertArrayNotHasKey( 'snippetsList', $localized );
}

/**
* As many snippets as the user shows on a page are sent with the page, even beyond 100.
*
* @return void
*/
public function test_enqueue_localizes_a_page_of_snippets_beyond_one_hundred(): void {
$existing = count( get_snippets() );

for ( $i = $existing; $i < 120; $i++ ) {
save_snippet( new Snippet( [ 'name' => "Per-page fixture $i" ] ) );
}

update_user_option( get_current_user_id(), 'snippets_per_page', 110 );

$this->enqueue_assets();
$localized = $this->get_localized_data();

delete_user_option( get_current_user_id(), 'snippets_per_page' );

$this->assertCount( 110, $localized['snippetsList'] );
}

/**
* Snippet lists at or below the configured limit are localized inline.
*
Expand Down Expand Up @@ -162,12 +184,13 @@ public static function provide_inline_limit_offsets(): array {
}

/**
* Snippet lists above the configured limit are omitted.
* A library larger than the inline limit is cut to the limit; the rest load after the page.
*
* @return void
*/
public function test_enqueue_omits_snippets_above_inline_limit(): void {
public function test_enqueue_cuts_snippets_above_inline_limit(): void {
save_snippet( new Snippet( [ 'name' => 'Oversized Inline Snippet Fixture' ] ) );
save_snippet( new Snippet( [ 'name' => 'Second Oversized Inline Snippet Fixture' ] ) );
$inline_limit = count( get_snippets() ) - 1;
$filter = static fn() => $inline_limit;
add_filter( 'code_snippets/manage/inline_snippets_limit', $filter );
Expand All @@ -177,7 +200,7 @@ public function test_enqueue_omits_snippets_above_inline_limit(): void {

remove_filter( 'code_snippets/manage/inline_snippets_limit', $filter );

$this->assertArrayNotHasKey( 'snippetsList', $localized );
$this->assertCount( $inline_limit, $localized['snippetsList'] );
}

/**
Expand Down
Loading