chore: merge

This commit is contained in:
James
2024-04-01 17:47:28 -04:00
28 changed files with 339 additions and 198 deletions

View File

@@ -229,8 +229,8 @@ jobs:
- plugin-nested-docs
- plugin-seo
# - refresh-permissions
# - uploads
- versions
- uploads
steps:
- name: Use Node.js 18

View File

@@ -0,0 +1,45 @@
import httpStatus from 'http-status'
import { findByIDOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
import type { CollectionRouteHandlerWithID } from '../types.js'
import { routeError } from '../routeError.js'
export const preview: CollectionRouteHandlerWithID = async ({ id, collection, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const result = await findByIDOperation({
id,
collection,
depth: isNumber(depth) ? Number(depth) : undefined,
draft: searchParams.get('draft') === 'true',
req,
})
let previewURL: string
const generatePreviewURL = req.payload.config.collections.find(
(config) => config.slug === collection.config.slug,
)?.admin?.preview
if (typeof generatePreviewURL === 'function') {
try {
previewURL = await generatePreviewURL(result, {
locale: req.locale,
token: req.user?.token,
})
} catch (err) {
routeError({
collection,
err,
req,
})
}
}
return Response.json(previewURL, {
status: httpStatus.OK,
})
}

View File

@@ -0,0 +1,44 @@
import httpStatus from 'http-status'
import { findOneOperation } from 'payload/operations'
import { isNumber } from 'payload/utilities'
import type { GlobalRouteHandler } from '../types.js'
import { routeError } from '../routeError.js'
export const preview: GlobalRouteHandler = async ({ globalConfig, req }) => {
const { searchParams } = req
const depth = searchParams.get('depth')
const result = await findOneOperation({
slug: globalConfig.slug,
depth: isNumber(depth) ? Number(depth) : undefined,
draft: searchParams.get('draft') === 'true',
globalConfig,
req,
})
let previewURL: string
const generatePreviewURL = req.payload.config.globals.find(
(config) => config.slug === globalConfig.slug,
)?.admin?.preview
if (typeof generatePreviewURL === 'function') {
try {
previewURL = await generatePreviewURL(result, {
locale: req.locale,
token: req.user?.token,
})
} catch (err) {
routeError({
err,
req,
})
}
}
return Response.json(previewURL, {
status: httpStatus.OK,
})
}

View File

@@ -34,6 +34,7 @@ import { find } from './collections/find.js'
import { findByID } from './collections/findByID.js'
import { findVersionByID } from './collections/findVersionByID.js'
import { findVersions } from './collections/findVersions.js'
import { preview as previewCollection } from './collections/preview.js'
import { restoreVersion } from './collections/restoreVersion.js'
import { update } from './collections/update.js'
import { updateByID } from './collections/updateByID.js'
@@ -42,6 +43,7 @@ import { docAccess as docAccessGlobal } from './globals/docAccess.js'
import { findOne } from './globals/findOne.js'
import { findVersionByID as findVersionByIdGlobal } from './globals/findVersionByID.js'
import { findVersions as findVersionsGlobal } from './globals/findVersions.js'
import { preview as previewGlobal } from './globals/preview.js'
import { restoreVersion as restoreVersionGlobal } from './globals/restoreVersion.js'
import { update as updateGlobal } from './globals/update.js'
import { routeError } from './routeError.js'
@@ -60,6 +62,7 @@ const endpoints = {
getFile,
init,
me,
preview: previewCollection,
versions: findVersions,
},
PATCH: {
@@ -88,6 +91,7 @@ const endpoints = {
'doc-versions': findVersionsGlobal,
'doc-versions-by-id': findVersionByIdGlobal,
findOne,
preview: previewGlobal,
},
POST: {
'doc-access': docAccessGlobal,
@@ -171,6 +175,7 @@ export const GET =
endpoints: req.payload.config.endpoints,
request,
})
if (disableEndpoints) return disableEndpoints
collection = req.payload.collections?.[slug1]
@@ -212,10 +217,16 @@ export const GET =
if (slug2 === 'file') {
// /:collection/file/:filename
res = await endpoints.collection.GET.getFile({ collection, filename: slug3, req })
} else if (slug3 in endpoints.collection.GET) {
// /:collection/:id/preview
res = await (endpoints.collection.GET[slug3] as CollectionRouteHandlerWithID)({
id: slug2,
collection,
req,
})
} else if (`doc-${slug2}-by-id` in endpoints.collection.GET) {
// /:collection/access/:id
// /:collection/versions/:id
res = await (
endpoints.collection.GET[`doc-${slug2}-by-id`] as CollectionRouteHandlerWithID
)({ id: slug3, collection, req })
@@ -229,6 +240,7 @@ export const GET =
endpoints: globalConfig.endpoints,
request,
})
if (disableEndpoints) return disableEndpoints
const customEndpointResponse = await handleCustomEndpoints({
@@ -236,6 +248,7 @@ export const GET =
entitySlug: `${slug1}/${slug2}`,
payloadRequest: req,
})
if (customEndpointResponse) return customEndpointResponse
switch (slug.length) {
@@ -244,9 +257,16 @@ export const GET =
res = await endpoints.global.GET.findOne({ globalConfig, req })
break
case 3:
if (`doc-${slug3}` in endpoints.global.GET) {
if (slug3 in endpoints.global.GET) {
// /globals/:slug/preview
res = await (endpoints.global.GET[slug3] as GlobalRouteHandler)({
globalConfig,
req,
})
} else if (`doc-${slug3}` in endpoints.global.GET) {
// /globals/:slug/access
// /globals/:slug/versions
// /globals/:slug/preview
res = await (endpoints.global.GET?.[`doc-${slug3}`] as GlobalRouteHandler)({
globalConfig,
req,

View File

@@ -45,6 +45,7 @@
"@payloadcms/translations": "workspace:*",
"@swc-node/core": "^1.13.0",
"@swc-node/sourcemap-support": "^0.5.0",
"@types/probe-image-size": "^7.2.4",
"bson-objectid": "2.0.4",
"conf": "10.2.0",
"console-table-printer": "2.11.2",
@@ -55,7 +56,6 @@
"find-up": "4.1.0",
"get-tsconfig": "^4.7.2",
"http-status": "1.6.2",
"image-size": "^1.1.1",
"joi": "^17.12.1",
"json-schema-to-typescript": "11.0.3",
"jsonwebtoken": "9.0.1",
@@ -67,6 +67,7 @@
"pino-pretty": "10.2.0",
"pirates": "^4.0.6",
"pluralize": "8.0.0",
"probe-image-size": "^7.2.3",
"sanitize-filename": "1.6.3",
"scheduler": "0.23.0",
"scmp": "2.1.0"

View File

@@ -1,11 +1 @@
export type CustomPreviewButtonProps = React.ComponentType<
DefaultPreviewButtonProps & {
DefaultButton: React.ComponentType<DefaultPreviewButtonProps>
}
>
export type DefaultPreviewButtonProps = {
disabled: boolean
label: string
preview: () => void
}
export type CustomPreviewButton = React.ComponentType

View File

@@ -1 +1 @@
export type CustomPublishButtonProps = React.ComponentType
export type CustomPublishButton = React.ComponentType

View File

@@ -1 +1 @@
export type CustomSaveButtonProps = React.ComponentType
export type CustomSaveButton = React.ComponentType

View File

@@ -1 +1 @@
export type CustomSaveDraftButtonProps = React.ComponentType
export type CustomSaveDraftButton = React.ComponentType

View File

@@ -2,11 +2,10 @@ export type { RichTextAdapter, RichTextFieldProps } from './RichText.js'
export type { CellComponentProps, DefaultCellComponentProps } from './elements/Cell.js'
export type { ConditionalDateProps } from './elements/DatePicker.js'
export type { DayPickerProps, SharedProps, TimePickerProps } from './elements/DatePicker.js'
export type { DefaultPreviewButtonProps } from './elements/PreviewButton.js'
export type { CustomPreviewButtonProps } from './elements/PreviewButton.js'
export type { CustomPublishButtonProps } from './elements/PublishButton.js'
export type { CustomSaveButtonProps } from './elements/SaveButton.js'
export type { CustomSaveDraftButtonProps } from './elements/SaveDraftButton.js'
export type { CustomPreviewButton } from './elements/PreviewButton.js'
export type { CustomPublishButton } from './elements/PublishButton.js'
export type { CustomSaveButton } from './elements/SaveButton.js'
export type { CustomSaveDraftButton } from './elements/SaveDraftButton.js'
export type {
DocumentTab,
DocumentTabComponent,

View File

@@ -50,6 +50,7 @@ export const createClientCollectionConfig = (collection: SanitizedCollectionConf
if ('upload' in sanitized && typeof sanitized.upload === 'object') {
sanitized.upload = { ...sanitized.upload }
delete sanitized.upload.handlers
delete sanitized.upload.adminThumbnail
}
if ('auth' in sanitized && typeof sanitized.auth === 'object') {

View File

@@ -2,10 +2,10 @@ import type { GraphQLInputObjectType, GraphQLNonNull, GraphQLObjectType } from '
import type { DeepRequired } from 'ts-essentials'
import type {
CustomPreviewButtonProps,
CustomPublishButtonProps,
CustomSaveButtonProps,
CustomSaveDraftButtonProps,
CustomPreviewButton,
CustomPublishButton,
CustomSaveButton,
CustomSaveDraftButton,
} from '../../admin/types.js'
import type { Auth, ClientUser, IncomingAuthType } from '../../auth/types.js'
import type {
@@ -211,23 +211,23 @@ export type CollectionAdminOptions = {
/**
* Replaces the "Preview" button
*/
PreviewButton?: CustomPreviewButtonProps
PreviewButton?: CustomPreviewButton
/**
* Replaces the "Publish" button
* + drafts must be enabled
*/
PublishButton?: CustomPublishButtonProps
PublishButton?: CustomPublishButton
/**
* Replaces the "Save" button
* + drafts must be disabled
*/
SaveButton?: CustomSaveButtonProps
SaveButton?: CustomSaveButton
/**
* Replaces the "Save Draft" button
* + drafts must be enabled
* + autosave must be disabled
*/
SaveDraftButton?: CustomSaveDraftButtonProps
SaveDraftButton?: CustomSaveDraftButton
}
views?: {
/**

View File

@@ -2,10 +2,10 @@ import type { GraphQLNonNull, GraphQLObjectType } from 'graphql'
import type { DeepRequired } from 'ts-essentials'
import type {
CustomPreviewButtonProps,
CustomPublishButtonProps,
CustomSaveButtonProps,
CustomSaveDraftButtonProps,
CustomPreviewButton,
CustomPublishButton,
CustomSaveButton,
CustomSaveDraftButton,
} from '../../admin/types.js'
import type { User } from '../../auth/types.js'
import type {
@@ -79,23 +79,23 @@ export type GlobalAdminOptions = {
/**
* Replaces the "Preview" button
*/
PreviewButton?: CustomPreviewButtonProps
PreviewButton?: CustomPreviewButton
/**
* Replaces the "Publish" button
* + drafts must be enabled
*/
PublishButton?: CustomPublishButtonProps
PublishButton?: CustomPublishButton
/**
* Replaces the "Save" button
* + drafts must be disabled
*/
SaveButton?: CustomSaveButtonProps
SaveButton?: CustomSaveButton
/**
* Replaces the "Save Draft" button
* + drafts must be enabled
* + autosave must be disabled
*/
SaveDraftButton?: CustomSaveDraftButtonProps
SaveDraftButton?: CustomSaveDraftButton
}
views?: {
/**

View File

@@ -1,15 +1,14 @@
import { imageSize } from 'image-size'
import fs from 'fs'
import probeImageSize from 'probe-image-size'
import type { PayloadRequest } from '../types/index.js'
import type { ProbedImageSize } from './types.js'
export function getImageSize(file: PayloadRequest['file']): ProbedImageSize {
if (file.tempFilePath) {
const dimensions = imageSize(file.tempFilePath)
return { height: dimensions.height, width: dimensions.width }
const data = fs.readFileSync(file.tempFilePath)
return probeImageSize.sync(data)
}
const buffer = Buffer.from(file.data)
const dimensions = imageSize(buffer)
return { height: dimensions.height, width: dimensions.width }
return probeImageSize.sync(file.data)
}

View File

@@ -154,15 +154,9 @@ export const DocumentControls: React.FC<{
</div>
<div className={`${baseClass}__controls-wrapper`}>
<div className={`${baseClass}__controls`}>
{/* {(collectionConfig?.admin?.preview || globalConfig?.admin?.preview) && (
<PreviewButton
CustomComponent={
collectionConfig?.admin?.components?.edit?.PreviewButton ||
globalConfig?.admin?.components?.elements?.PreviewButton
}
generatePreviewURL={collectionConfig?.admin?.preview || globalConfig?.admin?.preview}
/>
)} */}
{componentMap?.isPreviewEnabled && (
<PreviewButton CustomComponent={componentMap.PreviewButton} />
)}
{hasSavePermission && (
<React.Fragment>
{collectionConfig?.versions?.drafts || globalConfig?.versions?.drafts ? (

View File

@@ -1,31 +1,24 @@
'use client'
import type { GeneratePreviewURL } from 'payload/config'
import type { CustomPreviewButtonProps, DefaultPreviewButtonProps } from 'payload/types'
import React from 'react'
import React, { useCallback, useRef, useState } from 'react'
import { toast } from 'react-toastify'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { useAuth } from '../../providers/Auth/index.js'
import { useConfig } from '../../providers/Config/index.js'
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { Button } from '../Button/index.js'
import { usePreviewURL } from './usePreviewURL.js'
const baseClass = 'preview-btn'
const DefaultPreviewButton: React.FC<DefaultPreviewButtonProps> = ({
disabled,
label,
preview,
}) => {
const DefaultPreviewButton: React.FC = () => {
const { generatePreviewURL, label } = usePreviewURL()
return (
<Button
buttonStyle="secondary"
className={baseClass}
disabled={disabled}
onClick={preview}
// disabled={disabled}
onClick={() =>
generatePreviewURL({
openPreviewWindow: true,
})
}
size="small"
>
{label}
@@ -33,66 +26,12 @@ const DefaultPreviewButton: React.FC<DefaultPreviewButtonProps> = ({
)
}
export type PreviewButtonProps = {
CustomComponent?: CustomPreviewButtonProps
generatePreviewURL?: GeneratePreviewURL
type Props = {
CustomComponent?: React.ReactNode
}
export const PreviewButton: React.FC<PreviewButtonProps> = ({
CustomComponent,
generatePreviewURL,
}) => {
const { id, collectionSlug, globalSlug } = useDocumentInfo()
export const PreviewButton: React.FC<Props> = ({ CustomComponent }) => {
if (CustomComponent) return CustomComponent
const [isLoading, setIsLoading] = useState(false)
const { code: locale } = useLocale()
const { token } = useAuth()
const {
routes: { api },
serverURL,
} = useConfig()
const { t } = useTranslation()
const isGeneratingPreviewURL = useRef(false)
// we need to regenerate the preview URL every time the button is clicked
// to do this we need to fetch the document data fresh from the API
// this will ensure the latest data is used when generating the preview URL
const preview = useCallback(async () => {
if (!generatePreviewURL || isGeneratingPreviewURL.current) return
isGeneratingPreviewURL.current = true
try {
setIsLoading(true)
let url = `${serverURL}${api}`
if (collectionSlug) url = `${url}/${collectionSlug}/${id}`
if (globalSlug) url = `${url}/globals/${globalSlug}`
const data = await fetch(`${url}?draft=true&locale=${locale}&fallback-locale=null`).then(
(res) => res.json(),
)
const previewURL = await generatePreviewURL(data, { locale, token })
if (!previewURL) throw new Error()
setIsLoading(false)
isGeneratingPreviewURL.current = false
window.open(previewURL, '_blank')
} catch (err) {
setIsLoading(false)
isGeneratingPreviewURL.current = false
toast.error(t('error:previewing'))
}
}, [serverURL, api, collectionSlug, globalSlug, id, generatePreviewURL, locale, token, t])
return (
<RenderCustomComponent
CustomComponent={CustomComponent}
DefaultComponent={DefaultPreviewButton}
componentProps={{
DefaultButton: DefaultPreviewButton,
disabled: isLoading || !generatePreviewURL,
label: isLoading ? t('general:loading') : t('version:preview'),
preview,
}}
/>
)
return <DefaultPreviewButton />
}

View File

@@ -0,0 +1,70 @@
'use client'
import { useCallback, useRef, useState } from 'react'
import { toast } from 'react-toastify'
import { useConfig } from '../../providers/Config/index.js'
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
export const usePreviewURL = (): {
generatePreviewURL: ({ openPreviewWindow }: { openPreviewWindow?: boolean }) => void
isLoading: boolean
label: string
previewURL: string
} => {
const { id, collectionSlug, globalSlug } = useDocumentInfo()
const [isLoading, setIsLoading] = useState(false)
const [previewURL, setPreviewURL] = useState('')
const { code: locale } = useLocale()
const {
routes: { api },
serverURL,
} = useConfig()
const { t } = useTranslation()
const isGeneratingPreviewURL = useRef(false)
// we need to regenerate the preview URL every time the button is clicked
// to do this we need to fetch the document data fresh from the API
// this will ensure the latest data is used when generating the preview URL
const generatePreviewURL = useCallback(
async ({ openPreviewWindow = false }) => {
if (isGeneratingPreviewURL.current) return
isGeneratingPreviewURL.current = true
try {
setIsLoading(true)
let url = `${serverURL}${api}`
if (collectionSlug) url = `${url}/${collectionSlug}/${id}/preview`
if (globalSlug) url = `${url}/globals/${globalSlug}/preview`
const res = await fetch(`${url}${locale ? `?locale=${locale}` : ''}`)
if (!res.ok) throw new Error()
const newPreviewURL = await res.json()
if (!newPreviewURL) throw new Error()
setPreviewURL(newPreviewURL)
setIsLoading(false)
isGeneratingPreviewURL.current = false
if (openPreviewWindow) window.open(newPreviewURL, '_blank')
} catch (err) {
setIsLoading(false)
isGeneratingPreviewURL.current = false
toast.error(t('error:previewing'))
}
},
[serverURL, api, collectionSlug, globalSlug, id, locale, t],
)
return {
generatePreviewURL,
isLoading,
label: isLoading ? t('general:loading') : t('version:preview'),
previewURL,
}
}

View File

@@ -70,8 +70,8 @@ export const buildComponentMap = (args: {
const SaveDraftButtonComponent = collectionConfig?.admin?.components?.edit?.SaveDraftButton
const SaveDraftButton = SaveDraftButtonComponent ? <SaveDraftButtonComponent /> : undefined
/* const PreviewButtonComponent = collectionConfig?.admin?.components?.edit?.PreviewButton
const PreviewButton = PreviewButtonComponent ? <PreviewButtonComponent /> : undefined */
const PreviewButtonComponent = collectionConfig?.admin?.components?.edit?.PreviewButton
const PreviewButton = PreviewButtonComponent ? <PreviewButtonComponent /> : undefined
const PublishButtonComponent = collectionConfig?.admin?.components?.edit?.PublishButton
const PublishButton = PublishButtonComponent ? <PublishButtonComponent /> : undefined
@@ -111,7 +111,7 @@ export const buildComponentMap = (args: {
BeforeListTable,
Edit: <Edit collectionSlug={collectionConfig.slug} />,
List: <List collectionSlug={collectionConfig.slug} />,
/* PreviewButton, */
PreviewButton,
PublishButton,
SaveButton,
SaveDraftButton,
@@ -123,6 +123,7 @@ export const buildComponentMap = (args: {
fieldSchema: fields,
readOnly: readOnlyOverride,
}),
isPreviewEnabled: !!collectionConfig?.admin?.preview,
}
return {
@@ -143,8 +144,8 @@ export const buildComponentMap = (args: {
const SaveDraftButton = globalConfig?.admin?.components?.elements?.SaveDraftButton
const SaveDraftButtonComponent = SaveDraftButton ? <SaveDraftButton /> : undefined
/* const PreviewButton = globalConfig?.admin?.components?.elements?.PreviewButton
const PreviewButtonComponent = PreviewButton ? <PreviewButton /> : undefined */
const PreviewButton = globalConfig?.admin?.components?.elements?.PreviewButton
const PreviewButtonComponent = PreviewButton ? <PreviewButton /> : undefined
const PublishButton = globalConfig?.admin?.components?.elements?.PublishButton
const PublishButtonComponent = PublishButton ? <PublishButton /> : undefined
@@ -164,7 +165,7 @@ export const buildComponentMap = (args: {
const componentMap: GlobalComponentMap = {
Edit: <Edit globalSlug={globalConfig.slug} />,
/* PreviewButton: PreviewButtonComponent, */
PreviewButton: PreviewButtonComponent,
PublishButton: PublishButtonComponent,
SaveButton: SaveButtonComponent,
SaveDraftButton: SaveDraftButtonComponent,
@@ -176,6 +177,7 @@ export const buildComponentMap = (args: {
fieldSchema: fields,
readOnly: readOnlyOverride,
}),
isPreviewEnabled: !!globalConfig?.admin?.preview,
}
return {

View File

@@ -13,8 +13,6 @@ import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types'
import { isPlainObject } from 'payload/utilities'
import React, { Fragment } from 'react'
import type { RadioFieldProps } from '../../..//fields/RadioGroup/index.js'
import type { RichTextFieldProps } from '../../..//fields/RichText/index.js'
import type { ArrayFieldProps } from '../../../fields/Array/index.js'
import type { BlocksFieldProps } from '../../../fields/Blocks/index.js'
import type { CheckboxFieldProps } from '../../../fields/Checkbox/index.js'
@@ -26,7 +24,9 @@ import type { GroupFieldProps } from '../../../fields/Group/index.js'
import type { JSONFieldProps } from '../../../fields/JSON/index.js'
import type { NumberFieldProps } from '../../../fields/Number/index.js'
import type { PointFieldProps } from '../../../fields/Point/index.js'
import type { RadioFieldProps } from '../../../fields/RadioGroup/index.js'
import type { RelationshipFieldProps } from '../../../fields/Relationship/types.js'
import type { RichTextFieldProps } from '../../../fields/RichText/index.js'
import type { RowFieldProps } from '../../../fields/Row/types.js'
import type { SelectFieldProps } from '../../../fields/Select/index.js'
import type { TabsFieldProps } from '../../../fields/Tabs/index.js'
@@ -199,6 +199,8 @@ export const mapFields = (args: {
? field.label
: undefined,
labels: 'labels' in field ? field.labels : undefined,
options: 'options' in field ? field.options : undefined,
relationTo: 'relationTo' in field ? field.relationTo : undefined,
}
switch (field.type) {
@@ -727,9 +729,10 @@ export const mapFields = (args: {
if (!disableAddingID && !hasID) {
// TODO: For all fields (not just this one) we need to add the name to both .fieldComponentProps.name AND .name. This can probably be improved
result.unshift({
result.push({
name: 'id',
type: 'text',
CustomField: null,
cellComponentProps: {
name: 'id',
},

View File

@@ -103,12 +103,13 @@ export type GlobalComponentMap = ConfigComponentMapBase
export type ConfigComponentMapBase = {
Edit: React.ReactNode
/* PreviewButton: React.ReactNode */
PreviewButton: React.ReactNode
PublishButton: React.ReactNode
SaveButton: React.ReactNode
SaveDraftButton: React.ReactNode
actionsMap: ActionMap
fieldMap: FieldMap
isPreviewEnabled: boolean
}
export type ComponentMap = {

83
pnpm-lock.yaml generated
View File

@@ -715,6 +715,9 @@ importers:
'@swc-node/sourcemap-support':
specifier: ^0.5.0
version: 0.5.0
'@types/probe-image-size':
specifier: ^7.2.4
version: 7.2.4
bson-objectid:
specifier: 2.0.4
version: 2.0.4
@@ -745,9 +748,6 @@ importers:
http-status:
specifier: 1.6.2
version: 1.6.2
image-size:
specifier: ^1.1.1
version: 1.1.1
joi:
specifier: ^17.12.1
version: 17.12.2
@@ -781,6 +781,9 @@ importers:
pluralize:
specifier: 8.0.0
version: 8.0.0
probe-image-size:
specifier: ^7.2.3
version: 7.2.3
sanitize-filename:
specifier: 1.6.3
version: 1.6.3
@@ -5974,7 +5977,7 @@ packages:
resolution: {integrity: sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A==}
dependencies:
'@types/estree': 1.0.5
'@types/json-schema': 7.0.15
'@types/json-schema': 7.0.12
/@types/esprima@4.0.6:
resolution: {integrity: sha512-lIk+kSt9lGv5hxK6aZNjiUEGZqKmOTpmg0tKiJQI+Ow98fLillxsiZNik5+RcP7mXL929KiTH/D9jGtpDlMbVw==}
@@ -6129,7 +6132,6 @@ packages:
/@types/json-schema@7.0.12:
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
dev: true
/@types/json-schema@7.0.15:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -6197,6 +6199,12 @@ packages:
- supports-color
dev: true
/@types/needle@3.3.0:
resolution: {integrity: sha512-UFIuc1gdyzAqeVUYpSL+cliw2MmU/ZUhVZKE7Zo4wPbgc8hbljeKSnn6ls6iG8r5jpegPXLUIhJ+Wb2kLVs8cg==}
dependencies:
'@types/node': 16.18.85
dev: false
/@types/node-fetch@2.6.11:
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
dependencies:
@@ -6293,6 +6301,13 @@ packages:
resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==}
dev: false
/@types/probe-image-size@7.2.4:
resolution: {integrity: sha512-HVqYj3L+D+S/6qpQRv5qMxrD/5pglzZuhP7ZIqgVSZ+Ck4z1TCFkNIRG8WesFueQTqWFTSgkkAl6f8lwxFPQSw==}
dependencies:
'@types/needle': 3.3.0
'@types/node': 16.18.85
dev: false
/@types/prompts@2.4.9:
resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==}
dependencies:
@@ -8644,6 +8659,17 @@ packages:
dependencies:
ms: 2.0.0
/debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
dev: false
/debug@4.3.4(supports-color@5.5.0):
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@@ -10985,14 +11011,6 @@ packages:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
/image-size@1.1.1:
resolution: {integrity: sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==}
engines: {node: '>=16.x'}
hasBin: true
dependencies:
queue: 6.0.2
dev: false
/immer@9.0.21:
resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==}
@@ -12068,7 +12086,7 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 16.18.85
'@types/node': 20.11.28
merge-stream: 2.0.0
supports-color: 8.1.1
dev: true
@@ -13071,6 +13089,18 @@ packages:
/natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
/needle@2.9.1:
resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==}
engines: {node: '>= 4.4.x'}
hasBin: true
dependencies:
debug: 3.2.7
iconv-lite: 0.4.24
sax: 1.3.0
transitivePeerDependencies:
- supports-color
dev: false
/negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
@@ -14796,6 +14826,16 @@ packages:
engines: {node: '>=6'}
dev: false
/probe-image-size@7.2.3:
resolution: {integrity: sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==}
dependencies:
lodash.merge: 4.6.2
needle: 2.9.1
stream-parser: 0.3.1
transitivePeerDependencies:
- supports-color
dev: false
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: true
@@ -14930,12 +14970,6 @@ packages:
requiresBuild: true
dev: true
/queue@6.0.2:
resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==}
dependencies:
inherits: 2.0.4
dev: false
/quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
@@ -15627,7 +15661,6 @@ packages:
/sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
dev: true
/saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
@@ -16115,6 +16148,14 @@ packages:
stubs: 3.0.0
dev: true
/stream-parser@0.3.1:
resolution: {integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==}
dependencies:
debug: 2.6.9
transitivePeerDependencies:
- supports-color
dev: false
/stream-shift@1.0.3:
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
dev: true

View File

@@ -2,7 +2,7 @@
import { useConfig } from '@payloadcms/ui/providers/Config'
import LinkImport from 'next/link.js'
import { useParams } from 'next/navigation'
import { useParams } from 'next/navigation.js'
import React from 'react'
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default

View File

@@ -1,9 +1,9 @@
import type { EditViewComponent } from 'payload/types'
import { SetStepNav } from '@payloadcms/ui/elements/StepNav'
import { notFound, redirect } from 'next/navigation.js'
import React, { Fragment } from 'react'
import type { EditViewComponent } from '../../../../../packages/payload/types.js'
export const CustomEditView: EditViewComponent = ({ initPageResult }) => {
if (!initPageResult) {
notFound()

View File

@@ -37,7 +37,6 @@ import {
} from './shared.js'
import {
customIdCollectionId,
customIdCollectionSlug,
customViews2CollectionSlug,
geoCollectionSlug,
globalSlug,

View File

@@ -21,6 +21,7 @@ export const customEditLabel = 'Custom Edit Label'
export const customTabLabel = 'Custom Tab Label'
export const customTabViewPath = '/custom-tab-component'
export const customTabViewTitle = 'Custom View With Tab Component'
export const customTabLabelViewTitle = 'Custom Tab Label View'

View File

@@ -12,5 +12,3 @@ export const AdminThumbnailCol: CollectionConfig = {
},
fields: [],
}
export default AdminThumbnailCol

View File

@@ -7,7 +7,7 @@ import { devUser } from '../credentials.js'
import removeFiles from '../helpers/removeFiles.js'
import { Uploads1 } from './collections/Upload1/index.js'
import Uploads2 from './collections/Upload2/index.js'
import AdminThumbnailCol from './collections/admin-thumbnail/index.js'
import { AdminThumbnailCol } from './collections/admin-thumbnail/index.js'
import {
audioSlug,
enlargeSlug,

View File

@@ -12,9 +12,7 @@ import { initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2E } from '../helpers/initPayloadE2E.js'
import { RESTClient } from '../helpers/rest.js'
import { adminThumbnailSrc } from './collections/admin-thumbnail/RegisterThumbnailFn.js'
import { adminThumbnailSlug, audioSlug, mediaSlug, relationSlug } from './shared.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@@ -72,13 +70,18 @@ describe('uploads', () => {
test('should see upload filename in relation list', async () => {
await page.goto(relationURL.list)
await wait(110)
const field = page.locator('.cell-image')
await expect(field).toContainText('image.png')
})
test('should see upload versioned filename in relation list', async () => {
await page.goto(relationURL.list)
const field = page.locator('.cell-versionedImage')
await expect(field).toContainText('image')
})
test('should show upload filename in upload collection list', async () => {
await page.goto(mediaURL.list)
const audioUpload = page.locator('tr.row-1 .cell-filename')
@@ -90,7 +93,6 @@ describe('uploads', () => {
test('should create file upload', async () => {
await page.goto(mediaURL.create)
await page.setInputFiles('input[type="file"]', path.resolve(dirname, './image.png'))
const filename = page.locator('.file-field__filename')
@@ -168,12 +170,11 @@ describe('uploads', () => {
test('should show draft uploads in the relation list', async () => {
await page.goto(relationURL.list)
// from the list edit the first document
await page.locator('.row-1 a').click()
// edit the versioned image
await page.locator('.field-versionedImage .icon--edit').click()
await page.locator('.field-type:nth-of-type(2) .icon--edit').click()
// fill the title with 'draft'
await page.locator('#field-title').fill('draft')
@@ -185,7 +186,7 @@ describe('uploads', () => {
await page.locator('.doc-drawer__header-close').click()
// remove the selected versioned image
await page.locator('.field-versionedImage .icon--x').click()
await page.locator('.field-type:nth-of-type(2) .icon--x').click()
// choose from existing
await page.locator('.list-drawer__toggler').click()
@@ -195,20 +196,12 @@ describe('uploads', () => {
test('should restrict mimetype based on filterOptions', async () => {
await page.goto(audioURL.edit(audioDoc.id))
await wait(200)
// remove the selection and open the list drawer
await page.locator('.file-details__remove').click()
await page.locator('.upload__toggler.list-drawer__toggler').click()
const listDrawer = page.locator('[id^=list-drawer_1_]')
await expect(listDrawer).toBeVisible()
await wait(200) // list is loading
// ensure the only card is the audio file
const rows = listDrawer.locator('table tbody tr')
expect(await rows.count()).toEqual(1)
const filename = rows.locator('.cell-filename')
await expect(filename).toHaveText('audio.mp3')
// upload an image and try to select it
await listDrawer.locator('button.list-drawer__create-new-button.doc-drawer__toggler').click()
@@ -217,30 +210,29 @@ describe('uploads', () => {
.locator('[id^=doc-drawer_media_2_] .file-field__upload input[type="file"]')
.setInputFiles(path.resolve(dirname, './image.png'))
await page.locator('[id^=doc-drawer_media_2_] button#action-save').click()
await wait(200)
await expect(page.locator('.Toastify')).toContainText('successfully')
// save the document and expect an error
await page.locator('button#action-save').click()
await wait(200)
await expect(page.locator('.Toastify')).toContainText('The following field is invalid: audio')
await expect(page.locator('.Toastify')).toContainText('Please correct invalid fields.')
})
test('Should execute adminThumbnail and provide thumbnail when set', async () => {
await page.goto(adminThumbnailURL.list)
await wait(200)
await page.waitForURL(adminThumbnailURL.list)
// Ensure sure false or null shows generic file svg
const genericUploadImage = page.locator('tr.row-1 .thumbnail svg')
const genericUploadImage = page.locator('tr.row-1 .thumbnail img')
await expect(genericUploadImage).toBeVisible()
// Ensure adminThumbnail fn returns correct value based on audio/mp3 mime
const audioUploadImage = page.locator('tr.row-2 .thumbnail img')
expect(await audioUploadImage.getAttribute('src')).toContain(adminThumbnailSrc)
const audioUploadImage = page.locator('tr.row-2 .thumbnail svg')
await expect(audioUploadImage).toBeVisible()
})
test('Should detect correct mimeType', async () => {
await page.goto(mediaURL.create)
await page.waitForURL(mediaURL.create)
await page.setInputFiles('input[type="file"]', path.resolve(dirname, './image.png'))
await saveDocAndAssert(page)
@@ -274,12 +266,14 @@ describe('uploads', () => {
const createFocalCrop = async (page: Page, position: 'bottom-right' | 'top-left') => {
const { dragX, dragY, focalX, focalY } = positions[position]
await page.goto(mediaURL.create)
await page.waitForURL(mediaURL.create)
// select and upload file
const fileChooserPromise = page.waitForEvent('filechooser')
await page.getByText('Select a file').click()
const fileChooser = await fileChooserPromise
await wait(1000)
await fileChooser.setFiles(path.join(dirname, 'test-image.jpg'))
await page.locator('.file-field__edit').click()
// set crop
@@ -299,18 +293,18 @@ describe('uploads', () => {
await expect(page.locator('.edit-upload__input input[name="X %"]')).toHaveValue(`${focalX}`)
await expect(page.locator('.edit-upload__input input[name="Y %"]')).toHaveValue(`${focalY}`)
// apply crop
await page.locator('button:has-text("Apply Changes")').click()
await page.waitForSelector('button#action-save')
await page.locator('button#action-save').click()
await expect(page.locator('.Toastify')).toContainText('successfully')
await wait(1000) // Wait for the save
}
await createFocalCrop(page, 'bottom-right') // green square
await wait(1000) // wait for edit view navigation (saving images)
// get the ID of the doc
const greenSquareMediaID = page.url().split('/').pop()
const greenSquareMediaID = page.url().split('/').pop() // get the ID of the doc
await createFocalCrop(page, 'top-left') // red square
await wait(1000) // wait for edit view navigation (saving images)
const redSquareMediaID = page.url().split('/').pop()
const redSquareMediaID = page.url().split('/').pop() // get the ID of the doc
const { doc: greenDoc } = await client.findByID({
id: greenSquareMediaID,