Compare commits

..

35 Commits

Author SHA1 Message Date
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
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
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
Elliot DeNolf
20ac2b86cf chore(release): v3.0.0-beta.5 [skip ci] 2024-04-11 15:11:14 -04:00
Elliot DeNolf
b88455166a fix: improve config finding (#5800) 2024-04-11 15:08:04 -04:00
Elliot DeNolf
9b86de1f9d fix(ui): scss paths 2024-04-11 15:06:27 -04:00
Elliot DeNolf
1393c72281 fix: improve config finding 2024-04-11 15:06:09 -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
114 changed files with 1245 additions and 2559 deletions

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

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.4",
"version": "3.0.0-beta.9",
"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",
"node-mocks-http": "^1.14.1",
"nodemon": "3.0.3",
"open": "^10.1.0",

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.4",
"version": "3.0.0-beta.9",
"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.4",
"version": "3.0.0-beta.9",
"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.4",
"version": "3.0.0-beta.9",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.4",
"version": "3.0.0-beta.9",
"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",
"payload": "workspace:*"
},
"publishConfig": {

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.4",
"version": "3.0.0-beta.9",
"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\"",

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

@@ -7,11 +7,18 @@ import path from 'path'
* If no tsconfig.json file is found, returns the current working directory.
* @returns An object containing the source and output paths.
*/
const getTSConfigPaths = (): { outPath: string; srcPath: string } => {
const getTSConfigPaths = (): {
configPath?: string
outPath?: string
rootPath?: string
srcPath?: string
} => {
const tsConfigPath = findUp.sync('tsconfig.json')
if (!tsConfigPath) {
return { outPath: process.cwd(), srcPath: process.cwd() }
return {
rootPath: process.cwd(),
}
}
try {
@@ -23,13 +30,25 @@ const getTSConfigPaths = (): { outPath: string; srcPath: string } => {
const tsConfig = JSON.parse(rawTsConfig)
const srcPath = tsConfig.compilerOptions?.rootDir || process.cwd()
const outPath = tsConfig.compilerOptions?.outDir || process.cwd()
return { outPath, srcPath }
const rootPath = process.cwd()
const srcPath = tsConfig.compilerOptions?.rootDir || path.resolve(process.cwd(), 'src')
const outPath = tsConfig.compilerOptions?.outDir || path.resolve(process.cwd(), 'dist')
const tsConfigDir = path.dirname(tsConfigPath)
let configPath = tsConfig.compilerOptions?.paths?.['@payload-config']?.[0]
if (configPath) {
configPath = path.resolve(tsConfigDir, configPath)
}
return {
configPath,
outPath,
rootPath,
srcPath,
}
} catch (error) {
console.error(`Error parsing tsconfig.json: ${error}`) // Do not throw the error, as we can still continue with the other config path finding methods
return { outPath: process.cwd(), srcPath: process.cwd() }
return {
rootPath: process.cwd(),
}
}
}
@@ -49,12 +68,17 @@ export const findConfig = (): string => {
return path.resolve(process.cwd(), process.env.PAYLOAD_CONFIG_PATH)
}
const { outPath, srcPath } = getTSConfigPaths()
const { configPath, outPath, rootPath, srcPath } = getTSConfigPaths()
const searchPaths = process.env.NODE_ENV === 'production' ? [outPath, srcPath] : [srcPath]
const searchPaths =
process.env.NODE_ENV === 'production'
? [configPath, outPath, srcPath, rootPath]
: [configPath, srcPath, rootPath]
// eslint-disable-next-line no-restricted-syntax
for (const searchPath of searchPaths) {
if (!searchPath) continue
const configPath = findUp.sync(
(dir) => {
const tsPath = path.join(dir, 'payload.config.ts')

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.4",
"version": "3.0.0-beta.9",
"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.4",
"version": "3.0.0-beta.9",
"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.4",
"version": "3.0.0-beta.9",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.0.0-beta.4",
"version": "3.0.0-beta.9",
"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.4",
"version": "3.0.0-beta.9",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.0.0-beta.4",
"version": "3.0.0-beta.9",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.0.0-beta.4",
"version": "3.0.0-beta.9",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

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.4",
"version": "3.0.0-beta.9",
"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",

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

@@ -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

@@ -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.4",
"version": "3.0.0-beta.9",
"description": "The officially supported Slate richtext adapter for Payload",
"repository": {
"type": "git",

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.4",
"version": "3.0.0-beta.9",
"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.4",
"version": "3.0.0-beta.9",
"type": "module",
"homepage": "https://payloadcms.com",
"repository": {
@@ -82,9 +82,9 @@
"types": "./src/utilities/*.ts"
},
"./scss": {
"import": "./src/scss.scss",
"require": "./src/scss.scss",
"default": "./src/scss.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/scss.scss",
"require": "./dist/scss.scss",
"default": "./dist/scss.scss"
"import": "./dist/scss/styles.scss",
"require": "./dist/scss/styles.scss",
"default": "./dist/scss/styles.scss"
},
"./scss/app.scss": "./dist/scss/app.scss"
}
@@ -216,7 +216,7 @@
"uuid": "9.0.1"
},
"peerDependencies": {
"next": "14.2.0-canary.23",
"next": "^14.2",
"payload": "workspace:*",
"react": "^18.0.0"
},

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');
}

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