Compare commits

..

40 Commits

Author SHA1 Message Date
Elliot DeNolf
ea4203bb32 chore(release): v3.0.0-beta.10 [skip ci] 2024-04-12 14:58:12 -04:00
Elliot DeNolf
568b5073c8 chore(deps): sync pnpm-lock.yaml 2024-04-12 14:56:42 -04:00
Elliot DeNolf
471e1f4827 chore: specify next canary peer dep 2024-04-12 14:56:04 -04:00
Elliot DeNolf
b9185a6fcd chore: fix more publish config exports 2024-04-12 14:48:34 -04:00
Elliot DeNolf
80496aa94c fix: remove all exports null (#5830) 2024-04-12 14:35:36 -04:00
Alessio Gravili
5fd6e3c1a8 fix!: upgrade minimum required node version from 18.17.0 to v18.20.2. Some old node versions have issues with our loader (#5829) 2024-04-12 14:01:33 -04:00
Alessio Gravili
54590c1700 fix(richtext-lexical)!: fix output of internal list HTML converter (#5827)
BREAKING: Changes the classnames of the converted HTML
2024-04-12 12:10:44 -04:00
Elliot DeNolf
b1259be8f2 chore(release): v3.0.0-beta.9 [skip ci] 2024-04-12 12:06:41 -04:00
Elliot DeNolf
cd161e4b16 feat!: remove pointer files (#5826) 2024-04-12 11:58:34 -04:00
Alessio Gravili
cb4214fe6e fix(richtext-lexical)!: fix output of internal list HTML converter
BREAKING: Changes the classnames of the converted HTML
2024-04-12 11:58:05 -04:00
Elliot DeNolf
9d42751a42 feat!: remove more pointer files 2024-04-12 11:39:08 -04:00
Elliot DeNolf
c2c637b359 chore: clean up unused files 2024-04-12 11:35:25 -04:00
Paul
2f446e11d6 chore: bump nextjs dependencies to ^14.2 (#5820)
fix(plugin-seo): overriding existing endpoints
2024-04-12 12:32:45 -03:00
Elliot DeNolf
4f566b088c feat!: remove pointer files 2024-04-12 11:31:35 -04:00
Dan Ribbens
0d40d87b31 fix(db-postgres): relationship query pagination (#5803) 2024-04-12 11:18:40 -04:00
Jacob Fletcher
94c0095b3b chore(ui): removes all static font assets (#5821) 2024-04-12 09:57:31 -04:00
Elliot DeNolf
4328060637 feat(pcs): vercel blob storage adapter (#5811) 2024-04-12 09:37:35 -04:00
Elliot DeNolf
d98d0fd5bd chore(pcs): use proper getFilePrefix 2024-04-12 09:30:19 -04:00
Elliot DeNolf
5db2863d08 feat(pcs): export utilities 2024-04-12 09:30:10 -04:00
Jacob Fletcher
ff5e438d6d chore(ui): replaces suisse-intl font with system fallbacks 2024-04-12 09:16:43 -04:00
Paul
bfd5f13ee9 chore: add types for local api find/update operations (#5808) 2024-04-12 10:15:57 -03:00
Elliot DeNolf
8043188f36 chore(pcs): update README 2024-04-12 09:12:34 -04:00
Elliot DeNolf
e3e0998772 chore: ignore vercelBlob pointers, adjust peer deps 2024-04-12 00:34:41 -04:00
Elliot DeNolf
86adc6f282 chore: add vercelBlob to exports 2024-04-11 23:05:22 -04:00
Elliot DeNolf
b51b519d30 feat(plugin-cloud-storage): vercel blob storage adapter 2024-04-11 22:58:55 -04:00
Alessio Gravili
c70dcb6a59 feat(richtext-lexical): add HorizontalRuleFeature, improve block handle positioning (#5806) 2024-04-11 16:51:54 -04:00
Alessio Gravili
2486c7dba0 fix(richtext-lexical): incorrect margin for nested unordered lists 2024-04-11 16:42:35 -04:00
Paul
1456fcdcad chore: type locale from localization config on the payload request (#5801) 2024-04-11 17:38:35 -03:00
Alessio Gravili
a216800c72 chore(richtext-lexical): fix build error 2024-04-11 16:31:40 -04:00
Alessio Gravili
c3d8597c13 feat(richtext-lexical): add HorizontalRuleFeature 2024-04-11 16:24:04 -04:00
Alessio Gravili
844663ce1a fix(richtext-lexical): limit unnecessary floating handle positioning updates 2024-04-11 15:55:55 -04:00
Alessio Gravili
055e6af7b7 feat(richtext-lexical): improve floating handle y-positioning by positioning it in the center for smaller elements. 2024-04-11 15:55:43 -04:00
Alessio Gravili
479e6ecddc fix(richtext-lexical): incorrect floating handle y-position calculation next to certain kinds of HTML elements like HR 2024-04-11 15:55:26 -04:00
Jacob Fletcher
b9456e8244 fix(next): safely handles missing json body in post requests (#5797) 2024-04-11 15:53:50 -04:00
Jacob Fletcher
432dfef435 chore(next): installs merriweather as google font and removes static assets 2024-04-11 15:31:19 -04:00
Elliot DeNolf
429c6f7a48 chore(release): v3.0.0-beta.6 [skip ci] 2024-04-11 15:20:10 -04:00
Elliot DeNolf
6d41f6c56d fix(ui): actual scss paths [skip ci] 2024-04-11 15:17:33 -04:00
Jacob Fletcher
bcb538aee2 fix(next): awaits logout operation in api route handler 2024-04-11 14:57:35 -04:00
Jacob Fletcher
01e8f8c649 Merge branch 'beta' into fix/post-body-parse 2024-04-11 14:15:56 -04:00
Jacob Fletcher
330e4a7724 fix(next): safely handles missing json body in post requests 2024-04-11 13:42:21 -04:00
120 changed files with 1309 additions and 2577 deletions

View File

@@ -1 +1 @@
v18.19.1
v18.20.2

2
.nvmrc
View File

@@ -1 +1 @@
v18.19.1
v18.20.2

View File

@@ -138,7 +138,7 @@ import { CallToAction } from '../blocks/CallToAction'
Here's an overview of all the included features:
| Feature Name | Included by default | Description |
| ------------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|--------------------------------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`BoldTextFeature`** | Yes | Handles the bold text format |
| **`ItalicTextFeature`** | Yes | Handles the italic text format |
| **`UnderlineTextFeature`** | Yes | Handles the underline text format |
@@ -157,7 +157,8 @@ Here's an overview of all the included features:
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
| **`BlockQuoteFeature`** | Yes | Allows you to create block-level quotes |
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an <hr> element |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](/docs/fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
## Creating your own, custom Feature
@@ -234,6 +235,19 @@ This method employs `convertLexicalToHTML` from `@payloadcms/richtext-lexical`,
Because every `Feature` is able to provide html converters, and because the `htmlFeature` can modify those or provide their own, we need to consolidate them with the default html Converters using the `consolidateHTMLConverters` function.
#### CSS
Payload's lexical HTML converter does not generate CSS for you, but it does add classes to the generated HTML. You can use these classes to style the HTML in your frontend.
Here is some "base" CSS you can use to ensure that nested lists render correctly:
```css
/* Base CSS for Lexical HTML */
.nestedListItem, .list-check {
list-style-type: none;
}
```
#### Creating your own HTML Converter
HTML Converters are typed as `HTMLConverter`, which contains the node type it should handle, and a function that accepts the serialized node from the lexical editor, and outputs the HTML string. Here's the HTML Converter of the Upload node as an example:

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"private": true,
"type": "module",
"workspaces:": [
@@ -127,7 +127,7 @@
"lint-staged": "^14.0.1",
"minimist": "1.2.8",
"mongodb-memory-server": "^9.0",
"next": "14.2.0-canary.22",
"next": "^14.2.0-canary.23",
"node-mocks-http": "^1.14.1",
"nodemon": "3.0.3",
"open": "^10.1.0",
@@ -163,7 +163,7 @@
"react": "18.2.0"
},
"engines": {
"node": ">=18.17.0",
"node": ">=18.20.2",
"pnpm": ">=8"
},
"lint-staged": {

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"license": "MIT",
"type": "module",
"homepage": "https://payloadcms.com",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"description": "The officially supported MongoDB database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"description": "The officially supported Postgres database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -120,7 +120,7 @@ export const findMany = async function find({
const findPromise = db.query[tableName].findMany(findManyArgs)
if (pagination !== false && (orderedIDs ? orderedIDs?.length >= limit : true)) {
if (pagination !== false && (orderedIDs ? orderedIDs?.length <= limit : true)) {
const selectCountMethods: ChainedMethods = []
joinAliases.forEach(({ condition, table }) => {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"type": "module",

View File

@@ -39,7 +39,13 @@
}
},
"publishConfig": {
"exports": null,
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"

View File

@@ -32,7 +32,13 @@
}
},
"publishConfig": {
"exports": null,
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"main": "./src/index.js",
"types": "./src/index.js",
"type": "module",
@@ -77,7 +77,7 @@
},
"peerDependencies": {
"http-status": "1.6.2",
"next": "14.2.0-canary.23",
"next": "^14.2.0-canary.23",
"payload": "workspace:*"
},
"publishConfig": {
@@ -102,7 +102,7 @@
"registry": "https://registry.npmjs.org/"
},
"engines": {
"node": ">=18.17.0"
"node": ">=18.20.2"
},
"files": [
"dist"

View File

@@ -22,6 +22,16 @@ export const metadata = {
title: 'Next.js',
}
import { Merriweather } from 'next/font/google'
const merriweather = Merriweather({
display: 'swap',
style: ['normal', 'italic'],
subsets: ['latin'],
variable: '--font-serif',
weight: ['400', '900'],
})
export const RootLayout = async ({
children,
config: configPromise,
@@ -82,7 +92,7 @@ export const RootLayout = async ({
})
return (
<html dir={dir} lang={languageCode}>
<html className={merriweather.variable} dir={dir} lang={languageCode}>
<body>
<RootProvider
componentMap={componentMap}

View File

@@ -5,7 +5,7 @@ import { logoutOperation } from 'payload/operations'
import type { CollectionRouteHandler } from '../types.js'
export const logout: CollectionRouteHandler = async ({ collection, req }) => {
const result = logoutOperation({
const result = await logoutOperation({
collection,
req,
})

View File

@@ -1,7 +1,7 @@
import type { Collection, PayloadRequest } from 'payload/types'
import httpStatus from 'http-status'
import { APIError, ValidationError } from 'payload/errors'
import { APIError } from 'payload/errors'
export type ErrorResponse = { data?: any; errors: unknown[]; stack?: string }

View File

@@ -1,4 +1,3 @@
@import './fonts.scss';
@import './styles.scss';
@import './toastify.scss';
@import './colors.scss';
@@ -20,9 +19,9 @@
--theme-overlay: rgba(5, 5, 5, 0.5);
--theme-baseline: #{$baseline-px};
--theme-baseline-body-size: #{$baseline-body-size};
--font-body: 'Suisse Intl', system-ui;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif;
--font-mono: monospace;
--font-serif: 'Merriweather', serif;
--style-radius-s: #{$style-radius-s};
--style-radius-m: #{$style-radius-m};

View File

@@ -1,75 +0,0 @@
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-Medium.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-SemiBold.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-Bold.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
font-weight: 400;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-regular.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-regular.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 400;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-italic.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-italic.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
font-weight: 900;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-900.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-900.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 900;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-900italic.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-900italic.woff') format('woff');
}

View File

@@ -21,13 +21,6 @@ $baseline: math.div($baseline-px, $baseline-body-size) + rem;
@return (math.div($baseline-px, $baseline-body-size) * $multiplier) + rem;
}
//////////////////////////////
// FONTS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
//////////////////////////////
$font-body: 'Suisse Intl' !default;
$font-mono: monospace !default;
//////////////////////////////
// COLORS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
//////////////////////////////

View File

@@ -18,7 +18,11 @@ export const getDataAndFile: GetDataAndFile = async ({ collection, config, reque
const [contentType] = (request.headers.get('Content-Type') || '').split(';')
if (contentType === 'application/json') {
data = await request.json()
try {
data = await request.json()
} catch (error) {
data = {}
}
} else if (contentType === 'multipart/form-data') {
// possible upload request
if (collection?.config?.upload) {

View File

@@ -1,27 +0,0 @@
/fields/
/components/
/auth.d.ts
/auth.js
/components.d.ts
/components.js
/config.d.ts
/config.js
/database.d.ts
/database.js
/errors.d.ts
/errors.js
/graphql.d.ts
/graphql.js
/types.d.ts
/types.js
/utilities.d.ts
/utilities.js
/versions.d.ts
/versions.js
/operations.js
/operations.d.ts
/node.js
/node.d.ts
/uploads.js
/uploads.d.ts
/i18n

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./src/index.ts",
@@ -22,7 +22,7 @@
}
},
"scripts": {
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types && tsx ../../scripts/exportPointerFiles.ts ../packages/payload dist/exports",
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec \"pnpm build:tsc\"",
@@ -113,7 +113,7 @@
"ts-essentials": "7.0.3"
},
"engines": {
"node": ">=18.17.0"
"node": ">=18.20.2"
},
"files": [
"bin.js",

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
@import './dist/admin/scss/vars';
@import './dist/admin/scss/z-index';
@import './dist/admin/scss/type';
@import './dist/admin/scss/queries';
@import './dist/admin/scss/resets';
@import './dist/admin/scss/svg';

View File

@@ -23,10 +23,10 @@ export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
depth?: number
disableVerificationEmail?: boolean
draft?: boolean
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
file?: File
filePath?: string
locale?: string
locale?: GeneratedTypes['locale']
overrideAccess?: boolean
overwriteExistingFiles?: boolean
req?: PayloadRequest

View File

@@ -16,8 +16,8 @@ export type BaseOptions<T extends keyof GeneratedTypes['collections']> = {
*/
context?: RequestContext
depth?: number
fallbackLocale?: string
locale?: string
fallbackLocale?: GeneratedTypes['locale']
locale?: GeneratedTypes['locale']
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean

View File

@@ -14,9 +14,9 @@ export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
context?: RequestContext
depth?: number
draft?: boolean
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
id: number | string
locale?: string
locale?: GeneratedTypes['locale']
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean

View File

@@ -16,9 +16,9 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
depth?: number
disableErrors?: boolean
draft?: boolean
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
limit?: number
locale?: string
locale?: 'all' | GeneratedTypes['locale']
overrideAccess?: boolean
page?: number
pagination?: boolean

View File

@@ -15,9 +15,9 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
depth?: number
disableErrors?: boolean
draft?: boolean
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
id: number | string
locale?: string
locale?: 'all' | GeneratedTypes['locale']
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean

View File

@@ -15,9 +15,9 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
depth?: number
disableErrors?: boolean
draft?: boolean
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
id: string
locale?: string
locale?: 'all' | GeneratedTypes['locale']
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean

View File

@@ -15,9 +15,9 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
context?: RequestContext
depth?: number
draft?: boolean
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
limit?: number
locale?: string
locale?: 'all' | GeneratedTypes['locale']
overrideAccess?: boolean
page?: number
req?: PayloadRequest

View File

@@ -13,9 +13,9 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
context?: RequestContext
depth?: number
draft?: boolean
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
id: string
locale?: string
locale?: GeneratedTypes['locale']
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean

View File

@@ -21,10 +21,10 @@ export type BaseOptions<TSlug extends keyof GeneratedTypes['collections']> = {
data: DeepPartial<GeneratedTypes['collections'][TSlug]>
depth?: number
draft?: boolean
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
file?: File
filePath?: string
locale?: string
locale?: GeneratedTypes['locale']
overrideAccess?: boolean
overwriteExistingFiles?: boolean
req?: PayloadRequest

View File

@@ -1,4 +1,5 @@
import type { AccessResult } from '../../config/types.js'
import type { GeneratedTypes } from '../../index.js'
import type { PayloadRequest, Where } from '../../types/index.js'
import type { SanitizedGlobalConfig } from '../config/types.js'
@@ -13,7 +14,6 @@ type Args = {
depth?: number
draft?: boolean
globalConfig: SanitizedGlobalConfig
locale?: string
overrideAccess?: boolean
req: PayloadRequest
showHiddenFields?: boolean

View File

@@ -9,8 +9,8 @@ export type Options<T extends keyof GeneratedTypes['globals']> = {
context?: RequestContext
depth?: number
draft?: boolean
fallbackLocale?: string
locale?: string
fallbackLocale?: GeneratedTypes['locale']
locale?: 'all' | GeneratedTypes['locale']
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean

View File

@@ -10,9 +10,9 @@ export type Options<T extends keyof GeneratedTypes['globals']> = {
context?: RequestContext
depth?: number
disableErrors?: boolean
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
id: string
locale?: string
locale?: 'all' | GeneratedTypes['locale']
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean

View File

@@ -10,9 +10,9 @@ import { findVersionsOperation } from '../findVersions.js'
export type Options<T extends keyof GeneratedTypes['globals']> = {
context?: RequestContext
depth?: number
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
limit?: number
locale?: string
locale?: 'all' | GeneratedTypes['locale']
overrideAccess?: boolean
page?: number
req?: PayloadRequest

View File

@@ -8,9 +8,9 @@ import { restoreVersionOperation } from '../restoreVersion.js'
export type Options<T extends keyof GeneratedTypes['globals']> = {
context?: RequestContext
depth?: number
fallbackLocale?: string
fallbackLocale?: GeneratedTypes['locale']
id: string
locale?: string
locale?: GeneratedTypes['locale']
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean

View File

@@ -12,8 +12,8 @@ export type Options<TSlug extends keyof GeneratedTypes['globals']> = {
data: DeepPartial<Omit<GeneratedTypes['globals'][TSlug], 'id'>>
depth?: number
draft?: boolean
fallbackLocale?: string
locale?: string
fallbackLocale?: GeneratedTypes['locale']
locale?: GeneratedTypes['locale']
overrideAccess?: boolean
req?: PayloadRequest
showHiddenFields?: boolean

View File

@@ -484,6 +484,7 @@ type GeneratedTypes = {
globals: {
[slug: number | string | symbol]: GlobalTypeWithID & Record<string, unknown>
}
locale: null | string
user: TypeWithID & Record<string, unknown> & { collection: string }
}

View File

@@ -42,7 +42,7 @@ export type CustomPayloadRequest<U = unknown> = {
* The requested locale if specified
* Only available for localized collections
*/
locale?: string
locale?: GeneratedTypes['locale']
/**
* The payload object
*/

View File

@@ -67,6 +67,25 @@ function generateEntitySchemas(
}
}
function generateLocaleEntitySchemas(localization: SanitizedConfig['localization']): JSONSchema4 {
if (localization && 'locales' in localization && localization?.locales) {
const localesFromConfig = localization?.locales
const locales = [...localesFromConfig].map((locale) => {
return locale.code
}, [])
return {
type: 'string',
enum: locales,
}
}
return {
type: 'null',
}
}
function generateAuthEntitySchemas(entities: SanitizedCollectionConfig[]): JSONSchema4 {
const properties: JSONSchema4[] = [...entities]
.filter(({ auth }) => Boolean(auth))
@@ -577,9 +596,10 @@ export function configToJSONSchema(
properties: {
collections: generateEntitySchemas(config.collections || []),
globals: generateEntitySchemas(config.globals || []),
locale: generateLocaleEntitySchemas(config.localization),
user: generateAuthEntitySchemas(config.collections),
},
required: ['user', 'collections', 'globals'],
required: ['user', 'locale', 'collections', 'globals'],
title: 'Config',
}
}

View File

@@ -1,2 +0,0 @@
export { Minimal } from '../templates/Minimal'
export { Default } from '../templates/Default'

View File

@@ -1,2 +0,0 @@
export { getFileByPath } from './dist/uploads/getFileByPath.js';
//# sourceMappingURL=uploads.d.ts.map

View File

@@ -1,3 +0,0 @@
export { getFileByPath } from './dist/uploads/getFileByPath.js';
//# sourceMappingURL=uploads.js.map

View File

@@ -1,10 +1,3 @@
azure.d.ts
azure.js
gcs.d.ts
gcs.js
s3.d.ts
s3.js
dev/tmp
dev/yarn.lock

View File

@@ -63,6 +63,7 @@ This plugin supports the following adapters:
- [Azure Blob Storage](#azure-blob-storage-adapter)
- [AWS S3-style Storage](#s3-adapter)
- [Google Cloud Storage](#gcs-adapter)
- [Vercel Blob Storage](#vercel-blob-adapter)
However, you can create your own adapter for any third-party service you would like to use.
@@ -176,6 +177,20 @@ const adapter = gcsAdapter({
// Now you can pass this adapter to the plugin
```
### Vercel Blob Adapter
To use the Vercel Blob adapter, you need to have `@vercel/blob` installed in your project dependencies.
```ts
import { vercelBlobAdapter } from '@payloadcms/plugin-cloud-storage/vercelBlob'
const adapter = vercelBlobAdapter({
token: process.env.BLOB_READ_WRITE_TOKEN || '',
})
```
Credit to @JarvisPrestidge for the original implementation of this plugin.
### Payload Access Control
Payload ships with access control that runs _even on statically served files_. The same `read` access control property on your `upload`-enabled collections is used, and it allows you to restrict who can request your uploaded files.

View File

@@ -1,7 +1,7 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"description": "The official cloud storage plugin for Payload CMS",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"main": "./src/index.ts",
"types": "./src/index.ts",
"type": "module",
@@ -13,7 +13,7 @@
"directory": "packages/plugin-cloud-storage"
},
"scripts": {
"build": "pnpm build:swc && pnpm build:types && tsx ../../scripts/exportPointerFiles.ts ../packages/plugin-cloud-storage dist/exports",
"build": "pnpm build:swc && pnpm build:types",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"clean": "rimraf {dist,*.tsbuildinfo}",
@@ -26,6 +26,7 @@
"@azure/abort-controller": "^1.0.0",
"@azure/storage-blob": "^12.11.0",
"@google-cloud/storage": "^7.7.0",
"@vercel/blob": "^0.22.3",
"payload": "workspace:*"
},
"peerDependenciesMeta": {
@@ -43,6 +44,9 @@
},
"@google-cloud/storage": {
"optional": true
},
"@vercel/blob": {
"optional": true
}
},
"files": [
@@ -56,6 +60,7 @@
"@azure/storage-blob": "^12.11.0",
"@google-cloud/storage": "^7.7.0",
"@types/find-node-modules": "^2.1.2",
"@vercel/blob": "^0.22.3",
"payload": "workspace:*"
},
"dependencies": {

View File

@@ -0,0 +1,14 @@
import type { GenerateURL } from '@payloadcms/plugin-cloud-storage/types'
import path from 'path'
type GenerateUrlArgs = {
baseUrl: string
prefix?: string
}
export const getGenerateUrl = ({ baseUrl }: GenerateUrlArgs): GenerateURL => {
return ({ filename, prefix = '' }) => {
return `${baseUrl}/${path.posix.join(prefix, filename)}`
}
}

View File

@@ -0,0 +1,19 @@
import type { HandleDelete } from '@payloadcms/plugin-cloud-storage/types'
import { del } from '@vercel/blob'
import path from 'path'
type HandleDeleteArgs = {
baseUrl: string
prefix?: string
token: string
}
export const getHandleDelete = ({ baseUrl, token }: HandleDeleteArgs): HandleDelete => {
return async ({ doc: { prefix = '' }, filename }) => {
const fileUrl = `${baseUrl}/${path.posix.join(prefix, filename)}`
const deletedBlob = await del(fileUrl, { token })
return deletedBlob
}
}

View File

@@ -0,0 +1,40 @@
import type { HandleUpload } from '@payloadcms/plugin-cloud-storage/types'
import { put } from '@vercel/blob'
import path from 'path'
import type { VercelBlobAdapterUploadOptions } from './index.js'
type HandleUploadArgs = VercelBlobAdapterUploadOptions & {
baseUrl: string
prefix?: string
token: string
}
export const getHandleUpload = ({
access = 'public',
addRandomSuffix,
baseUrl,
cacheControlMaxAge,
prefix = '',
token,
}: HandleUploadArgs): HandleUpload => {
return async ({ data, file: { buffer, filename, mimeType } }) => {
const fileKey = path.posix.join(data.prefix || prefix, filename)
const result = await put(fileKey, buffer, {
access,
addRandomSuffix,
cacheControlMaxAge,
contentType: mimeType,
token,
})
// Get filename with suffix from returned url
if (addRandomSuffix) {
data.filename = result.url.replace(`${baseUrl}/`, '')
}
return data
}
}

View File

@@ -0,0 +1,82 @@
import type { Adapter, GeneratedAdapter } from '@payloadcms/plugin-cloud-storage/types'
import { getGenerateUrl } from './generateURL.js'
import { getHandleDelete } from './handleDelete.js'
import { getHandleUpload } from './handleUpload.js'
import { getStaticHandler } from './staticHandler.js'
export interface VercelBlobAdapterArgs {
options?: VercelBlobAdapterUploadOptions
/**
* Vercel Blob storage read/write token
*
* Usually process.env.BLOB_READ_WRITE_TOKEN set by Vercel
*/
token: string
}
export interface VercelBlobAdapterUploadOptions {
/**
* Access control level
*
* @default 'public'
*/
access?: 'public'
/**
* Add a random suffix to the uploaded file name
*
* @default false
*/
addRandomSuffix?: boolean
/**
* Cache-Control max-age in seconds
*
* @default 31536000 (1 year)
*/
cacheControlMaxAge?: number
}
const defaultUploadOptions: VercelBlobAdapterUploadOptions = {
access: 'public',
addRandomSuffix: false,
cacheControlMaxAge: 60 * 60 * 24 * 365, // 1 year
}
export const vercelBlobAdapter =
({ options = {}, token }: VercelBlobAdapterArgs): Adapter =>
({ collection, prefix }): GeneratedAdapter => {
if (!token) {
throw new Error('The token argument is required for the Vercel Blob adapter.')
}
// Parse storeId from token
const storeId = token.match(/^vercel_blob_rw_([a-z\d]+)_[a-z\d]+$/i)?.[1].toLowerCase()
if (!storeId) {
throw new Error(
'Invalid token format for Vercel Blob adapter. Should be vercel_blob_rw_<store_id>_<random_string>.',
)
}
const { access, addRandomSuffix, cacheControlMaxAge } = {
...defaultUploadOptions,
...options,
}
const baseUrl = `https://${storeId}.${access}.blob.vercel-storage.com`
return {
generateURL: getGenerateUrl({ baseUrl, prefix }),
handleDelete: getHandleDelete({ baseUrl, prefix, token }),
handleUpload: getHandleUpload({
access,
addRandomSuffix,
baseUrl,
cacheControlMaxAge,
prefix,
token,
}),
staticHandler: getStaticHandler({ baseUrl, token }, collection),
}
}

View File

@@ -0,0 +1,52 @@
import type { StaticHandler } from '@payloadcms/plugin-cloud-storage/types'
import type { CollectionConfig, PayloadRequest, UploadConfig } from 'payload/types'
import { head } from '@vercel/blob'
import path from 'path'
import { getFilePrefix } from '../../utilities/getFilePrefix.js'
type StaticHandlerArgs = {
baseUrl: string
token: string
}
export const getStaticHandler = (
{ baseUrl, token }: StaticHandlerArgs,
collection: CollectionConfig,
): StaticHandler => {
return async (req, { params: { filename } }) => {
try {
const prefix = await getFilePrefix({ collection, req })
const fileUrl = `${baseUrl}/${path.posix.join(prefix, filename)}`
const blobMetadata = await head(fileUrl, { token })
if (!blobMetadata) {
return new Response(null, { status: 404, statusText: 'Not Found' })
}
const { contentDisposition, contentType, size } = blobMetadata
const response = await fetch(fileUrl)
const blob = await response.blob()
if (!blob) {
return new Response(null, { status: 204, statusText: 'No Content' })
}
const bodyBuffer = await blob.arrayBuffer()
return new Response(bodyBuffer, {
headers: new Headers({
'Content-Disposition': contentDisposition,
'Content-Length': String(size),
'Content-Type': contentType,
}),
status: 200,
})
} catch (err: unknown) {
req.payload.logger.error({ err, msg: 'Unexpected error in staticHandler' })
return new Response('Internal Server Error', { status: 500 })
}
}
}

View File

@@ -0,0 +1 @@
export { getFilePrefix } from '../utilities/getFilePrefix.js'

View File

@@ -0,0 +1 @@
export { vercelBlobAdapter } from '../adapters/vercelBlob/index.js'

View File

@@ -1,7 +1,7 @@
{
"name": "@payloadcms/plugin-cloud",
"description": "The official Payload Cloud plugin",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"main": "./src/index.ts",
"types": "./src/index.ts",
"license": "MIT",

View File

@@ -1,7 +1,7 @@
{
"name": "@payloadcms/plugin-form-builder",
"description": "Form builder plugin for Payload CMS",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"description": "The official Nested Docs plugin for Payload",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",
@@ -52,7 +52,13 @@
}
},
"publishConfig": {
"exports": null,
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",
@@ -54,7 +54,13 @@
}
},
"publishConfig": {
"exports": null,
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"

View File

@@ -45,7 +45,7 @@ const seo =
Field: (props) => (
<MetaTitle
{...props}
hasGenerateTitleFn={typeof pluginConfig.generateTitle === 'function'}
hasGenerateTitleFn={typeof pluginConfig?.generateTitle === 'function'}
/>
),
},
@@ -62,7 +62,7 @@ const seo =
<MetaDescription
{...props}
hasGenerateDescriptionFn={
typeof pluginConfig.generateDescription === 'function'
typeof pluginConfig?.generateDescription === 'function'
}
/>
),
@@ -82,7 +82,7 @@ const seo =
Field: (props) => (
<MetaImage
{...props}
hasGenerateImageFn={typeof pluginConfig.generateImage === 'function'}
hasGenerateImageFn={typeof pluginConfig?.generateImage === 'function'}
/>
),
},
@@ -105,7 +105,7 @@ const seo =
Field: (props) => (
<Preview
{...props}
hasGenerateURLFn={typeof pluginConfig.generateURL === 'function'}
hasGenerateURLFn={typeof pluginConfig?.generateURL === 'function'}
/>
),
},
@@ -194,11 +194,12 @@ const seo =
return collection
}) || [],
endpoints: [
...config.endpoints,
{
handler: async (req) => {
const args: Parameters<GenerateTitle>[0] =
req.data as unknown as Parameters<GenerateTitle>[0]
const result = await pluginConfig.generateTitle(args)
const result = pluginConfig.generateTitle ? await pluginConfig.generateTitle(args) : ''
return new Response(JSON.stringify({ result }), { status: 200 })
},
method: 'post',
@@ -208,7 +209,9 @@ const seo =
handler: async (req) => {
const args: Parameters<GenerateDescription>[0] =
req.data as unknown as Parameters<GenerateDescription>[0]
const result = await pluginConfig.generateDescription(args)
const result = pluginConfig.generateDescription
? await pluginConfig.generateDescription(args)
: ''
return new Response(JSON.stringify({ result }), { status: 200 })
},
method: 'post',
@@ -218,7 +221,7 @@ const seo =
handler: async (req) => {
const args: Parameters<GenerateURL>[0] =
req.data as unknown as Parameters<GenerateURL>[0]
const result = await pluginConfig.generateURL(args)
const result = pluginConfig.generateURL ? await pluginConfig.generateURL(args) : ''
return new Response(JSON.stringify({ result }), { status: 200 })
},
method: 'post',
@@ -228,7 +231,7 @@ const seo =
handler: async (req) => {
const args: Parameters<GenerateImage>[0] =
req.data as unknown as Parameters<GenerateImage>[0]
const result = await pluginConfig.generateImage(args)
const result = pluginConfig.generateImage ? await pluginConfig.generateImage(args) : ''
return new Response(result, { status: 200 })
},
method: 'post',

View File

@@ -1,4 +0,0 @@
/utilities.d.ts
/utilities.js
/components.d.ts
/components.js

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"description": "The officially supported Lexical richtext adapter for Payload",
"repository": {
"type": "git",
@@ -14,7 +14,7 @@
"types": "./src/index.ts",
"type": "module",
"scripts": {
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types && tsx ../../scripts/exportPointerFiles.ts ../packages/richtext-lexical dist/exports",
"build": "pnpm copyfiles && pnpm build:swc && pnpm build:types",
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build",
@@ -81,9 +81,9 @@
"types": "./dist/index.d.ts"
},
"./*": {
"import": "./src/exports/*.ts",
"require": "./src/exports/*.ts",
"types": "./src/exports/*.ts"
"import": "./dist/exports/*.js",
"require": "./dist/exports/*.js",
"types": "./dist/exports/*.d.ts"
}
},
"main": "./dist/index.js",
@@ -91,7 +91,7 @@
"types": "./dist/index.d.ts"
},
"engines": {
"node": ">=18.17.0"
"node": ">=18.20.2"
},
"files": [
"dist",

View File

@@ -1,5 +1,5 @@
import type { SerializedEditorState } from 'lexical'
import type { Field, RichTextField, TextField } from 'payload/types'
import type { Field, RichTextField } from 'payload/types'
import type { AdapterProps, LexicalRichTextAdapter } from '../../../../../types.js'
import type { SanitizedServerEditorConfig } from '../../../../lexical/config/types.js'
@@ -10,6 +10,12 @@ import { defaultHTMLConverters } from '../converter/defaultConverters.js'
import { convertLexicalToHTML } from '../converter/index.js'
type Props = {
/**
* Whether the lexicalHTML field should be hidden in the admin panel
*
* @default true
*/
hidden?: boolean
name: string
}
@@ -53,13 +59,16 @@ export const lexicalHTML: (
**/
lexicalFieldName: string,
props: Props,
) => TextField = (lexicalFieldName, props) => {
const { name = 'lexicalHTML' } = props
) => Field = (lexicalFieldName, props) => {
const { name = 'lexicalHTML', hidden = true } = props
return {
name,
type: 'text',
type: 'code',
admin: {
hidden: true,
editorOptions: {
language: 'html',
},
hidden,
},
hooks: {
afterRead: [

View File

@@ -0,0 +1,80 @@
'use client'
import type { NodeKey } from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection.js'
import lexicalUtilsImport from '@lexical/utils'
const { mergeRegister } = lexicalUtilsImport
import lexicalImport from 'lexical'
const {
$getNodeByKey,
$getSelection,
$isNodeSelection,
CLICK_COMMAND,
COMMAND_PRIORITY_LOW,
KEY_BACKSPACE_COMMAND,
KEY_DELETE_COMMAND,
} = lexicalImport
import { useCallback, useEffect } from 'react'
import { $isHorizontalRuleNode } from '../nodes/HorizontalRuleNode.js'
/**
* React component rendered in the lexical editor, WITHIN the hr element created by createDOM of the HorizontalRuleNode.
*
* @param nodeKey every node has a unique key (this key is not saved to the database and thus may differ between sessions). It's useful for working with the CURRENT lexical editor state
*/
export function HorizontalRuleComponent({ nodeKey }: { nodeKey: NodeKey }) {
const [editor] = useLexicalComposerContext()
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey)
const onDelete = useCallback(
(event: KeyboardEvent) => {
if (isSelected && $isNodeSelection($getSelection())) {
event.preventDefault()
const node = $getNodeByKey(nodeKey)
if ($isHorizontalRuleNode(node)) {
node.remove()
return true
}
}
return false
},
[isSelected, nodeKey],
)
useEffect(() => {
return mergeRegister(
editor.registerCommand(
CLICK_COMMAND,
(event: MouseEvent) => {
const hrElem = editor.getElementByKey(nodeKey)
if (event.target === hrElem) {
if (!event.shiftKey) {
clearSelection()
}
setSelected(!isSelected)
return true
}
return false
},
COMMAND_PRIORITY_LOW,
),
editor.registerCommand(KEY_DELETE_COMMAND, onDelete, COMMAND_PRIORITY_LOW),
editor.registerCommand(KEY_BACKSPACE_COMMAND, onDelete, COMMAND_PRIORITY_LOW),
)
}, [clearSelection, editor, isSelected, nodeKey, onDelete, setSelected])
useEffect(() => {
const hrElem = editor.getElementByKey(nodeKey)
if (hrElem !== null) {
hrElem.className = isSelected ? 'selected' : ''
}
}, [editor, isSelected, nodeKey])
return null
}

View File

@@ -0,0 +1,49 @@
'use client'
import type { FeatureProviderProviderClient } from '../types.js'
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
import { HorizontalRuleIcon } from '../../lexical/ui/icons/HorizontalRule/index.js'
import { createClientComponent } from '../createClientComponent.js'
import { MarkdownTransformer } from './markdownTransformer.js'
import { HorizontalRuleNode, INSERT_HORIZONTAL_RULE_COMMAND } from './nodes/HorizontalRuleNode.js'
import { HorizontalRulePlugin } from './plugin/index.js'
const HorizontalRuleFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
return {
clientFeatureProps: props,
feature: () => ({
clientFeatureProps: props,
markdownTransformers: [MarkdownTransformer],
nodes: [HorizontalRuleNode],
plugins: [
{
Component: HorizontalRulePlugin,
position: 'normal',
},
],
slashMenu: {
options: [
{
displayName: 'Basic',
key: 'basic',
options: [
new SlashMenuOption(`horizontalrule`, {
Icon: HorizontalRuleIcon,
displayName: `Horizontal Rule`,
keywords: ['hr', 'horizontal rule', 'line', 'separator'],
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)
},
}),
],
},
],
},
}),
}
}
export const HorizontalRuleFeatureClientComponent = createClientComponent(
HorizontalRuleFeatureClient,
)

View File

@@ -0,0 +1,37 @@
import type { HTMLConverter } from '../converters/html/converter/types.js'
import type { FeatureProviderProviderServer } from '../types.js'
import type { SerializedHorizontalRuleNode } from './nodes/HorizontalRuleNode.js'
import { HorizontalRuleFeatureClientComponent } from './feature.client.js'
import { MarkdownTransformer } from './markdownTransformer.js'
import { HorizontalRuleNode } from './nodes/HorizontalRuleNode.js'
export const HorizontalRuleFeature: FeatureProviderProviderServer<undefined, undefined> = (
props,
) => {
return {
feature: () => {
return {
ClientComponent: HorizontalRuleFeatureClientComponent,
clientFeatureProps: null,
markdownTransformers: [MarkdownTransformer],
nodes: [
{
converters: {
html: {
converter: () => {
return `<hr/>`
},
nodeTypes: [HorizontalRuleNode.getType()],
} as HTMLConverter<SerializedHorizontalRuleNode>,
},
node: HorizontalRuleNode,
},
],
serverFeatureProps: props,
}
},
key: 'horizontalrule',
serverFeatureProps: props,
}
}

View File

@@ -0,0 +1,26 @@
import type { ElementTransformer } from '@lexical/markdown'
import {
$createHorizontalRuleNode,
$isHorizontalRuleNode,
HorizontalRuleNode,
} from './nodes/HorizontalRuleNode.js'
export const MarkdownTransformer: ElementTransformer = {
type: 'element',
dependencies: [HorizontalRuleNode],
export: (node, exportChildren) => {
if (!$isHorizontalRuleNode(node)) {
return null
}
return '---'
},
// match ---
regExp: /^---\s*$/,
replace: (parentNode) => {
const node = $createHorizontalRuleNode()
if (node) {
parentNode.replace(node)
}
},
}

View File

@@ -0,0 +1,123 @@
import type {
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
LexicalCommand,
LexicalNode,
SerializedLexicalNode,
} from 'lexical'
import lexicalImport from 'lexical'
const { $applyNodeReplacement, DecoratorNode, createCommand } = lexicalImport
import * as React from 'react'
const HorizontalRuleComponent = React.lazy(() =>
import('../component/index.js').then((module) => ({
default: module.HorizontalRuleComponent,
})),
)
/**
* Serialized representation of a horizontal rule node. Serialized = converted to JSON. This is what is stored in the database / in the lexical editor state.
*/
export type SerializedHorizontalRuleNode = SerializedLexicalNode
export const INSERT_HORIZONTAL_RULE_COMMAND: LexicalCommand<void> = createCommand(
'INSERT_HORIZONTAL_RULE_COMMAND',
)
/**
* This node is a DecoratorNode. DecoratorNodes allow you to render React components in the editor.
*
* They need both createDom and decorate functions. createDom => outside of the html. decorate => React Component inside of the html.
*
* If we used DecoratorBlockNode instead, we would only need a decorate method
*/
export class HorizontalRuleNode extends DecoratorNode<React.ReactElement> {
static clone(node: HorizontalRuleNode): HorizontalRuleNode {
return new HorizontalRuleNode(node.__key)
}
static getType(): string {
return 'horizontalrule'
}
/**
* Defines what happens if you copy an hr element from another page and paste it into the lexical editor
*
* This also determines the behavior of lexical's internal HTML -> Lexical converter
*/
static importDOM(): DOMConversionMap | null {
return {
hr: () => ({
conversion: convertHorizontalRuleElement,
priority: 0,
}),
}
}
/**
* The data for this node is stored serialized as JSON. This is the "load function" of that node: it takes the saved data and converts it into a node.
*/
static importJSON(serializedNode: SerializedHorizontalRuleNode): HorizontalRuleNode {
return $createHorizontalRuleNode()
}
/**
* Determines how the hr element is rendered in the lexical editor. This is only the "initial" / "outer" HTML element.
*/
createDOM(): HTMLElement {
return document.createElement('hr')
}
/**
* Allows you to render a React component within whatever createDOM returns.
*/
decorate(): React.ReactElement {
return <HorizontalRuleComponent nodeKey={this.__key} />
}
/**
* Opposite of importDOM, this function defines what happens when you copy an hr element from the lexical editor and paste it into another page.
*
* This also determines the behavior of lexical's internal Lexical -> HTML converter
*/
exportDOM(): DOMExportOutput {
return { element: document.createElement('hr') }
}
/**
* Opposite of importJSON. This determines what data is saved in the database / in the lexical editor state.
*/
exportJSON(): SerializedLexicalNode {
return {
type: 'horizontalrule',
version: 1,
}
}
getTextContent(): string {
return '\n'
}
isInline(): false {
return false
}
updateDOM(): boolean {
return false
}
}
function convertHorizontalRuleElement(): DOMConversionOutput {
return { node: $createHorizontalRuleNode() }
}
export function $createHorizontalRuleNode(): HorizontalRuleNode {
return $applyNodeReplacement(new HorizontalRuleNode())
}
export function $isHorizontalRuleNode(
node: LexicalNode | null | undefined,
): node is HorizontalRuleNode {
return node instanceof HorizontalRuleNode
}

View File

@@ -0,0 +1,20 @@
@import '../../../../scss/styles.scss';
hr {
padding: 2px 2px;
border: none;
margin: 1rem 0;
cursor: pointer;
}
hr:after {
content: '';
display: block;
height: 2px;
background-color: var(--theme-elevation-250);
}
hr.selected {
outline: 2px solid var(--theme-success-500);
user-select: none;
}

View File

@@ -0,0 +1,48 @@
'use client'
import lexicalComposerContextImport from '@lexical/react/LexicalComposerContext.js'
const { useLexicalComposerContext } = lexicalComposerContextImport
import lexicalUtilsImport from '@lexical/utils'
const { $insertNodeToNearestRoot } = lexicalUtilsImport
import lexicalImport from 'lexical'
const { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR } = lexicalImport
import { useEffect } from 'react'
import {
$createHorizontalRuleNode,
INSERT_HORIZONTAL_RULE_COMMAND,
} from '../nodes/HorizontalRuleNode.js'
import './index.scss'
/**
* Registers the INSERT_HORIZONTAL_RULE_COMMAND lexical command and defines the behavior for when it is called.
*/
export function HorizontalRulePlugin(): null {
const [editor] = useLexicalComposerContext()
useEffect(() => {
return editor.registerCommand(
INSERT_HORIZONTAL_RULE_COMMAND,
(type) => {
const selection = $getSelection()
if (!$isRangeSelection(selection)) {
return false
}
const focusNode = selection.focus.getNode()
if (focusNode !== null) {
const horizontalRuleNode = $createHorizontalRuleNode()
$insertNodeToNearestRoot(horizontalRuleNode)
}
return true
},
COMMAND_PRIORITY_EDITOR,
)
}, [editor])
return null
}

View File

@@ -1,6 +1,7 @@
import type { SerializedListItemNode, SerializedListNode } from '@lexical/list'
import lexicalListImport from '@lexical/list'
import { v4 as uuidv4 } from 'uuid'
const { ListItemNode, ListNode } = lexicalListImport
import type { HTMLConverter } from '../converters/html/converter/types.js'
@@ -19,13 +20,15 @@ export const ListHTMLConverter: HTMLConverter<SerializedListNode> = {
payload,
})
return `<${node?.tag} class="${node?.listType}">${childrenText}</${node?.tag}>`
return `<${node?.tag} class="list-${node?.listType}">${childrenText}</${node?.tag}>`
},
nodeTypes: [ListNode.getType()],
}
export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
converter: async ({ converters, node, parent, payload }) => {
const hasSubLists = node.children.some((child) => child.type === 'list')
const childrenText = await convertLexicalNodesToHTML({
converters,
lexicalNodes: node.children,
@@ -37,19 +40,30 @@ export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
})
if ('listType' in parent && parent?.listType === 'check') {
const uuid = uuidv4()
return `<li aria-checked=${node.checked ? 'true' : 'false'} class="${
'list-item-checkbox' + node.checked
? 'list-item-checkbox-checked'
: 'list-item-checkbox-unchecked'
'list-item-checkbox' +
(node.checked ? ' list-item-checkbox-checked' : ' list-item-checkbox-unchecked') +
(hasSubLists ? ' nestedListItem' : '')
}"
role="checkbox"
tabIndex=${-1}
value=${node?.value}
>
${childrenText}
${
hasSubLists
? childrenText
: `
<input type="checkbox" id="${uuid}"${node.checked ? ' checked' : ''}>
<label for="${uuid}">${childrenText}</label><br>
`
}
</li>`
} else {
return `<li value=${node?.value}>${childrenText}</li>`
return `<li ${hasSubLists ? `class="nestedListItem" ` : ''}value=${node?.value}>${childrenText}</li>`
}
},
nodeTypes: [ListItemNode.getType()],

View File

@@ -13,6 +13,7 @@ import { SubscriptFeature } from '../../../features/format/subscript/feature.ser
import { SuperscriptFeature } from '../../../features/format/superscript/feature.server.js'
import { UnderlineFeature } from '../../../features/format/underline/feature.server.js'
import { HeadingFeature } from '../../../features/heading/feature.server.js'
import { HorizontalRuleFeature } from '../../../features/horizontalrule/feature.server.js'
import { IndentFeature } from '../../../features/indent/feature.server.js'
import { LinkFeature } from '../../../features/link/feature.server.js'
import { CheckListFeature } from '../../../features/lists/checklist/feature.server.js'
@@ -48,6 +49,7 @@ export const defaultEditorFeatures: FeatureProviderServer<unknown, unknown>[] =
RelationshipFeature(),
BlockQuoteFeature(),
UploadFeature(),
HorizontalRuleFeature(),
]
export const defaultEditorConfig: ServerEditorConfig = {

View File

@@ -174,7 +174,9 @@ function useAddBlockHandle(
pageX < left - horizontalBuffer ||
pageX > right + horizontalBuffer
) {
setHoveredElement(null)
if (hoveredElement !== null) {
setHoveredElement(null)
}
return
}
@@ -198,10 +200,12 @@ function useAddBlockHandle(
if (!_emptyBlockElem) {
return
}
setHoveredElement({
elem: _emptyBlockElem,
node: blockNode,
})
if (hoveredElement?.node !== blockNode || hoveredElement?.elem !== _emptyBlockElem) {
setHoveredElement({
elem: _emptyBlockElem,
node: blockNode,
})
}
}
// Since the draggableBlockElem is outside the actual editor, we need to listen to the document
@@ -212,7 +216,7 @@ function useAddBlockHandle(
return () => {
document?.removeEventListener('mousemove', onDocumentMouseMove)
}
}, [scrollerElem, anchorElem, editor])
}, [scrollerElem, anchorElem, editor, hoveredElement])
useEffect(() => {
if (menuRef.current && hoveredElement?.node) {

View File

@@ -159,7 +159,9 @@ function useDraggableBlockMenu(
return
}
setDraggableBlockElem(_draggableBlockElem)
if (draggableBlockElem !== _draggableBlockElem) {
setDraggableBlockElem(_draggableBlockElem)
}
}
// Since the draggableBlockElem is outside the actual editor, we need to listen to the document
@@ -170,7 +172,7 @@ function useDraggableBlockMenu(
return () => {
document?.removeEventListener('mousemove', onDocumentMouseMove)
}
}, [scrollerElem, anchorElem, editor, calculateDistanceFromScrollerElem])
}, [scrollerElem, anchorElem, editor, calculateDistanceFromScrollerElem, draggableBlockElem])
useEffect(() => {
if (menuRef.current) {
@@ -324,7 +326,9 @@ function useDraggableBlockMenu(
} else {
targetNode.insertBefore(draggedNode)
}*/
setDraggableBlockElem(null)
if (draggableBlockElem !== null) {
setDraggableBlockElem(null)
}
})
return true

View File

@@ -0,0 +1,48 @@
const replacedElements = [
'IMG',
'INPUT',
'TEXTAREA',
'SELECT',
'BUTTON',
'VIDEO',
'OBJECT',
'EMBED',
'IFRAME',
'HR',
]
/**
* From ChatGPT, only that verified it works for HR elements.
*
* HTML Elements can have CSS lineHeight applied to them, but it doesn't always affect the visual layout.
* This function checks if the line-height property has an effect on the element's layout.
* @param htmlElem
*/
export function doesLineHeightAffectElement(htmlElem: HTMLElement) {
if (!htmlElem) return false
// Check for replaced elements, elements that typically don't support line-height adjustments,
// and elements without visible content
if (
replacedElements.includes(htmlElem.tagName) ||
htmlElem.offsetHeight === 0 ||
htmlElem.offsetWidth === 0
) {
return false
}
// Check for specific CSS properties that negate line-height's visual effects
const style = window.getComputedStyle(htmlElem)
if (
style.display === 'table-cell' ||
style.position === 'absolute' ||
style.visibility === 'hidden' ||
style.opacity === '0'
) {
return false
}
// This is a basic check, and there can be more complex scenarios where line-height doesn't have an effect.
return true
}

View File

@@ -1,27 +1,41 @@
import { doesLineHeightAffectElement } from './doesLineHeightAffectElement.js'
export function setHandlePosition(
targetElem: HTMLElement | null,
floatingElem: HTMLElement,
handleElem: HTMLElement,
anchorElem: HTMLElement,
leftOffset: number = 0, // SPACE
) {
if (!targetElem) {
floatingElem.style.opacity = '0'
floatingElem.style.transform = 'translate(-10000px, -10000px)'
handleElem.style.opacity = '0'
handleElem.style.transform = 'translate(-10000px, -10000px)'
return
}
const targetRect = targetElem.getBoundingClientRect()
const targetStyle = window.getComputedStyle(targetElem)
const floatingElemRect = floatingElem.getBoundingClientRect()
const floatingElemRect = handleElem.getBoundingClientRect()
const anchorElementRect = anchorElem.getBoundingClientRect()
const top =
targetRect.top +
(parseInt(targetStyle.lineHeight, 10) - floatingElemRect.height) / 2 -
anchorElementRect.top
let top: number
const shouldDisplayHandleInCenter = targetRect.height < 60
if (!shouldDisplayHandleInCenter) {
// No need to let line height affect the re-positioning of the floating element if line height has no
// visual effect on the element. Otherwise, the floating element will be positioned incorrectly.
const actualLineHeight = doesLineHeightAffectElement(targetElem)
? parseInt(targetStyle.lineHeight, 10)
: 0
top = targetRect.top + (actualLineHeight - floatingElemRect.height) / 2 - anchorElementRect.top
} else {
top =
targetRect.top - floatingElemRect.height / 2 - anchorElementRect.top + targetRect.height / 2
}
const left = leftOffset
floatingElem.style.opacity = '1'
floatingElem.style.transform = `translate(${left}px, ${top}px)`
handleElem.style.opacity = '1'
handleElem.style.transform = `translate(${left}px, ${top}px)`
}

View File

@@ -331,6 +331,10 @@
list-style-position: inside;
}
&__ul ul {
margin: 0;
}
&__listItem {
margin: 0 0px 0.4em 16px;
}

View File

@@ -0,0 +1,16 @@
import React from 'react'
export const HorizontalRuleIcon: React.FC = () => (
<svg
aria-hidden="true"
className="icon"
fill="none"
focusable="false"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<rect fill="currentColor" height="1" width="12" x="4" y="9.5" />
</svg>
)

View File

@@ -1,4 +1,3 @@
@import 'fonts';
@import 'styles';
@import './toastify.scss';
@import './colors.scss';
@@ -20,9 +19,9 @@
--theme-overlay: rgba(5, 5, 5, 0.5);
--theme-baseline: #{$baseline-px};
--theme-baseline-body-size: #{$baseline-body-size};
--font-body: 'Suisse Intl', system-ui;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif;
--font-mono: monospace;
--font-serif: 'Merriweather', serif;
--style-radius-s: #{$style-radius-s};
--style-radius-m: #{$style-radius-m};

View File

@@ -1,75 +0,0 @@
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-Medium.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-SemiBold.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-Bold.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
font-weight: 400;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-regular.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-regular.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 400;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-italic.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-italic.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
font-weight: 900;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-900.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-900.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 900;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-900italic.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-900italic.woff') format('woff');
}

View File

@@ -21,13 +21,6 @@ $baseline: math.div($baseline-px, $baseline-body-size) + rem;
@return (math.div($baseline-px, $baseline-body-size) * $multiplier) + rem;
}
//////////////////////////////
// FONTS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
//////////////////////////////
$font-body: 'Suisse Intl' !default;
$font-mono: monospace !default;
//////////////////////////////
// COLORS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
//////////////////////////////

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"description": "The officially supported Slate richtext adapter for Payload",
"repository": {
"type": "git",
@@ -50,13 +50,19 @@
}
},
"publishConfig": {
"exports": null,
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"
},
"engines": {
"node": ">=18.17.0"
"node": ">=18.20.2"
},
"files": [
"dist"

View File

@@ -1,4 +1,3 @@
@import 'fonts';
@import 'styles';
@import './toastify.scss';
@import './colors.scss';
@@ -20,9 +19,9 @@
--theme-overlay: rgba(5, 5, 5, 0.5);
--theme-baseline: #{$baseline-px};
--theme-baseline-body-size: #{$baseline-body-size};
--font-body: 'Suisse Intl', system-ui;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif;
--font-mono: monospace;
--font-serif: 'Merriweather', serif;
--style-radius-s: #{$style-radius-s};
--style-radius-m: #{$style-radius-m};

View File

@@ -1,75 +0,0 @@
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-Medium.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-SemiBold.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Suisse Intl';
src:
url('../assets/fonts/SuisseIntl-Bold.woff2') format('woff2'),
url('../assets/fonts/SuisseIntl-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
font-weight: 400;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-regular.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-regular.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 400;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-italic.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-italic.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
font-weight: 900;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-900.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-900.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 900;
src:
local(''),
url('../assets/fonts/merriweather-v30-latin-900italic.woff2') format('woff2'),
url('../assets/fonts/merriweather-v30-latin-900italic.woff') format('woff');
}

View File

@@ -21,13 +21,6 @@ $baseline: math.div($baseline-px, $baseline-body-size) + rem;
@return (math.div($baseline-px, $baseline-body-size) * $multiplier) + rem;
}
//////////////////////////////
// FONTS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
//////////////////////////////
$font-body: 'Suisse Intl' !default;
$font-mono: monospace !default;
//////////////////////////////
// COLORS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
//////////////////////////////

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/translations",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"main": "./src/exports/index.ts",
"types": "./src/types.ts",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/ui",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.10",
"type": "module",
"homepage": "https://payloadcms.com",
"repository": {
@@ -82,9 +82,9 @@
"types": "./src/utilities/*.ts"
},
"./scss": {
"import": "./src/styles.scss",
"require": "./src/styles.scss",
"default": "./src/styles.scss"
"import": "./src/scss/styles.scss",
"require": "./src/scss/styles.scss",
"default": "./src/scss/styles.scss"
},
"./scss/app.scss": "./src/scss/app.scss"
},
@@ -163,9 +163,9 @@
"default": "./dist/prod/styles.css"
},
"./scss": {
"import": "./dist/styles.scss",
"require": "./dist/styles.scss",
"default": "./dist/styles.scss"
"import": "./dist/scss/styles.scss",
"require": "./dist/scss/styles.scss",
"default": "./dist/scss/styles.scss"
},
"./scss/app.scss": "./dist/scss/app.scss"
}
@@ -216,12 +216,12 @@
"uuid": "9.0.1"
},
"peerDependencies": {
"next": "14.2.0-canary.23",
"next": "^14.2.0-canary.23",
"payload": "workspace:*",
"react": "^18.0.0"
},
"engines": {
"node": ">=18.17.0"
"node": ">=18.20.2"
},
"files": [
"dist"

Some files were not shown because too many files have changed in this diff Show More