diff --git a/.gitignore b/.gitignore index 09de291459..a2d52d3f49 100644 --- a/.gitignore +++ b/.gitignore @@ -228,3 +228,6 @@ build # Ignore built components components/index.js components/styles.css + +# Ignore generated +demo/generated-types.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 4253a1d439..b31b4f0752 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -65,6 +65,19 @@ "name": "Launch Chrome against Localhost", "url": "http://localhost:3000/admin", "webRoot": "${workspaceFolder}" - } + }, + { + "type": "node", + "request": "launch", + "name": "Debug Payload Generate Types", + "program": "${workspaceFolder}/src/bin/generateTypes.ts", + "env": { + "PAYLOAD_CONFIG_PATH": "demo/payload.config.ts", + }, + "outFiles": [ + "${workspaceFolder}/dist/**/*.js", + "!**/node_modules/**" + ] + }, ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index b37733886c..e1f34f0a8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,24 @@ -## [0.12.8-beta.0](https://github.com/payloadcms/payload/compare/v0.12.3...v0.12.8-beta.0) (2021-11-10) +## [0.12.9-beta.0](https://github.com/payloadcms/payload/compare/v0.12.3...v0.12.9-beta.0) (2021-11-24) ### Bug Fixes * [#351](https://github.com/payloadcms/payload/issues/351) ([94c2b8d](https://github.com/payloadcms/payload/commit/94c2b8d80b046c067057d4ad089ed6a2edd656cf)) * bug with relationship cell when no doc is available ([40b33d9](https://github.com/payloadcms/payload/commit/40b33d9f5e99285cb0de148dbe059259817fcad8)) +* ensures 'like' query param remains functional in all cases ([20d4e72](https://github.com/payloadcms/payload/commit/20d4e72a951dfcbf1cc301d0938e5095932436b9)) +* ensures buildQuery works with fields as well as simultaneous or / and ([72fc413](https://github.com/payloadcms/payload/commit/72fc413764c6c42ba64a45f01d99b68ad3bd46c4)) +* ensures relationship field search can return more than 10 options ([57c0346](https://github.com/payloadcms/payload/commit/57c0346a00286a3df695ea46e5c2630494183b5b)) * ensures richtext links retain proper formatting ([abf61d0](https://github.com/payloadcms/payload/commit/abf61d0734c09fd0fc5c5b827cb0631e11701f71)) +* ensures querying by relationship subpaths works ([37b21b0](https://github.com/payloadcms/payload/commit/37b21b07628e892e85c2cf979d9e2c8af0d291f7)) +* issue with querying by id and using comma-separated values ([d9e1b5e](https://github.com/payloadcms/payload/commit/d9e1b5ede33616893f9ba6990eeccf5410b75ae1)) +* updates field description type to include react nodes ([291c193](https://github.com/payloadcms/payload/commit/291c193ad4a9ec8ce9310cc63c714eba10eca102)) + ### Features +* :tada: builds a way to automatically generate types for collections and globals! docs coming soon. * adds relationship filter field ([463c4e6](https://github.com/payloadcms/payload/commit/463c4e60de8e647fca6268b826d826f9c6e45412)) +* azure cosmos compatibility ([6fd5ac2](https://github.com/payloadcms/payload/commit/6fd5ac2c082a5a5e6f510d781b2a2e12b7b62cb9)) * ensures update hooks have access to full original docs even in spite of access control ([b2c5b7e](https://github.com/payloadcms/payload/commit/b2c5b7e5752e829c7a53c054decceb43ec33065e)) * improves querying logic ([4c85747](https://github.com/payloadcms/payload/commit/4c8574784995b1cb1f939648f4d2158286089b3d)) diff --git a/demo/collections/Preview.ts b/demo/collections/Preview.ts index 81c7c241fc..be8437e39a 100644 --- a/demo/collections/Preview.ts +++ b/demo/collections/Preview.ts @@ -8,10 +8,13 @@ const Preview: CollectionConfig = { }, admin: { useAsTitle: 'title', - preview: (doc, { token }) => { + preview: async (doc, { token }) => { const { title } = doc; if (title) { - return `http://localhost:3000/previewable-posts/${title}?preview=true&token=${token}`; + const mockAsyncReq = await fetch(`http://localhost:3000/api/previewable-post?depth=0`) + const mockJSON = await mockAsyncReq.json(); + const mockParam = mockJSON?.docs?.[0]?.title || ''; + return `http://localhost:3000/previewable-posts/${title}?preview=true&token=${token}&mockParam=${mockParam}`; } return null; diff --git a/demo/payload.config.ts b/demo/payload.config.ts index ddddca5107..ef9540c334 100644 --- a/demo/payload.config.ts +++ b/demo/payload.config.ts @@ -37,6 +37,9 @@ import UnstoredMedia from './collections/UnstoredMedia'; export default buildConfig({ cookiePrefix: 'payload', serverURL: 'http://localhost:3000', + typescript: { + outputFile: path.resolve(__dirname, './generated-types.ts'), + }, admin: { user: 'admins', indexHTML: path.resolve(__dirname, './client/index.html'), diff --git a/package.json b/package.json index 776927a577..4571d08c13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "0.12.8-beta.0", + "version": "0.12.9-beta.0", "description": "Node, React and MongoDB Headless CMS and Application Framework", "license": "SEE LICENSE IN license.md", "author": { @@ -34,6 +34,7 @@ "build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec 'yarn build:tsc'", "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", "dev": "cross-env PAYLOAD_CONFIG_PATH=demo/payload.config.ts nodemon", "test": "yarn test:int && yarn test:client", "pretest": "yarn build", @@ -107,6 +108,7 @@ "file-loader": "^6.2.0", "find-up": "5.0.0", "flatley": "^5.2.0", + "fs-extra": "^10.0.0", "graphql": "15.4.0", "graphql-playground-middleware-express": "^1.7.14", "graphql-query-complexity": "^0.7.0", @@ -118,6 +120,7 @@ "isomorphic-fetch": "^3.0.0", "jest": "^26.6.3", "joi": "^17.3.0", + "json-schema-to-typescript": "^10.1.5", "jsonwebtoken": "^8.5.1", "method-override": "^3.0.0", "micro-memoize": "^4.0.9", @@ -202,6 +205,7 @@ "@types/isomorphic-fetch": "^0.0.35", "@types/jest": "^26.0.15", "@types/joi": "^14.3.4", + "@types/json-schema": "^7.0.9", "@types/jsonwebtoken": "^8.5.0", "@types/method-override": "^0.0.31", "@types/mini-css-extract-plugin": "^1.2.1", diff --git a/payload-types.ts b/payload-types.ts new file mode 100644 index 0000000000..1f8b81bc6b --- /dev/null +++ b/payload-types.ts @@ -0,0 +1,686 @@ +// auto-generated by payload + +export interface Config {} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "navigation-array". + */ +export interface NavigationArray { + array?: { + text?: string; + textarea?: string; + id?: string; + }[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "global-with-access". + */ +export interface GlobalWithStrictAccess { + title: string; + relationship: (string | LocalizedPost)[]; + singleRelationship: string | LocalizedPost; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "localized-posts". + */ +export interface LocalizedPost { + title: string; + summary?: string; + description: string; + richText?: { + [k: string]: unknown; + }[]; + priority: number; + localizedGroup?: { + text?: string; + }; + nonLocalizedGroup?: { + text?: string; + }; + nonLocalizedArray?: { + localizedEmbeddedText?: string; + id?: string; + }[]; + richTextBlocks?: { + content?: { + [k: string]: unknown; + }[]; + id?: string; + blockName?: string; + blockType: 'richTextBlock'; + }[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "blocks-global". + */ +export interface BlocksGlobal { + blocks?: ( + | { + author: string | PublicUser; + quote: string; + color: string; + id?: string; + blockName?: string; + blockType: 'quote'; + } + | { + label: string; + url: string; + id?: string; + blockName?: string; + blockType: 'cta'; + } + )[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "public-users". + */ +export interface PublicUser { + email?: string; + resetPasswordToken?: string; + resetPasswordExpiration?: string; + _verified?: boolean; + _verificationToken?: string; + loginAttempts?: number; + lockUntil?: string; + adminOnly?: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "admins". + */ +export interface Admin { + email?: string; + resetPasswordToken?: string; + resetPasswordExpiration?: string; + enableAPIKey?: boolean; + apiKey?: string; + apiKeyIndex?: string; + loginAttempts?: number; + lockUntil?: string; + roles: ('admin' | 'editor' | 'moderator' | 'user' | 'viewer')[]; + publicUser?: (string | PublicUser)[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "all-fields". + */ +export interface AllFields { + text: string; + descriptionText?: string; + descriptionFunction?: string; + image?: string | Media; + select: 'option-1' | 'option-2' | 'option-3' | 'option-4'; + selectMany: ('option-1' | 'option-2' | 'option-3' | 'option-4')[]; + dayOnlyDateFieldExample: string; + timeOnlyDateFieldExample?: string; + radioGroupExample: 'option-1' | 'option-2' | 'option-3'; + email?: string; + number?: number; + group?: { + nestedText1?: string; + nestedText2?: string; + }; + array?: { + arrayText1: string; + arrayText2: string; + arrayText3?: string; + checkbox?: boolean; + id?: string; + }[]; + blocks: ( + | { + testEmail: string; + id?: string; + blockName?: string; + blockType: 'email'; + } + | { + testNumber: number; + id?: string; + blockName?: string; + blockType: 'number'; + } + | { + author: string | PublicUser; + quote: string; + color: string; + id?: string; + blockName?: string; + blockType: 'quote'; + } + | { + label: string; + url: string; + id?: string; + blockName?: string; + blockType: 'cta'; + } + )[]; + relationship?: string | Conditions; + relationshipHasMany?: (string | LocalizedPost)[]; + relationshipMultipleCollections?: + | { + value: string | LocalizedPost; + relationTo: 'localized-posts'; + } + | { + value: string | Conditions; + relationTo: 'conditions'; + }; + textarea?: string; + richText: { + [k: string]: unknown; + }[]; + slug: string; + checkbox?: boolean; + dateFieldExample?: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "media". + */ +export interface Media { + url?: string; + filename?: string; + mimeType?: string; + filesize?: number; + width?: number; + height?: number; + sizes?: { + maintainedAspectRatio?: { + url?: string; + width?: number; + height?: number; + mimeType?: string; + filesize?: number; + filename?: string; + }; + tablet?: { + url?: string; + width?: number; + height?: number; + mimeType?: string; + filesize?: number; + filename?: string; + }; + mobile?: { + url?: string; + width?: number; + height?: number; + mimeType?: string; + filesize?: number; + filename?: string; + }; + icon?: { + url?: string; + width?: number; + height?: number; + mimeType?: string; + filesize?: number; + filename?: string; + }; + }; + alt: string; + foundUploadSizes?: boolean; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "conditions". + */ +export interface Conditions { + title: string; + enableTest?: boolean; + number?: number; + simpleCondition: string; + orCondition: string; + nestedConditions?: string; + blocks: ( + | { + testEmail: string; + id?: string; + blockName?: string; + blockType: 'email'; + } + | { + testNumber: number; + id?: string; + blockName?: string; + blockType: 'number'; + } + | { + author: string | PublicUser; + quote: string; + color: string; + id?: string; + blockName?: string; + blockType: 'quote'; + } + | { + label: string; + url: string; + id?: string; + blockName?: string; + blockType: 'cta'; + } + )[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "auto-label". + */ +export interface AutoLabel { + autoLabelField?: string; + noLabel?: string; + labelOverride?: string; + testRelationship?: string | AllFields; + specialBlock?: { + testNumber?: number; + id?: string; + blockName?: string; + blockType: 'number'; + }[]; + noLabelBlock?: { + testNumber?: number; + id?: string; + blockName?: string; + blockType: 'number'; + }[]; + items?: { + itemName?: string; + id?: string; + }[]; + noLabelArray?: { + textField?: string; + id?: string; + }[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "code". + */ +export interface Code { + code: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "custom-components". + */ +export interface CustomComponent { + title: string; + description: string; + componentDescription?: string; + array?: { + nestedArrayCustomField?: string; + id?: string; + }[]; + group?: { + nestedGroupCustomField?: string; + }; + nestedText1?: string; + nestedText2?: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "custom-id". + */ +export interface CustomID { + id?: number; + name: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "files". + */ +export interface File { + url?: string; + filename?: string; + mimeType?: string; + filesize?: number; + type: 'Type 1' | 'Type 2' | 'Type 3'; + owner: string | Admin; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "default-values". + */ +export interface DefaultValueTest { + text?: string; + image?: string | Media; + select?: 'option-1' | 'option-2' | 'option-3' | 'option-4'; + selectMany?: ('option-1' | 'option-2' | 'option-3' | 'option-4')[]; + radioGroupExample?: 'option-1' | 'option-2' | 'option-3'; + email?: string; + number?: number; + group?: { + nestedText1?: string; + nestedText2?: string; + }; + array?: { + arrayText1?: string; + arrayText2?: string; + arrayText3?: string; + checkbox?: boolean; + id?: string; + }[]; + blocks?: ( + | { + testEmail: string; + id?: string; + blockName?: string; + blockType: 'email'; + } + | { + testNumber: number; + id?: string; + blockName?: string; + blockType: 'number'; + } + | { + author: string | PublicUser; + quote: string; + color: string; + id?: string; + blockName?: string; + blockType: 'quote'; + } + | { + label: string; + url: string; + id?: string; + blockName?: string; + blockType: 'cta'; + } + )[]; + relationship?: string | Conditions; + relationshipHasMany?: (string | LocalizedPost)[]; + relationshipMultipleCollections?: + | { + value: string | LocalizedPost; + relationTo: 'localized-posts'; + } + | { + value: string | Conditions; + relationTo: 'conditions'; + }; + textarea?: string; + slug?: string; + checkbox?: boolean; + richText?: { + [k: string]: unknown; + }[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "blocks". + */ +export interface Blocks { + layout: ( + | { + testEmail: string; + id?: string; + blockName?: string; + blockType: 'email'; + } + | { + testNumber: number; + id?: string; + blockName?: string; + blockType: 'number'; + } + | { + author: string | PublicUser; + quote: string; + color: string; + id?: string; + blockName?: string; + blockType: 'quote'; + } + | { + label: string; + url: string; + id?: string; + blockName?: string; + blockType: 'cta'; + } + )[]; + nonLocalizedLayout: ( + | { + testEmail: string; + id?: string; + blockName?: string; + blockType: 'email'; + } + | { + testNumber: number; + id?: string; + blockName?: string; + blockType: 'number'; + } + | { + author: string | PublicUser; + quote: string; + color: string; + id?: string; + blockName?: string; + blockType: 'quote'; + } + | { + label: string; + url: string; + id?: string; + blockName?: string; + blockType: 'cta'; + } + )[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "hidden-fields". + */ +export interface HiddenFields { + title: string; + hiddenAdmin: string; + hiddenAPI: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "hooks". + */ +export interface Hook { + title: string; + description: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "localized-arrays". + */ +export interface LocalizedArray { + array: { + allowPublicReadability?: boolean; + arrayText1: string; + arrayText2: string; + arrayText3?: string; + id?: string; + }[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "local-operations". + */ +export interface LocalOperation { + title: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "nested-arrays". + */ +export interface NestedArray { + array: { + parentIdentifier: string; + nestedArray: { + childIdentifier: string; + deeplyNestedArray: { + grandchildIdentifier?: string; + id?: string; + }[]; + id?: string; + }[]; + id?: string; + }[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "previewable-post". + */ +export interface PreviewablePost { + title: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "relationship-a". + */ +export interface RelationshipA { + post?: string | RelationshipB; + LocalizedPost?: (string | LocalizedPost)[]; + postLocalizedMultiple?: ( + | { + value: string | LocalizedPost; + relationTo: 'localized-posts'; + } + | { + value: string | AllFields; + relationTo: 'all-fields'; + } + | { + value: number | CustomID; + relationTo: 'custom-id'; + } + )[]; + postManyRelationships?: { + value: string | RelationshipB; + relationTo: 'relationship-b'; + }; + postMaxDepth?: string | RelationshipB; + customID?: (number | CustomID)[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "relationship-b". + */ +export interface RelationshipB { + title?: string; + post?: (string | RelationshipA)[]; + postManyRelationships?: + | { + value: string | RelationshipA; + relationTo: 'relationship-a'; + } + | { + value: string | Media; + relationTo: 'media'; + }; + localizedPosts?: ( + | { + value: string | LocalizedPost; + relationTo: 'localized-posts'; + } + | { + value: string | PreviewablePost; + relationTo: 'previewable-post'; + } + )[]; + strictAccess?: string | StrictAccess; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "strict-access". + */ +export interface StrictAccess { + address: string; + city: string; + state: string; + zip: number; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "rich-text". + */ +export interface RichText { + defaultRichText: { + [k: string]: unknown; + }[]; + customRichText: { + [k: string]: unknown; + }[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "select". + */ +export interface Select { + Select: 'one' | 'two' | 'three'; + SelectHasMany: ('one' | 'two' | 'three')[]; + SelectJustStrings: ('blue' | 'green' | 'yellow')[]; + Radio: 'one' | 'two' | 'three'; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "validations". + */ +export interface Validation { + text: string; + lessThan10: number; + greaterThan10LessThan50: number; + atLeast3Rows: { + greaterThan30: number; + id?: string; + }[]; + array: { + lessThan20: number; + id?: string; + }[]; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "uniques". + */ +export interface Unique { + title: string; + description?: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "unstored-media". + */ +export interface UnstoredMedia { + url?: string; + filename?: string; + mimeType?: string; + filesize?: number; + width?: number; + height?: number; + sizes?: { + tablet?: { + url?: string; + width?: number; + height?: number; + mimeType?: string; + filesize?: number; + filename?: string; + }; + }; + alt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "geolocation". + */ +export interface Geolocation { + location?: [number, number]; + localizedPoint?: [number, number]; +} diff --git a/src/admin/components/elements/PreviewButton/index.tsx b/src/admin/components/elements/PreviewButton/index.tsx index 25f360fa9e..7b1672eeaf 100644 --- a/src/admin/components/elements/PreviewButton/index.tsx +++ b/src/admin/components/elements/PreviewButton/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useAuth } from '@payloadcms/config-provider'; import Button from '../Button'; import { Props } from './types'; @@ -6,13 +6,34 @@ import { useLocale } from '../../utilities/Locale'; const baseClass = 'preview-btn'; -const PreviewButton: React.FC = ({ generatePreviewURL, data }) => { +const PreviewButton: React.FC = (props) => { + const { + generatePreviewURL, + data + } = props; + + const [url, setUrl] = useState(undefined); + const locale = useLocale(); const { token } = useAuth(); - if (generatePreviewURL && typeof generatePreviewURL === 'function') { - const url = generatePreviewURL(data, { locale, token }); + useEffect(() => { + if (generatePreviewURL && typeof generatePreviewURL === 'function') { + const makeRequest = async () => { + const previewURL = await generatePreviewURL(data, { locale, token }); + setUrl(previewURL); + } + makeRequest(); + } + }, [ + generatePreviewURL, + locale, + token, + data + ]); + + if (url) { return (