Compare commits

..

32 Commits

Author SHA1 Message Date
Dan Ribbens
a3ab1c336b chore: drizzle schema snapshot version migration 2024-05-07 12:21:39 -04:00
Dan Ribbens
c974256f15 chore: drizzle-kit 0.20.18-08d50a4 2024-05-07 09:57:36 -04:00
Dan Ribbens
db613f35b2 chore(db-postgres): change drizzle imports 2024-05-03 14:08:08 -04:00
Dan Ribbens
2dd162269a chore: update drizzle-kit@beta 2024-05-03 13:52:53 -04:00
Elliot DeNolf
9c13089a2f chore: add dotenv to test dir [skip ci] 2024-05-03 13:42:01 -04:00
Paul
b642cb2d93 chore!: update plugin exports to be named and consistent (#6195)
BREAKING CHANGE: All plugins have been updated to use named exports and the names have been updated to be consistent.

// before
import { cloudStorage } from '@payloadcms/plugin-cloud-storage'
// current
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'

//before
import { payloadCloud } from '@payloadcms/plugin-cloud'
// current
import { payloadCloudPlugin } from '@payloadcms/plugin-cloud'

//before
import formBuilder from '@payloadcms/plugin-form-builder'
// current
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'

//before
import { nestedDocs } from '@payloadcms/plugin-nested-docs'
// current
import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'

//before
import { redirects } from '@payloadcms/plugin-redirects'
// current
import { redirectsPlugin } from '@payloadcms/plugin-redirects'

// before
import search from '@payloadcms/plugin-search'
// current
import { searchPlugin } from '@payloadcms/plugin-search'

//before
import { sentry } from '@payloadcms/plugin-sentry'
// current
import { sentryPlugin } from '@payloadcms/plugin-sentry'

// before
import { seo } from '@payloadcms/plugin-seo'
// current
import { seoPlugin } from '@payloadcms/plugin-seo'
2024-05-03 13:36:36 -03:00
Elliot DeNolf
9e5d521567 ci: allow examples/* scopes 2024-05-03 11:41:16 -04:00
Jessica Chowdhury
6eabc99e01 chore: translate checkbox result in collection list view (#6165) 2024-05-03 10:44:43 -04:00
Jacob Fletcher
ea917dd811 feat(next): supports custom login redirects in initPage (#6186) 2024-05-03 09:48:57 -04:00
Jacob Fletcher
070d8e1731 feat(next): supports custom login redirects in initPage 2024-05-03 09:24:34 -04:00
Jacob Fletcher
664c60d2bc chore(next): restructures initPage 2024-05-03 09:17:17 -04:00
Jarrod Flesch
e25814e1ee fix: cascade graphql locales through relationships (#6166) 2024-05-03 08:33:53 -04:00
Jarrod Flesch
27ea117731 fix: only allow save after form is modified (#6189) 2024-05-03 08:28:37 -04:00
Alessio Gravili
7ab156e117 feat(richtext-lexical)!: finalize ClientFeature interface (#6191)
**BREAKING:**
If you have own, custom lexical features, there will be a bunch of breaking API changes for you. The saved JSON data is not affected.

- `floatingSelectToolbar` has been changed to `toolbarInline`

- `slashMenu.dynamicOptions `and `slashMenu.options` have been changed to `slashMenu.groups` and `slashMenu.dynamicGroups`

- `toolbarFixed.sections` is now `toolbarFixed.groups`

- Slash menu group `options` and toolbar group `entries` have both been renamed to `items`

- Toolbar group item `onClick` has been renamed to `onSelect` to match slash menu properties

- slashMenu item `onSelect` is no longer auto-wrapped inside an `editor.update`. If you perform editor updates in them, you have to wrap it inside an `editor.update` callback yourself. Within our own features this extra control has removed a good amount of unnecessary, nested `editor.update` calls, which is good

- Slash menu items are no longer initialized using the `new` keyword, as they are now types and no longer classes. You can convert them to an object and add the `key` property as an object property instead of an argument to the previous SlashMenuItem constructor

- CSS classnames for slash menu and toolbars, as well as their items, have changed

- `CheckListFeature` is now exported as and has been renamed to `ChecklistFeature`

For guidance on migration, check out how we migrated our own features in this PR's diff: https://github.com/payloadcms/payload/pull/6191/files
2024-05-02 21:38:15 -04:00
Paul
f2d415663f fix: ensures confirm password remains on form state (#6190) 2024-05-02 18:53:35 -03:00
Jarrod Flesch
bdf08a19d1 fix: moves ts-essentials to prod deps (#6187) 2024-05-02 16:37:05 -04:00
Elliot DeNolf
cb90e9f622 chore(release): v3.0.0-beta.23 [skip ci] 2024-05-02 16:05:19 -04:00
Elliot DeNolf
b05a5e1fb6 chore(deps): bump turborepo 2024-05-02 15:57:18 -04:00
Elliot DeNolf
92a5da1006 chore: move stripe postman out of src [skip ci] 2024-05-02 15:50:44 -04:00
Paul
75a95469b2 feat(plugin-stripe): update plugin stripe for v3 (#6019) 2024-05-02 16:19:27 -03:00
Jarrod Flesch
c0ae287d46 fix: reset password validations (#6153)
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: James <james@trbl.design>
Co-authored-by: Alessio Gravili <alessio@gravili.de>
2024-05-02 15:08:47 -04:00
Patrik
a2b92aa3ff fix(ui): watch "where" query param inside route and reset WhereBuilder (#6184) 2024-05-02 13:25:51 -04:00
Friggo
544a2285d3 chore(translations): czech translation improvements (#6078) 2024-05-02 12:54:18 -04:00
Elliot DeNolf
8c39950ea3 chore(release): v3.0.0-beta.22 [skip ci] 2024-05-02 12:27:28 -04:00
Yiannis Demetriades
6d642fe9b9 fix(templates): adds back missing CSS import in blank 3.0 template (#6183) 2024-05-02 11:30:58 -04:00
Jacob Fletcher
f175a741bc chore(next): exports initPage utility (#6182) 2024-05-02 11:22:13 -04:00
Jacob Fletcher
3290376f80 fix(next): ensures admin access only blocks admin routes 2024-05-02 11:07:43 -04:00
Jacob Fletcher
2b5c1ba99a chore(next): exports initPage utility 2024-05-02 10:32:17 -04:00
Alessio Gravili
bcb3f08386 chore: hide test flakes, improve playwright CI logs, significantly reduce playwright timeouts, add back test retries, cache playwright browsers in CI, disable CI telemetry, improve test throttle utility (#6155) 2024-05-01 17:35:41 -04:00
Wilson
b729b9bebd docs: add before login comments (#6101) 2024-05-01 15:59:11 -04:00
Tylan Davis
26ee91eb48 docs: adjust line breaks in code blocks (#6001) 2024-05-01 15:57:39 -04:00
Paul
43a17f67a0 chore(richtext-lexical): export types for additional props (#6173) 2024-05-01 15:03:06 -03:00
247 changed files with 3189 additions and 1525 deletions

View File

@@ -13,6 +13,8 @@ concurrency:
env:
NODE_VERSION: 18.20.2
PNPM_VERSION: 8.15.7
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
changes:
@@ -89,6 +91,8 @@ jobs:
- run: pnpm install
- run: pnpm run build:all
env:
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
- name: Cache build
uses: actions/cache@v4
@@ -253,7 +257,8 @@ jobs:
- plugin-seo
- versions
- uploads
env:
SUITE_NAME: ${{ matrix.suite }}
steps:
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
@@ -281,11 +286,33 @@ jobs:
run: pnpm docker:start
if: ${{ matrix.suite == 'plugin-cloud-storage' }}
- name: Install Playwright
run: pnpm exec playwright install --with-deps
- name: Store Playwright's Version
run: |
# Extract the version number using a more targeted regex pattern with awk
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --depth=0 | awk '/@playwright\/test/ {print $2}')
echo "Playwright's Version: $PLAYWRIGHT_VERSION"
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
- name: Cache Playwright Browsers for Playwright's Version
id: cache-playwright-browsers
uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
- name: Setup Playwright - Browsers and Dependencies
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
- name: Setup Playwright - Dependencies-only
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: E2E Tests
run: pnpm test:e2e ${{ matrix.suite }}
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e ${{ matrix.suite }}
env:
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
NEXT_TELEMETRY_DISABLED: 1
- uses: actions/upload-artifact@v4
if: always()
@@ -295,6 +322,13 @@ jobs:
if-no-files-found: ignore
retention-days: 1
# Disabled until this is fixed: https://github.com/daun/playwright-report-summary/issues/156
# - uses: daun/playwright-report-summary@v3
# with:
# report-file: results_${{ matrix.suite }}.json
# report-tag: ${{ matrix.suite }}
# job-summary: true
app-build-with-packed:
runs-on: ubuntu-latest
needs: build

View File

@@ -66,7 +66,7 @@ jobs:
translations
ui
templates
examples
examples(\/(\w|-)+)?
deps
# Disallow uppercase letters at the beginning of the subject

7
.vscode/launch.json vendored
View File

@@ -58,6 +58,13 @@
"PAYLOAD_PUBLIC_CLOUD_STORAGE_ADAPTER": "s3"
}
},
{
"command": "node --no-deprecation test/dev.js collections-graphql",
"cwd": "${workspaceFolder}",
"name": "Run Dev GraphQL",
"request": "launch",
"type": "node-terminal"
},
{
"command": "node --no-deprecation test/dev.js fields",
"cwd": "${workspaceFolder}",

View File

@@ -49,7 +49,8 @@ export default buildConfig({
{
label: 'Arabic',
code: 'ar',
// opt-in to setting default text-alignment on Input fields to rtl (right-to-left) when current locale is rtl
// opt-in to setting default text-alignment on Input fields to rtl (right-to-left)
// when current locale is rtl
rtl: true,
},
],
@@ -134,13 +135,9 @@ to support localization, you need to specify each field that you would like to l
```js
{
name: 'title',
type
:
'text',
// highlight-start
localized
:
true,
type: 'text',
// highlight-start
localized: true,
// highlight-end
}
```

View File

@@ -20,7 +20,8 @@ The initial request made to Payload will begin a new transaction and attach it t
```ts
const afterChange: CollectionAfterChangeHook = async ({ req }) => {
// because req.transactionID is assigned from Payload and passed through, my-slug will only persist if the entire request is successful
// because req.transactionID is assigned from Payload and passed through,
// my-slug will only persist if the entire request is successful
await req.payload.create({
req,
collection: 'my-slug',

View File

@@ -196,7 +196,8 @@ const Pages: CollectionConfig = {
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
// The HTMLConverter Feature is the feature which manages the HTML serializers. If you do not pass any arguments to it, it will use the default serializers.
// The HTMLConverter Feature is the feature which manages the HTML serializers.
// If you do not pass any arguments to it, it will use the default serializers.
HTMLConverterFeature({}),
],
}),

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"private": true,
"type": "module",
"scripts": {
@@ -107,7 +107,7 @@
"copyfiles": "2.4.1",
"cross-env": "7.0.3",
"dotenv": "8.6.0",
"drizzle-kit": "0.20.14-1f2c838",
"drizzle-kit": "0.20.18-08d50a4",
"drizzle-orm": "0.29.4",
"escape-html": "^1.0.3",
"eslint-plugin-payload": "workspace:*",
@@ -154,7 +154,7 @@
"tempy": "^1.0.1",
"ts-node": "10.9.1",
"tsx": "^4.7.1",
"turbo": "^1.13.2",
"turbo": "^1.13.3",
"typescript": "5.4.5",
"uuid": "^9.0.1",
"yocto-queue": "^1.0.0"

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -43,7 +43,7 @@
"dependencies": {
"@libsql/client": "^0.5.2",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.20.14-1f2c838",
"drizzle-kit": "0.20.18-08d50a4",
"drizzle-orm": "0.29.4",
"pg": "8.11.3",
"prompts": "2.4.2",

View File

@@ -3,13 +3,10 @@ import type { DrizzleSnapshotJSON } from 'drizzle-kit/payload'
import type { CreateMigration } from 'payload/database'
import fs from 'fs'
import { createRequire } from 'module'
import prompts from 'prompts'
import type { PostgresAdapter } from './types.js'
const require = createRequire(import.meta.url)
const migrationTemplate = (
upSQL?: string,
downSQL?: string,
@@ -45,12 +42,12 @@ const getDefaultDrizzleSnapshot = (): DrizzleSnapshotJSON => ({
schemas: {},
tables: {},
},
dialect: 'pg',
dialect: 'postgresql',
enums: {},
prevId: '00000000-0000-0000-0000-00000000000',
schemas: {},
tables: {},
version: '5',
version: '6',
})
export const createMigration: CreateMigration = async function createMigration(
@@ -62,7 +59,9 @@ export const createMigration: CreateMigration = async function createMigration(
fs.mkdirSync(dir)
}
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/payload')
const { generateDrizzleJson, generateMigration, upPgSnapshot } = await import(
'drizzle-kit/payload'
)
const [yyymmdd, hhmmss] = new Date().toISOString().split('T')
const formattedDate = yyymmdd.replace(/\D/g, '')
@@ -86,11 +85,15 @@ export const createMigration: CreateMigration = async function createMigration(
.reverse()?.[0]
if (latestSnapshot) {
const latestSnapshotJSON = JSON.parse(
const latestSnapshotJSON: DrizzleSnapshotJSON = JSON.parse(
fs.readFileSync(`${dir}/${latestSnapshot}`, 'utf8'),
) as DrizzleSnapshotJSON
drizzleJsonBefore = latestSnapshotJSON
if (latestSnapshotJSON.version < drizzleJsonBefore.version) {
drizzleJsonBefore = upPgSnapshot(latestSnapshotJSON)
} else {
drizzleJsonBefore = latestSnapshotJSON
}
}
const drizzleJsonAfter = generateDrizzleJson(this.schema)

View File

@@ -3,7 +3,6 @@ import type { Payload } from 'payload'
import type { Migration } from 'payload/database'
import type { PayloadRequestWithData } from 'payload/types'
import { createRequire } from 'module'
import {
commitTransaction,
initTransaction,
@@ -18,8 +17,6 @@ import { createMigrationTable } from './utilities/createMigrationTable.js'
import { migrationTableExists } from './utilities/migrationTableExists.js'
import { parseError } from './utilities/parseError.js'
const require = createRequire(import.meta.url)
export async function migrate(this: PostgresAdapter): Promise<void> {
const { payload } = this
const migrationFiles = await readMigrationFiles({ payload })
@@ -85,7 +82,7 @@ export async function migrate(this: PostgresAdapter): Promise<void> {
}
async function runMigrationFile(payload: Payload, migration: Migration, batch: number) {
const { generateDrizzleJson } = require('drizzle-kit/payload')
const { generateDrizzleJson } = await import('drizzle-kit/payload')
const start = Date.now()
const req = { payload } as PayloadRequestWithData

View File

@@ -1,12 +1,9 @@
import { eq } from 'drizzle-orm'
import { numeric, timestamp, varchar } from 'drizzle-orm/pg-core'
import { createRequire } from 'module'
import prompts from 'prompts'
import type { PostgresAdapter } from '../types.js'
const require = createRequire(import.meta.url)
/**
* Pushes the development schema to the database using Drizzle.
*
@@ -14,7 +11,7 @@ const require = createRequire(import.meta.url)
* @returns {Promise<void>} - A promise that resolves once the schema push is complete.
*/
export const pushDevSchema = async (db: PostgresAdapter) => {
const { pushSchema } = require('drizzle-kit/payload')
const { pushSchema } = await import('drizzle-kit/payload')
// This will prompt if clarifications are needed for Drizzle to push new schema
const { apply, hasDataLoss, statementsToExecute, warnings } = await pushSchema(

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",
@@ -29,14 +29,14 @@
},
"dependencies": {
"graphql-scalars": "1.22.2",
"pluralize": "8.0.0"
"pluralize": "8.0.0",
"ts-essentials": "7.0.3"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"@types/pluralize": "^0.0.33",
"graphql-http": "^1.22.0",
"payload": "workspace:*",
"ts-essentials": "7.0.3"
"payload": "workspace:*"
},
"peerDependencies": {
"graphql": "^16.8.1",

View File

@@ -28,6 +28,7 @@ export default function countResolver(collection: Collection): Resolver {
req = isolateObjectProperty(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = fallbackLocale
context.req = req
const options = {
collection,

View File

@@ -29,6 +29,7 @@ export default function getDeleteResolver<TSlug extends keyof GeneratedTypes['co
req = isolateObjectProperty(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
context.req = req
const options = {
id: args.id,

View File

@@ -29,6 +29,7 @@ export default function duplicateResolver<T extends keyof GeneratedTypes['collec
const fallbackLocale = req.fallbackLocale
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
context.req = req
const options = {
id: args.id,

View File

@@ -34,6 +34,7 @@ export default function findResolver(collection: Collection): Resolver {
req = isolateObjectProperty(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
context.req = req
const options = {
collection,

View File

@@ -31,6 +31,7 @@ export default function findByIDResolver<T extends keyof GeneratedTypes['collect
req = isolateObjectProperty(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
context.req = req
const options = {
id: args.id,

View File

@@ -29,6 +29,7 @@ export default function findVersionByIDResolver(collection: Collection): Resolve
req = isolateObjectProperty(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
context.req = req
const options = {
id: args.id,

View File

@@ -31,6 +31,7 @@ export default function findVersionsResolver(collection: Collection): Resolver {
req = isolateObjectProperty(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
context.req = req
const options = {
collection,

View File

@@ -32,6 +32,7 @@ export default function updateResolver<TSlug extends keyof GeneratedTypes['colle
req = isolateObjectProperty(req, 'fallbackLocale')
req.locale = args.locale || locale
req.fallbackLocale = args.fallbackLocale || fallbackLocale
context.req = req
const options = {
id: args.id,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -5,3 +5,4 @@ export { createPayloadRequest } from '../utilities/createPayloadRequest.js'
export { getNextRequestI18n } from '../utilities/getNextRequestI18n.js'
export { getPayloadHMR, reload } from '../utilities/getPayloadHMR.js'
export { headersWithCors } from '../utilities/headersWithCors.js'
export { initPage } from '../utilities/initPage/index.js'

View File

@@ -218,6 +218,8 @@ export const buildFormState = async ({ req }: { req: PayloadRequestWithData }) =
!req.payload.collections[collectionSlug].config.auth.disableLocalStrategy
) {
if (formState.password) result.password = formState.password
if (formState['confirm-password'])
result['confirm-password'] = formState['confirm-password']
if (formState.email) result.email = formState.email
}
}

View File

@@ -0,0 +1,59 @@
import type { Permissions } from 'payload/auth'
import type {
SanitizedCollectionConfig,
SanitizedConfig,
SanitizedGlobalConfig,
} from 'payload/types'
import { notFound } from 'next/navigation.js'
import { isAdminAuthRoute, isAdminRoute } from './shared.js'
export const handleAdminPage = ({
adminRoute,
config,
permissions,
route,
}: {
adminRoute: string
config: SanitizedConfig
permissions: Permissions
route: string
}) => {
if (isAdminRoute(route, adminRoute)) {
const routeSegments = route.replace(adminRoute, '').split('/').filter(Boolean)
const [entityType, entitySlug, createOrID] = routeSegments
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
const globalSlug = entityType === 'globals' ? entitySlug : undefined
const docID = collectionSlug && createOrID !== 'create' ? createOrID : undefined
let collectionConfig: SanitizedCollectionConfig | undefined
let globalConfig: SanitizedGlobalConfig | undefined
if (collectionSlug) {
collectionConfig = config.collections.find((collection) => collection.slug === collectionSlug)
if (!collectionConfig) {
notFound()
}
}
if (globalSlug) {
globalConfig = config.globals.find((global) => global.slug === globalSlug)
if (!globalConfig) {
notFound()
}
}
if (!permissions.canAccessAdmin && !isAdminAuthRoute(route, adminRoute)) {
notFound()
}
return {
collectionConfig,
docID,
globalConfig,
}
}
}

View File

@@ -0,0 +1,47 @@
import { redirect } from 'next/navigation.js'
import QueryString from 'qs'
import { isAdminAuthRoute, isAdminRoute } from './shared.js'
export const handleAuthRedirect = ({
adminRoute,
redirectUnauthenticatedUser,
route,
searchParams,
}: {
adminRoute: string
redirectUnauthenticatedUser: boolean | string
route: string
searchParams: { [key: string]: string | string[] }
}) => {
if (!isAdminAuthRoute(route, adminRoute)) {
if (searchParams && 'redirect' in searchParams) delete searchParams.redirect
const redirectRoute = encodeURIComponent(
route + Object.keys(searchParams ?? {}).length
? `${QueryString.stringify(searchParams, { addQueryPrefix: true })}`
: undefined,
)
const adminLoginRoute = `${adminRoute}/login`
const customLoginRoute =
typeof redirectUnauthenticatedUser === 'string' ? redirectUnauthenticatedUser : undefined
const loginRoute = isAdminRoute(route, adminRoute)
? adminLoginRoute
: customLoginRoute || '/login'
const parsedLoginRouteSearchParams = QueryString.parse(loginRoute.split('?')[1] ?? '')
const searchParamsWithRedirect = `${QueryString.stringify(
{
...parsedLoginRouteSearchParams,
...(redirectRoute ? { redirect: redirectRoute } : {}),
},
{ addQueryPrefix: true },
)}`
redirect(`${loginRoute.split('?')[0]}${searchParamsWithRedirect}`)
}
}

View File

@@ -1,39 +1,18 @@
import type {
InitPageResult,
PayloadRequestWithData,
SanitizedCollectionConfig,
SanitizedConfig,
SanitizedGlobalConfig,
VisibleEntities,
} from 'payload/types'
import type { InitPageResult, PayloadRequestWithData, VisibleEntities } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { findLocaleFromCode } from '@payloadcms/ui/utilities/findLocaleFromCode'
import { headers as getHeaders } from 'next/headers.js'
import { notFound, redirect } from 'next/navigation.js'
import { parseCookies } from 'payload/auth'
import { createLocalReq, isEntityHidden } from 'payload/utilities'
import qs from 'qs'
import { getPayloadHMR } from '../utilities/getPayloadHMR.js'
import { getRequestLanguage } from './getRequestLanguage.js'
import type { Args } from './types.js'
type Args = {
config: Promise<SanitizedConfig> | SanitizedConfig
redirectUnauthenticatedUser?: boolean
route: string
searchParams: { [key: string]: string | string[] | undefined }
}
const authRoutes = [
'/login',
'/logout',
'/create-first-user',
'/forgot',
'/reset',
'/verify',
'/logout-inactivity',
]
import { getPayloadHMR } from '../getPayloadHMR.js'
import { getRequestLanguage } from '../getRequestLanguage.js'
import { handleAdminPage } from './handleAdminPage.js'
import { handleAuthRedirect } from './handleAuthRedirect.js'
export const initPage = async ({
config: configPromise,
@@ -44,7 +23,14 @@ export const initPage = async ({
const headers = getHeaders()
const localeParam = searchParams?.locale as string
const payload = await getPayloadHMR({ config: configPromise })
const { collections, globals, localization, routes } = payload.config
const {
collections,
globals,
i18n: i18nConfig,
localization,
routes: { admin: adminRoute },
} = payload.config
const queryString = `${qs.stringify(searchParams ?? {}, { addQueryPrefix: true })}`
const defaultLocale =
@@ -55,7 +41,7 @@ export const initPage = async ({
const language = getRequestLanguage({ config: payload.config, cookies, headers })
const i18n = await initI18n({
config: payload.config.i18n,
config: i18nConfig,
context: 'client',
language,
})
@@ -81,58 +67,29 @@ export const initPage = async ({
req.user = user
const visibleEntities: VisibleEntities = {
collections: payload.config.collections
collections: collections
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
.filter(Boolean),
globals: payload.config.globals
globals: globals
.map(({ slug, admin: { hidden } }) => (!isEntityHidden({ hidden, user }) ? slug : null))
.filter(Boolean),
}
const {
routes: { admin: adminRoute },
} = payload.config
const routeSegments = route.replace(adminRoute, '').split('/').filter(Boolean)
const [entityType, entitySlug, createOrID] = routeSegments
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
const globalSlug = entityType === 'globals' ? entitySlug : undefined
const docID = collectionSlug && createOrID !== 'create' ? createOrID : undefined
const isAuthRoute = authRoutes.some((r) => r === route.replace(adminRoute, ''))
if (redirectUnauthenticatedUser && !user && !isAuthRoute) {
if (searchParams && 'redirect' in searchParams) delete searchParams.redirect
const stringifiedSearchParams = Object.keys(searchParams ?? {}).length
? `?${qs.stringify(searchParams)}`
: ''
redirect(`${routes.admin}/login?redirect=${route + stringifiedSearchParams}`)
if (redirectUnauthenticatedUser && !user) {
handleAuthRedirect({
adminRoute,
redirectUnauthenticatedUser,
route,
searchParams,
})
}
if (!permissions.canAccessAdmin && !isAuthRoute) {
notFound()
}
let collectionConfig: SanitizedCollectionConfig
let globalConfig: SanitizedGlobalConfig
if (collectionSlug) {
collectionConfig = collections.find((collection) => collection.slug === collectionSlug)
if (!collectionConfig) {
notFound()
}
}
if (globalSlug) {
globalConfig = globals.find((global) => global.slug === globalSlug)
if (!globalConfig) {
notFound()
}
}
const { collectionConfig, docID, globalConfig } = handleAdminPage({
adminRoute,
config: payload.config,
permissions,
route,
})
return {
collectionConfig,

View File

@@ -0,0 +1,17 @@
export const authRoutes = [
'/login',
'/logout',
'/create-first-user',
'/forgot',
'/reset',
'/verify',
'/logout-inactivity',
]
export const isAdminRoute = (route: string, adminRoute: string) => {
return route.startsWith(adminRoute)
}
export const isAdminAuthRoute = (route: string, adminRoute: string) => {
return authRoutes.some((r) => route.replace(adminRoute, '').startsWith(r))
}

View File

@@ -0,0 +1,22 @@
import type { SanitizedConfig } from 'payload/types'
export type Args = {
/**
* Your sanitized Payload config.
* If unresolved, this function will await the promise.
*/
config: Promise<SanitizedConfig> | SanitizedConfig
/**
* If true, redirects unauthenticated users to the admin login page.
* If a string is provided, the user will be redirected to that specific URL.
*/
redirectUnauthenticatedUser?: boolean | string
/**
* The current route, i.e. `/admin/collections/posts`.
*/
route: string
/**
* The search parameters of the current route provided to all pages in Next.js.
*/
searchParams: { [key: string]: string | string[] | undefined }
}

View File

@@ -7,7 +7,7 @@ import { DefaultTemplate } from '@payloadcms/ui/templates/Default'
import React, { Fragment } from 'react'
import { getNextRequestI18n } from '../../utilities/getNextRequestI18n.js'
import { initPage } from '../../utilities/initPage.js'
import { initPage } from '../../utilities/initPage/index.js'
import { NotFoundClient } from './index.client.js'
export const generatePageMetadata = async ({
@@ -46,10 +46,13 @@ export const NotFoundPage = async ({
[key: string]: string | string[]
}
}) => {
const config = await configPromise
const { routes: { admin: adminRoute } = {} } = config
const initPageResult = await initPage({
config: configPromise,
config,
redirectUnauthenticatedUser: true,
route: '/not-found',
route: `${adminRoute}/not-found`,
searchParams,
})

View File

@@ -0,0 +1,103 @@
'use client'
import type { FormState } from 'payload/types'
import { ConfirmPassword } from '@payloadcms/ui/fields/ConfirmPassword'
import { HiddenInput } from '@payloadcms/ui/fields/HiddenInput'
import { Password } from '@payloadcms/ui/fields/Password'
import { Form, useFormFields } from '@payloadcms/ui/forms/Form'
import { FormSubmit } from '@payloadcms/ui/forms/Submit'
import { useAuth } from '@payloadcms/ui/providers/Auth'
import { useConfig } from '@payloadcms/ui/providers/Config'
import { useTranslation } from '@payloadcms/ui/providers/Translation'
import { useRouter } from 'next/navigation.js'
import React from 'react'
import { toast } from 'react-toastify'
type Args = {
token: string
}
const initialState: FormState = {
'confirm-password': {
initialValue: '',
valid: false,
value: '',
},
password: {
initialValue: '',
valid: false,
value: '',
},
}
export const ResetPasswordClient: React.FC<Args> = ({ token }) => {
const i18n = useTranslation()
const {
admin: { user: userSlug },
routes: { admin, api },
serverURL,
} = useConfig()
const history = useRouter()
const { fetchFullUser } = useAuth()
const onSuccess = React.useCallback(
async (data) => {
if (data.token) {
await fetchFullUser()
history.push(`${admin}`)
} else {
history.push(`${admin}/login`)
toast.success(i18n.t('general:updatedSuccessfully'), { autoClose: 3000 })
}
},
[fetchFullUser, history, admin, i18n],
)
return (
<Form
action={`${serverURL}${api}/${userSlug}/reset-password`}
initialState={initialState}
method="POST"
onSuccess={onSuccess}
>
<PasswordToConfirm />
<ConfirmPassword />
<HiddenInput forceUsePathFromProps name="token" value={token} />
<FormSubmit>{i18n.t('authentication:resetPassword')}</FormSubmit>
</Form>
)
}
const PasswordToConfirm = () => {
const { t } = useTranslation()
const { value: confirmValue } = useFormFields(([fields]) => {
return fields['confirm-password']
})
const validate = React.useCallback(
(value: string) => {
if (!value) {
return t('validation:required')
}
if (value === confirmValue) {
return true
}
return t('fields:passwordsDoNotMatch')
},
[confirmValue, t],
)
return (
<Password
autoComplete="off"
label={t('authentication:newPassword')}
name="password"
required
validate={validate}
/>
)
}

View File

@@ -1,15 +1,5 @@
.reset-password {
display: flex;
align-items: center;
flex-wrap: wrap;
min-height: 100vh;
&__wrap {
margin: 0 auto var(--base);
width: 100%;
svg {
width: 100%;
}
form > .field-type {
margin-bottom: var(--base);
}
}

View File

@@ -2,15 +2,11 @@ import type { AdminViewProps } from 'payload/types'
import { Button } from '@payloadcms/ui/elements/Button'
import { Translation } from '@payloadcms/ui/elements/Translation'
import { ConfirmPassword } from '@payloadcms/ui/fields/ConfirmPassword'
import { HiddenInput } from '@payloadcms/ui/fields/HiddenInput'
import { Password } from '@payloadcms/ui/fields/Password'
import { Form } from '@payloadcms/ui/forms/Form'
import { FormSubmit } from '@payloadcms/ui/forms/Submit'
import { MinimalTemplate } from '@payloadcms/ui/templates/Minimal'
import LinkImport from 'next/link.js'
import React from 'react'
import { ResetPasswordClient } from './index.client.js'
import './index.scss'
export const resetPasswordBaseClass = 'reset-password'
@@ -22,7 +18,9 @@ export { generateResetPasswordMetadata } from './meta.js'
export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params }) => {
const { req } = initPageResult
const { token } = params
const {
segments: [_, token],
} = params
const {
i18n,
@@ -31,21 +29,9 @@ export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params
} = req
const {
admin: { user: userSlug },
routes: { admin, api },
serverURL,
routes: { admin },
} = config
// const onSuccess = async (data) => {
// if (data.token) {
// await fetchFullUser()
// history.push(`${admin}`)
// } else {
// history.push(`${admin}/login`)
// toast.success(i18n.t('general:updatedSuccessfully'), { autoClose: 3000 })
// }
// }
if (user) {
return (
<MinimalTemplate className={resetPasswordBaseClass}>
@@ -73,22 +59,7 @@ export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params
<MinimalTemplate className={resetPasswordBaseClass}>
<div className={`${resetPasswordBaseClass}__wrap`}>
<h1>{i18n.t('authentication:resetPassword')}</h1>
<Form
action={`${serverURL}${api}/${userSlug}/reset-password`}
method="POST"
// onSuccess={onSuccess}
redirect={admin}
>
<Password
autoComplete="off"
label={i18n.t('authentication:newPassword')}
name="password"
required
/>
<ConfirmPassword />
<HiddenInput forceUsePathFromProps name="token" value={token} />
<FormSubmit>{i18n.t('authentication:resetPassword')}</FormSubmit>
</Form>
<ResetPasswordClient token={token} />
</div>
</MinimalTemplate>
)

View File

@@ -1,7 +1,7 @@
import type { SanitizedConfig } from 'payload/config'
import type { AdminViewComponent } from 'payload/types'
import type { initPage } from '../../utilities/initPage.js'
import type { initPage } from '../../utilities/initPage/index.js'
import { Account } from '../Account/index.js'
import { CreateFirstUserView } from '../CreateFirstUser/index.js'

View File

@@ -7,7 +7,7 @@ import { MinimalTemplate } from '@payloadcms/ui/templates/Minimal'
import { notFound, redirect } from 'next/navigation.js'
import React, { Fragment } from 'react'
import { initPage } from '../../utilities/initPage.js'
import { initPage } from '../../utilities/initPage/index.js'
import { getViewFromConfig } from './getViewFromConfig.js'
export { generatePageMetadata } from './meta.js'

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",
@@ -115,6 +115,7 @@
"sanitize-filename": "1.6.3",
"scheduler": "0.23.0",
"scmp": "2.1.0",
"ts-essentials": "7.0.3",
"uuid": "^9.0.1"
},
"devDependencies": {
@@ -158,8 +159,7 @@
"passport-strategy": "1.0.0",
"rimraf": "3.0.2",
"serve-static": "1.15.0",
"sharp": "0.32.6",
"ts-essentials": "7.0.3"
"sharp": "0.32.6"
},
"peerDependencies": {
"@swc/core": "^1.4.13",

View File

@@ -141,6 +141,10 @@ export const loginOperation = async <TSlug extends keyof GeneratedTypes['collect
user,
})
// /////////////////////////////////////
// beforeLogin - Collection
// /////////////////////////////////////
await collectionConfig.hooks.beforeLogin.reduce(async (priorHook, hook) => {
await priorHook

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "The official cloud storage plugin for Payload CMS",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1 +1 @@
export { cloudStorage } from './plugin.js'
export { cloudStoragePlugin } from './plugin.js'

View File

@@ -15,7 +15,7 @@ import { getBeforeChangeHook } from './hooks/beforeChange.js'
// Optionally, the adapter can specify any Webpack config overrides if they are necessary.
export const cloudStorage =
export const cloudStoragePlugin =
(pluginOptions: PluginOptions) =>
(incomingConfig: Config): Config => {
const { collections: allCollectionOptions, enabled } = pluginOptions

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,3 +1,3 @@
export { payloadCloud } from './plugin.js'
export { payloadCloudPlugin } from './plugin.js'
export { createKey } from './utilities/createKey.js'
export { getStorageClient } from './utilities/getStorageClient.js'

View File

@@ -4,7 +4,7 @@ import type { Payload } from 'payload'
import nodemailer from 'nodemailer'
import { defaults } from 'payload/config'
import { payloadCloud } from './plugin.js'
import { payloadCloudPlugin } from './plugin.js'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
const mockedPayload: Payload = jest.fn() as unknown as Payload
@@ -34,7 +34,7 @@ describe('plugin', () => {
describe('not in Payload Cloud', () => {
// eslint-disable-next-line jest/expect-expect
it('should return unmodified config', async () => {
const plugin = payloadCloud()
const plugin = payloadCloudPlugin()
const config = await plugin(createConfig())
assertNoCloudStorage(config)
@@ -52,7 +52,7 @@ describe('plugin', () => {
describe('storage', () => {
// eslint-disable-next-line jest/expect-expect
it('should default to using payload cloud storage', async () => {
const plugin = payloadCloud()
const plugin = payloadCloudPlugin()
const config = await plugin(createConfig())
assertCloudStorage(config)
@@ -60,7 +60,7 @@ describe('plugin', () => {
// eslint-disable-next-line jest/expect-expect
it('should allow opt-out', async () => {
const plugin = payloadCloud({ storage: false })
const plugin = payloadCloudPlugin({ storage: false })
const config = await plugin(createConfig())
assertNoCloudStorage(config)
@@ -70,7 +70,7 @@ describe('plugin', () => {
describe('email', () => {
// eslint-disable-next-line jest/expect-expect
it('should default to using payload cloud email', async () => {
const plugin = payloadCloud()
const plugin = payloadCloudPlugin()
const config = await plugin(createConfig())
expect(createTransportSpy).toHaveBeenCalledWith(
@@ -82,7 +82,7 @@ describe('plugin', () => {
// eslint-disable-next-line jest/expect-expect
it('should allow opt-out', async () => {
const plugin = payloadCloud({ email: false })
const plugin = payloadCloudPlugin({ email: false })
const config = await plugin(createConfig())
expect(config.email).toBeUndefined()
@@ -93,7 +93,7 @@ describe('plugin', () => {
delete process.env.PAYLOAD_CLOUD_EMAIL_API_KEY
delete process.env.PAYLOAD_CLOUD_DEFAULT_DOMAIN
const plugin = payloadCloud()
const plugin = payloadCloudPlugin()
const config = await plugin(createConfig())
expect(config.email).toBeUndefined()
@@ -120,7 +120,7 @@ describe('plugin', () => {
}),
})
const plugin = payloadCloud()
const plugin = payloadCloudPlugin()
const config = await plugin(configWithTransport)
expect(logSpy).toHaveBeenCalledWith(
@@ -141,7 +141,7 @@ describe('plugin', () => {
}),
})
const plugin = payloadCloud()
const plugin = payloadCloudPlugin()
const config = await plugin(configWithPartialEmail)
const emailConfig = config.email as Awaited<ReturnType<typeof nodemailerAdapter>>

View File

@@ -11,7 +11,7 @@ import {
} from './hooks/uploadCache.js'
import { getStaticHandler } from './staticHandler.js'
export const payloadCloud =
export const payloadCloudPlugin =
(pluginOptions?: PluginOptions) =>
async (incomingConfig: Config): Promise<Config> => {
let config = { ...incomingConfig }

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-form-builder",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "Form builder plugin for Payload CMS",
"keywords": [
"payload",

View File

@@ -8,7 +8,7 @@ import { generateFormCollection } from './collections/Forms/index.js'
export { fields } from './collections/Forms/fields.js'
export { getPaymentTotal } from './utilities/getPaymentTotal.js'
const FormBuilder =
export const formBuilderPlugin =
(incomingFormConfig: PluginConfig) =>
(config: Config): Config => {
const formConfig: PluginConfig = {
@@ -51,5 +51,3 @@ const FormBuilder =
],
}
}
export default FormBuilder

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "The official Nested Docs plugin for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -12,7 +12,7 @@ import { populateBreadcrumbs } from './utilities/populateBreadcrumbs.js'
export { createBreadcrumbsField, createParentField }
export const nestedDocs =
export const nestedDocsPlugin =
(pluginConfig: PluginConfig): Plugin =>
(config) => ({
...config,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "Redirects plugin for Payload",
"keywords": [
"payload",

View File

@@ -4,7 +4,7 @@ import type { PluginConfig } from './types.js'
import deepMerge from './deepMerge.js'
const redirects =
export const redirectsPlugin =
(pluginConfig: PluginConfig) =>
(incomingConfig: Config): Config => ({
...incomingConfig,
@@ -78,5 +78,3 @@ const redirects =
),
],
})
export { redirects }

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "Search plugin for Payload",
"keywords": [
"payload",

View File

@@ -6,7 +6,7 @@ import deleteFromSearch from './Search/hooks/deleteFromSearch.js'
import syncWithSearch from './Search/hooks/syncWithSearch.js'
import { generateSearchCollection } from './Search/index.js'
const Search =
export const searchPlugin =
(incomingSearchConfig: SearchConfig) =>
(config: Config): Config => {
const { collections } = config
@@ -61,5 +61,3 @@ const Search =
return config
}
export default Search

View File

@@ -1,2 +1,2 @@
export { sentry } from './plugin'
export type { PluginOptions } from './types'
export { sentryPlugin } from './plugin.js'
export type { PluginOptions } from './types.js'

View File

@@ -1,32 +1,32 @@
import type { Config } from 'payload/config'
import { defaults } from 'payload/config'
import { sentry } from './plugin'
import { sentryPlugin } from './plugin'
describe('plugin', () => {
it('should run the plugin', () => {
const plugin = sentry({ enabled: true, dsn: 'asdf' })
const plugin = sentryPlugin({ enabled: true, dsn: 'asdf' })
const config = plugin(createConfig())
assertPluginRan(config)
})
it('should default enable: true', () => {
const plugin = sentry({ dsn: 'asdf' })
const plugin = sentryPlugin({ dsn: 'asdf' })
const config = plugin(createConfig())
assertPluginRan(config)
})
it('should not run if dsn is not provided', () => {
const plugin = sentry({ enabled: true, dsn: null })
const plugin = sentryPlugin({ enabled: true, dsn: null })
const config = plugin(createConfig())
assertPluginDidNotRun(config)
})
it('should respect enabled: false', () => {
const plugin = sentry({ enabled: false, dsn: null })
const plugin = sentryPlugin({ enabled: false, dsn: null })
const config = plugin(createConfig())
assertPluginDidNotRun(config)

View File

@@ -6,7 +6,7 @@ import type { PluginOptions } from './types.js'
import { captureException } from './captureException.js'
import { startSentry } from './startSentry.js'
export const sentry =
export const sentryPlugin =
(pluginOptions: PluginOptions) =>
(incomingConfig: Config): Config => {
const config = { ...incomingConfig }

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "SEO plugin for Payload",
"keywords": [
"payload",

View File

@@ -20,7 +20,7 @@ import { translations } from './translations/index.js'
import { Overview } from './ui/Overview.js'
import { Preview } from './ui/Preview.js'
const seo =
export const seoPlugin =
(pluginConfig: PluginConfig) =>
(config: Config): Config => {
const seoFields: GroupField[] = [
@@ -297,5 +297,3 @@ const seo =
},
}
}
export { seo }

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-stripe",
"version": "0.0.16",
"version": "3.0.0-beta.23",
"description": "Stripe plugin for Payload",
"keywords": [
"payload",
@@ -24,8 +24,8 @@
"exports": {
".": {
"import": "./src/index.ts",
"require": "./src/index.ts",
"types": "./src/index.ts"
"types": "./src/index.ts",
"default": "./src/index.ts"
}
},
"main": "./src/index.ts",
@@ -36,12 +36,14 @@
"types.d.ts"
],
"scripts": {
"build": "echo \"Build temporarily disabled.\" && exit 0",
"build": "pnpm copyfiles && 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}",
"prepublishOnly": "pnpm clean && pnpm turbo run build && pnpm test",
"test": "echo 'No tests available.'"
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
"lint": "eslint src",
"lint:fix": "eslint --fix --ext .ts,.tsx src",
"prepublishOnly": "pnpm clean && pnpm turbo build"
},
"dependencies": {
"@payloadcms/ui": "workspace:*",
@@ -51,17 +53,19 @@
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",
"@payloadcms/next": "workspace:*",
"@payloadcms/translations": "workspace:*",
"@payloadcms/ui": "workspace:*",
"@types/express": "^4.17.9",
"@types/lodash.get": "^4.4.7",
"@types/react": "18.2.74",
"@types/uuid": "^9.0.0",
"payload": "workspace:*",
"prettier": "^2.7.1",
"webpack": "^5.78.0"
"payload": "workspace:*"
},
"peerDependencies": {
"payload": "workspace:*",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
"@payloadcms/translations": "workspace:*",
"@payloadcms/ui": "workspace:*",
"payload": "workspace:*"
},
"publishConfig": {
"exports": {
@@ -72,6 +76,7 @@
}
},
"main": "./dist/index.js",
"registry": "https://registry.npmjs.org/",
"types": "./dist/index.d.ts"
},
"homepage:": "https://payloadcms.com"

View File

@@ -1,10 +1,10 @@
import type { Config } from 'payload/config'
import type { SanitizedStripeConfig, StripeConfig } from './types'
import type { SanitizedStripeConfig, StripeConfig } from './types.js'
import { getFields } from './fields/getFields'
import { getFields } from './fields/getFields.js'
const stripePlugin =
export const stripePlugin =
(incomingStripeConfig: StripeConfig) =>
(config: Config): Config => {
const { collections } = config
@@ -42,5 +42,3 @@ const stripePlugin =
}),
}
}
export default stripePlugin

View File

@@ -1,8 +1,8 @@
import type { CollectionConfig, Field } from 'payload/types'
import type { SanitizedStripeConfig } from '../types'
import type { SanitizedStripeConfig } from '../types.js'
import { LinkToDoc } from '../ui/LinkToDoc'
import { LinkToDoc } from '../ui/LinkToDoc.js'
interface Args {
collection: CollectionConfig
@@ -39,13 +39,12 @@ export const getFields = ({ collection, stripeConfig, syncConfig }: Args): Field
type: 'ui',
admin: {
components: {
Field: (args) =>
LinkToDoc({
...args,
isTestKey: stripeConfig.isTestKey,
nameOfIDField: 'stripeID',
stripeResourceType: syncConfig.stripeResourceType,
}),
Field: LinkToDoc,
},
custom: {
isTestKey: stripeConfig.isTestKey,
nameOfIDField: 'stripeID',
stripeResourceType: syncConfig.stripeResourceType,
},
position: 'sidebar',
},

View File

@@ -3,11 +3,12 @@ import type { CollectionBeforeValidateHook, CollectionConfig } from 'payload/typ
import { APIError } from 'payload/errors'
import Stripe from 'stripe'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
import { deepen } from '../utilities/deepen'
import { deepen } from '../utilities/deepen.js'
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
// api version can only be the latest, stripe recommends ts ignoring it
const stripe = new Stripe(stripeSecretKey || '', { apiVersion: '2022-08-01' })
type HookArgsWithCustomCollection = Omit<
@@ -52,12 +53,15 @@ export const createNewInStripe: CollectionBeforeValidateHookWithArgs = async (ar
if (syncConfig) {
// combine all fields of this object and match their respective values within the document
let syncedFields = syncConfig.fields.reduce((acc, field) => {
const { fieldPath, stripeProperty } = field
let syncedFields = syncConfig.fields.reduce(
(acc, field) => {
const { fieldPath, stripeProperty } = field
acc[stripeProperty] = dataRef[fieldPath]
return acc
}, {} as Record<string, any>)
acc[stripeProperty] = dataRef[fieldPath]
return acc
},
{} as Record<string, any>,
)
syncedFields = deepen(syncedFields)
@@ -72,6 +76,7 @@ export const createNewInStripe: CollectionBeforeValidateHookWithArgs = async (ar
try {
// NOTE: Typed as "any" because the "create" method is not standard across all Stripe resources
const stripeResource = await stripe?.[syncConfig.stripeResourceType]?.create(
// @ts-expect-error
syncedFields,
)
@@ -105,6 +110,7 @@ export const createNewInStripe: CollectionBeforeValidateHookWithArgs = async (ar
// NOTE: Typed as "any" because the "create" method is not standard across all Stripe resources
const stripeResource = await stripe?.[syncConfig.stripeResourceType]?.create(
// @ts-expect-error
syncedFields,
)

View File

@@ -3,9 +3,10 @@ import type { CollectionAfterDeleteHook, CollectionConfig } from 'payload/types'
import { APIError } from 'payload/errors'
import Stripe from 'stripe'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
// api version can only be the latest, stripe recommends ts ignoring it
const stripe = new Stripe(stripeSecretKey || '', { apiVersion: '2022-08-01' })
type HookArgsWithCustomCollection = Omit<Parameters<CollectionAfterDeleteHook>[0], 'collection'> & {

View File

@@ -3,11 +3,12 @@ import type { CollectionBeforeChangeHook, CollectionConfig } from 'payload/types
import { APIError } from 'payload/errors'
import Stripe from 'stripe'
import type { StripeConfig } from '../types'
import type { StripeConfig } from '../types.js'
import { deepen } from '../utilities/deepen'
import { deepen } from '../utilities/deepen.js'
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
// api version can only be the latest, stripe recommends ts ignoring it
const stripe = new Stripe(stripeSecretKey || '', { apiVersion: '2022-08-01' })
type HookArgsWithCustomCollection = Omit<
@@ -39,12 +40,15 @@ export const syncExistingWithStripe: CollectionBeforeChangeHookWithArgs = async
if (syncConfig) {
if (operation === 'update') {
// combine all fields of this object and match their respective values within the document
let syncedFields = syncConfig.fields.reduce((acc, field) => {
const { fieldPath, stripeProperty } = field
let syncedFields = syncConfig.fields.reduce(
(acc, field) => {
const { fieldPath, stripeProperty } = field
acc[stripeProperty] = data[fieldPath]
return acc
}, {} as Record<string, any>)
acc[stripeProperty] = data[fieldPath]
return acc
},
{} as Record<string, any>,
)
syncedFields = deepen(syncedFields)

View File

@@ -9,7 +9,10 @@ import { syncExistingWithStripe } from './hooks/syncExistingWithStripe.js'
import { stripeREST } from './routes/rest.js'
import { stripeWebhooks } from './routes/webhooks.js'
const stripePlugin =
export { LinkToDoc } from './ui/LinkToDoc.js'
export { stripeProxy } from './utilities/stripeProxy.js'
export const stripePlugin =
(incomingStripeConfig: StripeConfig) =>
(config: Config): Config => {
const { collections } = config
@@ -112,5 +115,3 @@ const stripePlugin =
endpoints,
}
}
export default stripePlugin

View File

@@ -1,9 +0,0 @@
export const createNewInStripe = () => null
export const deleteFromStripe = () => null
export const stripeREST = () => null
export const stripeWebhooks = () => null
export const syncExistingWithStripe = () => null
export default {
raw: () => {}, // mock express fn
}

View File

@@ -33,7 +33,9 @@ export const stripeREST = async (args: {
}
responseJSON = await stripeProxy({
// @ts-expect-error
stripeArgs,
// @ts-expect-error
stripeMethod,
stripeSecretKey,
})

View File

@@ -19,6 +19,7 @@ export const stripeWebhooks = async (args: {
if (stripeWebhooksEndpointSecret) {
const stripe = new Stripe(stripeSecretKey, {
// api version can only be the latest, stripe recommends ts ignoring it
apiVersion: '2022-08-01',
appInfo: {
name: 'Stripe Payload Plugin',
@@ -26,17 +27,14 @@ export const stripeWebhooks = async (args: {
},
})
const body = await req.text()
const stripeSignature = req.headers.get('stripe-signature')
if (stripeSignature) {
let event: Stripe.Event | undefined
try {
event = stripe.webhooks.constructEvent(
await req.text(),
stripeSignature,
stripeWebhooksEndpointSecret,
)
event = stripe.webhooks.constructEvent(body, stripeSignature, stripeWebhooksEndpointSecret)
} catch (err: unknown) {
const msg: string = err instanceof Error ? err.message : JSON.stringify(err)
req.payload.logger.error(`Error constructing Stripe event: ${msg}`)

View File

@@ -1,17 +1,15 @@
'use client'
import type { CustomComponent } from 'payload/config'
import type { UIField } from 'payload/types'
import { useFieldProps } from '@payloadcms/ui/forms/FieldPropsProvider'
// import CopyToClipboard from 'payload/dist/admin/components/elements/CopyToClipboard'
import { useFormFields } from '@payloadcms/ui/forms/Form'
import React from 'react'
export const LinkToDoc: React.FC<
UIField & {
isTestKey: boolean
nameOfIDField: string
stripeResourceType: string
}
> = (props) => {
const { isTestKey, nameOfIDField, stripeResourceType } = props
export const LinkToDoc: CustomComponent<UIField> = () => {
const { custom } = useFieldProps()
const { isTestKey, nameOfIDField, stripeResourceType } = custom
const field = useFormFields(([fields]) => fields[nameOfIDField])
const { value: stripeID } = field || {}

View File

@@ -1,7 +1,7 @@
import lodashGet from 'lodash.get'
import Stripe from 'stripe'
import type { StripeProxy } from '../types'
import type { StripeProxy } from '../types.js'
export const stripeProxy: StripeProxy = async ({ stripeArgs, stripeMethod, stripeSecretKey }) => {
const stripe = new Stripe(stripeSecretKey, {

View File

@@ -1,8 +1,8 @@
import { v4 as uuid } from 'uuid'
import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types'
import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types.js'
import { deepen } from '../utilities/deepen'
import { deepen } from '../utilities/deepen.js'
type HandleCreatedOrUpdated = (
args: Parameters<StripeWebhookHandler>[0] & {
@@ -62,12 +62,15 @@ export const handleCreatedOrUpdated: HandleCreatedOrUpdated = async (args) => {
const foundDoc = payloadQuery.docs[0] as any
// combine all properties of the Stripe doc and match their respective fields within the document
let syncedData = syncConfig.fields.reduce((acc, field) => {
const { fieldPath, stripeProperty } = field
let syncedData = syncConfig.fields.reduce(
(acc, field) => {
const { fieldPath, stripeProperty } = field
acc[fieldPath] = stripeDoc[stripeProperty]
return acc
}, {} as Record<string, any>)
acc[fieldPath] = stripeDoc[stripeProperty]
return acc
},
{} as Record<string, any>,
)
syncedData = deepen({
...syncedData,

View File

@@ -1,4 +1,4 @@
import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types'
import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types.js'
type HandleDeleted = (
args: Parameters<StripeWebhookHandler>[0] & {
@@ -58,6 +58,7 @@ export const handleDeleted: HandleDeleted = async (args) => {
if (logs) payload.logger.info(`- Deleting Payload document with ID: '${foundDoc.id}'...`)
try {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
payload.delete({
id: foundDoc.id,
collection: collectionSlug,

View File

@@ -1,9 +1,9 @@
import type { StripeWebhookHandler } from '../types'
import type { StripeWebhookHandler } from '../types.js'
import { handleCreatedOrUpdated } from './handleCreatedOrUpdated'
import { handleDeleted } from './handleDeleted'
import { handleCreatedOrUpdated } from './handleCreatedOrUpdated.js'
import { handleDeleted } from './handleDeleted.js'
export const handleWebhooks: StripeWebhookHandler = async (args) => {
export const handleWebhooks: StripeWebhookHandler = (args) => {
const { event, payload, stripeConfig } = args
if (stripeConfig?.logs)
@@ -21,7 +21,7 @@ export const handleWebhooks: StripeWebhookHandler = async (args) => {
if (syncConfig) {
switch (method) {
case 'created': {
await handleCreatedOrUpdated({
void handleCreatedOrUpdated({
...args,
resourceType,
stripeConfig,
@@ -30,7 +30,7 @@ export const handleWebhooks: StripeWebhookHandler = async (args) => {
break
}
case 'updated': {
await handleCreatedOrUpdated({
void handleCreatedOrUpdated({
...args,
resourceType,
stripeConfig,
@@ -39,7 +39,7 @@ export const handleWebhooks: StripeWebhookHandler = async (args) => {
break
}
case 'deleted': {
await handleDeleted({
void handleDeleted({
...args,
resourceType,
stripeConfig,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "3.0.0-beta.21",
"version": "3.0.0-beta.23",
"description": "The officially supported Lexical richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -2,5 +2,5 @@ export { RichTextCell } from '../cell/index.js'
export { RichTextField } from '../field/index.js'
export { defaultEditorLexicalConfig } from '../field/lexical/config/client/default.js'
export { ToolbarButton } from '../field/lexical/plugins/FloatingSelectToolbar/ToolbarButton/index.js'
export { ToolbarDropdown } from '../field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/index.js'
export { ToolbarButton } from '../field/lexical/plugins/toolbars/inline/ToolbarButton/index.js'
export { ToolbarDropdown } from '../field/lexical/plugins/toolbars/inline/ToolbarDropdown/index.js'

View File

@@ -9,58 +9,52 @@ import { AlignJustifyIcon } from '../../lexical/ui/icons/AlignJustify/index.js'
import { AlignLeftIcon } from '../../lexical/ui/icons/AlignLeft/index.js'
import { AlignRightIcon } from '../../lexical/ui/icons/AlignRight/index.js'
import { createClientComponent } from '../createClientComponent.js'
import { AlignDropdownSectionWithEntries } from './floatingSelectToolbarAlignDropdownSection.js'
import { alignGroupWithItems } from './inlineToolbarAlignGroup.js'
const AlignFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
return {
clientFeatureProps: props,
feature: () => ({
clientFeatureProps: props,
floatingSelectToolbar: {
sections: [
AlignDropdownSectionWithEntries([
toolbarInline: {
groups: [
alignGroupWithItems([
{
ChildComponent: AlignLeftIcon,
isActive: () => false,
key: 'align-left',
key: 'alignLeft',
label: `Align Left`,
onClick: ({ editor }) => {
onSelect: ({ editor }) => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'left')
},
order: 1,
},
]),
AlignDropdownSectionWithEntries([
{
ChildComponent: AlignCenterIcon,
isActive: () => false,
key: 'align-center',
key: 'alignCenter',
label: `Align Center`,
onClick: ({ editor }) => {
onSelect: ({ editor }) => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'center')
},
order: 2,
},
]),
AlignDropdownSectionWithEntries([
{
ChildComponent: AlignRightIcon,
isActive: () => false,
key: 'align-right',
key: 'alignRight',
label: `Align Right`,
onClick: ({ editor }) => {
onSelect: ({ editor }) => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right')
},
order: 3,
},
]),
AlignDropdownSectionWithEntries([
{
ChildComponent: AlignJustifyIcon,
isActive: () => false,
key: 'align-justify',
key: 'alignJustify',
label: `Align Justify`,
onClick: ({ editor }) => {
onSelect: ({ editor }) => {
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'justify')
},
order: 4,

View File

@@ -1,18 +0,0 @@
import type {
FloatingToolbarSection,
FloatingToolbarSectionEntry,
} from '../../lexical/plugins/FloatingSelectToolbar/types.js'
import { AlignLeftIcon } from '../../lexical/ui/icons/AlignLeft/index.js'
export const AlignDropdownSectionWithEntries = (
entries: FloatingToolbarSectionEntry[],
): FloatingToolbarSection => {
return {
type: 'dropdown',
ChildComponent: AlignLeftIcon,
entries,
key: 'dropdown-align',
order: 2,
}
}

View File

@@ -0,0 +1,16 @@
import type {
InlineToolbarGroup,
InlineToolbarGroupItem,
} from '../../lexical/plugins/toolbars/inline/types.js'
import { AlignLeftIcon } from '../../lexical/ui/icons/AlignLeft/index.js'
export const alignGroupWithItems = (items: InlineToolbarGroupItem[]): InlineToolbarGroup => {
return {
type: 'dropdown',
ChildComponent: AlignLeftIcon,
items,
key: 'align',
order: 2,
}
}

View File

@@ -6,10 +6,9 @@ import { $getSelection } from 'lexical'
import type { FeatureProviderProviderClient } from '../types.js'
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
import { BlockquoteIcon } from '../../lexical/ui/icons/Blockquote/index.js'
import { TextDropdownSectionWithEntries } from '../common/floatingSelectToolbarTextDropdownSection/index.js'
import { createClientComponent } from '../createClientComponent.js'
import { inlineToolbarTextDropdownGroupWithItems } from '../shared/inlineToolbar/textDropdownGroup.js'
import { MarkdownTransformer } from './markdownTransformer.js'
const BlockQuoteFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
@@ -17,15 +16,40 @@ const BlockQuoteFeatureClient: FeatureProviderProviderClient<undefined> = (props
clientFeatureProps: props,
feature: () => ({
clientFeatureProps: props,
floatingSelectToolbar: {
sections: [
TextDropdownSectionWithEntries([
markdownTransformers: [MarkdownTransformer],
nodes: [QuoteNode],
slashMenu: {
groups: [
{
displayName: 'Basic',
items: [
{
Icon: BlockquoteIcon,
displayName: 'Blockquote',
key: 'blockquote',
keywords: ['quote', 'blockquote'],
onSelect: ({ editor }) => {
editor.update(() => {
const selection = $getSelection()
$setBlocksType(selection, () => $createQuoteNode())
})
},
},
],
key: 'basic',
},
],
},
toolbarInline: {
groups: [
inlineToolbarTextDropdownGroupWithItems([
{
ChildComponent: BlockquoteIcon,
isActive: () => false,
key: 'blockquote',
label: `Blockquote`,
onClick: ({ editor }) => {
onSelect: ({ editor }) => {
editor.update(() => {
const selection = $getSelection()
$setBlocksType(selection, () => $createQuoteNode())
@@ -36,28 +60,6 @@ const BlockQuoteFeatureClient: FeatureProviderProviderClient<undefined> = (props
]),
],
},
markdownTransformers: [MarkdownTransformer],
nodes: [QuoteNode],
slashMenu: {
options: [
{
displayName: 'Basic',
key: 'basic',
options: [
new SlashMenuOption(`blockquote`, {
Icon: BlockquoteIcon,
displayName: `Blockquote`,
keywords: ['quote', 'blockquote'],
onSelect: () => {
const selection = $getSelection()
$setBlocksType(selection, () => $createQuoteNode())
},
}),
],
},
],
},
}),
}
}

View File

@@ -6,7 +6,6 @@ import { getTranslation } from '@payloadcms/translations'
import type { FeatureProviderProviderClient } from '../types.js'
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js'
import { BlockIcon } from '../../lexical/ui/icons/Block/index.js'
import { createClientComponent } from '../createClientComponent.js'
import { BlockNode } from './nodes/BlocksNode.js'
@@ -30,32 +29,31 @@ const BlocksFeatureClient: FeatureProviderProviderClient<BlocksFeatureClientProp
},
],
slashMenu: {
options: [
groups: [
{
displayName: 'Blocks',
key: 'blocks',
options: [
...props.reducedBlocks.map((block) => {
return new SlashMenuOption('block-' + block.slug, {
Icon: BlockIcon,
displayName: ({ i18n }) => {
if (!block.labels.singular) {
return block.slug
}
items: props.reducedBlocks.map((block) => {
return {
Icon: BlockIcon,
displayName: ({ i18n }) => {
if (!block.labels.singular) {
return block.slug
}
return getTranslation(block.labels.singular, i18n)
},
keywords: ['block', 'blocks', block.slug],
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_BLOCK_COMMAND, {
id: null,
blockName: '',
blockType: block.slug,
})
},
})
}),
],
return getTranslation(block.labels.singular, i18n)
},
key: 'block-' + block.slug,
keywords: ['block', 'blocks', block.slug],
onSelect: ({ editor }) => {
editor.dispatchCommand(INSERT_BLOCK_COMMAND, {
id: null,
blockName: '',
blockType: block.slug,
})
},
}
}),
key: 'blocks',
},
],
},

View File

@@ -1,15 +0,0 @@
import type {
FloatingToolbarSection,
FloatingToolbarSectionEntry,
} from '../../../lexical/plugins/FloatingSelectToolbar/types.js'
export const FeaturesSectionWithEntries = (
entries: FloatingToolbarSectionEntry[],
): FloatingToolbarSection => {
return {
type: 'buttons',
entries,
key: 'features',
order: 5,
}
}

View File

@@ -1,18 +0,0 @@
import type {
FloatingToolbarSection,
FloatingToolbarSectionEntry,
} from '../../../lexical/plugins/FloatingSelectToolbar/types.js'
import { TextIcon } from '../../../lexical/ui/icons/Text/index.js'
export const TextDropdownSectionWithEntries = (
entries: FloatingToolbarSectionEntry[],
): FloatingToolbarSection => {
return {
type: 'dropdown',
ChildComponent: TextIcon,
entries,
key: 'dropdown-text',
order: 1,
}
}

View File

@@ -10,7 +10,7 @@ export const TestRecorderFeature: FeatureProviderProviderServer<undefined, undef
serverFeatureProps: props,
}
},
key: 'testrecorder',
key: 'testRecorder',
serverFeatureProps: props,
}
}

View File

@@ -10,7 +10,7 @@ export const TreeViewFeature: FeatureProviderProviderServer<undefined, undefined
serverFeatureProps: props,
}
},
key: 'treeview',
key: 'treeView',
serverFeatureProps: props,
}
}

View File

@@ -5,7 +5,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
import { BoldIcon } from '../../../lexical/ui/icons/Bold/index.js'
import { createClientComponent } from '../../createClientComponent.js'
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
import {
BOLD_ITALIC_STAR,
BOLD_ITALIC_UNDERSCORE,
@@ -24,9 +24,10 @@ const BoldFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
return {
clientFeatureProps: props,
floatingSelectToolbar: {
sections: [
SectionWithEntries([
markdownTransformers,
toolbarInline: {
groups: [
inlineToolbarFormatGroupWithItems([
{
ChildComponent: BoldIcon,
isActive: ({ selection }) => {
@@ -36,7 +37,7 @@ const BoldFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
return false
},
key: 'bold',
onClick: ({ editor }) => {
onSelect: ({ editor }) => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')
},
order: 1,
@@ -44,7 +45,6 @@ const BoldFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
]),
],
},
markdownTransformers,
}
},
}

View File

@@ -1,15 +0,0 @@
import type {
FloatingToolbarSection,
FloatingToolbarSectionEntry,
} from '../../../lexical/plugins/FloatingSelectToolbar/types.js'
export const SectionWithEntries = (
entries: FloatingToolbarSectionEntry[],
): FloatingToolbarSection => {
return {
type: 'buttons',
entries,
key: 'format',
order: 4,
}
}

View File

@@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
import { CodeIcon } from '../../../lexical/ui/icons/Code/index.js'
import { createClientComponent } from '../../createClientComponent.js'
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
import { INLINE_CODE } from './markdownTransformers.js'
const InlineCodeFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
@@ -15,9 +15,11 @@ const InlineCodeFeatureClient: FeatureProviderProviderClient<undefined> = (props
feature: () => {
return {
clientFeatureProps: props,
floatingSelectToolbar: {
sections: [
SectionWithEntries([
markdownTransformers: [INLINE_CODE],
toolbarInline: {
groups: [
inlineToolbarFormatGroupWithItems([
{
ChildComponent: CodeIcon,
isActive: ({ selection }) => {
@@ -26,8 +28,8 @@ const InlineCodeFeatureClient: FeatureProviderProviderClient<undefined> = (props
}
return false
},
key: 'code',
onClick: ({ editor }) => {
key: 'inlineCode',
onSelect: ({ editor }) => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code')
},
order: 7,
@@ -35,8 +37,6 @@ const InlineCodeFeatureClient: FeatureProviderProviderClient<undefined> = (props
]),
],
},
markdownTransformers: [INLINE_CODE],
}
},
}

View File

@@ -12,7 +12,7 @@ export const InlineCodeFeature: FeatureProviderProviderServer<undefined, undefin
serverFeatureProps: props,
}
},
key: 'inlinecode',
key: 'inlineCode',
serverFeatureProps: props,
}
}

View File

@@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js'
import { ItalicIcon } from '../../../lexical/ui/icons/Italic/index.js'
import { createClientComponent } from '../../createClientComponent.js'
import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js'
import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js'
import { ITALIC_STAR, ITALIC_UNDERSCORE } from './markdownTransformers.js'
const ItalicFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
@@ -16,9 +16,10 @@ const ItalicFeatureClient: FeatureProviderProviderClient<undefined> = (props) =>
return {
clientFeatureProps: props,
floatingSelectToolbar: {
sections: [
SectionWithEntries([
markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE],
toolbarInline: {
groups: [
inlineToolbarFormatGroupWithItems([
{
ChildComponent: ItalicIcon,
isActive: ({ selection }) => {
@@ -28,7 +29,7 @@ const ItalicFeatureClient: FeatureProviderProviderClient<undefined> = (props) =>
return false
},
key: 'italic',
onClick: ({ editor }) => {
onSelect: ({ editor }) => {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic')
},
order: 2,
@@ -36,7 +37,6 @@ const ItalicFeatureClient: FeatureProviderProviderClient<undefined> = (props) =>
]),
],
},
markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE],
}
},
}

View File

@@ -0,0 +1,15 @@
import type {
InlineToolbarGroup,
InlineToolbarGroupItem,
} from '../../../lexical/plugins/toolbars/inline/types.js'
export const inlineToolbarFormatGroupWithItems = (
items: InlineToolbarGroupItem[],
): InlineToolbarGroup => {
return {
type: 'buttons',
items,
key: 'format',
order: 4,
}
}

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