diff --git a/package.json b/package.json index c0a74afd170..56129200f3b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload-monorepo", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "private": true, "type": "module", "scripts": { @@ -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" diff --git a/packages/create-payload-app/package.json b/packages/create-payload-app/package.json index f7343e2df02..d50e9ed1b9a 100644 --- a/packages/create-payload-app/package.json +++ b/packages/create-payload-app/package.json @@ -1,6 +1,6 @@ { "name": "create-payload-app", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/db-mongodb/package.json b/packages/db-mongodb/package.json index 8b1ca22e887..2379776ad17 100644 --- a/packages/db-mongodb/package.json +++ b/packages/db-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-mongodb", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "The officially supported MongoDB database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/db-postgres/package.json b/packages/db-postgres/package.json index 6863d6087ce..214f6c82f13 100644 --- a/packages/db-postgres/package.json +++ b/packages/db-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-postgres", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "The officially supported Postgres database adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/email-nodemailer/package.json b/packages/email-nodemailer/package.json index 2af70e24cfb..e9dbff75e15 100644 --- a/packages/email-nodemailer/package.json +++ b/packages/email-nodemailer/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/email-nodemailer", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "Payload Nodemailer Email Adapter", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/email-resend/package.json b/packages/email-resend/package.json index 6010f1fb242..9c0010dd024 100644 --- a/packages/email-resend/package.json +++ b/packages/email-resend/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/email-resend", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "Payload Resend Email Adapter", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 60d3e8fcfbd..222ed641ea1 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/graphql", - "version": "3.0.0-beta.22", + "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", diff --git a/packages/next/package.json b/packages/next/package.json index 061cff6720d..641231f5c86 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/next", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/next/src/routes/rest/buildFormState.ts b/packages/next/src/routes/rest/buildFormState.ts index 88ca38ce667..c37ea7db8fd 100644 --- a/packages/next/src/routes/rest/buildFormState.ts +++ b/packages/next/src/routes/rest/buildFormState.ts @@ -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 } } diff --git a/packages/next/src/utilities/initPage/shared.ts b/packages/next/src/utilities/initPage/shared.ts index 5a9f50e506d..3e480c54e04 100644 --- a/packages/next/src/utilities/initPage/shared.ts +++ b/packages/next/src/utilities/initPage/shared.ts @@ -13,5 +13,5 @@ export const isAdminRoute = (route: string, adminRoute: string) => { } export const isAdminAuthRoute = (route: string, adminRoute: string) => { - return authRoutes.some((r) => r === route.replace(adminRoute, '')) + return authRoutes.some((r) => route.replace(adminRoute, '').startsWith(r)) } diff --git a/packages/next/src/views/ResetPassword/index.client.tsx b/packages/next/src/views/ResetPassword/index.client.tsx new file mode 100644 index 00000000000..936993d2b82 --- /dev/null +++ b/packages/next/src/views/ResetPassword/index.client.tsx @@ -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 = ({ 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 ( +
+ + + + {i18n.t('authentication:resetPassword')} + + ) +} + +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 ( + + ) +} diff --git a/packages/next/src/views/ResetPassword/index.scss b/packages/next/src/views/ResetPassword/index.scss index de3c6f1eeff..d48d6edc37a 100644 --- a/packages/next/src/views/ResetPassword/index.scss +++ b/packages/next/src/views/ResetPassword/index.scss @@ -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); } } diff --git a/packages/next/src/views/ResetPassword/index.tsx b/packages/next/src/views/ResetPassword/index.tsx index ebe5593d1c2..a3106f74edd 100644 --- a/packages/next/src/views/ResetPassword/index.tsx +++ b/packages/next/src/views/ResetPassword/index.tsx @@ -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 = ({ initPageResult, params }) => { const { req } = initPageResult - const { token } = params + const { + segments: [_, token], + } = params const { i18n, @@ -31,21 +29,9 @@ export const ResetPassword: React.FC = ({ 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 ( @@ -73,22 +59,7 @@ export const ResetPassword: React.FC = ({ initPageResult, params

{i18n.t('authentication:resetPassword')}

-
- - - - {i18n.t('authentication:resetPassword')} - +
) diff --git a/packages/payload/package.json b/packages/payload/package.json index 2be37913632..e7f0f475323 100644 --- a/packages/payload/package.json +++ b/packages/payload/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "3.0.0-beta.22", + "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", diff --git a/packages/plugin-cloud-storage/package.json b/packages/plugin-cloud-storage/package.json index 93d92bf23a5..b2527946eff 100644 --- a/packages/plugin-cloud-storage/package.json +++ b/packages/plugin-cloud-storage/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-cloud-storage", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "The official cloud storage plugin for Payload CMS", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/plugin-cloud/package.json b/packages/plugin-cloud/package.json index e18cc88230b..8a1961e3232 100644 --- a/packages/plugin-cloud/package.json +++ b/packages/plugin-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-cloud", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "The official Payload Cloud plugin", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/plugin-form-builder/package.json b/packages/plugin-form-builder/package.json index 5a9972a8cfc..5b4f49e44a4 100644 --- a/packages/plugin-form-builder/package.json +++ b/packages/plugin-form-builder/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-form-builder", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "Form builder plugin for Payload CMS", "keywords": [ "payload", diff --git a/packages/plugin-nested-docs/package.json b/packages/plugin-nested-docs/package.json index b12bf4a0f7f..929dec18ef7 100644 --- a/packages/plugin-nested-docs/package.json +++ b/packages/plugin-nested-docs/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-nested-docs", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "The official Nested Docs plugin for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/plugin-redirects/package.json b/packages/plugin-redirects/package.json index 4e773bcb97e..ef841a965a6 100644 --- a/packages/plugin-redirects/package.json +++ b/packages/plugin-redirects/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-redirects", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "Redirects plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-search/package.json b/packages/plugin-search/package.json index b3670a13509..72c8c2e57ec 100644 --- a/packages/plugin-search/package.json +++ b/packages/plugin-search/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-search", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "Search plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-seo/package.json b/packages/plugin-seo/package.json index e4a52ed102d..d3dfd3f13d5 100644 --- a/packages/plugin-seo/package.json +++ b/packages/plugin-seo/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/plugin-seo", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "SEO plugin for Payload", "keywords": [ "payload", diff --git a/packages/plugin-stripe/package.json b/packages/plugin-stripe/package.json index 5e22c7bf032..cf8ecfdf7c6 100644 --- a/packages/plugin-stripe/package.json +++ b/packages/plugin-stripe/package.json @@ -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" diff --git a/packages/plugin-stripe/src/payload-stripe-plugin.postman_collection.json b/packages/plugin-stripe/payload-stripe-plugin.postman_collection.json similarity index 100% rename from packages/plugin-stripe/src/payload-stripe-plugin.postman_collection.json rename to packages/plugin-stripe/payload-stripe-plugin.postman_collection.json diff --git a/packages/plugin-stripe/src/admin.ts b/packages/plugin-stripe/src/admin.ts index cdccab1991c..92f0c24bfb4 100644 --- a/packages/plugin-stripe/src/admin.ts +++ b/packages/plugin-stripe/src/admin.ts @@ -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 diff --git a/packages/plugin-stripe/src/fields/getFields.ts b/packages/plugin-stripe/src/fields/getFields.ts index 19c4267df31..f680809799f 100644 --- a/packages/plugin-stripe/src/fields/getFields.ts +++ b/packages/plugin-stripe/src/fields/getFields.ts @@ -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', }, diff --git a/packages/plugin-stripe/src/hooks/createNewInStripe.ts b/packages/plugin-stripe/src/hooks/createNewInStripe.ts index 38b62c18f16..1f65d7337b0 100644 --- a/packages/plugin-stripe/src/hooks/createNewInStripe.ts +++ b/packages/plugin-stripe/src/hooks/createNewInStripe.ts @@ -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) + acc[stripeProperty] = dataRef[fieldPath] + return acc + }, + {} as Record, + ) 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, ) diff --git a/packages/plugin-stripe/src/hooks/deleteFromStripe.ts b/packages/plugin-stripe/src/hooks/deleteFromStripe.ts index 29576d30695..c0b17397e46 100644 --- a/packages/plugin-stripe/src/hooks/deleteFromStripe.ts +++ b/packages/plugin-stripe/src/hooks/deleteFromStripe.ts @@ -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[0], 'collection'> & { diff --git a/packages/plugin-stripe/src/hooks/syncExistingWithStripe.ts b/packages/plugin-stripe/src/hooks/syncExistingWithStripe.ts index 6c1ec8e5c80..dd1e010c67c 100644 --- a/packages/plugin-stripe/src/hooks/syncExistingWithStripe.ts +++ b/packages/plugin-stripe/src/hooks/syncExistingWithStripe.ts @@ -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 - - acc[stripeProperty] = data[fieldPath] - return acc - }, {} as Record) + let syncedFields = syncConfig.fields.reduce( + (acc, field) => { + const { fieldPath, stripeProperty } = field + + acc[stripeProperty] = data[fieldPath] + return acc + }, + {} as Record, + ) syncedFields = deepen(syncedFields) diff --git a/packages/plugin-stripe/src/index.ts b/packages/plugin-stripe/src/index.ts index 8817b618037..301bf740802 100644 --- a/packages/plugin-stripe/src/index.ts +++ b/packages/plugin-stripe/src/index.ts @@ -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 diff --git a/packages/plugin-stripe/src/mocks/mockFile.js b/packages/plugin-stripe/src/mocks/mockFile.js deleted file mode 100644 index 266dba7fc2d..00000000000 --- a/packages/plugin-stripe/src/mocks/mockFile.js +++ /dev/null @@ -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 -} diff --git a/packages/plugin-stripe/src/routes/rest.ts b/packages/plugin-stripe/src/routes/rest.ts index 7503d3dc841..76709154f51 100644 --- a/packages/plugin-stripe/src/routes/rest.ts +++ b/packages/plugin-stripe/src/routes/rest.ts @@ -33,7 +33,9 @@ export const stripeREST = async (args: { } responseJSON = await stripeProxy({ + // @ts-expect-error stripeArgs, + // @ts-expect-error stripeMethod, stripeSecretKey, }) diff --git a/packages/plugin-stripe/src/routes/webhooks.ts b/packages/plugin-stripe/src/routes/webhooks.ts index f706391ae44..7d2b1dc8033 100644 --- a/packages/plugin-stripe/src/routes/webhooks.ts +++ b/packages/plugin-stripe/src/routes/webhooks.ts @@ -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}`) diff --git a/packages/plugin-stripe/src/ui/LinkToDoc.tsx b/packages/plugin-stripe/src/ui/LinkToDoc.tsx index 7ce678144d1..04ba99831b2 100644 --- a/packages/plugin-stripe/src/ui/LinkToDoc.tsx +++ b/packages/plugin-stripe/src/ui/LinkToDoc.tsx @@ -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 = () => { + const { custom } = useFieldProps() + const { isTestKey, nameOfIDField, stripeResourceType } = custom const field = useFormFields(([fields]) => fields[nameOfIDField]) const { value: stripeID } = field || {} diff --git a/packages/plugin-stripe/src/utilities/stripeProxy.ts b/packages/plugin-stripe/src/utilities/stripeProxy.ts index 6b9d6b7eaf9..ed4d1c01931 100644 --- a/packages/plugin-stripe/src/utilities/stripeProxy.ts +++ b/packages/plugin-stripe/src/utilities/stripeProxy.ts @@ -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, { diff --git a/packages/plugin-stripe/src/webhooks/handleCreatedOrUpdated.ts b/packages/plugin-stripe/src/webhooks/handleCreatedOrUpdated.ts index 1c3e14442d8..6f644cdec6a 100644 --- a/packages/plugin-stripe/src/webhooks/handleCreatedOrUpdated.ts +++ b/packages/plugin-stripe/src/webhooks/handleCreatedOrUpdated.ts @@ -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[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) + acc[fieldPath] = stripeDoc[stripeProperty] + return acc + }, + {} as Record, + ) syncedData = deepen({ ...syncedData, diff --git a/packages/plugin-stripe/src/webhooks/handleDeleted.ts b/packages/plugin-stripe/src/webhooks/handleDeleted.ts index 4242ee320f9..5185a87e465 100644 --- a/packages/plugin-stripe/src/webhooks/handleDeleted.ts +++ b/packages/plugin-stripe/src/webhooks/handleDeleted.ts @@ -1,4 +1,4 @@ -import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types' +import type { SanitizedStripeConfig, StripeWebhookHandler } from '../types.js' type HandleDeleted = ( args: Parameters[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, diff --git a/packages/plugin-stripe/src/webhooks/index.ts b/packages/plugin-stripe/src/webhooks/index.ts index 97c36a56d68..1db3197cb4f 100644 --- a/packages/plugin-stripe/src/webhooks/index.ts +++ b/packages/plugin-stripe/src/webhooks/index.ts @@ -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, diff --git a/packages/richtext-lexical/package.json b/packages/richtext-lexical/package.json index 28f5ebbc3e2..67cea1e7ab6 100644 --- a/packages/richtext-lexical/package.json +++ b/packages/richtext-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/richtext-lexical", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "The officially supported Lexical richtext adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/richtext-lexical/src/exports/components.ts b/packages/richtext-lexical/src/exports/components.ts index eb51fcf147f..13a033a1650 100644 --- a/packages/richtext-lexical/src/exports/components.ts +++ b/packages/richtext-lexical/src/exports/components.ts @@ -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' diff --git a/packages/richtext-lexical/src/field/features/align/feature.client.tsx b/packages/richtext-lexical/src/field/features/align/feature.client.tsx index 03861eb8e69..3acbcc76f0e 100644 --- a/packages/richtext-lexical/src/field/features/align/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/align/feature.client.tsx @@ -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 = (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, diff --git a/packages/richtext-lexical/src/field/features/align/floatingSelectToolbarAlignDropdownSection.ts b/packages/richtext-lexical/src/field/features/align/floatingSelectToolbarAlignDropdownSection.ts deleted file mode 100644 index 5944c5c887e..00000000000 --- a/packages/richtext-lexical/src/field/features/align/floatingSelectToolbarAlignDropdownSection.ts +++ /dev/null @@ -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, - } -} diff --git a/packages/richtext-lexical/src/field/features/align/inlineToolbarAlignGroup.ts b/packages/richtext-lexical/src/field/features/align/inlineToolbarAlignGroup.ts new file mode 100644 index 00000000000..e559e82e67c --- /dev/null +++ b/packages/richtext-lexical/src/field/features/align/inlineToolbarAlignGroup.ts @@ -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, + } +} diff --git a/packages/richtext-lexical/src/field/features/blockquote/feature.client.tsx b/packages/richtext-lexical/src/field/features/blockquote/feature.client.tsx index 95a67878ab8..ce6ac160e2f 100644 --- a/packages/richtext-lexical/src/field/features/blockquote/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/blockquote/feature.client.tsx @@ -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 = (props) => { @@ -17,15 +16,40 @@ const BlockQuoteFeatureClient: FeatureProviderProviderClient = (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 = (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()) - }, - }), - ], - }, - ], - }, }), } } diff --git a/packages/richtext-lexical/src/field/features/blocks/feature.client.tsx b/packages/richtext-lexical/src/field/features/blocks/feature.client.tsx index 69bd4bdb5bb..fd8e7a47cb1 100644 --- a/packages/richtext-lexical/src/field/features/blocks/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/blocks/feature.client.tsx @@ -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 { - 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', }, ], }, diff --git a/packages/richtext-lexical/src/field/features/common/floatingSelectToolbarFeaturesButtonsSection/index.ts b/packages/richtext-lexical/src/field/features/common/floatingSelectToolbarFeaturesButtonsSection/index.ts deleted file mode 100644 index 977fb6b05c4..00000000000 --- a/packages/richtext-lexical/src/field/features/common/floatingSelectToolbarFeaturesButtonsSection/index.ts +++ /dev/null @@ -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, - } -} diff --git a/packages/richtext-lexical/src/field/features/common/floatingSelectToolbarTextDropdownSection/index.ts b/packages/richtext-lexical/src/field/features/common/floatingSelectToolbarTextDropdownSection/index.ts deleted file mode 100644 index e55f20e7c88..00000000000 --- a/packages/richtext-lexical/src/field/features/common/floatingSelectToolbarTextDropdownSection/index.ts +++ /dev/null @@ -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, - } -} diff --git a/packages/richtext-lexical/src/field/features/debug/testrecorder/feature.client.tsx b/packages/richtext-lexical/src/field/features/debug/testRecorder/feature.client.tsx similarity index 100% rename from packages/richtext-lexical/src/field/features/debug/testrecorder/feature.client.tsx rename to packages/richtext-lexical/src/field/features/debug/testRecorder/feature.client.tsx diff --git a/packages/richtext-lexical/src/field/features/debug/testrecorder/feature.server.ts b/packages/richtext-lexical/src/field/features/debug/testRecorder/feature.server.ts similarity index 94% rename from packages/richtext-lexical/src/field/features/debug/testrecorder/feature.server.ts rename to packages/richtext-lexical/src/field/features/debug/testRecorder/feature.server.ts index 566069b334a..8639091649b 100644 --- a/packages/richtext-lexical/src/field/features/debug/testrecorder/feature.server.ts +++ b/packages/richtext-lexical/src/field/features/debug/testRecorder/feature.server.ts @@ -10,7 +10,7 @@ export const TestRecorderFeature: FeatureProviderProviderServer = (props) => { return { clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - SectionWithEntries([ + markdownTransformers, + toolbarInline: { + groups: [ + inlineToolbarFormatGroupWithItems([ { ChildComponent: BoldIcon, isActive: ({ selection }) => { @@ -36,7 +37,7 @@ const BoldFeatureClient: FeatureProviderProviderClient = (props) => { return false }, key: 'bold', - onClick: ({ editor }) => { + onSelect: ({ editor }) => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold') }, order: 1, @@ -44,7 +45,6 @@ const BoldFeatureClient: FeatureProviderProviderClient = (props) => { ]), ], }, - markdownTransformers, } }, } diff --git a/packages/richtext-lexical/src/field/features/format/common/floatingSelectToolbarSection.ts b/packages/richtext-lexical/src/field/features/format/common/floatingSelectToolbarSection.ts deleted file mode 100644 index d6eed43f6ad..00000000000 --- a/packages/richtext-lexical/src/field/features/format/common/floatingSelectToolbarSection.ts +++ /dev/null @@ -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, - } -} diff --git a/packages/richtext-lexical/src/field/features/format/inlinecode/feature.client.tsx b/packages/richtext-lexical/src/field/features/format/inlineCode/feature.client.tsx similarity index 81% rename from packages/richtext-lexical/src/field/features/format/inlinecode/feature.client.tsx rename to packages/richtext-lexical/src/field/features/format/inlineCode/feature.client.tsx index 2cc6afb2fed..f36201a5824 100644 --- a/packages/richtext-lexical/src/field/features/format/inlinecode/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/format/inlineCode/feature.client.tsx @@ -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 = (props) => { @@ -15,9 +15,11 @@ const InlineCodeFeatureClient: FeatureProviderProviderClient = (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 = (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 = (props ]), ], }, - - markdownTransformers: [INLINE_CODE], } }, } diff --git a/packages/richtext-lexical/src/field/features/format/inlinecode/feature.server.ts b/packages/richtext-lexical/src/field/features/format/inlineCode/feature.server.ts similarity index 95% rename from packages/richtext-lexical/src/field/features/format/inlinecode/feature.server.ts rename to packages/richtext-lexical/src/field/features/format/inlineCode/feature.server.ts index b1ca2b1ee69..c7d2d2136fe 100644 --- a/packages/richtext-lexical/src/field/features/format/inlinecode/feature.server.ts +++ b/packages/richtext-lexical/src/field/features/format/inlineCode/feature.server.ts @@ -12,7 +12,7 @@ export const InlineCodeFeature: FeatureProviderProviderServer = (props) => { @@ -16,9 +16,10 @@ const ItalicFeatureClient: FeatureProviderProviderClient = (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 = (props) => return false }, key: 'italic', - onClick: ({ editor }) => { + onSelect: ({ editor }) => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic') }, order: 2, @@ -36,7 +37,6 @@ const ItalicFeatureClient: FeatureProviderProviderClient = (props) => ]), ], }, - markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE], } }, } diff --git a/packages/richtext-lexical/src/field/features/format/shared/inlineToolbarFormatGroup.ts b/packages/richtext-lexical/src/field/features/format/shared/inlineToolbarFormatGroup.ts new file mode 100644 index 00000000000..d26dd61d686 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/format/shared/inlineToolbarFormatGroup.ts @@ -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, + } +} diff --git a/packages/richtext-lexical/src/field/features/format/strikethrough/feature.client.tsx b/packages/richtext-lexical/src/field/features/format/strikethrough/feature.client.tsx index 4fae03769ae..5463f73b99d 100644 --- a/packages/richtext-lexical/src/field/features/format/strikethrough/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/format/strikethrough/feature.client.tsx @@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js' import { StrikethroughIcon } from '../../../lexical/ui/icons/Strikethrough/index.js' import { createClientComponent } from '../../createClientComponent.js' -import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js' +import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js' import { STRIKETHROUGH } from './markdownTransformers.js' const StrikethroughFeatureClient: FeatureProviderProviderClient = (props) => { @@ -16,9 +16,10 @@ const StrikethroughFeatureClient: FeatureProviderProviderClient = (pr return { clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - SectionWithEntries([ + markdownTransformers: [STRIKETHROUGH], + toolbarInline: { + groups: [ + inlineToolbarFormatGroupWithItems([ { ChildComponent: StrikethroughIcon, isActive: ({ selection }) => { @@ -28,7 +29,7 @@ const StrikethroughFeatureClient: FeatureProviderProviderClient = (pr return false }, key: 'strikethrough', - onClick: ({ editor }) => { + onSelect: ({ editor }) => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough') }, order: 4, @@ -36,7 +37,6 @@ const StrikethroughFeatureClient: FeatureProviderProviderClient = (pr ]), ], }, - markdownTransformers: [STRIKETHROUGH], } }, } diff --git a/packages/richtext-lexical/src/field/features/format/subscript/feature.client.tsx b/packages/richtext-lexical/src/field/features/format/subscript/feature.client.tsx index 055bd020021..02a219871a8 100644 --- a/packages/richtext-lexical/src/field/features/format/subscript/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/format/subscript/feature.client.tsx @@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js' import { SubscriptIcon } from '../../../lexical/ui/icons/Subscript/index.js' import { createClientComponent } from '../../createClientComponent.js' -import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js' +import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js' const SubscriptFeatureClient: FeatureProviderProviderClient = (props) => { return { @@ -14,9 +14,9 @@ const SubscriptFeatureClient: FeatureProviderProviderClient = (props) feature: () => { return { clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - SectionWithEntries([ + toolbarInline: { + groups: [ + inlineToolbarFormatGroupWithItems([ { ChildComponent: SubscriptIcon, isActive: ({ selection }) => { @@ -26,7 +26,7 @@ const SubscriptFeatureClient: FeatureProviderProviderClient = (props) return false }, key: 'subscript', - onClick: ({ editor }) => { + onSelect: ({ editor }) => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript') }, order: 5, diff --git a/packages/richtext-lexical/src/field/features/format/superscript/feature.client.tsx b/packages/richtext-lexical/src/field/features/format/superscript/feature.client.tsx index 0288ccae96a..a482a056195 100644 --- a/packages/richtext-lexical/src/field/features/format/superscript/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/format/superscript/feature.client.tsx @@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js' import { SuperscriptIcon } from '../../../lexical/ui/icons/Superscript/index.js' import { createClientComponent } from '../../createClientComponent.js' -import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js' +import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js' const SuperscriptFeatureClient: FeatureProviderProviderClient = (props) => { return { @@ -14,9 +14,9 @@ const SuperscriptFeatureClient: FeatureProviderProviderClient = (prop feature: () => { return { clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - SectionWithEntries([ + toolbarInline: { + groups: [ + inlineToolbarFormatGroupWithItems([ { ChildComponent: SuperscriptIcon, isActive: ({ selection }) => { @@ -26,7 +26,7 @@ const SuperscriptFeatureClient: FeatureProviderProviderClient = (prop return false }, key: 'superscript', - onClick: ({ editor }) => { + onSelect: ({ editor }) => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript') }, order: 6, diff --git a/packages/richtext-lexical/src/field/features/format/underline/feature.client.tsx b/packages/richtext-lexical/src/field/features/format/underline/feature.client.tsx index d30e3abc35b..2028ebe27e6 100644 --- a/packages/richtext-lexical/src/field/features/format/underline/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/format/underline/feature.client.tsx @@ -6,7 +6,7 @@ import type { FeatureProviderProviderClient } from '../../types.js' import { UnderlineIcon } from '../../../lexical/ui/icons/Underline/index.js' import { createClientComponent } from '../../createClientComponent.js' -import { SectionWithEntries } from '../common/floatingSelectToolbarSection.js' +import { inlineToolbarFormatGroupWithItems } from '../shared/inlineToolbarFormatGroup.js' const UnderlineFeatureClient: FeatureProviderProviderClient = (props) => { return { @@ -14,9 +14,9 @@ const UnderlineFeatureClient: FeatureProviderProviderClient = (props) feature: () => { return { clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - SectionWithEntries([ + toolbarInline: { + groups: [ + inlineToolbarFormatGroupWithItems([ { ChildComponent: UnderlineIcon, isActive: ({ selection }) => { @@ -26,7 +26,7 @@ const UnderlineFeatureClient: FeatureProviderProviderClient = (props) return false }, key: 'underline', - onClick: ({ editor }) => { + onSelect: ({ editor }) => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline') }, order: 3, diff --git a/packages/richtext-lexical/src/field/features/heading/feature.client.tsx b/packages/richtext-lexical/src/field/features/heading/feature.client.tsx index 776c4a0710c..43ef5a9bd3f 100644 --- a/packages/richtext-lexical/src/field/features/heading/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/heading/feature.client.tsx @@ -9,15 +9,14 @@ import { $getSelection } from 'lexical' import type { FeatureProviderProviderClient } from '../types.js' import type { HeadingFeatureProps } from './feature.server.js' -import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js' import { H1Icon } from '../../lexical/ui/icons/H1/index.js' import { H2Icon } from '../../lexical/ui/icons/H2/index.js' import { H3Icon } from '../../lexical/ui/icons/H3/index.js' import { H4Icon } from '../../lexical/ui/icons/H4/index.js' import { H5Icon } from '../../lexical/ui/icons/H5/index.js' import { H6Icon } from '../../lexical/ui/icons/H6/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 setHeading = (headingSize: HeadingTagType) => { @@ -42,47 +41,52 @@ const HeadingFeatureClient: FeatureProviderProviderClient = feature: () => { return { clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - ...enabledHeadingSizes.map((headingSize, i) => - TextDropdownSectionWithEntries([ - { - ChildComponent: iconImports[headingSize], - isActive: () => false, - key: headingSize, - label: `Heading ${headingSize.charAt(1)}`, - onClick: ({ editor }) => { - editor.update(() => { - setHeading(headingSize) - }) - }, - order: i + 2, - }, - ]), - ), - ], - }, markdownTransformers: [MarkdownTransformer(enabledHeadingSizes)], nodes: [HeadingNode], slashMenu: { - options: [ - ...enabledHeadingSizes.map((headingSize) => { - return { - displayName: 'Basic', - key: 'basic', - options: [ - new SlashMenuOption(`heading-${headingSize.charAt(1)}`, { - Icon: iconImports[headingSize], - displayName: `Heading ${headingSize.charAt(1)}`, - keywords: ['heading', headingSize], - onSelect: () => { - setHeading(headingSize) - }, + groups: enabledHeadingSizes?.length + ? [ + { + displayName: 'Basic', + items: enabledHeadingSizes.map((headingSize) => { + return { + Icon: iconImports[headingSize], + displayName: `Heading ${headingSize.charAt(1)}`, + key: `heading-${headingSize.charAt(1)}`, + keywords: ['heading', headingSize], + onSelect: ({ editor }) => { + editor.update(() => { + setHeading(headingSize) + }) + }, + } + }), + key: 'basic', + }, + ] + : [], + }, + toolbarInline: { + groups: enabledHeadingSizes?.length + ? [ + inlineToolbarTextDropdownGroupWithItems( + enabledHeadingSizes.map((headingSize, i) => { + return { + ChildComponent: iconImports[headingSize], + isActive: () => false, + key: headingSize, + label: `Heading ${headingSize.charAt(1)}`, + onSelect: ({ editor }) => { + editor.update(() => { + setHeading(headingSize) + }) + }, + order: i + 2, + } }), - ], - } - }), - ], + ), + ] + : [], }, } }, diff --git a/packages/richtext-lexical/src/field/features/horizontalrule/component/index.tsx b/packages/richtext-lexical/src/field/features/horizontalRule/component/index.tsx similarity index 100% rename from packages/richtext-lexical/src/field/features/horizontalrule/component/index.tsx rename to packages/richtext-lexical/src/field/features/horizontalRule/component/index.tsx diff --git a/packages/richtext-lexical/src/field/features/horizontalrule/feature.client.tsx b/packages/richtext-lexical/src/field/features/horizontalRule/feature.client.tsx similarity index 86% rename from packages/richtext-lexical/src/field/features/horizontalrule/feature.client.tsx rename to packages/richtext-lexical/src/field/features/horizontalRule/feature.client.tsx index fcc51f23bda..11faf6827a7 100644 --- a/packages/richtext-lexical/src/field/features/horizontalrule/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/horizontalRule/feature.client.tsx @@ -2,7 +2,6 @@ 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' @@ -23,20 +22,21 @@ const HorizontalRuleFeatureClient: FeatureProviderProviderClient = (p }, ], slashMenu: { - options: [ + groups: [ { displayName: 'Basic', - key: 'basic', - options: [ - new SlashMenuOption(`horizontalrule`, { + items: [ + { Icon: HorizontalRuleIcon, displayName: `Horizontal Rule`, + key: 'horizontalRule', keywords: ['hr', 'horizontal rule', 'line', 'separator'], onSelect: ({ editor }) => { editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) }, - }), + }, ], + key: 'basic', }, ], }, diff --git a/packages/richtext-lexical/src/field/features/horizontalrule/feature.server.ts b/packages/richtext-lexical/src/field/features/horizontalRule/feature.server.ts similarity index 97% rename from packages/richtext-lexical/src/field/features/horizontalrule/feature.server.ts rename to packages/richtext-lexical/src/field/features/horizontalRule/feature.server.ts index 47e4a72c509..2346100c24f 100644 --- a/packages/richtext-lexical/src/field/features/horizontalrule/feature.server.ts +++ b/packages/richtext-lexical/src/field/features/horizontalRule/feature.server.ts @@ -30,7 +30,7 @@ export const HorizontalRuleFeature: FeatureProviderProviderServer = (props) => { return { clientFeatureProps: props, feature: () => ({ clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - IndentSectionWithEntries([ + toolbarInline: { + groups: [ + indentGroupWithItems([ { ChildComponent: IndentDecreaseIcon, isActive: () => false, @@ -37,21 +37,19 @@ const IndentFeatureClient: FeatureProviderProviderClient = (props) => } return false }, - key: 'indent-decrease', + key: 'indentDecrease', label: `Decrease Indent`, - onClick: ({ editor }) => { + onSelect: ({ editor }) => { editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined) }, order: 1, }, - ]), - IndentSectionWithEntries([ { ChildComponent: IndentIncreaseIcon, isActive: () => false, - key: 'indent-increase', + key: 'indentIncrease', label: `Increase Indent`, - onClick: ({ editor }) => { + onSelect: ({ editor }) => { editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined) }, order: 2, diff --git a/packages/richtext-lexical/src/field/features/indent/floatingSelectToolbarIndentSection.ts b/packages/richtext-lexical/src/field/features/indent/floatingSelectToolbarIndentSection.ts deleted file mode 100644 index 4750ec28401..00000000000 --- a/packages/richtext-lexical/src/field/features/indent/floatingSelectToolbarIndentSection.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { - FloatingToolbarSection, - FloatingToolbarSectionEntry, -} from '../../lexical/plugins/FloatingSelectToolbar/types.js' - -export const IndentSectionWithEntries = ( - entries: FloatingToolbarSectionEntry[], -): FloatingToolbarSection => { - return { - type: 'buttons', - entries, - key: 'indent', - order: 3, - } -} diff --git a/packages/richtext-lexical/src/field/features/indent/index.scss b/packages/richtext-lexical/src/field/features/indent/index.scss deleted file mode 100644 index 40ef1ca66f8..00000000000 --- a/packages/richtext-lexical/src/field/features/indent/index.scss +++ /dev/null @@ -1,4 +0,0 @@ -.floating-select-toolbar-popup__section-indent { - display: flex; - gap: 2px; -} diff --git a/packages/richtext-lexical/src/field/features/indent/inlineToolbarIndentGroup.ts b/packages/richtext-lexical/src/field/features/indent/inlineToolbarIndentGroup.ts new file mode 100644 index 00000000000..623c58068f9 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/indent/inlineToolbarIndentGroup.ts @@ -0,0 +1,13 @@ +import type { + InlineToolbarGroup, + InlineToolbarGroupItem, +} from '../../lexical/plugins/toolbars/inline/types.js' + +export const indentGroupWithItems = (items: InlineToolbarGroupItem[]): InlineToolbarGroup => { + return { + type: 'buttons', + items, + key: 'indent', + order: 3, + } +} diff --git a/packages/richtext-lexical/src/field/features/link/feature.client.tsx b/packages/richtext-lexical/src/field/features/link/feature.client.tsx index afc874f5891..9549772c5bb 100644 --- a/packages/richtext-lexical/src/field/features/link/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/link/feature.client.tsx @@ -9,8 +9,8 @@ import type { LinkFields } from './nodes/types.js' import { LinkIcon } from '../../lexical/ui/icons/Link/index.js' import { getSelectedNode } from '../../lexical/utils/getSelectedNode.js' -import { FeaturesSectionWithEntries } from '../common/floatingSelectToolbarFeaturesButtonsSection/index.js' import { createClientComponent } from '../createClientComponent.js' +import { inlineToolbarFeatureButtonsGroupWithItems } from '../shared/inlineToolbar/featureButtonsGroup.js' import { AutoLinkNode } from './nodes/AutoLinkNode.js' import { $isLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from './nodes/LinkNode.js' import { AutoLinkPlugin } from './plugins/autoLink/index.js' @@ -26,9 +26,28 @@ const LinkFeatureClient: FeatureProviderProviderClient = (props) => clientFeatureProps: props, feature: () => ({ clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - FeaturesSectionWithEntries([ + nodes: [LinkNode, AutoLinkNode], + plugins: [ + { + Component: LinkPlugin, + position: 'normal', + }, + { + Component: AutoLinkPlugin, + position: 'normal', + }, + { + Component: ClickableLinkPlugin, + position: 'normal', + }, + { + Component: FloatingLinkEditorPlugin, + position: 'floatingAnchorElem', + }, + ], + toolbarInline: { + groups: [ + inlineToolbarFeatureButtonsGroupWithItems([ { ChildComponent: LinkIcon, isActive: ({ selection }) => { @@ -41,7 +60,7 @@ const LinkFeatureClient: FeatureProviderProviderClient = (props) => }, key: 'link', label: `Link`, - onClick: ({ editor, isActive }) => { + onSelect: ({ editor, isActive }) => { if (!isActive) { let selectedText = null editor.getEditorState().read(() => { @@ -67,25 +86,6 @@ const LinkFeatureClient: FeatureProviderProviderClient = (props) => ]), ], }, - nodes: [LinkNode, AutoLinkNode], - plugins: [ - { - Component: LinkPlugin, - position: 'normal', - }, - { - Component: AutoLinkPlugin, - position: 'normal', - }, - { - Component: ClickableLinkPlugin, - position: 'normal', - }, - { - Component: FloatingLinkEditorPlugin, - position: 'floatingAnchorElem', - }, - ], }), } } diff --git a/packages/richtext-lexical/src/field/features/lists/checklist/feature.client.tsx b/packages/richtext-lexical/src/field/features/lists/checklist/feature.client.tsx index aeb6291b69a..37b867ce378 100644 --- a/packages/richtext-lexical/src/field/features/lists/checklist/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/lists/checklist/feature.client.tsx @@ -3,15 +3,14 @@ import { INSERT_CHECK_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/list import type { ClientFeature, FeatureProviderProviderClient } from '../../types.js' -import { SlashMenuOption } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js' import { ChecklistIcon } from '../../../lexical/ui/icons/Checklist/index.js' -import { TextDropdownSectionWithEntries } from '../../common/floatingSelectToolbarTextDropdownSection/index.js' import { createClientComponent } from '../../createClientComponent.js' +import { inlineToolbarTextDropdownGroupWithItems } from '../../shared/inlineToolbar/textDropdownGroup.js' import { LexicalListPlugin } from '../plugin/index.js' import { CHECK_LIST } from './markdownTransformers.js' import { LexicalCheckListPlugin } from './plugin/index.js' -const CheckListFeatureClient: FeatureProviderProviderClient = (props) => { +const ChecklistFeatureClient: FeatureProviderProviderClient = (props) => { return { clientFeatureProps: props, feature: ({ featureProviderMap }) => { @@ -22,7 +21,7 @@ const CheckListFeatureClient: FeatureProviderProviderClient = (props) }, ] - if (!featureProviderMap.has('unorderedlist') && !featureProviderMap.has('orderedlist')) { + if (!featureProviderMap.has('unorderedList') && !featureProviderMap.has('orderedList')) { plugins.push({ Component: LexicalListPlugin, position: 'normal', @@ -31,49 +30,50 @@ const CheckListFeatureClient: FeatureProviderProviderClient = (props) return { clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - TextDropdownSectionWithEntries([ - { - ChildComponent: ChecklistIcon, - isActive: () => false, - key: 'checkList', - label: `Check List`, - onClick: ({ editor }) => { - editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined) - }, - order: 12, - }, - ]), - ], - }, markdownTransformers: [CHECK_LIST], nodes: - featureProviderMap.has('unorderedlist') || featureProviderMap.has('orderedlist') + featureProviderMap.has('unorderedList') || featureProviderMap.has('orderedList') ? [] : [ListNode, ListItemNode], plugins, slashMenu: { - options: [ + groups: [ { displayName: 'Lists', - key: 'lists', - options: [ - new SlashMenuOption('checklist', { + items: [ + { Icon: ChecklistIcon, displayName: 'Check List', + key: 'checklist', keywords: ['check list', 'check', 'checklist', 'cl'], onSelect: ({ editor }) => { editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined) }, - }), + }, ], + key: 'lists', }, ], }, + toolbarInline: { + groups: [ + inlineToolbarTextDropdownGroupWithItems([ + { + ChildComponent: ChecklistIcon, + isActive: () => false, + key: 'checklist', + label: `Check List`, + onSelect: ({ editor }) => { + editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined) + }, + order: 12, + }, + ]), + ], + }, } }, } } -export const CheckListFeatureClientComponent = createClientComponent(CheckListFeatureClient) +export const ChecklistFeatureClientComponent = createClientComponent(ChecklistFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/lists/checklist/feature.server.ts b/packages/richtext-lexical/src/field/features/lists/checklist/feature.server.ts index 0eac95080fb..9476640fb40 100644 --- a/packages/richtext-lexical/src/field/features/lists/checklist/feature.server.ts +++ b/packages/richtext-lexical/src/field/features/lists/checklist/feature.server.ts @@ -4,17 +4,17 @@ import type { FeatureProviderProviderServer } from '../../types.js' import { createNode } from '../../typeUtilities.js' import { ListHTMLConverter, ListItemHTMLConverter } from '../htmlConverter.js' -import { CheckListFeatureClientComponent } from './feature.client.js' +import { ChecklistFeatureClientComponent } from './feature.client.js' import { CHECK_LIST } from './markdownTransformers.js' -export const CheckListFeature: FeatureProviderProviderServer = (props) => { +export const ChecklistFeature: FeatureProviderProviderServer = (props) => { return { feature: ({ featureProviderMap }) => { return { - ClientComponent: CheckListFeatureClientComponent, + ClientComponent: ChecklistFeatureClientComponent, markdownTransformers: [CHECK_LIST], nodes: - featureProviderMap.has('unorderedlist') || featureProviderMap.has('orderedlist') + featureProviderMap.has('unorderedList') || featureProviderMap.has('orderedList') ? [] : [ createNode({ diff --git a/packages/richtext-lexical/src/field/features/lists/checklist/markdownTransformers.ts b/packages/richtext-lexical/src/field/features/lists/checklist/markdownTransformers.ts index 5c40b9f602c..eb96d99cefb 100644 --- a/packages/richtext-lexical/src/field/features/lists/checklist/markdownTransformers.ts +++ b/packages/richtext-lexical/src/field/features/lists/checklist/markdownTransformers.ts @@ -2,7 +2,7 @@ import type { ElementTransformer } from '@lexical/markdown' import { $isListNode, ListItemNode, ListNode } from '@lexical/list' -import { listExport, listReplace } from '../common/markdown.js' +import { listExport, listReplace } from '../shared/markdown.js' export const CHECK_LIST: ElementTransformer = { type: 'element', diff --git a/packages/richtext-lexical/src/field/features/lists/orderedlist/feature.client.tsx b/packages/richtext-lexical/src/field/features/lists/orderedList/feature.client.tsx similarity index 74% rename from packages/richtext-lexical/src/field/features/lists/orderedlist/feature.client.tsx rename to packages/richtext-lexical/src/field/features/lists/orderedList/feature.client.tsx index dfd9feca5b1..55564a67fdc 100644 --- a/packages/richtext-lexical/src/field/features/lists/orderedlist/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/lists/orderedList/feature.client.tsx @@ -3,10 +3,9 @@ import { INSERT_ORDERED_LIST_COMMAND, ListItemNode, ListNode } from '@lexical/li import type { FeatureProviderProviderClient } from '../../types.js' -import { SlashMenuOption } from '../../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js' import { OrderedListIcon } from '../../../lexical/ui/icons/OrderedList/index.js' -import { TextDropdownSectionWithEntries } from '../../common/floatingSelectToolbarTextDropdownSection/index.js' import { createClientComponent } from '../../createClientComponent.js' +import { inlineToolbarTextDropdownGroupWithItems } from '../../shared/inlineToolbar/textDropdownGroup.js' import { LexicalListPlugin } from '../plugin/index.js' import { ORDERED_LIST } from './markdownTransformer.js' @@ -16,25 +15,9 @@ const OrderedListFeatureClient: FeatureProviderProviderClient = (prop feature: ({ featureProviderMap }) => { return { clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - TextDropdownSectionWithEntries([ - { - ChildComponent: OrderedListIcon, - isActive: () => false, - key: 'orderedList', - label: `Ordered List`, - onClick: ({ editor }) => { - editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined) - }, - order: 10, - }, - ]), - ], - }, markdownTransformers: [ORDERED_LIST], - nodes: featureProviderMap.has('unorderedlist') ? [] : [ListNode, ListItemNode], - plugins: featureProviderMap.has('unorderedlist') + nodes: featureProviderMap.has('orderedList') ? [] : [ListNode, ListItemNode], + plugins: featureProviderMap.has('orderedList') ? [] : [ { @@ -43,23 +26,40 @@ const OrderedListFeatureClient: FeatureProviderProviderClient = (prop }, ], slashMenu: { - options: [ + groups: [ { displayName: 'Lists', - key: 'lists', - options: [ - new SlashMenuOption('orderedlist', { + items: [ + { Icon: OrderedListIcon, displayName: 'Ordered List', + key: 'orderedList', keywords: ['ordered list', 'ol'], onSelect: ({ editor }) => { editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined) }, - }), + }, ], + key: 'lists', }, ], }, + toolbarInline: { + groups: [ + inlineToolbarTextDropdownGroupWithItems([ + { + ChildComponent: OrderedListIcon, + isActive: () => false, + key: 'orderedList', + label: `Ordered List`, + onSelect: ({ editor }) => { + editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined) + }, + order: 10, + }, + ]), + ], + }, } }, } diff --git a/packages/richtext-lexical/src/field/features/lists/orderedlist/feature.server.ts b/packages/richtext-lexical/src/field/features/lists/orderedList/feature.server.ts similarity index 98% rename from packages/richtext-lexical/src/field/features/lists/orderedlist/feature.server.ts rename to packages/richtext-lexical/src/field/features/lists/orderedList/feature.server.ts index 201778ea651..379da7c945b 100644 --- a/packages/richtext-lexical/src/field/features/lists/orderedlist/feature.server.ts +++ b/packages/richtext-lexical/src/field/features/lists/orderedList/feature.server.ts @@ -32,7 +32,7 @@ export const OrderedListFeature: FeatureProviderProviderServer = (pr feature: () => { return { clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - TextDropdownSectionWithEntries([ - { - ChildComponent: UnorderedListIcon, - isActive: () => false, - key: 'unorderedList', - label: `Unordered List`, - onClick: ({ editor }) => { - editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined) - }, - order: 11, - }, - ]), - ], - }, markdownTransformers: [UNORDERED_LIST], nodes: [ListNode, ListItemNode], plugins: [ @@ -42,23 +26,40 @@ const UnorderedListFeatureClient: FeatureProviderProviderClient = (pr }, ], slashMenu: { - options: [ + groups: [ { displayName: 'Lists', - key: 'lists', - options: [ - new SlashMenuOption('unorderedlist', { + items: [ + { Icon: UnorderedListIcon, displayName: 'Unordered List', + key: 'unorderedList', keywords: ['unordered list', 'ul'], onSelect: ({ editor }) => { editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined) }, - }), + }, ], + key: 'lists', }, ], }, + toolbarInline: { + groups: [ + inlineToolbarTextDropdownGroupWithItems([ + { + ChildComponent: UnorderedListIcon, + isActive: () => false, + key: 'unorderedList', + label: `Unordered List`, + onSelect: ({ editor }) => { + editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined) + }, + order: 11, + }, + ]), + ], + }, } }, } diff --git a/packages/richtext-lexical/src/field/features/lists/unorderedlist/feature.server.ts b/packages/richtext-lexical/src/field/features/lists/unorderedList/feature.server.ts similarity index 97% rename from packages/richtext-lexical/src/field/features/lists/unorderedlist/feature.server.ts rename to packages/richtext-lexical/src/field/features/lists/unorderedList/feature.server.ts index 80606fc7da4..f2fd228e9d1 100644 --- a/packages/richtext-lexical/src/field/features/lists/unorderedlist/feature.server.ts +++ b/packages/richtext-lexical/src/field/features/lists/unorderedList/feature.server.ts @@ -32,7 +32,7 @@ export const UnorderedListFeature: FeatureProviderProviderServer = (props) => { return { clientFeatureProps: props, feature: () => ({ clientFeatureProps: props, - floatingSelectToolbar: { - sections: [ - TextDropdownSectionWithEntries([ - { - ChildComponent: TextIcon, - isActive: () => false, - key: 'normal-text', - label: 'Normal Text', - onClick: ({ editor }) => { - editor.update(() => { - const selection = $getSelection() - $setBlocksType(selection, () => $createParagraphNode()) - }) - }, - order: 1, - }, - ]), - ], - }, slashMenu: { - options: [ + groups: [ { displayName: 'Basic', - key: 'basic', - options: [ - new SlashMenuOption('paragraph', { + items: [ + { Icon: TextIcon, displayName: 'Paragraph', + key: 'paragraph', keywords: ['normal', 'paragraph', 'p', 'text'], onSelect: ({ editor }) => { editor.update(() => { @@ -50,11 +30,31 @@ const ParagraphFeatureClient: FeatureProviderProviderClient = (props) $setBlocksType(selection, () => $createParagraphNode()) }) }, - }), + }, ], + key: 'basic', }, ], }, + toolbarInline: { + groups: [ + inlineToolbarTextDropdownGroupWithItems([ + { + ChildComponent: TextIcon, + isActive: () => false, + key: 'paragraph', + label: 'Normal Text', + onSelect: ({ editor }) => { + editor.update(() => { + const selection = $getSelection() + $setBlocksType(selection, () => $createParagraphNode()) + }) + }, + order: 1, + }, + ]), + ], + }, }), } } diff --git a/packages/richtext-lexical/src/field/features/relationship/feature.client.tsx b/packages/richtext-lexical/src/field/features/relationship/feature.client.tsx index e716902612f..5148250371a 100644 --- a/packages/richtext-lexical/src/field/features/relationship/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/relationship/feature.client.tsx @@ -5,7 +5,6 @@ import { withMergedProps } from '@payloadcms/ui/elements/withMergedProps' import type { FeatureProviderProviderClient } from '../types.js' import type { RelationshipFeatureProps } from './feature.server.js' -import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js' import { RelationshipIcon } from '../../lexical/ui/icons/Relationship/index.js' import { createClientComponent } from '../createClientComponent.js' import { INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND } from './drawer/commands.js' @@ -30,14 +29,14 @@ const RelationshipFeatureClient: FeatureProviderProviderClient { // dispatch INSERT_RELATIONSHIP_WITH_DRAWER_COMMAND @@ -45,8 +44,9 @@ const RelationshipFeatureClient: FeatureProviderProviderClient { + return { + type: 'buttons', + items, + key: 'features', + order: 5, + } +} diff --git a/packages/richtext-lexical/src/field/features/shared/inlineToolbar/textDropdownGroup.ts b/packages/richtext-lexical/src/field/features/shared/inlineToolbar/textDropdownGroup.ts new file mode 100644 index 00000000000..02a7f0c5aec --- /dev/null +++ b/packages/richtext-lexical/src/field/features/shared/inlineToolbar/textDropdownGroup.ts @@ -0,0 +1,18 @@ +import type { + InlineToolbarGroup, + InlineToolbarGroupItem, +} from '../../../lexical/plugins/toolbars/inline/types.js' + +import { TextIcon } from '../../../lexical/ui/icons/Text/index.js' + +export const inlineToolbarTextDropdownGroupWithItems = ( + items: InlineToolbarGroupItem[], +): InlineToolbarGroup => { + return { + type: 'dropdown', + ChildComponent: TextIcon, + items, + key: 'text', + order: 1, + } +} diff --git a/packages/richtext-lexical/src/field/features/types.ts b/packages/richtext-lexical/src/field/features/types.ts index 46bb02e72ff..0e92cae59c5 100644 --- a/packages/richtext-lexical/src/field/features/types.ts +++ b/packages/richtext-lexical/src/field/features/types.ts @@ -17,8 +17,9 @@ import type React from 'react' import type { AdapterProps } from '../../types.js' import type { ClientEditorConfig, ServerEditorConfig } from '../lexical/config/types.js' -import type { FloatingToolbarSection } from '../lexical/plugins/FloatingSelectToolbar/types.js' import type { SlashMenuGroup } from '../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js' +import type { FixedToolbarGroup } from '../lexical/plugins/toolbars/fixed/types.js' +import type { InlineToolbarGroup } from '../lexical/plugins/toolbars/inline/types.js' import type { HTMLConverter } from './converters/html/converter/types.js' export type PopulationPromise = ({ @@ -131,10 +132,6 @@ export type ClientFeature = { * Return props, to make it easy to retrieve passed in props to this Feature for the client if anyone wants to */ clientFeatureProps: ClientComponentProps - - floatingSelectToolbar?: { - sections: FloatingToolbarSection[] - } hooks?: { load?: ({ incomingEditorState, @@ -149,6 +146,9 @@ export type ClientFeature = { } markdownTransformers?: Transformer[] nodes?: Array | LexicalNodeReplacement> + /** + * Plugins are react component which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality + */ plugins?: Array< | { // plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality @@ -172,14 +172,39 @@ export type ClientFeature = { } > slashMenu?: { - dynamicOptions?: ({ + /** + * Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash). + * Thus, to re-calculate the available groups, this function will be called every time you type after the /. + * + * The groups provided by dynamicGroups will be merged with the static groups provided by the groups property. + */ + dynamicGroups?: ({ editor, queryString, }: { editor: LexicalEditor queryString: string }) => SlashMenuGroup[] - options?: SlashMenuGroup[] + /** + * Static array of groups together with the items in them. These will always be present. + * While typing after the /, they will be filtered by the query string and the keywords, key and display name of the items. + */ + groups?: SlashMenuGroup[] + } + /** + * An opt-in, classic fixed toolbar which stays at the top of the editor + */ + toolbarFixed?: { + groups: FixedToolbarGroup[] + } + /** + * The default, floating toolbar which appears when you select text. + */ + toolbarInline?: { + /** + * Array of toolbar groups / sections. Each section can contain multiple toolbar items. + */ + groups: InlineToolbarGroup[] } } @@ -302,6 +327,9 @@ export type ResolvedClientFeatureMap = Map> export type ClientFeatureProviderMap = Map> +/** + * Plugins are react component which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality + */ export type SanitizedPlugin = | { // plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality @@ -381,13 +409,13 @@ export type SanitizedServerFeatures = Required< } export type SanitizedClientFeatures = Required< - Pick, 'markdownTransformers' | 'nodes'> + Pick< + ResolvedClientFeature, + 'markdownTransformers' | 'nodes' | 'toolbarFixed' | 'toolbarInline' + > > & { /** The keys of all enabled features */ enabledFeatures: string[] - floatingSelectToolbar: { - sections: FloatingToolbarSection[] - } hooks: { load: Array< ({ @@ -404,11 +432,24 @@ export type SanitizedClientFeatures = Required< }) => SerializedEditorState > } + /** + * Plugins are react component which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality + */ plugins?: Array slashMenu: { - dynamicOptions: Array< + /** + * Dynamic groups allow you to add different groups depending on the query string (so, the text after the slash). + * Thus, to re-calculate the available groups, this function will be called every time you type after the /. + * + * The groups provided by dynamicGroups will be merged with the static groups provided by the groups property. + */ + dynamicGroups: Array< ({ editor, queryString }: { editor: LexicalEditor; queryString: string }) => SlashMenuGroup[] > - groupsWithOptions: SlashMenuGroup[] + /** + * Static array of groups together with the items in them. These will always be present. + * While typing after the /, they will be filtered by the query string and the keywords, key and display name of the items. + */ + groups: SlashMenuGroup[] } } diff --git a/packages/richtext-lexical/src/field/features/upload/feature.client.tsx b/packages/richtext-lexical/src/field/features/upload/feature.client.tsx index 032a95e2dbc..57d3869c9b6 100644 --- a/packages/richtext-lexical/src/field/features/upload/feature.client.tsx +++ b/packages/richtext-lexical/src/field/features/upload/feature.client.tsx @@ -2,7 +2,6 @@ import type { FeatureProviderProviderClient } from '../types.js' -import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js' import { UploadIcon } from '../../lexical/ui/icons/Upload/index.js' import { createClientComponent } from '../createClientComponent.js' import { INSERT_UPLOAD_WITH_DRAWER_COMMAND } from './drawer/commands.js' @@ -30,22 +29,23 @@ const UploadFeatureClient: FeatureProviderProviderClient { editor.dispatchCommand(INSERT_UPLOAD_WITH_DRAWER_COMMAND, { replace: false, }) }, - }), + }, ], + key: 'basic', }, ], }, diff --git a/packages/richtext-lexical/src/field/lexical/LexicalEditor.scss b/packages/richtext-lexical/src/field/lexical/LexicalEditor.scss index c1d37ab978c..09b9e1403ee 100644 --- a/packages/richtext-lexical/src/field/lexical/LexicalEditor.scss +++ b/packages/richtext-lexical/src/field/lexical/LexicalEditor.scss @@ -1,3 +1,5 @@ +@import '../../scss/styles'; + .rich-text-lexical { .editor { position: relative; @@ -23,7 +25,7 @@ .editor-placeholder { position: absolute; top: 8px; - left: 0px; + left: 0; font-size: 15px; color: var(--theme-elevation-500); /* Prevent text selection */ @@ -36,21 +38,3 @@ pointer-events: none; } } - -.floating-select-toolbar-popup__section-dropdown-align { - display: flex; - gap: 2px; -} -.floating-select-toolbar-popup__section-features { - display: flex; - gap: 2px; -} -.floating-select-toolbar-popup__section-dropdown-text { - display: flex; - gap: 2px; -} - -.floating-select-toolbar-popup__section-format { - display: flex; - gap: 2px; -} diff --git a/packages/richtext-lexical/src/field/lexical/LexicalEditor.tsx b/packages/richtext-lexical/src/field/lexical/LexicalEditor.tsx index 9446a7bd2c3..ec93bfae6a3 100644 --- a/packages/richtext-lexical/src/field/lexical/LexicalEditor.tsx +++ b/packages/richtext-lexical/src/field/lexical/LexicalEditor.tsx @@ -12,11 +12,11 @@ import type { LexicalProviderProps } from './LexicalProvider.js' import { EditorPlugin } from './EditorPlugin.js' import './LexicalEditor.scss' -import { FloatingSelectToolbarPlugin } from './plugins/FloatingSelectToolbar/index.js' import { MarkdownShortcutPlugin } from './plugins/MarkdownShortcut/index.js' import { SlashMenuPlugin } from './plugins/SlashMenu/index.js' import { AddBlockHandlePlugin } from './plugins/handles/AddBlockHandlePlugin/index.js' import { DraggableBlockPlugin } from './plugins/handles/DraggableBlockPlugin/index.js' +import { FloatingSelectToolbarPlugin } from './plugins/toolbars/inline/Toolbar/index.js' import { LexicalContentEditable } from './ui/ContentEditable.js' export const LexicalEditor: React.FC> = ( diff --git a/packages/richtext-lexical/src/field/lexical/config/client/sanitize.ts b/packages/richtext-lexical/src/field/lexical/config/client/sanitize.ts index f192194f15b..9e025d9bb41 100644 --- a/packages/richtext-lexical/src/field/lexical/config/client/sanitize.ts +++ b/packages/richtext-lexical/src/field/lexical/config/client/sanitize.ts @@ -10,9 +10,6 @@ export const sanitizeClientFeatures = ( ): SanitizedClientFeatures => { const sanitized: SanitizedClientFeatures = { enabledFeatures: [], - floatingSelectToolbar: { - sections: [], - }, hooks: { load: [], save: [], @@ -21,8 +18,14 @@ export const sanitizeClientFeatures = ( nodes: [], plugins: [], slashMenu: { - dynamicOptions: [], - groupsWithOptions: [], + dynamicGroups: [], + groups: [], + }, + toolbarFixed: { + groups: [], + }, + toolbarInline: { + groups: [], }, } @@ -53,60 +56,82 @@ export const sanitizeClientFeatures = ( }) } - if (feature.floatingSelectToolbar?.sections?.length) { - for (const section of feature.floatingSelectToolbar.sections) { - // 1. find the section with the same key or create new one - let foundSection = sanitized.floatingSelectToolbar.sections.find( - (sanitizedSection) => sanitizedSection.key === section.key, + if (feature.toolbarInline?.groups?.length) { + for (const group of feature.toolbarInline.groups) { + // 1. find the group with the same key or create new one + let foundGroup = sanitized.toolbarInline.groups.find( + (sanitizedGroup) => sanitizedGroup.key === group.key, ) - if (!foundSection) { - foundSection = { - ...section, - entries: [], + if (!foundGroup) { + foundGroup = { + ...group, + items: [], } } else { - sanitized.floatingSelectToolbar.sections = - sanitized.floatingSelectToolbar.sections.filter( - (sanitizedSection) => sanitizedSection.key !== section.key, - ) + sanitized.toolbarInline.groups = sanitized.toolbarInline.groups.filter( + (sanitizedGroup) => sanitizedGroup.key !== group.key, + ) } // 2. Add options to group options array and add to sanitized.slashMenu.groupsWithOptions - if (section?.entries?.length) { - foundSection.entries = foundSection.entries.concat(section.entries) + if (group?.items?.length) { + foundGroup.items = foundGroup.items.concat(group.items) } - sanitized.floatingSelectToolbar?.sections.push(foundSection) + sanitized.toolbarInline?.groups.push(foundGroup) } } - if (feature.slashMenu?.options) { - if (feature.slashMenu.dynamicOptions?.length) { - sanitized.slashMenu.dynamicOptions = sanitized.slashMenu.dynamicOptions.concat( - feature.slashMenu.dynamicOptions, + if (feature.toolbarFixed?.groups?.length) { + for (const group of feature.toolbarFixed.groups) { + // 1. find the group with the same key or create new one + let foundGroup = sanitized.toolbarFixed.groups.find( + (sanitizedGroup) => sanitizedGroup.key === group.key, ) + if (!foundGroup) { + foundGroup = { + ...group, + items: [], + } + } else { + sanitized.toolbarFixed.groups = sanitized.toolbarFixed.groups.filter( + (sanitizedGroup) => sanitizedGroup.key !== group.key, + ) + } + + // 2. Add options to group options array and add to sanitized.slashMenu.groupsWithOptions + if (group?.items?.length) { + foundGroup.items = foundGroup.items.concat(group.items) + } + sanitized.toolbarFixed?.groups.push(foundGroup) } + } - for (const optionGroup of feature.slashMenu.options) { - // 1. find the group with the same name or create new one - let group = sanitized.slashMenu.groupsWithOptions.find( - (group) => group.key === optionGroup.key, + if (feature.slashMenu?.groups) { + if (feature.slashMenu.dynamicGroups?.length) { + sanitized.slashMenu.dynamicGroups = sanitized.slashMenu.dynamicGroups.concat( + feature.slashMenu.dynamicGroups, ) + } + + for (const optionGroup of feature.slashMenu.groups) { + // 1. find the group with the same name or create new one + let group = sanitized.slashMenu.groups.find((group) => group.key === optionGroup.key) if (!group) { group = { ...optionGroup, - options: [], + items: [], } } else { - sanitized.slashMenu.groupsWithOptions = sanitized.slashMenu.groupsWithOptions.filter( + sanitized.slashMenu.groups = sanitized.slashMenu.groups.filter( (group) => group.key !== optionGroup.key, ) } // 2. Add options to group options array and add to sanitized.slashMenu.groupsWithOptions - if (optionGroup?.options?.length) { - group.options = group.options.concat(optionGroup.options) + if (optionGroup?.items?.length) { + group.items = group.items.concat(optionGroup.items) } - sanitized.slashMenu.groupsWithOptions.push(group) + sanitized.slashMenu.groups.push(group) } } @@ -118,8 +143,20 @@ export const sanitizeClientFeatures = ( sanitized.enabledFeatures.push(feature.key) }) - // Sort sanitized.floatingSelectToolbar.sections by order property - sanitized.floatingSelectToolbar.sections.sort((a, b) => { + // Sort sanitized.toolbarInline.groups by order property + sanitized.toolbarInline.groups.sort((a, b) => { + if (a.order && b.order) { + return a.order - b.order + } else if (a.order) { + return -1 + } else if (b.order) { + return 1 + } else { + return 0 + } + }) + // Sort sanitized.toolbarFixed.groups by order property + sanitized.toolbarFixed.groups.sort((a, b) => { if (a.order && b.order) { return a.order - b.order } else if (a.order) { @@ -131,9 +168,24 @@ export const sanitizeClientFeatures = ( } }) - // Sort sanitized.floatingSelectToolbar.sections.[section].entries by order property - for (const section of sanitized.floatingSelectToolbar.sections) { - section.entries.sort((a, b) => { + // Sort sanitized.toolbarInline.groups.[group].entries by order property + for (const group of sanitized.toolbarInline.groups) { + group.items.sort((a, b) => { + if (a.order && b.order) { + return a.order - b.order + } else if (a.order) { + return -1 + } else if (b.order) { + return 1 + } else { + return 0 + } + }) + } + + // Sort sanitized.toolbarFixed.groups.[group].entries by order property + for (const group of sanitized.toolbarFixed.groups) { + group.items.sort((a, b) => { if (a.order && b.order) { return a.order - b.order } else if (a.order) { diff --git a/packages/richtext-lexical/src/field/lexical/config/server/default.ts b/packages/richtext-lexical/src/field/lexical/config/server/default.ts index 3d059088caa..0c4fb0abe73 100644 --- a/packages/richtext-lexical/src/field/lexical/config/server/default.ts +++ b/packages/richtext-lexical/src/field/lexical/config/server/default.ts @@ -1,24 +1,24 @@ import type { EditorConfig as LexicalEditorConfig } from 'lexical' import type { FeatureProviderServer } from '../../../features/types.js' -import type { SanitizedServerEditorConfig, ServerEditorConfig } from '../types.js' +import type { ServerEditorConfig } from '../types.js' import { AlignFeature } from '../../../features/align/feature.server.js' import { BlockQuoteFeature } from '../../../features/blockquote/feature.server.js' import { BoldFeature } from '../../../features/format/bold/feature.server.js' -import { InlineCodeFeature } from '../../../features/format/inlinecode/feature.server.js' +import { InlineCodeFeature } from '../../../features/format/inlineCode/feature.server.js' import { ItalicFeature } from '../../../features/format/italic/feature.server.js' import { StrikethroughFeature } from '../../../features/format/strikethrough/feature.server.js' import { SubscriptFeature } from '../../../features/format/subscript/feature.server.js' 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 { 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' -import { OrderedListFeature } from '../../../features/lists/orderedlist/feature.server.js' -import { UnorderedListFeature } from '../../../features/lists/unorderedlist/feature.server.js' +import { ChecklistFeature } from '../../../features/lists/checklist/feature.server.js' +import { OrderedListFeature } from '../../../features/lists/orderedList/feature.server.js' +import { UnorderedListFeature } from '../../../features/lists/unorderedList/feature.server.js' import { ParagraphFeature } from '../../../features/paragraph/feature.server.js' import { RelationshipFeature } from '../../../features/relationship/feature.server.js' import { UploadFeature } from '../../../features/upload/feature.server.js' @@ -43,7 +43,7 @@ export const defaultEditorFeatures: FeatureProviderServer[] = IndentFeature(), UnorderedListFeature(), OrderedListFeature(), - CheckListFeature(), + ChecklistFeature(), LinkFeature(), RelationshipFeature(), BlockQuoteFeature(), diff --git a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/index.tsx b/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/index.tsx deleted file mode 100644 index 29618bf4308..00000000000 --- a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/index.tsx +++ /dev/null @@ -1,70 +0,0 @@ -'use client' -import React from 'react' - -const baseClass = 'floating-select-toolbar-popup__dropdown' - -import type { LexicalEditor } from 'lexical' - -import type { FloatingToolbarSectionEntry } from '../types.js' - -import { DropDown, DropDownItem } from './DropDown.js' -import './index.scss' - -export const ToolbarEntry = ({ - anchorElem, - editor, - entry, -}: { - anchorElem: HTMLElement - editor: LexicalEditor - entry: FloatingToolbarSectionEntry -}) => { - if (entry.Component) { - return ( - entry?.Component && ( - - ) - ) - } - - return ( - - {entry?.ChildComponent && } - {entry.label} - - ) -} - -export const ToolbarDropdown = ({ - Icon, - anchorElem, - classNames, - editor, - entries, - sectionKey, -}: { - Icon?: React.FC - anchorElem: HTMLElement - classNames?: string[] - editor: LexicalEditor - entries: FloatingToolbarSectionEntry[] - sectionKey: string -}) => { - return ( - - {entries.length && - entries.map((entry) => { - return ( - - ) - })} - - ) -} diff --git a/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/LexicalMenu.tsx b/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/LexicalMenu.tsx index 836c53d2147..d844ed5b9c0 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/LexicalMenu.tsx +++ b/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/LexicalMenu.tsx @@ -18,7 +18,7 @@ import { import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import type { MenuTextMatch } from '../useMenuTriggerMatch.js' -import type { SlashMenuGroup, SlashMenuOption } from './types.js' +import type { SlashMenuGroupInternal, SlashMenuItem, SlashMenuItemInternal } from './types.js' export type MenuResolution = { getRect: () => DOMRect @@ -30,10 +30,10 @@ const baseClass = 'slash-menu-popup' export type MenuRenderFn = ( anchorElementRef: MutableRefObject, itemProps: { - groupsWithOptions: Array - selectOptionAndCleanUp: (selectedOption: SlashMenuOption) => void - selectedOptionKey: null | string - setSelectedOptionKey: (optionKey: string) => void + groups: Array + selectItemAndCleanUp: (selectedItem: SlashMenuItem) => void + selectedItemKey: null | string + setSelectedItemKey: (itemKey: string) => void }, matchingString: null | string, ) => JSX.Element | ReactPortal | null @@ -63,10 +63,10 @@ const scrollIntoViewIfNeeded = (target: HTMLElement) => { * Walk backwards along user input and forward through entity title to try * and replace more of the user's text with entity. */ -function getFullMatchOffset(documentText: string, entryText: string, offset: number): number { +function getFullMatchOffset(documentText: string, entryText: string, offset: number) { let triggerOffset = offset for (let i = triggerOffset; i <= entryText.length; i++) { - if (documentText.substr(-i) === entryText.substr(0, i)) { + if (documentText.substring(documentText.length - i) === entryText.substring(0, i)) { triggerOffset = i } } @@ -194,27 +194,27 @@ export function useDynamicPositioning( export const SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND: LexicalCommand<{ index: number - option: SlashMenuOption + item: SlashMenuItemInternal }> = createCommand('SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND') export function LexicalMenu({ anchorElementRef, close, editor, - // groupsWithOptions filtering is already handled in SlashMenu/index.tsx. Thus, groupsWithOptions always contains the matching options. - groupsWithOptions, + // groups filtering is already handled in SlashMenu/index.tsx. Thus, groups always contains the matching items. + groups, menuRenderFn, - onSelectOption, + onSelectItem, resolution, shouldSplitNodeWithQuery = false, }: { anchorElementRef: MutableRefObject close: () => void editor: LexicalEditor - groupsWithOptions: Array + groups: Array menuRenderFn: MenuRenderFn - onSelectOption: ( - option: SlashMenuOption, + onSelectItem: ( + item: SlashMenuItem, textNodeContainingQuery: TextNode | null, closeMenu: () => void, matchingString: string, @@ -222,55 +222,55 @@ export function LexicalMenu({ resolution: MenuResolution shouldSplitNodeWithQuery?: boolean }): JSX.Element | null { - const [selectedOptionKey, setSelectedOptionKey] = useState(null) + const [selectedItemKey, setSelectedItemKey] = useState(null) const matchingString = (resolution.match && resolution.match.matchingString) || '' - const updateSelectedOption = useCallback( - (option: SlashMenuOption) => { + const updateSelectedItem = useCallback( + (item: SlashMenuItem) => { const rootElem = editor.getRootElement() if (rootElem !== null) { - rootElem.setAttribute('aria-activedescendant', `${baseClass}__item-${option.key}`) - setSelectedOptionKey(option.key) + rootElem.setAttribute('aria-activedescendant', `${baseClass}__item-${item.key}`) + setSelectedItemKey(item.key) } }, [editor], ) - const setSelectedOptionKeyToFirstMatchingOption = useCallback(() => { - // set selected option to the first of the matching ones - if (groupsWithOptions !== null && matchingString != null) { - // groupsWithOptions filtering is already handled in SlashMenu/index.tsx. Thus, groupsWithOptions always contains the matching options. - const allOptions = groupsWithOptions.flatMap((group) => group.options) + const setSelectedItemKeyToFirstMatchingItem = useCallback(() => { + // set selected item to the first of the matching ones + if (groups !== null && matchingString != null) { + // groups filtering is already handled in SlashMenu/index.tsx. Thus, groups always contains the matching items. + const allItems = groups.flatMap((group) => group.items) - if (allOptions.length) { - const firstMatchingOption = allOptions[0] - updateSelectedOption(firstMatchingOption) + if (allItems.length) { + const firstMatchingItem = allItems[0] + updateSelectedItem(firstMatchingItem) } } - }, [groupsWithOptions, updateSelectedOption, matchingString]) + }, [groups, updateSelectedItem, matchingString]) useEffect(() => { - setSelectedOptionKeyToFirstMatchingOption() - }, [matchingString, setSelectedOptionKeyToFirstMatchingOption]) + setSelectedItemKeyToFirstMatchingItem() + }, [matchingString, setSelectedItemKeyToFirstMatchingItem]) - const selectOptionAndCleanUp = useCallback( - (selectedOption: SlashMenuOption) => { + const selectItemAndCleanUp = useCallback( + (selectedItem: SlashMenuItem) => { editor.update(() => { const textNodeContainingQuery = resolution.match != null && shouldSplitNodeWithQuery ? $splitNodeContainingQuery(resolution.match) : null - onSelectOption( - selectedOption, + onSelectItem( + selectedItem, textNodeContainingQuery, close, resolution.match ? resolution.match.matchingString : '', ) }) }, - [editor, shouldSplitNodeWithQuery, resolution.match, onSelectOption, close], + [editor, shouldSplitNodeWithQuery, resolution.match, onSelectItem, close], ) useEffect(() => { @@ -283,25 +283,20 @@ export function LexicalMenu({ }, [editor]) useLayoutEffect(() => { - if (groupsWithOptions === null) { - setSelectedOptionKey(null) - } else if (selectedOptionKey === null) { - setSelectedOptionKeyToFirstMatchingOption() + if (groups === null) { + setSelectedItemKey(null) + } else if (selectedItemKey === null) { + setSelectedItemKeyToFirstMatchingItem() } - }, [ - groupsWithOptions, - selectedOptionKey, - updateSelectedOption, - setSelectedOptionKeyToFirstMatchingOption, - ]) + }, [groups, selectedItemKey, updateSelectedItem, setSelectedItemKeyToFirstMatchingItem]) useEffect(() => { return mergeRegister( editor.registerCommand( SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, - ({ option }) => { - if (option.ref && option.ref.current != null) { - scrollIntoViewIfNeeded(option.ref.current) + ({ item }) => { + if (item.ref && item.ref.current != null) { + scrollIntoViewIfNeeded(item.ref.current) return true } @@ -310,7 +305,7 @@ export function LexicalMenu({ COMMAND_PRIORITY_LOW, ), ) - }, [editor, updateSelectedOption]) + }, [editor, updateSelectedItem]) useEffect(() => { return mergeRegister( @@ -318,23 +313,19 @@ export function LexicalMenu({ KEY_ARROW_DOWN_COMMAND, (payload) => { const event = payload - if ( - groupsWithOptions !== null && - groupsWithOptions.length && - selectedOptionKey !== null - ) { - const allOptions = groupsWithOptions.flatMap((group) => group.options) - const selectedIndex = allOptions.findIndex((option) => option.key === selectedOptionKey) + if (groups !== null && groups.length && selectedItemKey !== null) { + const allItems = groups.flatMap((group) => group.items) + const selectedIndex = allItems.findIndex((item) => item.key === selectedItemKey) - const newSelectedIndex = selectedIndex !== allOptions.length - 1 ? selectedIndex + 1 : 0 + const newSelectedIndex = selectedIndex !== allItems.length - 1 ? selectedIndex + 1 : 0 - const newSelectedOption = allOptions[newSelectedIndex] + const newSelectedItem = allItems[newSelectedIndex] - updateSelectedOption(newSelectedOption) - if (newSelectedOption.ref != null && newSelectedOption.ref.current) { + updateSelectedItem(newSelectedItem) + if (newSelectedItem.ref != null && newSelectedItem.ref.current) { editor.dispatchCommand(SCROLL_TYPEAHEAD_OPTION_INTO_VIEW_COMMAND, { index: newSelectedIndex, - option: newSelectedOption, + item: newSelectedItem, }) } event.preventDefault() @@ -348,21 +339,17 @@ export function LexicalMenu({ KEY_ARROW_UP_COMMAND, (payload) => { const event = payload - if ( - groupsWithOptions !== null && - groupsWithOptions.length && - selectedOptionKey !== null - ) { - const allOptions = groupsWithOptions.flatMap((group) => group.options) - const selectedIndex = allOptions.findIndex((option) => option.key === selectedOptionKey) + if (groups !== null && groups.length && selectedItemKey !== null) { + const allItems = groups.flatMap((group) => group.items) + const selectedIndex = allItems.findIndex((item) => item.key === selectedItemKey) - const newSelectedIndex = selectedIndex !== 0 ? selectedIndex - 1 : allOptions.length - 1 + const newSelectedIndex = selectedIndex !== 0 ? selectedIndex - 1 : allItems.length - 1 - const newSelectedOption = allOptions[newSelectedIndex] + const newSelectedItem = allItems[newSelectedIndex] - updateSelectedOption(newSelectedOption) - if (newSelectedOption.ref != null && newSelectedOption.ref.current) { - scrollIntoViewIfNeeded(newSelectedOption.ref.current) + updateSelectedItem(newSelectedItem) + if (newSelectedItem.ref != null && newSelectedItem.ref.current) { + scrollIntoViewIfNeeded(newSelectedItem.ref.current) } event.preventDefault() event.stopImmediatePropagation() @@ -387,18 +374,18 @@ export function LexicalMenu({ (payload) => { const event = payload - if (groupsWithOptions === null || selectedOptionKey === null) { + if (groups === null || selectedItemKey === null) { return false } - const allOptions = groupsWithOptions.flatMap((group) => group.options) - const selectedOption = allOptions.find((option) => option.key === selectedOptionKey) - if (!selectedOption) { + const allItems = groups.flatMap((group) => group.items) + const selectedItem = allItems.find((item) => item.key === selectedItemKey) + if (!selectedItem) { return false } event.preventDefault() event.stopImmediatePropagation() - selectOptionAndCleanUp(selectedOption) + selectItemAndCleanUp(selectedItem) return true }, COMMAND_PRIORITY_LOW, @@ -406,12 +393,12 @@ export function LexicalMenu({ editor.registerCommand( KEY_ENTER_COMMAND, (event: KeyboardEvent | null) => { - if (groupsWithOptions === null || selectedOptionKey === null) { + if (groups === null || selectedItemKey === null) { return false } - const allOptions = groupsWithOptions.flatMap((group) => group.options) - const selectedOption = allOptions.find((option) => option.key === selectedOptionKey) - if (!selectedOption) { + const allItems = groups.flatMap((group) => group.items) + const selectedItem = allItems.find((item) => item.key === selectedItemKey) + if (!selectedItem) { return false } @@ -419,29 +406,22 @@ export function LexicalMenu({ event.preventDefault() event.stopImmediatePropagation() } - selectOptionAndCleanUp(selectedOption) + selectItemAndCleanUp(selectedItem) return true }, COMMAND_PRIORITY_LOW, ), ) - }, [ - selectOptionAndCleanUp, - close, - editor, - groupsWithOptions, - selectedOptionKey, - updateSelectedOption, - ]) + }, [selectItemAndCleanUp, close, editor, groups, selectedItemKey, updateSelectedItem]) const listItemProps = useMemo( () => ({ - groupsWithOptions, - selectOptionAndCleanUp, - selectedOptionKey, - setSelectedOptionKey, + groups, + selectItemAndCleanUp, + selectedItemKey, + setSelectedItemKey, }), - [selectOptionAndCleanUp, selectedOptionKey, groupsWithOptions], + [selectItemAndCleanUp, selectedItemKey, groups], ) return menuRenderFn( diff --git a/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/index.tsx b/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/index.tsx index 569e622c6c6..6e2b35a68b6 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/index.tsx +++ b/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/index.tsx @@ -21,7 +21,7 @@ import * as React from 'react' import type { MenuTextMatch, TriggerFn } from '../useMenuTriggerMatch.js' import type { MenuRenderFn, MenuResolution } from './LexicalMenu.js' -import type { SlashMenuGroup, SlashMenuOption } from './types.js' +import type { SlashMenuGroup, SlashMenuGroupInternal, SlashMenuItem } from './types.js' import { LexicalMenu, useMenuAnchorRef } from './LexicalMenu.js' @@ -128,13 +128,13 @@ export { useDynamicPositioning } from './LexicalMenu.js' export type TypeaheadMenuPluginProps = { anchorClassName?: string anchorElem: HTMLElement - groupsWithOptions: Array + groups: Array menuRenderFn: MenuRenderFn onClose?: () => void onOpen?: (resolution: MenuResolution) => void onQueryChange: (matchingString: null | string) => void - onSelectOption: ( - option: SlashMenuOption, + onSelectItem: ( + item: SlashMenuItem, textNodeContainingQuery: TextNode | null, closeMenu: () => void, matchingString: string, @@ -149,12 +149,12 @@ export const ENABLE_SLASH_MENU_COMMAND: LexicalCommand<{ export function LexicalTypeaheadMenuPlugin({ anchorClassName, anchorElem, - groupsWithOptions, + groups, menuRenderFn, onClose, onOpen, onQueryChange, - onSelectOption, + onSelectItem, triggerFn, }: TypeaheadMenuPluginProps): JSX.Element | null { const [editor] = useLexicalComposerContext() @@ -270,9 +270,9 @@ export function LexicalTypeaheadMenuPlugin({ anchorElementRef={anchorElementRef} close={closeTypeahead} editor={editor} - groupsWithOptions={groupsWithOptions} + groups={groups} menuRenderFn={menuRenderFn} - onSelectOption={onSelectOption} + onSelectItem={onSelectItem} resolution={resolution} shouldSplitNodeWithQuery /> diff --git a/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.ts b/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.ts index c17a5f4e4fb..c7378bb6e5e 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.ts +++ b/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.ts @@ -3,7 +3,7 @@ import type { LexicalEditor } from 'lexical' import type { MutableRefObject } from 'react' import type React from 'react' -export class SlashMenuOption { +export type SlashMenuItem = { // Icon for display Icon: React.FC @@ -13,41 +13,22 @@ export class SlashMenuOption { // TBD keyboardShortcut?: string // For extra searching. - keywords: Array - // What happens when you select this option? + keywords?: Array + // What happens when you select this item? onSelect: ({ editor, queryString }: { editor: LexicalEditor; queryString: string }) => void - - ref?: MutableRefObject - - constructor( - key: string, - options: { - Icon: React.FC - displayName?: (({ i18n }: { i18n: I18n }) => string) | string - keyboardShortcut?: string - keywords?: Array - onSelect: ({ editor, queryString }: { editor: LexicalEditor; queryString: string }) => void - }, - ) { - this.key = key - this.ref = { current: null } - this.setRefElement = this.setRefElement.bind(this) - - this.displayName = options.displayName - this.keywords = options.keywords || [] - this.Icon = options.Icon - this.keyboardShortcut = options.keyboardShortcut - this.onSelect = options.onSelect.bind(this) - } - - setRefElement(element: HTMLElement | null) { - this.ref = { current: element } - } } -export class SlashMenuGroup { +export type SlashMenuGroup = { // Used for class names and, if displayName is not provided, for display. displayName?: (({ i18n }: { i18n: I18n }) => string) | string + items: Array key: string - options: Array +} + +export type SlashMenuItemInternal = SlashMenuItem & { + ref: MutableRefObject +} + +export type SlashMenuGroupInternal = SlashMenuGroup & { + items: Array } diff --git a/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/index.tsx b/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/index.tsx index a2e732794c8..65a969410a8 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/index.tsx +++ b/packages/richtext-lexical/src/field/lexical/plugins/SlashMenu/index.tsx @@ -7,7 +7,12 @@ import { useCallback, useMemo, useState } from 'react' import * as React from 'react' import * as ReactDOM from 'react-dom' -import type { SlashMenuGroup, SlashMenuOption } from './LexicalTypeaheadMenuPlugin/types.js' +import type { + SlashMenuGroup, + SlashMenuGroupInternal, + SlashMenuItemInternal, + SlashMenuItem as SlashMenuItemType, +} from './LexicalTypeaheadMenuPlugin/types.js' import { useEditorConfigContext } from '../../config/client/EditorConfigProvider.js' import { LexicalTypeaheadMenuPlugin } from './LexicalTypeaheadMenuPlugin/index.js' @@ -18,27 +23,26 @@ const baseClass = 'slash-menu-popup' function SlashMenuItem({ isSelected, + item, onClick, onMouseEnter, - option, }: { index: number isSelected: boolean + item: SlashMenuItemInternal onClick: () => void onMouseEnter: () => void - option: SlashMenuOption }) { const { i18n } = useTranslation() - let className = `${baseClass}__item ${baseClass}__item-${option.key}` + let className = `${baseClass}__item ${baseClass}__item-${item.key}` if (isSelected) { className += ` ${baseClass}__item--selected` } - let title = option.key - if (option.displayName) { - title = - typeof option.displayName === 'function' ? option.displayName({ i18n }) : option.displayName + let title = item.key + if (item.displayName) { + title = typeof item.displayName === 'function' ? item.displayName({ i18n }) : item.displayName } // Crop title to max. 50 characters if (title.length > 25) { @@ -49,16 +53,16 @@ function SlashMenuItem({ @@ -69,7 +73,7 @@ export function SlashMenuPlugin({ anchorElem = document.body, }: { anchorElem?: HTMLElement -}): JSX.Element { +}): React.ReactElement { const [editor] = useLexicalComposerContext() const [queryString, setQueryString] = useState(null) const { editorConfig } = useEditorConfigContext() @@ -79,94 +83,97 @@ export function SlashMenuPlugin({ minLength: 0, }) - const getDynamicOptions = useCallback(() => { - let groupWithOptions: Array = [] + const getDynamicItems = useCallback(() => { + let groupWithItems: Array = [] - for (const dynamicOption of editorConfig.features.slashMenu.dynamicOptions) { - const dynamicGroupWithOptions = dynamicOption({ + for (const dynamicItem of editorConfig.features.slashMenu.dynamicGroups) { + const dynamicGroupWithItems = dynamicItem({ editor, queryString, }) - groupWithOptions = groupWithOptions.concat(dynamicGroupWithOptions) + groupWithItems = groupWithItems.concat(dynamicGroupWithItems) } - return groupWithOptions + return groupWithItems }, [editor, queryString, editorConfig?.features]) const groups: SlashMenuGroup[] = useMemo(() => { - let groupsWithOptions: SlashMenuGroup[] = [] - for (const groupWithOption of editorConfig?.features.slashMenu.groupsWithOptions ?? []) { - groupsWithOptions.push(groupWithOption) + let groupsWithItems: SlashMenuGroup[] = [] + for (const groupWithItem of editorConfig?.features.slashMenu.groups ?? []) { + groupsWithItems.push(groupWithItem) } if (queryString) { // Filter current groups first - groupsWithOptions = groupsWithOptions.map((group) => { - const filteredOptions = group.options.filter((option) => { - let optionTitle = option.key - if (option.displayName) { - optionTitle = - typeof option.displayName === 'function' - ? option.displayName({ i18n }) - : option.displayName + groupsWithItems = groupsWithItems.map((group) => { + const filteredItems = group.items.filter((item) => { + let itemTitle = item.key + if (item.displayName) { + itemTitle = + typeof item.displayName === 'function' ? item.displayName({ i18n }) : item.displayName } - return new RegExp(queryString, 'gi').exec(optionTitle) || option.keywords != null - ? option.keywords.some((keyword) => new RegExp(queryString, 'gi').exec(keyword)) - : false + if (new RegExp(queryString, 'gi').exec(itemTitle)) { + return true + } + if (item.keywords != null) { + return item.keywords.some((keyword) => new RegExp(queryString, 'gi').exec(keyword)) + } + return false }) - if (filteredOptions.length) { + if (filteredItems.length) { return { ...group, - options: filteredOptions, + items: filteredItems, } } return null }) - groupsWithOptions = groupsWithOptions.filter((group) => group != null) + groupsWithItems = groupsWithItems.filter((group) => group != null) // Now add dynamic groups - const dynamicOptionGroups = getDynamicOptions() + const dynamicItemGroups = getDynamicItems() - // merge dynamic options into groups - for (const dynamicGroup of dynamicOptionGroups) { + // merge dynamic items into groups + for (const dynamicGroup of dynamicItemGroups) { // 1. find the group with the same name or create new one - let group = groupsWithOptions.find((group) => group.key === dynamicGroup.key) + let group = groupsWithItems.find((group) => group.key === dynamicGroup.key) if (!group) { group = { ...dynamicGroup, - options: [], + items: [], } } else { - groupsWithOptions = groupsWithOptions.filter((group) => group.key !== dynamicGroup.key) + groupsWithItems = groupsWithItems.filter((group) => group.key !== dynamicGroup.key) } - // 2. Add options to group options array and add to sanitized.slashMenu.groupsWithOptions - if (group?.options?.length) { - group.options = group.options.concat(group.options) + // 2. Add items to group items array and add to sanitized.slashMenu.groupsWithItems + if (group?.items?.length) { + group.items = group.items.concat(group.items) } - groupsWithOptions.push(group) + groupsWithItems.push(group) } } - return groupsWithOptions - }, [getDynamicOptions, queryString, editorConfig?.features, i18n]) + return groupsWithItems + }, [getDynamicItems, queryString, editorConfig?.features, i18n]) - const onSelectOption = useCallback( + const onSelectItem = useCallback( ( - selectedOption: SlashMenuOption, + selectedItem: SlashMenuItemType, nodeToRemove: TextNode | null, closeMenu: () => void, matchingString: string, ) => { - editor.update(() => { - if (nodeToRemove) { + if (nodeToRemove) { + editor.update(() => { nodeToRemove.remove() - } - selectedOption.onSelect({ editor, queryString: matchingString }) - closeMenu() - }) + }) + } + selectedItem.onSelect({ editor, queryString: matchingString }) + + closeMenu() }, [editor], ) @@ -175,10 +182,10 @@ export function SlashMenuPlugin({ anchorElementRef.current && groups.length ? ReactDOM.createPortal( @@ -198,19 +205,19 @@ export function SlashMenuPlugin({ key={group.key} >
{groupTitle}
- {group.options.map((option, oi: number) => ( + {group.items.map((item, oi: number) => ( { - setSelectedOptionKey(option.key) - selectOptionAndCleanUp(option) + setSelectedItemKey(item.key) + selectItemAndCleanUp(item) }} onMouseEnter={() => { - setSelectedOptionKey(option.key) + setSelectedItemKey(item.key) }} - option={option} /> ))} @@ -222,7 +229,7 @@ export function SlashMenuPlugin({ : null } onQueryChange={setQueryString} - onSelectOption={onSelectOption} + onSelectItem={onSelectItem} triggerFn={checkForTriggerMatch} />
diff --git a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/types.ts b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/fixed/types.ts similarity index 74% rename from packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/types.ts rename to packages/richtext-lexical/src/field/lexical/plugins/toolbars/fixed/types.ts index 612862bed01..77afed47ec1 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/types.ts +++ b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/fixed/types.ts @@ -1,28 +1,28 @@ import type { BaseSelection, LexicalEditor } from 'lexical' import type React from 'react' -export type FloatingToolbarSection = +export type FixedToolbarGroup = | { ChildComponent?: React.FC - entries: Array + items: Array key: string order?: number type: 'dropdown' } | { - entries: Array + items: Array key: string order?: number type: 'buttons' } -export type FloatingToolbarSectionEntry = { +export type FixedToolbarGroupItem = { ChildComponent?: React.FC /** Use component to ignore the children and onClick properties. It does not use the default, pre-defined format Button component */ Component?: React.FC<{ anchorElem: HTMLElement editor: LexicalEditor - entry: FloatingToolbarSectionEntry + item: FixedToolbarGroupItem }> isActive?: ({ editor, selection }: { editor: LexicalEditor; selection: BaseSelection }) => boolean isEnabled?: ({ @@ -33,7 +33,7 @@ export type FloatingToolbarSectionEntry = { selection: BaseSelection }) => boolean key: string - /** The label is displayed as text if the entry is part of a dropdown section */ + /** The label is displayed as text if the item is part of a dropdown group */ label?: string onClick?: ({ editor, isActive }: { editor: LexicalEditor; isActive: boolean }) => void order?: number diff --git a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/index.scss b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/Toolbar/index.scss similarity index 88% rename from packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/index.scss rename to packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/Toolbar/index.scss index 89c2bdf8766..353d3792ccd 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/index.scss +++ b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/Toolbar/index.scss @@ -1,12 +1,12 @@ -@import '../../../../scss/styles.scss'; +@import '../../../../../../scss/styles'; html[data-theme='light'] { - .floating-select-toolbar-popup { + .inline-toolbar-popup { @include shadow-m; } } -.floating-select-toolbar-popup { +.inline-toolbar-popup { display: flex; align-items: center; background: var(--color-base-0); @@ -31,9 +31,10 @@ html[data-theme='light'] { border-top-color: var(--color-base-0); } - &__section { + &__group { display: flex; align-items: center; + gap: 2px; .icon { min-width: 20px; diff --git a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/index.tsx b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/Toolbar/index.tsx similarity index 81% rename from packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/index.tsx rename to packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/Toolbar/index.tsx index a005757278b..8539ce5694e 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/index.tsx +++ b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/Toolbar/index.tsx @@ -14,93 +14,88 @@ import { useCallback, useEffect, useRef, useState } from 'react' import * as React from 'react' import { createPortal } from 'react-dom' -import type { FloatingToolbarSection, FloatingToolbarSectionEntry } from './types.js' +import type { InlineToolbarGroup, InlineToolbarGroupItem } from '../types.js' -import { useEditorConfigContext } from '../../config/client/EditorConfigProvider.js' -import { getDOMRangeRect } from '../../utils/getDOMRangeRect.js' -import { setFloatingElemPosition } from '../../utils/setFloatingElemPosition.js' -import { ToolbarButton } from './ToolbarButton/index.js' -import { ToolbarDropdown } from './ToolbarDropdown/index.js' +import { useEditorConfigContext } from '../../../../config/client/EditorConfigProvider.js' +import { getDOMRangeRect } from '../../../../utils/getDOMRangeRect.js' +import { setFloatingElemPosition } from '../../../../utils/setFloatingElemPosition.js' +import { ToolbarButton } from '../ToolbarButton/index.js' +import { ToolbarDropdown } from '../ToolbarDropdown/index.js' import './index.scss' -function ButtonSectionEntry({ +function ButtonGroupItem({ anchorElem, editor, - entry, + item, }: { anchorElem: HTMLElement editor: LexicalEditor - entry: FloatingToolbarSectionEntry + item: InlineToolbarGroupItem }): React.ReactNode { - if (entry.Component) { + if (item.Component) { return ( - entry?.Component && ( - + item?.Component && ( + ) ) } return ( - - {entry?.ChildComponent && } + + {item?.ChildComponent && } ) } -function ToolbarSection({ +function ToolbarGroup({ anchorElem, editor, + group, index, - section, }: { anchorElem: HTMLElement editor: LexicalEditor + group: InlineToolbarGroup index: number - section: FloatingToolbarSection }): React.ReactNode { const { editorConfig } = useEditorConfigContext() const Icon = - section?.type === 'dropdown' && section.entries.length && section.ChildComponent - ? section.ChildComponent + group?.type === 'dropdown' && group.items.length && group.ChildComponent + ? group.ChildComponent : null return (
- {section.type === 'dropdown' && - section.entries.length && + {group.type === 'dropdown' && + group.items.length && (Icon ? ( ) : ( ))} - {section.type === 'buttons' && - section.entries.length && - section.entries.map((entry) => { + {group.type === 'buttons' && + group.items.length && + group.items.map((item) => { return ( - + ) })} - {index < editorConfig.features.floatingSelectToolbar?.sections.length - 1 && ( + {index < editorConfig.features.toolbarInline?.groups.length - 1 && (
)}
@@ -262,19 +257,19 @@ function FloatingSelectToolbar({ }, [editor, updateTextFormatFloatingToolbar]) return ( -
+
{editor.isEditable() && ( {editorConfig?.features && - editorConfig.features?.floatingSelectToolbar?.sections.map((section, i) => { + editorConfig.features?.toolbarInline?.groups.map((group, i) => { return ( - ) })} diff --git a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarButton/index.scss b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarButton/index.scss similarity index 92% rename from packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarButton/index.scss rename to packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarButton/index.scss index dd9e11e1933..1b51de17abc 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarButton/index.scss +++ b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarButton/index.scss @@ -1,4 +1,4 @@ -.floating-select-toolbar-popup__button { +.inline-toolbar-popup__button { display: flex; align-items: center; vertical-align: middle; diff --git a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarButton/index.tsx b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarButton/index.tsx similarity index 83% rename from packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarButton/index.tsx rename to packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarButton/index.tsx index ec1ce22d8c9..36449217d1e 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarButton/index.tsx +++ b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarButton/index.tsx @@ -4,18 +4,18 @@ import { mergeRegister } from '@lexical/utils' import { $getSelection } from 'lexical' import React, { useCallback, useEffect, useState } from 'react' -import type { FloatingToolbarSectionEntry } from '../types.js' +import type { InlineToolbarGroupItem } from '../types.js' import './index.scss' -const baseClass = 'floating-select-toolbar-popup__button' +const baseClass = 'inline-toolbar-popup__button' export const ToolbarButton = ({ children, - entry, + item, }: { children: React.JSX.Element - entry: FloatingToolbarSectionEntry + item: InlineToolbarGroupItem }) => { const [editor] = useLexicalComposerContext() const [enabled, setEnabled] = useState(true) @@ -25,20 +25,20 @@ export const ToolbarButton = ({ const updateStates = useCallback(() => { editor.getEditorState().read(() => { const selection = $getSelection() - if (entry.isActive) { - const isActive = entry.isActive({ editor, selection }) + if (item.isActive) { + const isActive = item.isActive({ editor, selection }) if (active !== isActive) { setActive(isActive) } } - if (entry.isEnabled) { - const isEnabled = entry.isEnabled({ editor, selection }) + if (item.isEnabled) { + const isEnabled = item.isEnabled({ editor, selection }) if (enabled !== isEnabled) { setEnabled(isEnabled) } } }) - }, [active, editor, enabled, entry]) + }, [active, editor, enabled, item]) useEffect(() => { updateStates() @@ -65,7 +65,7 @@ export const ToolbarButton = ({ baseClass, enabled === false ? 'disabled' : '', active ? 'active' : '', - entry?.key ? `${baseClass}-` + entry.key : '', + item?.key ? `${baseClass}-` + item.key : '', ] .filter(Boolean) .join(' '), @@ -77,7 +77,7 @@ export const ToolbarButton = ({ className={className} onClick={() => { if (enabled !== false) { - entry.onClick({ + item.onSelect({ editor, isActive: active, }) diff --git a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/DropDown.tsx b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarDropdown/DropDown.tsx similarity index 91% rename from packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/DropDown.tsx rename to packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarDropdown/DropDown.tsx index 56a27969c40..a22ed4b0d74 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/DropDown.tsx +++ b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarDropdown/DropDown.tsx @@ -6,9 +6,9 @@ import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } fro import React from 'react' import { createPortal } from 'react-dom' -import type { FloatingToolbarSectionEntry } from '../types.js' +import type { InlineToolbarGroupItem } from '../types.js' -const baseClass = 'floating-select-toolbar-popup__dropdown-item' +const baseClass = 'inline-toolbar-popup__dropdown-item' interface DropDownContextType { registerItem: (ref: React.RefObject) => void @@ -18,11 +18,11 @@ const DropDownContext = React.createContext(null) export function DropDownItem({ children, - entry, + item, title, }: { children: React.ReactNode - entry: FloatingToolbarSectionEntry + item: InlineToolbarGroupItem title?: string }): React.ReactNode { const [editor] = useLexicalComposerContext() @@ -33,20 +33,20 @@ export function DropDownItem({ const updateStates = useCallback(() => { editor.getEditorState().read(() => { const selection = $getSelection() - if (entry.isActive) { - const isActive = entry.isActive({ editor, selection }) + if (item.isActive) { + const isActive = item.isActive({ editor, selection }) if (active !== isActive) { setActive(isActive) } } - if (entry.isEnabled) { - const isEnabled = entry.isEnabled({ editor, selection }) + if (item.isEnabled) { + const isEnabled = item.isEnabled({ editor, selection }) if (enabled !== isEnabled) { setEnabled(isEnabled) } } }) - }, [active, editor, enabled, entry]) + }, [active, editor, enabled, item]) useEffect(() => { updateStates() @@ -73,12 +73,12 @@ export function DropDownItem({ baseClass, enabled === false ? 'disabled' : '', active ? 'active' : '', - entry?.key ? `${baseClass}-${entry.key}` : '', + item?.key ? `${baseClass}-${item.key}` : '', ] .filter(Boolean) .join(' '), ) - }, [enabled, active, className, entry.key]) + }, [enabled, active, className, item.key]) const ref = useRef(null) @@ -101,7 +101,7 @@ export function DropDownItem({ className={className} onClick={() => { if (enabled !== false) { - entry.onClick({ + item.onSelect({ editor, isActive: active, }) @@ -185,7 +185,7 @@ function DropDownItems({ return (
@@ -277,7 +277,7 @@ export function DropDown({ type="button" > - + {showDropDown && diff --git a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/index.scss b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarDropdown/index.scss similarity index 84% rename from packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/index.scss rename to packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarDropdown/index.scss index 6392f460c65..639cd1d426d 100644 --- a/packages/richtext-lexical/src/field/lexical/plugins/FloatingSelectToolbar/ToolbarDropdown/index.scss +++ b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarDropdown/index.scss @@ -1,6 +1,6 @@ -@import '../../../../../scss/styles.scss'; +@import '../../../../../../scss/styles'; -.floating-select-toolbar-popup__dropdown { +.inline-toolbar-popup__dropdown { display: flex; align-items: center; vertical-align: middle; @@ -29,7 +29,7 @@ &.active { background-color: var(--color-base-100); - .floating-select-toolbar-popup__dropdown-caret { + .inline-toolbar-popup__dropdown-caret { &:after { transform: rotate(0deg); } @@ -50,7 +50,7 @@ height: 4px; opacity: 0.3; - background-image: url(../../../ui/icons/Caret/index.svg); + background-image: url(../../../../ui/icons/Caret/index.svg); background-position-y: 0px; background-position-x: 0px; } @@ -63,7 +63,7 @@ width: 132.5px; z-index: 100; - .floating-select-toolbar-popup__dropdown-item { + .inline-toolbar-popup__dropdown-item { all: unset; // reset all default button styles cursor: pointer; color: var(--color-base-900); @@ -91,7 +91,7 @@ } html[data-theme='light'] { - .floating-select-toolbar-popup__dropdown { + .inline-toolbar-popup__dropdown { &-items { position: absolute; @include shadow-m; diff --git a/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarDropdown/index.tsx b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarDropdown/index.tsx new file mode 100644 index 00000000000..aacc94c5c6b --- /dev/null +++ b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/ToolbarDropdown/index.tsx @@ -0,0 +1,68 @@ +'use client' +import React from 'react' + +const baseClass = 'inline-toolbar-popup__dropdown' + +import type { LexicalEditor } from 'lexical' + +import type { InlineToolbarGroupItem } from '../types.js' + +import { DropDown, DropDownItem } from './DropDown.js' +import './index.scss' + +export const ToolbarItem = ({ + anchorElem, + editor, + item, +}: { + anchorElem: HTMLElement + editor: LexicalEditor + item: InlineToolbarGroupItem +}) => { + if (item.Component) { + return ( + item?.Component && ( + + ) + ) + } + + return ( + + {item?.ChildComponent && } + {item.label} + + ) +} + +export const ToolbarDropdown = ({ + Icon, + anchorElem, + classNames, + editor, + groupKey, + items, +}: { + Icon?: React.FC + anchorElem: HTMLElement + classNames?: string[] + editor: LexicalEditor + groupKey: string + items: InlineToolbarGroupItem[] +}) => { + return ( + + {items.length && + items.map((item) => { + return + })} + + ) +} diff --git a/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/types.ts b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/types.ts new file mode 100644 index 00000000000..f8cca048e21 --- /dev/null +++ b/packages/richtext-lexical/src/field/lexical/plugins/toolbars/inline/types.ts @@ -0,0 +1,40 @@ +import type { BaseSelection, LexicalEditor } from 'lexical' +import type React from 'react' + +export type InlineToolbarGroup = + | { + ChildComponent?: React.FC + items: Array + key: string + order?: number + type: 'dropdown' + } + | { + items: Array + key: string + order?: number + type: 'buttons' + } + +export type InlineToolbarGroupItem = { + ChildComponent?: React.FC + /** Use component to ignore the children and onClick properties. It does not use the default, pre-defined format Button component */ + Component?: React.FC<{ + anchorElem: HTMLElement + editor: LexicalEditor + item: InlineToolbarGroupItem + }> + isActive?: ({ editor, selection }: { editor: LexicalEditor; selection: BaseSelection }) => boolean + isEnabled?: ({ + editor, + selection, + }: { + editor: LexicalEditor + selection: BaseSelection + }) => boolean + key: string + /** The label is displayed as text if the item is part of a dropdown group */ + label?: string + onSelect?: ({ editor, isActive }: { editor: LexicalEditor; isActive: boolean }) => void + order?: number +} diff --git a/packages/richtext-lexical/src/field/lexical/theme/EditorTheme.scss b/packages/richtext-lexical/src/field/lexical/theme/EditorTheme.scss index 0bea850e3b8..971a1d857b3 100644 --- a/packages/richtext-lexical/src/field/lexical/theme/EditorTheme.scss +++ b/packages/richtext-lexical/src/field/lexical/theme/EditorTheme.scss @@ -16,9 +16,7 @@ } &__quote { - margin: 0; - margin-left: 20px; - margin-bottom: 10px; + margin: 0 0 10px 20px; font-size: 15px; color: rgb(101, 103, 107); border-left-color: rgb(206, 208, 212); @@ -115,9 +113,7 @@ padding: 8px 8px 8px 52px; line-height: 1.53; font-size: 13px; - margin: 0; - margin-top: 8px; - margin-bottom: 8px; + margin: 8px 0; tab-size: 2; /* white-space: pre; */ overflow-x: auto; diff --git a/packages/richtext-lexical/src/index.ts b/packages/richtext-lexical/src/index.ts index acf549659d9..f4022e72b7d 100644 --- a/packages/richtext-lexical/src/index.ts +++ b/packages/richtext-lexical/src/index.ts @@ -29,7 +29,7 @@ let defaultSanitizedServerEditorConfig: SanitizedServerEditorConfig = null export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider { return async ({ config }) => { - let resolvedFeatureMap: ResolvedServerFeatureMap = null + let resolvedFeatureMap: ResolvedServerFeatureMap let finalSanitizedEditorConfig: SanitizedServerEditorConfig // For server only if (!props || (!props.features && !props.lexical)) { @@ -315,11 +315,10 @@ export { type SerializedBlockNode, } from './field/features/blocks/nodes/BlocksNode.js' -export { TextDropdownSectionWithEntries } from './field/features/common/floatingSelectToolbarTextDropdownSection/index.js' export { LinebreakHTMLConverter } from './field/features/converters/html/converter/converters/linebreak.js' export { ParagraphHTMLConverter } from './field/features/converters/html/converter/converters/paragraph.js' - export { TextHTMLConverter } from './field/features/converters/html/converter/converters/text.js' + export { defaultHTMLConverters } from './field/features/converters/html/converter/defaultConverters.js' export { convertLexicalNodesToHTML, @@ -335,13 +334,13 @@ export { lexicalHTML, } from './field/features/converters/html/field/index.js' export { createClientComponent } from './field/features/createClientComponent.js' -export { TestRecorderFeature } from './field/features/debug/testrecorder/feature.server.js' -export { TreeViewFeature } from './field/features/debug/treeview/feature.server.js' +export { TestRecorderFeature } from './field/features/debug/testRecorder/feature.server.js' +export { TreeViewFeature } from './field/features/debug/treeView/feature.server.js' export { BoldFeature } from './field/features/format/bold/feature.server.js' +export { InlineCodeFeature } from './field/features/format/inlineCode/feature.server.js' -export { SectionWithEntries as FormatSectionWithEntries } from './field/features/format/common/floatingSelectToolbarSection.js' -export { InlineCodeFeature } from './field/features/format/inlinecode/feature.server.js' export { ItalicFeature } from './field/features/format/italic/feature.server.js' +export { inlineToolbarFormatGroupWithItems } from './field/features/format/shared/inlineToolbarFormatGroup.js' export { StrikethroughFeature } from './field/features/format/strikethrough/feature.server.js' export { SubscriptFeature } from './field/features/format/subscript/feature.server.js' export { SuperscriptFeature } from './field/features/format/superscript/feature.server.js' @@ -350,11 +349,11 @@ export { HeadingFeature, type HeadingFeatureProps, } from './field/features/heading/feature.server.js' +export { HorizontalRuleFeature } from './field/features/horizontalRule/feature.server.js' -export { HorizontalRuleFeature } from './field/features/horizontalrule/feature.server.js' export { IndentFeature } from './field/features/indent/feature.server.js' - export { LinkFeature, type LinkFeatureServerProps } from './field/features/link/feature.server.js' + export { $createAutoLinkNode, $isAutoLinkNode, @@ -371,9 +370,9 @@ export type { SerializedAutoLinkNode, SerializedLinkNode, } from './field/features/link/nodes/types.js' -export { CheckListFeature } from './field/features/lists/checklist/feature.server.js' -export { OrderedListFeature } from './field/features/lists/orderedlist/feature.server.js' -export { UnorderedListFeature } from './field/features/lists/unorderedlist/feature.server.js' +export { ChecklistFeature } from './field/features/lists/checklist/feature.server.js' +export { OrderedListFeature } from './field/features/lists/orderedList/feature.server.js' +export { UnorderedListFeature } from './field/features/lists/unorderedList/feature.server.js' export { LexicalPluginToLexicalFeature } from './field/features/migrations/lexicalPluginToLexical/feature.server.js' export { SlateBlockquoteConverter } from './field/features/migrations/slateToLexical/converter/converters/blockquote/index.js' export { SlateHeadingConverter } from './field/features/migrations/slateToLexical/converter/converters/heading/index.js' @@ -384,18 +383,18 @@ export { SlateOrderedListConverter } from './field/features/migrations/slateToLe export { SlateRelationshipConverter } from './field/features/migrations/slateToLexical/converter/converters/relationship/index.js' export { SlateUnknownConverter } from './field/features/migrations/slateToLexical/converter/converters/unknown/index.js' export { SlateUnorderedListConverter } from './field/features/migrations/slateToLexical/converter/converters/unorderedList/index.js' - export { SlateUploadConverter } from './field/features/migrations/slateToLexical/converter/converters/upload/index.js' + export { defaultSlateConverters } from './field/features/migrations/slateToLexical/converter/defaultConverters.js' export { convertSlateNodesToLexical, convertSlateToLexical, } from './field/features/migrations/slateToLexical/converter/index.js' - export type { SlateNode, SlateNodeConverter, } from './field/features/migrations/slateToLexical/converter/types.js' + export { SlateToLexicalFeature } from './field/features/migrations/slateToLexical/feature.server.js' export { ParagraphFeature } from './field/features/paragraph/feature.server.js' export { @@ -409,6 +408,8 @@ export { RelationshipNode, type SerializedRelationshipNode, } from './field/features/relationship/nodes/RelationshipNode.js' +export { inlineToolbarFeatureButtonsGroupWithItems } from './field/features/shared/inlineToolbar/featureButtonsGroup.js' +export { inlineToolbarTextDropdownGroupWithItems } from './field/features/shared/inlineToolbar/textDropdownGroup.js' export { createNode } from './field/features/typeUtilities.js' export type { ClientComponentProps, @@ -474,17 +475,17 @@ export type { } from './field/lexical/config/types.js' export { getEnabledNodes } from './field/lexical/nodes/index.js' -export { - type FloatingToolbarSection, - type FloatingToolbarSectionEntry, -} from './field/lexical/plugins/FloatingSelectToolbar/types.js' export { ENABLE_SLASH_MENU_COMMAND } from './field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/index.js' -export type { AdapterProps } - -export { +export type { SlashMenuGroup, - SlashMenuOption, + SlashMenuItem, } from './field/lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types.js' +export type { AdapterProps } + +export type { + InlineToolbarGroup, + InlineToolbarGroupItem, +} from './field/lexical/plugins/toolbars/inline/types.js' export { CAN_USE_DOM } from './field/lexical/utils/canUseDOM.js' export { cloneDeep } from './field/lexical/utils/cloneDeep.js' export { getDOMRangeRect } from './field/lexical/utils/getDOMRangeRect.js' diff --git a/packages/richtext-slate/package.json b/packages/richtext-slate/package.json index c7bfc855491..058bac9b795 100644 --- a/packages/richtext-slate/package.json +++ b/packages/richtext-slate/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/richtext-slate", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "The officially supported Slate richtext adapter for Payload", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-azure/package.json b/packages/storage-azure/package.json index 7e49f349917..c1abe98dc9b 100644 --- a/packages/storage-azure/package.json +++ b/packages/storage-azure/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-azure", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "Payload storage adapter for Azure Blob Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-gcs/package.json b/packages/storage-gcs/package.json index acf114e3d3d..0e6e23c864c 100644 --- a/packages/storage-gcs/package.json +++ b/packages/storage-gcs/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-gcs", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "Payload storage adapter for Google Cloud Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-s3/package.json b/packages/storage-s3/package.json index d755eb9ddb2..d342ce866fb 100644 --- a/packages/storage-s3/package.json +++ b/packages/storage-s3/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-s3", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "Payload storage adapter for Amazon S3", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/storage-vercel-blob/package.json b/packages/storage-vercel-blob/package.json index 2da4d261d93..394bd708d28 100644 --- a/packages/storage-vercel-blob/package.json +++ b/packages/storage-vercel-blob/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/storage-vercel-blob", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "description": "Payload storage adapter for Vercel Blob Storage", "homepage": "https://payloadcms.com", "repository": { diff --git a/packages/translations/package.json b/packages/translations/package.json index 597dd9fec11..608d56dc56f 100644 --- a/packages/translations/package.json +++ b/packages/translations/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/translations", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/translations/src/clientKeys.ts b/packages/translations/src/clientKeys.ts index 37d8e8e88bf..02f75a520db 100644 --- a/packages/translations/src/clientKeys.ts +++ b/packages/translations/src/clientKeys.ts @@ -27,6 +27,8 @@ export const clientTranslationKeys = [ 'authentication:loggingOut', 'authentication:login', 'authentication:logOut', + 'authentication:loggedIn', + 'authentication:loggedInChangePassword', 'authentication:logout', 'authentication:logoutUser', 'authentication:logoutSuccessful', diff --git a/packages/ui/package.json b/packages/ui/package.json index 3860282c38c..045a03f1726 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/ui", - "version": "3.0.0-beta.22", + "version": "3.0.0-beta.23", "homepage": "https://payloadcms.com", "repository": { "type": "git", diff --git a/packages/ui/src/elements/ListControls/index.tsx b/packages/ui/src/elements/ListControls/index.tsx index f1ec9935373..703d649173b 100644 --- a/packages/ui/src/elements/ListControls/index.tsx +++ b/packages/ui/src/elements/ListControls/index.tsx @@ -1,9 +1,9 @@ 'use client' -import type { ClientCollectionConfig, FieldAffectingData, Where } from 'payload/types' +import type { ClientCollectionConfig, Where } from 'payload/types' import * as facelessUIImport from '@faceless-ui/window-info' import { getTranslation } from '@payloadcms/translations' -import React, { useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import AnimateHeightImport from 'react-animate-height' const AnimateHeight = (AnimateHeightImport.default || @@ -59,11 +59,22 @@ export const ListControls: React.FC = (props) => { breakpoints: { s: smallBreak }, } = useWindowInfo() + const hasWhereParam = useRef(Boolean(searchParams?.where)) + const shouldInitializeWhereOpened = validateWhereQuery(searchParams?.where) const [visibleDrawer, setVisibleDrawer] = useState<'columns' | 'sort' | 'where'>( shouldInitializeWhereOpened ? 'where' : undefined, ) + useEffect(() => { + if (hasWhereParam.current && !searchParams?.where) { + setVisibleDrawer(undefined) + hasWhereParam.current = false + } else if (searchParams?.where) { + hasWhereParam.current = true + } + }, [setVisibleDrawer, searchParams?.where]) + return (
@@ -156,6 +167,7 @@ export const ListControls: React.FC = (props) => { {enableSort && ( diff --git a/packages/ui/src/fields/ConfirmPassword/index.tsx b/packages/ui/src/fields/ConfirmPassword/index.tsx index 4eebd8069eb..d6edcec71ba 100644 --- a/packages/ui/src/fields/ConfirmPassword/index.tsx +++ b/packages/ui/src/fields/ConfirmPassword/index.tsx @@ -18,7 +18,7 @@ export type ConfirmPasswordFieldProps = { export const ConfirmPassword: React.FC = (props) => { const { disabled } = props - const password = useFormFields(([fields]) => fields.password) + const password = useFormFields(([fields]) => fields?.password) const { t } = useTranslation() const validate = useCallback( diff --git a/packages/ui/src/forms/useField/index.tsx b/packages/ui/src/forms/useField/index.tsx index 3b56c9bfd2d..0183b625223 100644 --- a/packages/ui/src/forms/useField/index.tsx +++ b/packages/ui/src/forms/useField/index.tsx @@ -58,6 +58,7 @@ export const useField = (options: Options): FieldType => { const showError = valid === false && submitted const prevValid = useRef(valid) + const prevErrorMessage = useRef(field?.errorMessage) // Method to return from `useField`, used to // update field values from field component(s) @@ -175,8 +176,9 @@ export const useField = (options: Options): FieldType => { // Only dispatch if the validation result has changed // This will prevent unnecessary rerenders - if (valid !== prevValid.current) { + if (valid !== prevValid.current || errorMessage !== prevErrorMessage.current) { prevValid.current = valid + prevErrorMessage.current = errorMessage const update: UPDATE = { type: 'UPDATE', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 798cb66077e..a708aaa1708 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,7 +170,7 @@ importers: version: 9.1.8 next: specifier: ^14.3.0-canary.7 - version: 14.3.0-canary.7(@babel/core@7.24.4)(@playwright/test@1.43.0)(react-dom@18.2.0)(react@18.2.0) + version: 14.3.0-canary.7(@babel/core@7.24.4)(@playwright/test@1.43.0)(react-dom@18.2.0)(react@18.2.0)(sass@1.74.1) node-mocks-http: specifier: ^1.14.1 version: 1.14.1 @@ -253,8 +253,8 @@ importers: specifier: ^4.7.1 version: 4.7.2 turbo: - specifier: ^1.13.2 - version: 1.13.2 + specifier: ^1.13.3 + version: 1.13.3 typescript: specifier: 5.4.5 version: 5.4.5 @@ -535,6 +535,9 @@ importers: pluralize: specifier: 8.0.0 version: 8.0.0 + ts-essentials: + specifier: 7.0.3 + version: 7.0.3(typescript@5.4.5) devDependencies: '@payloadcms/eslint-config': specifier: workspace:* @@ -548,9 +551,6 @@ importers: payload: specifier: workspace:* version: link:../payload - ts-essentials: - specifier: 7.0.3 - version: 7.0.3(typescript@5.4.5) packages/live-preview: devDependencies: @@ -803,6 +803,9 @@ importers: scmp: specifier: 2.1.0 version: 2.1.0 + ts-essentials: + specifier: 7.0.3 + version: 7.0.3(typescript@5.4.5) uuid: specifier: ^9.0.1 version: 9.0.1 @@ -930,9 +933,6 @@ importers: sharp: specifier: 0.32.6 version: 0.32.6 - ts-essentials: - specifier: 7.0.3 - version: 7.0.3(typescript@5.4.5) packages/plugin-cloud: dependencies: @@ -1188,9 +1188,6 @@ importers: lodash.get: specifier: ^4.4.2 version: 4.4.2 - react: - specifier: ^18.0.0 - version: 18.2.0 stripe: specifier: ^10.2.0 version: 10.17.0 @@ -1201,6 +1198,12 @@ importers: '@payloadcms/eslint-config': specifier: workspace:* version: link:../eslint-config-payload + '@payloadcms/next': + specifier: workspace:* + version: link:../next + '@payloadcms/translations': + specifier: workspace:* + version: link:../translations '@types/express': specifier: ^4.17.9 version: 4.17.21 @@ -1216,12 +1219,6 @@ importers: payload: specifier: workspace:* version: link:../payload - prettier: - specifier: ^2.7.1 - version: 2.8.8 - webpack: - specifier: ^5.78.0 - version: 5.91.0(@swc/core@1.4.13)(esbuild@0.19.12)(webpack-cli@5.1.4) packages/richtext-lexical: dependencies: @@ -1485,7 +1482,7 @@ importers: version: 2.3.0 next: specifier: ^14.3.0-canary.7 - version: 14.3.0-canary.7(@babel/core@7.24.4)(@playwright/test@1.43.0)(react-dom@18.2.0)(react@18.2.0) + version: 14.3.0-canary.7(@babel/core@7.24.4)(@playwright/test@1.43.0)(react-dom@18.2.0)(react@18.2.0)(sass@1.74.1) object-to-formdata: specifier: 4.5.1 version: 4.5.1 @@ -12354,48 +12351,6 @@ packages: /next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - /next@14.3.0-canary.7(@babel/core@7.24.4)(@playwright/test@1.43.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-loPrWTCvHvZgOy3rgL9+2WpxNDxlRNt462ihqm/DUuyK8LUZV1F4H920YTAu1wEiYC8RrpNUbpz8K7KRYAkQiA==} - engines: {node: '>=18.17.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 - react: ^18.0.0 - react-dom: ^18.0.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - sass: - optional: true - dependencies: - '@next/env': 14.3.0-canary.7 - '@playwright/test': 1.43.0 - '@swc/helpers': 0.5.5 - busboy: 1.6.0 - caniuse-lite: 1.0.30001607 - graceful-fs: 4.2.11 - postcss: 8.4.31 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.1(@babel/core@7.24.4)(react@18.2.0) - optionalDependencies: - '@next/swc-darwin-arm64': 14.3.0-canary.7 - '@next/swc-darwin-x64': 14.3.0-canary.7 - '@next/swc-linux-arm64-gnu': 14.3.0-canary.7 - '@next/swc-linux-arm64-musl': 14.3.0-canary.7 - '@next/swc-linux-x64-gnu': 14.3.0-canary.7 - '@next/swc-linux-x64-musl': 14.3.0-canary.7 - '@next/swc-win32-arm64-msvc': 14.3.0-canary.7 - '@next/swc-win32-ia32-msvc': 14.3.0-canary.7 - '@next/swc-win32-x64-msvc': 14.3.0-canary.7 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - /next@14.3.0-canary.7(@babel/core@7.24.4)(@playwright/test@1.43.0)(react-dom@18.2.0)(react@18.2.0)(sass@1.74.1): resolution: {integrity: sha512-loPrWTCvHvZgOy3rgL9+2WpxNDxlRNt462ihqm/DUuyK8LUZV1F4H920YTAu1wEiYC8RrpNUbpz8K7KRYAkQiA==} engines: {node: '>=18.17.0'} @@ -12438,7 +12393,6 @@ packages: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - dev: false /node-abi@3.57.0: resolution: {integrity: sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==} @@ -13918,6 +13872,7 @@ packages: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true + dev: false /prettier@3.2.5: resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} @@ -15683,7 +15638,6 @@ packages: typescript: 5.4.5 dependencies: typescript: 5.4.5 - dev: true /ts-jest@29.1.2(@babel/core@7.24.4)(esbuild@0.19.12)(jest@29.7.0)(typescript@5.4.5): resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} @@ -15788,64 +15742,64 @@ packages: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - /turbo-darwin-64@1.13.2: - resolution: {integrity: sha512-CCSuD8CfmtncpohCuIgq7eAzUas0IwSbHfI8/Q3vKObTdXyN8vAo01gwqXjDGpzG9bTEVedD0GmLbD23dR0MLA==} + /turbo-darwin-64@1.13.3: + resolution: {integrity: sha512-glup8Qx1qEFB5jerAnXbS8WrL92OKyMmg5Hnd4PleLljAeYmx+cmmnsmLT7tpaVZIN58EAAwu8wHC6kIIqhbWA==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@1.13.2: - resolution: {integrity: sha512-0HySm06/D2N91rJJ89FbiI/AodmY8B3WDSFTVEpu2+8spUw7hOJ8okWOT0e5iGlyayUP9gr31eOeL3VFZkpfCw==} + /turbo-darwin-arm64@1.13.3: + resolution: {integrity: sha512-/np2xD+f/+9qY8BVtuOQXRq5f9LehCFxamiQnwdqWm5iZmdjygC5T3uVSYuagVFsZKMvX3ycySwh8dylGTl6lg==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@1.13.2: - resolution: {integrity: sha512-7HnibgbqZrjn4lcfIouzlPu8ZHSBtURG4c7Bedu7WJUDeZo+RE1crlrQm8wuwO54S0siYqUqo7GNHxu4IXbioQ==} + /turbo-linux-64@1.13.3: + resolution: {integrity: sha512-G+HGrau54iAnbXLfl+N/PynqpDwi/uDzb6iM9hXEDG+yJnSJxaHMShhOkXYJPk9offm9prH33Khx2scXrYVW1g==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@1.13.2: - resolution: {integrity: sha512-sUq4dbpk6SNKg/Hkwn256Vj2AEYSQdG96repio894h5/LEfauIK2QYiC/xxAeW3WBMc6BngmvNyURIg7ltrePg==} + /turbo-linux-arm64@1.13.3: + resolution: {integrity: sha512-qWwEl5VR02NqRyl68/3pwp3c/olZuSp+vwlwrunuoNTm6JXGLG5pTeme4zoHNnk0qn4cCX7DFrOboArlYxv0wQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@1.13.2: - resolution: {integrity: sha512-DqzhcrciWq3dpzllJR2VVIyOhSlXYCo4mNEWl98DJ3FZ08PEzcI3ceudlH6F0t/nIcfSItK1bDP39cs7YoZHEA==} + /turbo-windows-64@1.13.3: + resolution: {integrity: sha512-Nudr4bRChfJzBPzEmpVV85VwUYRCGKecwkBFpbp2a4NtrJ3+UP1VZES653ckqCu2FRyRuS0n03v9euMbAvzH+Q==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@1.13.2: - resolution: {integrity: sha512-WnPMrwfCXxK69CdDfS1/j2DlzcKxSmycgDAqV0XCYpK/812KB0KlvsVAt5PjEbZGXkY88pCJ1BLZHAjF5FcbqA==} + /turbo-windows-arm64@1.13.3: + resolution: {integrity: sha512-ouJCgsVLd3icjRLmRvHQDDZnmGzT64GBupM1Y+TjtYn2LVaEBoV6hicFy8x5DUpnqdLy+YpCzRMkWlwhmkX7sQ==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@1.13.2: - resolution: {integrity: sha512-rX/d9f4MgRT3yK6cERPAkfavIxbpBZowDQpgvkYwGMGDQ0Nvw1nc0NVjruE76GrzXQqoxR1UpnmEP54vBARFHQ==} + /turbo@1.13.3: + resolution: {integrity: sha512-n17HJv4F4CpsYTvKzUJhLbyewbXjq1oLCi90i5tW1TiWDz16ML1eDG7wi5dHaKxzh5efIM56SITnuVbMq5dk4g==} hasBin: true optionalDependencies: - turbo-darwin-64: 1.13.2 - turbo-darwin-arm64: 1.13.2 - turbo-linux-64: 1.13.2 - turbo-linux-arm64: 1.13.2 - turbo-windows-64: 1.13.2 - turbo-windows-arm64: 1.13.2 + turbo-darwin-64: 1.13.3 + turbo-darwin-arm64: 1.13.3 + turbo-linux-64: 1.13.3 + turbo-linux-arm64: 1.13.3 + turbo-windows-64: 1.13.3 + turbo-windows-arm64: 1.13.3 dev: true /type-check@0.4.0: diff --git a/scripts/release.ts b/scripts/release.ts index 75d49084132..45757174831 100755 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -52,7 +52,7 @@ const packageWhitelist = [ 'plugin-redirects', 'plugin-search', 'plugin-seo', - // 'plugin-stripe', + 'plugin-stripe', // 'plugin-sentry', ] diff --git a/test/admin/components/views/CustomView/index.client.tsx b/test/admin/components/views/CustomView/index.client.tsx new file mode 100644 index 00000000000..485b5548295 --- /dev/null +++ b/test/admin/components/views/CustomView/index.client.tsx @@ -0,0 +1,54 @@ +'use client' +import { ConfirmPassword } from '@payloadcms/ui/fields/ConfirmPassword' +import { Password } from '@payloadcms/ui/fields/Password' +import { Form, useFormFields } from '@payloadcms/ui/forms/Form' +import { FormSubmit } from '@payloadcms/ui/forms/Submit' +import React from 'react' + +export const ClientForm: React.FC = () => { + return ( +
+ + + + Submit + + ) +} + +const CustomPassword: React.FC = () => { + const confirmPassword = useFormFields(([fields]) => { + return fields['confirm-password'] + }) + + const confirmValue = confirmPassword.value + + return ( + { + if (value && confirmValue) { + return confirmValue === value ? true : 'Passwords must match!!!!' + } + + return 'Field is required' + }} + /> + ) +} diff --git a/test/admin/components/views/CustomView/index.tsx b/test/admin/components/views/CustomView/index.tsx index 8bb8a3d200d..169c0bbb285 100644 --- a/test/admin/components/views/CustomView/index.tsx +++ b/test/admin/components/views/CustomView/index.tsx @@ -1,13 +1,14 @@ +import type { AdminViewProps } from 'payload/types' + import LinkImport from 'next/link.js' import React from 'react' -import type { AdminViewProps } from '../../../../../packages/payload/types.js' - const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default import { Button } from '@payloadcms/ui/elements/Button' import { customNestedViewPath, customViewTitle } from '../../../shared.js' +import { ClientForm } from './index.client.js' export const CustomView: React.FC = ({ initPageResult }) => { const { @@ -48,6 +49,7 @@ export const CustomView: React.FC = ({ initPageResult }) => { > Go to Nested View +
) diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index 6d03e201aa5..587175d386c 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -7,7 +7,7 @@ import { BlockQuoteFeature, BlocksFeature, BoldFeature, - CheckListFeature, + ChecklistFeature, HeadingFeature, IndentFeature, InlineCodeFeature, @@ -90,7 +90,7 @@ export async function buildConfigWithDefaults( }, ], }), - CheckListFeature(), + ChecklistFeature(), UnorderedListFeature(), OrderedListFeature(), AlignFeature(), diff --git a/test/fields/collections/Lexical/e2e.spec.ts b/test/fields/collections/Lexical/e2e.spec.ts index 75c7baff902..07e56a3be97 100644 --- a/test/fields/collections/Lexical/e2e.spec.ts +++ b/test/fields/collections/Lexical/e2e.spec.ts @@ -209,16 +209,14 @@ describe('lexical', () => { } // The following text should now be selected: Node - const floatingToolbar_formatSection = page.locator( - '.floating-select-toolbar-popup__section-format', - ) + const floatingToolbar_formatSection = page.locator('.inline-toolbar-popup__group-format') await expect(floatingToolbar_formatSection).toBeVisible() - await expect(page.locator('.floating-select-toolbar-popup__button').first()).toBeVisible() + await expect(page.locator('.inline-toolbar-popup__button').first()).toBeVisible() const boldButton = floatingToolbar_formatSection - .locator('.floating-select-toolbar-popup__button') + .locator('.inline-toolbar-popup__button') .first() await expect(boldButton).toBeVisible() @@ -441,16 +439,14 @@ describe('lexical', () => { } // The following text should now be selectedelationship node 1 - const floatingToolbar_formatSection = page.locator( - '.floating-select-toolbar-popup__section-format', - ) + const floatingToolbar_formatSection = page.locator('.inline-toolbar-popup__group-format') await expect(floatingToolbar_formatSection).toBeVisible() - await expect(page.locator('.floating-select-toolbar-popup__button').first()).toBeVisible() + await expect(page.locator('.inline-toolbar-popup__button').first()).toBeVisible() const boldButton = floatingToolbar_formatSection - .locator('.floating-select-toolbar-popup__button') + .locator('.inline-toolbar-popup__button') .first() await expect(boldButton).toBeVisible() @@ -521,13 +517,11 @@ describe('lexical', () => { } // The following text should now be "Node" - const floatingToolbar = page.locator('.floating-select-toolbar-popup') + const floatingToolbar = page.locator('.inline-toolbar-popup') await expect(floatingToolbar).toBeVisible() - const linkButton = floatingToolbar - .locator('.floating-select-toolbar-popup__button-link') - .first() + const linkButton = floatingToolbar.locator('.inline-toolbar-popup__button-link').first() await expect(linkButton).toBeVisible() await linkButton.click() diff --git a/test/plugin-stripe/collections/Customers.ts b/test/plugin-stripe/collections/Customers.ts index c8fe704008e..b0e19f898be 100644 --- a/test/plugin-stripe/collections/Customers.ts +++ b/test/plugin-stripe/collections/Customers.ts @@ -1,6 +1,7 @@ import type { CollectionConfig } from 'payload/types' -import { LinkToDoc } from '../../../packages/plugin-stripe/src/ui/LinkToDoc.js' +import { LinkToDoc } from '@payloadcms/plugin-stripe' + import { customersSlug } from '../shared.js' export const Customers: CollectionConfig = { @@ -31,13 +32,12 @@ export const Customers: CollectionConfig = { type: 'ui', admin: { components: { - Field: (args) => - LinkToDoc({ - ...args, - isTestKey: process.env.PAYLOAD_PUBLIC_IS_STRIPE_TEST_KEY === 'true', - nameOfIDField: `${args.path}.stripeSubscriptionID`, - stripeResourceType: 'subscriptions', - }), + Field: LinkToDoc, + }, + custom: { + isTestKey: process.env.PAYLOAD_PUBLIC_IS_STRIPE_TEST_KEY === 'true', + nameOfIDField: `stripeSubscriptionID`, + stripeResourceType: 'subscriptions', }, }, label: 'Link', diff --git a/test/plugin-stripe/config.ts b/test/plugin-stripe/config.ts index 3a3437009ce..307383d7591 100644 --- a/test/plugin-stripe/config.ts +++ b/test/plugin-stripe/config.ts @@ -1,4 +1,4 @@ -import stripePlugin from '@payloadcms/plugin-stripe' +import { stripePlugin } from '@payloadcms/plugin-stripe' import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' import { devUser } from '../credentials.js'