fix(next): cannot filter trash (#13320)

### What?

- Updated `TrashView` to pass `trash: true` as a dedicated prop instead
of embedding it in the `query` object.
- Modified `renderListView` to correctly merge `trash` and `where`
queries by using both `queryFromArgs` and `queryFromReq`.
- Ensured filtering (via `where`) works correctly in the trash view.

### Why?

Previously, the `trash: true` flag was injected into the `query` object,
and `renderListView` only used `queryFromArgs`.
This caused the `where` clause from filters (added by the
`WhereBuilder`) to be overridden, breaking filtering in the trash view.

### How?

- Introduced an explicit `trash` prop in `renderListView` arguments.
- Updated `TrashView` to pass `trash: true` separately.
- Updated `renderListView` to apply the `trash` filter in addition to
any `where` conditions.
This commit is contained in:
Patrik
2025-07-29 17:29:04 -04:00
committed by GitHub
parent 183f313387
commit e7124f6176
6 changed files with 86 additions and 102 deletions

View File

@@ -34,13 +34,13 @@ npm i @payloadcms/plugin-csm
Then in the `plugins` array of your Payload Config, call the plugin and enable any collections that require Content Source Maps.
```ts
import { buildConfig } from "payload/config"
import contentSourceMaps from "@payloadcms/plugin-csm"
import { buildConfig } from 'payload/config'
import contentSourceMaps from '@payloadcms/plugin-csm'
const config = buildConfig({
collections: [
{
slug: "pages",
slug: 'pages',
fields: [
{
name: 'slug',
@@ -55,7 +55,7 @@ const config = buildConfig({
],
plugins: [
contentSourceMaps({
collections: ["pages"],
collections: ['pages'],
}),
],
})

View File

@@ -45,7 +45,7 @@ The following options are available:
| Path | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`url`** | String, or function that returns a string, pointing to your front-end application. This value is used as the iframe `src`. [More details](#url). |
| **`url`** | String, or function that returns a string, pointing to your front-end application. This value is used as the iframe `src`. [More details](#url). |
| **`breakpoints`** | Array of breakpoints to be used as “device sizes” in the preview window. Each item appears as an option in the toolbar. [More details](#breakpoints). |
| **`collections`** | Array of collection slugs to enable Live Preview on. |
| **`globals`** | Array of global slugs to enable Live Preview on. |

View File

@@ -19,17 +19,14 @@ type RenderTrashViewArgs = {
redirectAfterRestore?: boolean
} & AdminViewServerProps
export const TrashView: React.FC<
{ query?: any } & Omit<RenderTrashViewArgs, 'enableRowSelections'>
> = async (args) => {
export const TrashView: React.FC<Omit<RenderTrashViewArgs, 'enableRowSelections'>> = async (
args,
) => {
try {
const { List: TrashList } = await renderListView({
...args,
enableRowSelections: true,
query: {
...(args.query || {}),
trash: true, // force trash view
},
trash: true,
viewType: 'trash',
})

View File

@@ -40,6 +40,10 @@ type RenderListViewArgs = {
query: ListQuery
redirectAfterDelete?: boolean
redirectAfterDuplicate?: boolean
/**
* @experimental This prop is subject to change in future releases.
*/
trash?: boolean
} & AdminViewServerProps
/**
@@ -66,6 +70,7 @@ export const renderListView = async (
params,
query: queryFromArgs,
searchParams,
trash,
viewType,
} = args
@@ -153,7 +158,7 @@ export const renderListView = async (
where: combineWhereConstraints([query?.where, baseListFilter]),
})
if (query?.trash === true) {
if (trash === true) {
whereWithMergedSearch = {
and: [
whereWithMergedSearch,
@@ -217,7 +222,7 @@ export const renderListView = async (
enableRowSelections,
query,
req,
trash: query?.trash === true,
trash,
user,
where: whereWithMergedSearch,
}))
@@ -234,7 +239,7 @@ export const renderListView = async (
page: query?.page ? Number(query.page) : undefined,
req,
sort: query?.sort,
trash: query?.trash === true,
trash,
user,
where: whereWithMergedSearch,
})

View File

@@ -1,6 +1,7 @@
import type { Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { addListFilter } from 'helpers/e2e/addListFilter.js'
import * as path from 'path'
import { mapAsync } from 'payload'
import { fileURLToPath } from 'url'
@@ -537,6 +538,47 @@ describe('Trash', () => {
})
.toBe(0)
})
test('Should properly filter trashed docs through where query builder', async () => {
const createdDocs: Post[] = []
// Create 2 "Test Post" docs
await mapAsync([...Array(2)], async (item, index) => {
const doc = await createTrashedPostDoc({
title: `Test Post ${index + 1}`,
})
createdDocs.push(doc)
})
// Create 2 "Some Post" docs
await mapAsync([...Array(2)], async (item, index) => {
const doc = await createTrashedPostDoc({
title: `Some Post ${index + 1}`,
})
createdDocs.push(doc)
})
await page.goto(postsUrl.trash)
await addListFilter({
page,
fieldLabel: 'Title',
operatorLabel: 'is like',
value: 'Test',
})
await expect(page.locator('.cell-title', { hasText: 'Test Post' })).toHaveCount(2)
await expect(page.locator('.cell-title', { hasText: 'Some Post' })).toHaveCount(0)
// Cleanup: permanently delete the created docs
await mapAsync(createdDocs, async (doc) => {
await payload.delete({
collection: postsSlug,
id: doc.id,
trash: true, // Force permanent delete
})
})
})
})
describe('Edit view', () => {

View File

@@ -21,15 +21,8 @@
"skipLibCheck": true,
"emitDeclarationOnly": true,
"sourceMap": true,
"lib": [
"DOM",
"DOM.Iterable",
"ES2022"
],
"types": [
"node",
"jest"
],
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "jest"],
"incremental": true,
"isolatedModules": true,
"plugins": [
@@ -38,72 +31,36 @@
}
],
"paths": {
"@payload-config": [
"./test/joins/config.ts"
],
"@payloadcms/admin-bar": [
"./packages/admin-bar/src"
],
"@payloadcms/live-preview": [
"./packages/live-preview/src"
],
"@payloadcms/live-preview-react": [
"./packages/live-preview-react/src/index.ts"
],
"@payloadcms/live-preview-vue": [
"./packages/live-preview-vue/src/index.ts"
],
"@payloadcms/ui": [
"./packages/ui/src/exports/client/index.ts"
],
"@payloadcms/ui/shared": [
"./packages/ui/src/exports/shared/index.ts"
],
"@payloadcms/ui/rsc": [
"./packages/ui/src/exports/rsc/index.ts"
],
"@payloadcms/ui/scss": [
"./packages/ui/src/scss.scss"
],
"@payloadcms/ui/scss/app.scss": [
"./packages/ui/src/scss/app.scss"
],
"@payloadcms/next/*": [
"./packages/next/src/exports/*.ts"
],
"@payload-config": ["./test/_community/config.ts"],
"@payloadcms/admin-bar": ["./packages/admin-bar/src"],
"@payloadcms/live-preview": ["./packages/live-preview/src"],
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
"@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"],
"@payloadcms/ui": ["./packages/ui/src/exports/client/index.ts"],
"@payloadcms/ui/shared": ["./packages/ui/src/exports/shared/index.ts"],
"@payloadcms/ui/rsc": ["./packages/ui/src/exports/rsc/index.ts"],
"@payloadcms/ui/scss": ["./packages/ui/src/scss.scss"],
"@payloadcms/ui/scss/app.scss": ["./packages/ui/src/scss/app.scss"],
"@payloadcms/next/*": ["./packages/next/src/exports/*.ts"],
"@payloadcms/richtext-lexical/client": [
"./packages/richtext-lexical/src/exports/client/index.ts"
],
"@payloadcms/richtext-lexical/rsc": [
"./packages/richtext-lexical/src/exports/server/rsc.ts"
],
"@payloadcms/richtext-slate/rsc": [
"./packages/richtext-slate/src/exports/server/rsc.ts"
],
"@payloadcms/richtext-lexical/rsc": ["./packages/richtext-lexical/src/exports/server/rsc.ts"],
"@payloadcms/richtext-slate/rsc": ["./packages/richtext-slate/src/exports/server/rsc.ts"],
"@payloadcms/richtext-slate/client": [
"./packages/richtext-slate/src/exports/client/index.ts"
],
"@payloadcms/plugin-seo/client": [
"./packages/plugin-seo/src/exports/client.ts"
],
"@payloadcms/plugin-sentry/client": [
"./packages/plugin-sentry/src/exports/client.ts"
],
"@payloadcms/plugin-stripe/client": [
"./packages/plugin-stripe/src/exports/client.ts"
],
"@payloadcms/plugin-search/client": [
"./packages/plugin-search/src/exports/client.ts"
],
"@payloadcms/plugin-seo/client": ["./packages/plugin-seo/src/exports/client.ts"],
"@payloadcms/plugin-sentry/client": ["./packages/plugin-sentry/src/exports/client.ts"],
"@payloadcms/plugin-stripe/client": ["./packages/plugin-stripe/src/exports/client.ts"],
"@payloadcms/plugin-search/client": ["./packages/plugin-search/src/exports/client.ts"],
"@payloadcms/plugin-form-builder/client": [
"./packages/plugin-form-builder/src/exports/client.ts"
],
"@payloadcms/plugin-import-export/rsc": [
"./packages/plugin-import-export/src/exports/rsc.ts"
],
"@payloadcms/plugin-multi-tenant/rsc": [
"./packages/plugin-multi-tenant/src/exports/rsc.ts"
],
"@payloadcms/plugin-multi-tenant/rsc": ["./packages/plugin-multi-tenant/src/exports/rsc.ts"],
"@payloadcms/plugin-multi-tenant/utilities": [
"./packages/plugin-multi-tenant/src/exports/utilities.ts"
],
@@ -113,42 +70,25 @@
"@payloadcms/plugin-multi-tenant/client": [
"./packages/plugin-multi-tenant/src/exports/client.ts"
],
"@payloadcms/plugin-multi-tenant": [
"./packages/plugin-multi-tenant/src/index.ts"
],
"@payloadcms/plugin-multi-tenant": ["./packages/plugin-multi-tenant/src/index.ts"],
"@payloadcms/plugin-multi-tenant/translations/languages/all": [
"./packages/plugin-multi-tenant/src/translations/index.ts"
],
"@payloadcms/plugin-multi-tenant/translations/languages/*": [
"./packages/plugin-multi-tenant/src/translations/languages/*.ts"
],
"@payloadcms/next": [
"./packages/next/src/exports/*"
],
"@payloadcms/storage-azure/client": [
"./packages/storage-azure/src/exports/client.ts"
],
"@payloadcms/storage-s3/client": [
"./packages/storage-s3/src/exports/client.ts"
],
"@payloadcms/next": ["./packages/next/src/exports/*"],
"@payloadcms/storage-azure/client": ["./packages/storage-azure/src/exports/client.ts"],
"@payloadcms/storage-s3/client": ["./packages/storage-s3/src/exports/client.ts"],
"@payloadcms/storage-vercel-blob/client": [
"./packages/storage-vercel-blob/src/exports/client.ts"
],
"@payloadcms/storage-gcs/client": [
"./packages/storage-gcs/src/exports/client.ts"
],
"@payloadcms/storage-gcs/client": ["./packages/storage-gcs/src/exports/client.ts"],
"@payloadcms/storage-uploadthing/client": [
"./packages/storage-uploadthing/src/exports/client.ts"
]
}
},
"include": [
"${configDir}/src"
],
"exclude": [
"${configDir}/dist",
"${configDir}/build",
"${configDir}/temp",
"**/*.spec.ts"
]
"include": ["${configDir}/src"],
"exclude": ["${configDir}/dist", "${configDir}/build", "${configDir}/temp", "**/*.spec.ts"]
}