diff --git a/CHANGELOG.md b/CHANGELOG.md index 383d56f172..c095553068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## [0.13.13-beta.0](https://github.com/payloadcms/payload/compare/v0.13.6...v0.13.13-beta.0) (2021-12-28) +## [0.13.20-beta.0](https://github.com/payloadcms/payload/compare/v0.13.6...v0.13.20-beta.0) (2021-12-29) ### Bug Fixes @@ -6,9 +6,15 @@ * [#370](https://github.com/payloadcms/payload/issues/370), only performs password functions when auth enabled ([9738873](https://github.com/payloadcms/payload/commit/97388738def687f3b26eaf8de6b067f4d3758418)) * [#390](https://github.com/payloadcms/payload/issues/390), safari rich text link bug ([a16b99b](https://github.com/payloadcms/payload/commit/a16b99b0c87d55f768ed74ab35708a291fc7bbb0)) * [#393](https://github.com/payloadcms/payload/issues/393), ensures preview button gets up to date data ([2f47e39](https://github.com/payloadcms/payload/commit/2f47e39a9f765bd8ce437d4b7500a5b314a192a5)) +* [#408](https://github.com/payloadcms/payload/issues/408) ([5c3cfa4](https://github.com/payloadcms/payload/commit/5c3cfa4c93767a5ead9e816bf11a000ebdac9761)) +* [#408](https://github.com/payloadcms/payload/issues/408) ([e2c5d93](https://github.com/payloadcms/payload/commit/e2c5d93751cb1902d6dce2147953b97c2dc17939)) +* 407 ([a09570c](https://github.com/payloadcms/payload/commit/a09570c78dc923f3553f36d726e5cfac925290a0)) * allows null in ImageSize width and height types ([ba79fd4](https://github.com/payloadcms/payload/commit/ba79fd42dbf20ba712a0632da9193fcc516c0257)) +* cross-browser upload drag and drop ([4119eec](https://github.com/payloadcms/payload/commit/4119eec796794d6a026f34f8b097b379eb9895a0)) * ensures getDataByPath works ([140a3aa](https://github.com/payloadcms/payload/commit/140a3aa9eafa29b2a43bdfd8883c79c6ee4a93e4)) * ensures local findByID retains user ([05288ee](https://github.com/payloadcms/payload/commit/05288ee08c077019e4432bf385aeacc23a0643f3)) +* ensures row count is set properly in block fields ([9e091af](https://github.com/payloadcms/payload/commit/9e091af67e944e6a15d1d1174a18cde6deda40d7)) +* ensures searching relationships works with many pages of results ([961787d](https://github.com/payloadcms/payload/commit/961787d681882e5ab48bb676490555c93f5d4a2e)) * globals model typing ([da7c0c9](https://github.com/payloadcms/payload/commit/da7c0c984c1fb57038d620fc59bcd27972919ade)) diff --git a/demo/collections/Localized.ts b/demo/collections/Localized.ts index bc40b53579..8b85219847 100644 --- a/demo/collections/Localized.ts +++ b/demo/collections/Localized.ts @@ -107,6 +107,11 @@ const LocalizedPosts: CollectionConfig = { name: 'text', label: 'Text', }, + { + type: 'text', + name: 'demoHiddenField', + hidden: true, + }, ], }, { diff --git a/demo/collections/RelationshipA.ts b/demo/collections/RelationshipA.ts index 4c288fbb11..ba78729223 100644 --- a/demo/collections/RelationshipA.ts +++ b/demo/collections/RelationshipA.ts @@ -56,6 +56,11 @@ const RelationshipA: CollectionConfig = { hasMany: true, localized: true, }, + { + name: 'demoHiddenField', + type: 'text', + hidden: true, + }, ], timestamps: true, }; diff --git a/package.json b/package.json index 8adaa93d6c..207d4692b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "0.13.13-beta.0", + "version": "0.13.20-beta.0", "description": "Node, React and MongoDB Headless CMS and Application Framework", "license": "SEE LICENSE IN license.md", "author": { @@ -35,6 +35,7 @@ "demo:build:analyze": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.ts PAYLOAD_ANALYZE_BUNDLE=true node dist/bin/build", "demo:build": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.ts node dist/bin/build", "demo:generate:types": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.ts node dist/bin/generateTypes", + "demo:serve": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.ts NODE_ENV=production nodemon", "dev": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.ts nodemon", "test": "yarn test:int && yarn test:client", "pretest": "yarn build", diff --git a/src/admin/components/forms/field-types/Blocks/Blocks.tsx b/src/admin/components/forms/field-types/Blocks/Blocks.tsx index d6a18f20ce..77baf2bf61 100644 --- a/src/admin/components/forms/field-types/Blocks/Blocks.tsx +++ b/src/admin/components/forms/field-types/Blocks/Blocks.tsx @@ -141,6 +141,8 @@ const Blocks: React.FC = (props) => { moveRow(sourceIndex, destinationIndex); }, [moveRow]); + // Get preferences, and once retrieved, + // Reset rows with preferences included useEffect(() => { const fetchPreferences = async () => { const preferences = preferencesKey ? await getPreference(preferencesKey) : undefined; @@ -151,6 +153,12 @@ const Blocks: React.FC = (props) => { fetchPreferences(); }, [formContext, path, preferencesKey, getPreference]); + // Set row count on mount and when form context is reset + useEffect(() => { + const data = formContext.getDataByPath(path); + dispatchRows({ type: 'SET_ALL', data: data || [] }); + }, [formContext, path]); + useEffect(() => { setValue(rows?.length || 0); diff --git a/src/admin/components/forms/field-types/Relationship/index.tsx b/src/admin/components/forms/field-types/Relationship/index.tsx index 472e5be244..0f6daf670d 100644 --- a/src/admin/components/forms/field-types/Relationship/index.tsx +++ b/src/admin/components/forms/field-types/Relationship/index.tsx @@ -48,7 +48,6 @@ const Relationship: React.FC = (props) => { collections, } = useConfig(); - const formProcessing = useFormProcessing(); const hasMultipleRelations = Array.isArray(relationTo); @@ -308,7 +307,7 @@ const Relationship: React.FC = (props) => { } } : undefined} onMenuScrollToBottom={() => { - getResults({ lastFullyLoadedRelation, lastLoadedPage: lastLoadedPage + 1 }); + getResults({ lastFullyLoadedRelation, lastLoadedPage: lastLoadedPage + 1, search }); }} value={valueToRender} showError={showError} diff --git a/src/admin/components/views/collections/Edit/Upload/index.tsx b/src/admin/components/views/collections/Edit/Upload/index.tsx index ca8f5ddf44..3ae1e9204b 100644 --- a/src/admin/components/views/collections/Edit/Upload/index.tsx +++ b/src/admin/components/views/collections/Edit/Upload/index.tsx @@ -27,7 +27,6 @@ const validate = (value) => { const Upload: React.FC = (props) => { const inputRef = useRef(null); const dropRef = useRef(null); - const [fileList, setFileList] = useState(undefined); const [selectingFile, setSelectingFile] = useState(false); const [dragging, setDragging] = useState(false); const [dragCounter, setDragCounter] = useState(0); @@ -53,16 +52,16 @@ const Upload: React.FC = (props) => { const handleDragIn = useCallback((e) => { e.preventDefault(); e.stopPropagation(); - setDragCounter(dragCounter + 1); + setDragCounter((count) => count + 1); if (e.dataTransfer.items && e.dataTransfer.items.length > 0) { setDragging(true); } - }, [dragCounter]); + }, []); const handleDragOut = useCallback((e) => { e.preventDefault(); e.stopPropagation(); - setDragCounter(dragCounter - 1); + setDragCounter((count) => count - 1); if (dragCounter > 1) return; setDragging(false); }, [dragCounter]); @@ -73,7 +72,7 @@ const Upload: React.FC = (props) => { setDragging(false); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { - setFileList(e.dataTransfer.files); + setValue(e.dataTransfer.files[0]); setDragging(false); e.dataTransfer.clearData(); @@ -81,11 +80,13 @@ const Upload: React.FC = (props) => { } else { setDragging(false); } - }, []); + }, [setValue]); + // Only called when input is interacted with directly + // Not called when drag + drop is used + // Or when input is cleared const handleInputChange = useCallback(() => { setSelectingFile(false); - setFileList(inputRef?.current?.files || null); setValue(inputRef?.current?.files?.[0] || null); }, [inputRef, setValue]); @@ -113,13 +114,7 @@ const Upload: React.FC = (props) => { } return () => null; - }, [handleDragIn, handleDragOut, handleDrop, dropRef]); - - useEffect(() => { - if (inputRef.current && fileList !== undefined) { - inputRef.current.files = fileList; - } - }, [fileList]); + }, [handleDragIn, handleDragOut, handleDrop, value]); useEffect(() => { setReplacingFile(false); @@ -143,7 +138,6 @@ const Upload: React.FC = (props) => { collection={collection} handleRemove={() => { setReplacingFile(true); - setFileList(null); setValue(null); }} /> @@ -163,7 +157,6 @@ const Upload: React.FC = (props) => { buttonStyle="icon-label" iconStyle="with-border" onClick={() => { - setFileList(null); setValue(null); }} /> diff --git a/src/collections/operations/local/create.ts b/src/collections/operations/local/create.ts index 37d3b3fe17..865112f363 100644 --- a/src/collections/operations/local/create.ts +++ b/src/collections/operations/local/create.ts @@ -50,7 +50,9 @@ export default async function create(options: Options): Promise { locale, fallbackLocale, payload: this, - file: getFileByPath(filePath), + files: { + file: getFileByPath(filePath), + }, }, }); } diff --git a/src/collections/operations/local/local.spec.js b/src/collections/operations/local/local.spec.js index 44c77adcdc..b08b793df8 100644 --- a/src/collections/operations/local/local.spec.js +++ b/src/collections/operations/local/local.spec.js @@ -51,4 +51,71 @@ describe('Collections - Local', () => { expect(result.width).toStrictEqual(640); }); }); + + describe('Read with Hidden Fields', () => { + it('should allow a document with nested hidden fields to be retrieved with hidden fields shown.', async () => { + const demoHiddenField = 'this is going to be hidden'; + + const result = await payload.create({ + collection: 'localized-posts', + data: { + title: 'this post has a hidden field present', + description: 'desc', + priority: 1, + nonLocalizedGroup: { + text: '40w5g534gw34j', + }, + localizedGroup: { + text: '34lijgw45ligjw4li5j', + demoHiddenField, + }, + }, + }); + + expect(result.id).toBeDefined(); + expect(result.localizedGroup).toBeDefined(); + expect(result.localizedGroup.demoHiddenField).toBeUndefined(); + + const withHiddenFields = await payload.findByID({ + collection: 'localized-posts', + id: result.id, + showHiddenFields: true, + }); + + expect(withHiddenFields.localizedGroup.demoHiddenField).toStrictEqual(demoHiddenField); + expect(withHiddenFields.id).toStrictEqual(result.id); + }); + + it('should allow a document with a relationship to a document with hidden fields to read the related document hidden fields', async () => { + const demoHiddenField = 'this is going to be hidden'; + + const relationshipA = await payload.create({ + collection: 'relationship-a', + data: { + demoHiddenField, + }, + }); + + expect(relationshipA.id).toBeDefined(); + expect(relationshipA.demoHiddenField).toBeUndefined(); + + const relationshipB = await payload.create({ + collection: 'relationship-b', + data: { + title: 'pretty clever name here', + post: [relationshipA.id], + }, + }); + + expect(relationshipB.id).toBeDefined(); + + const relationshipBWithHiddenNestedField = await payload.findByID({ + collection: 'relationship-b', + id: relationshipB.id, + showHiddenFields: true, + }); + + expect(relationshipBWithHiddenNestedField.post[0].demoHiddenField).toStrictEqual(demoHiddenField); + }); + }); }); diff --git a/src/collections/operations/local/update.ts b/src/collections/operations/local/update.ts index 260bf78944..672cb3c4b4 100644 --- a/src/collections/operations/local/update.ts +++ b/src/collections/operations/local/update.ts @@ -49,7 +49,9 @@ export default async function update(options: Options): Promise { locale, fallbackLocale, payload: this, - file: getFileByPath(filePath), + files: { + file: getFileByPath(filePath), + }, }, }; diff --git a/src/express/types.ts b/src/express/types.ts index e8b6644519..0fe53e4c47 100644 --- a/src/express/types.ts +++ b/src/express/types.ts @@ -11,7 +11,9 @@ export type PayloadRequest = Request & { fallbackLocale?: string; collection?: Collection; payloadAPI: 'REST' | 'local' | 'graphQL' - file?: UploadedFile + files?: { + file: UploadedFile + } user: User | null payloadUploadSizes?: Record findByID?: { diff --git a/src/fields/accessPromise.ts b/src/fields/accessPromise.ts index 2908060753..36d28c8821 100644 --- a/src/fields/accessPromise.ts +++ b/src/fields/accessPromise.ts @@ -18,12 +18,12 @@ type Arguments = { currentDepth: number hook: HookName payload: Payload + showHiddenFields: boolean } const accessPromise = async ({ data, fullData, - originalDoc, field, operation, overrideAccess, @@ -34,6 +34,7 @@ const accessPromise = async ({ currentDepth, hook, payload, + showHiddenFields, }: Arguments): Promise => { const resultingData = data; @@ -56,6 +57,7 @@ const accessPromise = async ({ if ((field.type === 'relationship' || field.type === 'upload') && hook === 'afterRead') { relationshipPopulations.push(relationshipPopulationPromise({ + showHiddenFields, data, field, depth, diff --git a/src/fields/relationshipPopulationPromise.ts b/src/fields/relationshipPopulationPromise.ts index ad565c45eb..40f4df18d5 100644 --- a/src/fields/relationshipPopulationPromise.ts +++ b/src/fields/relationshipPopulationPromise.ts @@ -12,6 +12,7 @@ type PopulateArgs = { field: RelationshipField | UploadField index?: number payload: Payload + showHiddenFields: boolean } const populate = async ({ @@ -24,6 +25,7 @@ const populate = async ({ field, index, payload, + showHiddenFields, }: PopulateArgs) => { const dataToUpdate = dataReference; @@ -44,10 +46,11 @@ const populate = async ({ req, collection: relatedCollection.config.slug, id: idString as string, - depth, currentDepth: currentDepth + 1, overrideAccess, disableErrors: true, + depth, + showHiddenFields, }); } @@ -76,6 +79,7 @@ type PromiseArgs = { req: PayloadRequest overrideAccess: boolean payload: Payload + showHiddenFields: boolean } const relationshipPopulationPromise = ({ @@ -86,6 +90,7 @@ const relationshipPopulationPromise = ({ req, overrideAccess, payload, + showHiddenFields, }: PromiseArgs) => async (): Promise => { const resultingData = data; const populateDepth = fieldHasMaxDepth(field) && field.maxDepth < depth ? field.maxDepth : depth; @@ -106,6 +111,7 @@ const relationshipPopulationPromise = ({ field, index, payload, + showHiddenFields, }); } }; @@ -124,6 +130,7 @@ const relationshipPopulationPromise = ({ data: data[field.name], field, payload, + showHiddenFields, }); } }; diff --git a/src/fields/richTextRelationshipPromise.ts b/src/fields/richTextRelationshipPromise.ts index 883b9a7c1b..4392d11624 100644 --- a/src/fields/richTextRelationshipPromise.ts +++ b/src/fields/richTextRelationshipPromise.ts @@ -11,6 +11,7 @@ type Arguments = { payload: Payload field: RichTextField req: PayloadRequest + showHiddenFields: boolean } type RecurseRichTextArgs = { @@ -22,6 +23,7 @@ type RecurseRichTextArgs = { field: RichTextField req: PayloadRequest promises: Promise[] + showHiddenFields: boolean } const populate = async ({ @@ -33,6 +35,7 @@ const populate = async ({ currentDepth, payload, req, + showHiddenFields, }: Arguments & { id: string, collection: Collection @@ -49,6 +52,7 @@ const populate = async ({ overrideAccess, disableErrors: true, depth, + showHiddenFields, }); if (doc) { @@ -67,6 +71,7 @@ const recurseRichText = ({ currentDepth = 0, field, promises, + showHiddenFields, }: RecurseRichTextArgs) => { if (Array.isArray(children)) { (children as any[]).forEach((element) => { @@ -86,6 +91,7 @@ const recurseRichText = ({ payload, field, collection, + showHiddenFields, })); } @@ -99,6 +105,7 @@ const recurseRichText = ({ currentDepth, field, promises, + showHiddenFields, }); } }); @@ -113,6 +120,7 @@ const richTextRelationshipPromise = ({ depth, currentDepth, field, + showHiddenFields, }: Arguments) => async (): Promise => { const promises = []; @@ -125,6 +133,7 @@ const richTextRelationshipPromise = ({ currentDepth, field, promises, + showHiddenFields, }); await Promise.all(promises); diff --git a/src/fields/traverseFields.ts b/src/fields/traverseFields.ts index ac6b5f3cad..cec9a6aeb9 100644 --- a/src/fields/traverseFields.ts +++ b/src/fields/traverseFields.ts @@ -149,6 +149,7 @@ const traverseFields = (args: Arguments): void => { depth, field, currentDepth, + showHiddenFields, })); } } @@ -215,6 +216,7 @@ const traverseFields = (args: Arguments): void => { currentDepth, hook, payload, + showHiddenFields, })); hookPromises.push(() => hookPromise({ @@ -256,6 +258,7 @@ const traverseFields = (args: Arguments): void => { docWithLocales: docWithLocales?.[field.name]?.[i], path: `${path}${field.name}.${i}.`, skipValidation: skipValidationFromHere, + showHiddenFields, }); } } @@ -268,6 +271,7 @@ const traverseFields = (args: Arguments): void => { docWithLocales: docWithLocales?.[field.name], path: `${path}${field.name}.`, skipValidation: skipValidationFromHere, + showHiddenFields, }); } } @@ -286,6 +290,7 @@ const traverseFields = (args: Arguments): void => { docWithLocales: docWithLocales?.[field.name]?.[i], path: `${path}${field.name}.${i}.`, skipValidation: skipValidationFromHere, + showHiddenFields, }); } }); diff --git a/src/graphql/schema/buildObjectType.ts b/src/graphql/schema/buildObjectType.ts index 7483113847..1d22be100c 100644 --- a/src/graphql/schema/buildObjectType.ts +++ b/src/graphql/schema/buildObjectType.ts @@ -55,6 +55,7 @@ function buildObjectType(name: string, fields: Field[], parentName: string, base payload: context.req.payload, depth: args.depth, field, + showHiddenFields: false, }); await richTextRelationshipPromise(); diff --git a/src/uploads/uploadFile.ts b/src/uploads/uploadFile.ts index 0d5ed914db..c8421bf08f 100644 --- a/src/uploads/uploadFile.ts +++ b/src/uploads/uploadFile.ts @@ -1,4 +1,3 @@ -import { UploadedFile } from 'express-fileupload'; import mkdirp from 'mkdirp'; import path from 'path'; import { SanitizedConfig } from '../config/types'; @@ -39,7 +38,7 @@ const uploadFile = async ({ const { staticDir, imageSizes, disableLocalStorage } = collectionConfig.upload; - const file = ((req.files && req.files.file) ? req.files.file : req.file) as UploadedFile; + const { file } = req.files || {}; if (throwOnMissingFile && !file) { throw new MissingFile();