Compare commits
35 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1259be8f2 | ||
|
|
cd161e4b16 | ||
|
|
9d42751a42 | ||
|
|
c2c637b359 | ||
|
|
2f446e11d6 | ||
|
|
4f566b088c | ||
|
|
0d40d87b31 | ||
|
|
94c0095b3b | ||
|
|
4328060637 | ||
|
|
d98d0fd5bd | ||
|
|
5db2863d08 | ||
|
|
ff5e438d6d | ||
|
|
bfd5f13ee9 | ||
|
|
8043188f36 | ||
|
|
e3e0998772 | ||
|
|
86adc6f282 | ||
|
|
b51b519d30 | ||
|
|
c70dcb6a59 | ||
|
|
1456fcdcad | ||
|
|
a216800c72 | ||
|
|
c3d8597c13 | ||
|
|
844663ce1a | ||
|
|
055e6af7b7 | ||
|
|
479e6ecddc | ||
|
|
b9456e8244 | ||
|
|
432dfef435 | ||
|
|
429c6f7a48 | ||
|
|
6d41f6c56d | ||
|
|
20ac2b86cf | ||
|
|
b88455166a | ||
|
|
9b86de1f9d | ||
|
|
1393c72281 | ||
|
|
bcb538aee2 | ||
|
|
01e8f8c649 | ||
|
|
330e4a7724 |
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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)
|
||||
//////////////////////////////
|
||||
|
||||
@@ -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) {
|
||||
|
||||
27
packages/payload/.gitignore
vendored
27
packages/payload/.gitignore
vendored
@@ -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
|
||||
@@ -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
@@ -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';
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export { Minimal } from '../templates/Minimal'
|
||||
export { Default } from '../templates/Default'
|
||||
2
packages/payload/uploads.d.ts
vendored
2
packages/payload/uploads.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
export { getFileByPath } from './dist/uploads/getFileByPath.js';
|
||||
//# sourceMappingURL=uploads.d.ts.map
|
||||
@@ -1,3 +0,0 @@
|
||||
export { getFileByPath } from './dist/uploads/getFileByPath.js';
|
||||
|
||||
//# sourceMappingURL=uploads.js.map
|
||||
7
packages/plugin-cloud-storage/.gitignore
vendored
7
packages/plugin-cloud-storage/.gitignore
vendored
@@ -1,10 +1,3 @@
|
||||
azure.d.ts
|
||||
azure.js
|
||||
gcs.d.ts
|
||||
gcs.js
|
||||
s3.d.ts
|
||||
s3.js
|
||||
|
||||
dev/tmp
|
||||
dev/yarn.lock
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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)}`
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
1
packages/plugin-cloud-storage/src/exports/utilities.ts
Normal file
1
packages/plugin-cloud-storage/src/exports/utilities.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { getFilePrefix } from '../utilities/getFilePrefix.js'
|
||||
1
packages/plugin-cloud-storage/src/exports/vercelBlob.ts
Normal file
1
packages/plugin-cloud-storage/src/exports/vercelBlob.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { vercelBlobAdapter } from '../adapters/vercelBlob/index.js'
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
4
packages/richtext-lexical/.gitignore
vendored
4
packages/richtext-lexical/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
/utilities.d.ts
|
||||
/utilities.js
|
||||
/components.d.ts
|
||||
/components.js
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)`
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
@@ -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};
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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)
|
||||
//////////////////////////////
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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)
|
||||
//////////////////////////////
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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};
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user