From 6d89f2160d2b7483ec2f3a3ee35d122869b61ffc Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Thu, 29 Feb 2024 16:55:24 -0500 Subject: [PATCH 01/15] ci: proper dependency graph for turbo to build in right order --- packages/payload/package.json | 2 +- packages/plugin-form-builder/package.json | 4 ++-- packages/plugin-search/package.json | 2 +- packages/plugin-stripe/package.json | 4 ++-- packages/richtext-lexical/package.json | 6 +++--- packages/richtext-slate/package.json | 7 ++++--- packages/ui/package.json | 4 ++-- turbo.json | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/payload/package.json b/packages/payload/package.json index 2bd1860f7..9e648a4cb 100644 --- a/packages/payload/package.json +++ b/packages/payload/package.json @@ -41,7 +41,7 @@ "translateNewKeys": "ts-node -T ./scripts/translateNewKeys.ts" }, "dependencies": { - "@payloadcms/translations": "workspace:^", + "@payloadcms/translations": "workspace:*", "bson-objectid": "2.0.4", "conf": "10.2.0", "console-table-printer": "2.11.2", diff --git a/packages/plugin-form-builder/package.json b/packages/plugin-form-builder/package.json index 84666ec41..8f6e2a556 100644 --- a/packages/plugin-form-builder/package.json +++ b/packages/plugin-form-builder/package.json @@ -11,7 +11,7 @@ "scripts": { "build:swc": "swc ./src -d ./dist --config-file .swcrc", "build:types": "tsc --emitDeclarationOnly --outDir dist", - "build": "pnpm build:swc && pnpm build:types", + "build": "echo \"Build temporarily disabled.\" && exit 0", "clean": "rimraf {dist,*.tsbuildinfo}", "prepublishOnly": "pnpm clean && pnpm turbo build", "test": "echo \"No tests available.\"" @@ -21,7 +21,7 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "dependencies": { - "@payloadcms/ui": "workspace:^", + "@payloadcms/ui": "workspace:*", "deepmerge": "^4.2.2", "escape-html": "^1.0.3" }, diff --git a/packages/plugin-search/package.json b/packages/plugin-search/package.json index f6356b9df..fc5310805 100644 --- a/packages/plugin-search/package.json +++ b/packages/plugin-search/package.json @@ -30,7 +30,7 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "dependencies": { - "@payloadcms/ui": "workspace:^", + "@payloadcms/ui": "workspace:*", "ts-deepmerge": "^2.0.1" }, "devDependencies": { diff --git a/packages/plugin-stripe/package.json b/packages/plugin-stripe/package.json index a3a4cb58c..4d08859e9 100644 --- a/packages/plugin-stripe/package.json +++ b/packages/plugin-stripe/package.json @@ -7,7 +7,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "build": "pnpm build:swc && pnpm build:types", + "build": "echo \"Build temporarily disabled.\" && exit 0", "build:swc": "swc ./src -d ./dist --config-file .swcrc", "build:types": "tsc --emitDeclarationOnly --outDir dist", "clean": "rimraf {dist,*.tsbuildinfo}", @@ -31,7 +31,7 @@ "payload": "^1.1.8 || ^2.0.0" }, "dependencies": { - "@payloadcms/ui": "workspace:^", + "@payloadcms/ui": "workspace:*", "lodash.get": "^4.4.2", "stripe": "^10.2.0", "uuid": "^9.0.0" diff --git a/packages/richtext-lexical/package.json b/packages/richtext-lexical/package.json index f0045ebef..1e6acf5f3 100644 --- a/packages/richtext-lexical/package.json +++ b/packages/richtext-lexical/package.json @@ -29,7 +29,6 @@ "@lexical/selection": "0.13.1", "@lexical/utils": "0.13.1", "@payloadcms/translations": "workspace:*", - "@payloadcms/ui": "workspace:*", "bson-objectid": "2.0.4", "classnames": "^2.3.2", "deep-equal": "2.2.3", @@ -43,6 +42,7 @@ }, "devDependencies": { "@payloadcms/eslint-config": "workspace:*", + "@payloadcms/ui": "workspace:*", "@types/json-schema": "7.0.15", "@types/node": "20.6.2", "@types/react": "18.2.15", @@ -50,8 +50,8 @@ "payload": "workspace:*" }, "peerDependencies": { - "@payloadcms/translations": "workspace:^", - "@payloadcms/ui": "workspace:^", + "@payloadcms/translations": "workspace:*", + "@payloadcms/ui": "workspace:*", "payload": "^2.4.0" }, "exports": { diff --git a/packages/richtext-slate/package.json b/packages/richtext-slate/package.json index e2532e46d..f87af68fa 100644 --- a/packages/richtext-slate/package.json +++ b/packages/richtext-slate/package.json @@ -6,7 +6,7 @@ "license": "MIT", "homepage": "https://payloadcms.com", "author": "Payload CMS, Inc.", - "main": "./dist/index.js", + "main": "./src/index.ts", "types": "./dist/index.d.ts", "scripts": { "build": "pnpm copyfiles && pnpm build:swc && pnpm build:types", @@ -26,6 +26,7 @@ "slate-react": "0.92.0" }, "devDependencies": { + "@payloadcms/ui": "workspace:*", "@payloadcms/eslint-config": "workspace:*", "@types/node": "20.5.7", "@types/react": "18.2.15", @@ -33,8 +34,8 @@ }, "peerDependencies": { "payload": "^2.3.0", - "@payloadcms/translations": "workspace:^", - "@payloadcms/ui": "workspace:^" + "@payloadcms/translations": "workspace:*", + "@payloadcms/ui": "workspace:*" }, "exports": { ".": { diff --git a/packages/ui/package.json b/packages/ui/package.json index fff5fb116..858b514c2 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "@payloadcms/ui", "version": "0.0.1", - "main": "./dist/index.js", + "main": "./src/index.ts", "types": "./dist/index.d.ts", "scripts": { "build": "pnpm copyfiles && pnpm build:swc && pnpm build:types", @@ -44,7 +44,7 @@ "@faceless-ui/scroll-info": "1.3.0", "@faceless-ui/window-info": "2.1.2", "@monaco-editor/react": "4.5.1", - "@payloadcms/translations": "workspace:^", + "@payloadcms/translations": "workspace:*", "body-scroll-lock": "4.0.0-beta.0", "bson-objectid": "2.0.4", "date-fns": "2.30.0", diff --git a/turbo.json b/turbo.json index 29a1b5a3c..77b371bd9 100644 --- a/turbo.json +++ b/turbo.json @@ -5,7 +5,7 @@ "dependsOn": ["^clean"] }, "build": { - "cache": false, + "cache": true, "dependsOn": ["^build"], "outputs": ["./dist/**"] }, From 98b4c1388eb6abfd9f705b0b05a129f9094a141f Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Thu, 29 Feb 2024 21:26:18 -0500 Subject: [PATCH 02/15] fix(richtext-lexical): Working Blocks --- .../Blocks/component/BlockContent.tsx | 17 ++---- .../Blocks/component/FormSavePlugin.tsx | 1 - .../field/features/Blocks/component/index.tsx | 54 ++++++++++++------- .../features/Blocks/nodes/BlocksNode.tsx | 27 +--------- .../Blocks/utils/transformInputFormData.ts | 25 --------- .../Blocks/utils/transformInputFormSchema.ts | 31 ----------- packages/ui/src/forms/Form/index.tsx | 2 + packages/ui/src/forms/Form/types.ts | 2 + .../ui/src/forms/fields/Checkbox/index.tsx | 5 +- .../forms/fields/RadioGroup/Radio/index.tsx | 5 +- .../ui/src/forms/fields/RadioGroup/index.tsx | 6 ++- test/buildConfigWithDefaults.ts | 28 ++++++++-- 12 files changed, 84 insertions(+), 119 deletions(-) delete mode 100644 packages/richtext-lexical/src/field/features/Blocks/utils/transformInputFormData.ts delete mode 100644 packages/richtext-lexical/src/field/features/Blocks/utils/transformInputFormSchema.ts diff --git a/packages/richtext-lexical/src/field/features/Blocks/component/BlockContent.tsx b/packages/richtext-lexical/src/field/features/Blocks/component/BlockContent.tsx index 64f549195..e670515d9 100644 --- a/packages/richtext-lexical/src/field/features/Blocks/component/BlockContent.tsx +++ b/packages/richtext-lexical/src/field/features/Blocks/component/BlockContent.tsx @@ -11,7 +11,6 @@ import { ErrorPill, Pill, SectionTitle, - createNestedFieldPath, useDocumentInfo, useFormSubmitted, useTranslation, @@ -21,7 +20,6 @@ import { $getNodeByKey } from 'lexical' import React, { useCallback } from 'react' import type { ReducedBlock } from '../../../../../../ui/src/utilities/buildComponentMap/types' -import type { FieldProps } from '../../../../types' import type { BlockFields, BlockNode } from '../nodes/BlocksNode' import { FormSavePlugin } from './FormSavePlugin' @@ -89,8 +87,6 @@ export const BlockContent: React.FC = (props) => { .filter(Boolean) .join(' ') - const path = '' as const - const onFormChange = useCallback( ({ fullFieldsWithValues, @@ -101,9 +97,9 @@ export const BlockContent: React.FC = (props) => { }) => { newFormData = { ...newFormData, - id: formData.id, // TODO: Why does form updatee not include theeeeem - blockName: formData.blockName, // TODO: Why does form updatee not include theeeeem - blockType: formData.blockType, // TODO: Why does form updatee not include theeeeem + id: formData.id, + blockName: newFormData.blockName2, // TODO: Find a better solution for this. We have to wrap it in blockName2 when using it here, as blockName does not accept or provide any updated values for some reason. + blockType: formData.blockType, } // Recursively remove all undefined values from even being present in formData, as they will @@ -123,8 +119,6 @@ export const BlockContent: React.FC = (props) => { removeUndefinedAndNullRecursively(newFormData) removeUndefinedAndNullRecursively(formData) - console.log('before saving node data...', newFormData, 'old', formData) - // Only update if the data has actually changed. Otherwise, we may be triggering an unnecessary value change, // which would trigger the "Leave without saving" dialog unnecessarily if (!isDeepEqual(formData, newFormData)) { @@ -136,7 +130,6 @@ export const BlockContent: React.FC = (props) => { editor.update(() => { const node: BlockNode = $getNodeByKey(nodeKey) if (node) { - console.log('saving node data...', newFormData) node.setFields(newFormData as BlockFields) } }) @@ -197,14 +190,14 @@ export const BlockContent: React.FC = (props) => { ? getTranslation(labels.singular, i18n) : '[Singular Label]'} - + {fieldHasErrors && } {editor.isEditable() && ( @@ -404,6 +405,7 @@ ${steps.map(formatStep).join(`\n`)} e.preventDefault() }} title="Insert snapshot" + type="button" > Insert Snapshot @@ -415,6 +417,7 @@ ${steps.map(formatStep).join(`\n`)} e.preventDefault() }} title="Copy to clipboard" + type="button" > Copy @@ -426,6 +429,7 @@ ${steps.map(formatStep).join(`\n`)} e.preventDefault() }} title="Download as a file" + type="button" > Download From 59494833b70143e7b0d63019d97ced03ef13ad2a Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Thu, 29 Feb 2024 21:50:41 -0500 Subject: [PATCH 08/15] feat(richtext-lexical): treeview --- .../debug/treeview/feature.client.tsx | 22 +++++++++++++++++++ .../features/debug/treeview/feature.server.ts | 16 ++++++++++++++ .../field/features/debug/treeview/index.ts | 20 ----------------- .../debug/treeview/{ => plugin}/index.scss | 0 .../treeview/{plugin.tsx => plugin/index.tsx} | 2 +- packages/richtext-lexical/src/index.ts | 4 ++-- 6 files changed, 41 insertions(+), 23 deletions(-) create mode 100644 packages/richtext-lexical/src/field/features/debug/treeview/feature.client.tsx create mode 100644 packages/richtext-lexical/src/field/features/debug/treeview/feature.server.ts delete mode 100644 packages/richtext-lexical/src/field/features/debug/treeview/index.ts rename packages/richtext-lexical/src/field/features/debug/treeview/{ => plugin}/index.scss (100%) rename packages/richtext-lexical/src/field/features/debug/treeview/{plugin.tsx => plugin/index.tsx} (92%) diff --git a/packages/richtext-lexical/src/field/features/debug/treeview/feature.client.tsx b/packages/richtext-lexical/src/field/features/debug/treeview/feature.client.tsx new file mode 100644 index 000000000..4e3e72849 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/debug/treeview/feature.client.tsx @@ -0,0 +1,22 @@ +'use client' +import type { FeatureProviderProviderClient } from '../../types' + +import { createClientComponent } from '../../createClientComponent' +import { TreeViewPlugin } from './plugin' + +const TreeViewFeatureClient: FeatureProviderProviderClient = (props) => { + return { + clientFeatureProps: props, + feature: () => ({ + clientFeatureProps: props, + plugins: [ + { + Component: TreeViewPlugin, + position: 'bottom', + }, + ], + }), + } +} + +export const TreeViewFeatureClientComponent = createClientComponent(TreeViewFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/debug/treeview/feature.server.ts b/packages/richtext-lexical/src/field/features/debug/treeview/feature.server.ts new file mode 100644 index 000000000..9a9c7c537 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/debug/treeview/feature.server.ts @@ -0,0 +1,16 @@ +import type { FeatureProviderProviderServer } from '../../types' + +import { TreeViewFeatureClientComponent } from './feature.client' + +export const TreeViewFeature: FeatureProviderProviderServer = (props) => { + return { + feature: () => { + return { + ClientComponent: TreeViewFeatureClientComponent, + serverFeatureProps: props, + } + }, + key: 'treeview', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/features/debug/treeview/index.ts b/packages/richtext-lexical/src/field/features/debug/treeview/index.ts deleted file mode 100644 index 1aa215a45..000000000 --- a/packages/richtext-lexical/src/field/features/debug/treeview/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { FeatureProvider } from '../../types' - -export const TreeViewFeature = (): FeatureProvider => { - return { - feature: () => { - return { - plugins: [ - { - Component: () => - // @ts-expect-error-next-line - import('./plugin').then((module) => module.TreeViewPlugin), - position: 'bottom', - }, - ], - props: null, - } - }, - key: 'treeview', - } -} diff --git a/packages/richtext-lexical/src/field/features/debug/treeview/index.scss b/packages/richtext-lexical/src/field/features/debug/treeview/plugin/index.scss similarity index 100% rename from packages/richtext-lexical/src/field/features/debug/treeview/index.scss rename to packages/richtext-lexical/src/field/features/debug/treeview/plugin/index.scss diff --git a/packages/richtext-lexical/src/field/features/debug/treeview/plugin.tsx b/packages/richtext-lexical/src/field/features/debug/treeview/plugin/index.tsx similarity index 92% rename from packages/richtext-lexical/src/field/features/debug/treeview/plugin.tsx rename to packages/richtext-lexical/src/field/features/debug/treeview/plugin/index.tsx index e06dd5301..bea3da1f2 100644 --- a/packages/richtext-lexical/src/field/features/debug/treeview/plugin.tsx +++ b/packages/richtext-lexical/src/field/features/debug/treeview/plugin/index.tsx @@ -5,7 +5,7 @@ import * as React from 'react' import './index.scss' -export function TreeViewPlugin(): JSX.Element { +export function TreeViewPlugin(): React.ReactNode { const [editor] = useLexicalComposerContext() return ( Date: Thu, 29 Feb 2024 22:31:14 -0500 Subject: [PATCH 09/15] feat(richtext-lexical): format features --- .../bold/{index.ts => feature.client.tsx} | 20 +++++++------ .../features/format/bold/feature.server.ts | 29 +++++++++++++++++++ .../{index.ts => feature.client.tsx} | 19 +++++++----- .../format/inlinecode/feature.server.ts | 18 ++++++++++++ .../italic/{index.ts => feature.client.tsx} | 19 +++++++----- .../features/format/italic/feature.server.ts | 18 ++++++++++++ .../{index.ts => feature.client.tsx} | 21 ++++++++------ .../format/strikethrough/feature.server.ts | 21 ++++++++++++++ .../{index.ts => feature.client.tsx} | 20 +++++++------ .../format/subscript/feature.server.ts | 16 ++++++++++ .../{index.ts => feature.client.tsx} | 20 +++++++------ .../format/superscript/feature.server.ts | 16 ++++++++++ .../{index.ts => feature.client.tsx} | 20 +++++++------ .../format/underline/feature.server.ts | 16 ++++++++++ .../field/lexical/config/server/default.ts | 28 +++++++++--------- packages/richtext-lexical/src/index.ts | 14 ++++----- test/buildConfigWithDefaults.ts | 16 ++++++++++ 17 files changed, 251 insertions(+), 80 deletions(-) rename packages/richtext-lexical/src/field/features/format/bold/{index.ts => feature.client.tsx} (68%) create mode 100644 packages/richtext-lexical/src/field/features/format/bold/feature.server.ts rename packages/richtext-lexical/src/field/features/format/inlinecode/{index.ts => feature.client.tsx} (64%) create mode 100644 packages/richtext-lexical/src/field/features/format/inlinecode/feature.server.ts rename packages/richtext-lexical/src/field/features/format/italic/{index.ts => feature.client.tsx} (65%) create mode 100644 packages/richtext-lexical/src/field/features/format/italic/feature.server.ts rename packages/richtext-lexical/src/field/features/format/strikethrough/{index.ts => feature.client.tsx} (63%) create mode 100644 packages/richtext-lexical/src/field/features/format/strikethrough/feature.server.ts rename packages/richtext-lexical/src/field/features/format/subscript/{index.ts => feature.client.tsx} (61%) create mode 100644 packages/richtext-lexical/src/field/features/format/subscript/feature.server.ts rename packages/richtext-lexical/src/field/features/format/superscript/{index.ts => feature.client.tsx} (60%) create mode 100644 packages/richtext-lexical/src/field/features/format/superscript/feature.server.ts rename packages/richtext-lexical/src/field/features/format/underline/{index.ts => feature.client.tsx} (61%) create mode 100644 packages/richtext-lexical/src/field/features/format/underline/feature.server.ts diff --git a/packages/richtext-lexical/src/field/features/format/bold/index.ts b/packages/richtext-lexical/src/field/features/format/bold/feature.client.tsx similarity index 68% rename from packages/richtext-lexical/src/field/features/format/bold/index.ts rename to packages/richtext-lexical/src/field/features/format/bold/feature.client.tsx index e2443e7e2..79182a49b 100644 --- a/packages/richtext-lexical/src/field/features/format/bold/index.ts +++ b/packages/richtext-lexical/src/field/features/format/bold/feature.client.tsx @@ -1,7 +1,10 @@ +'use client' import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical' -import type { FeatureProvider } from '../../types' +import type { FeatureProviderProviderClient } from '../../types' +import { BoldIcon } from '../../../lexical/ui/icons/Bold' +import { createClientComponent } from '../../createClientComponent' import { SectionWithEntries } from '../common/floatingSelectToolbarSection' import { BOLD_ITALIC_STAR, @@ -10,9 +13,9 @@ import { BOLD_UNDERSCORE, } from './markdownTransformers' -export const BoldTextFeature = (): FeatureProvider => { +const BoldFeatureClient: FeatureProviderProviderClient = (props) => { return { - dependenciesSoft: ['italic'], + clientFeatureProps: props, feature: ({ featureProviderMap }) => { const markdownTransformers = [BOLD_STAR, BOLD_UNDERSCORE] if (featureProviderMap.get('italic')) { @@ -20,13 +23,12 @@ export const BoldTextFeature = (): FeatureProvider => { } return { + clientFeatureProps: props, floatingSelectToolbar: { sections: [ SectionWithEntries([ { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/Bold').then((module) => module.BoldIcon), + ChildComponent: BoldIcon, isActive: ({ selection }) => { if ($isRangeSelection(selection)) { return selection.hasFormat('bold') @@ -42,10 +44,10 @@ export const BoldTextFeature = (): FeatureProvider => { ]), ], }, - markdownTransformers: markdownTransformers, - props: null, + markdownTransformers, } }, - key: 'bold', } } + +export const BoldFeatureClientComponent = createClientComponent(BoldFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/format/bold/feature.server.ts b/packages/richtext-lexical/src/field/features/format/bold/feature.server.ts new file mode 100644 index 000000000..bf0357c6d --- /dev/null +++ b/packages/richtext-lexical/src/field/features/format/bold/feature.server.ts @@ -0,0 +1,29 @@ +import type { FeatureProviderProviderServer } from '../../types' + +import { BoldFeatureClientComponent } from './feature.client' +import { + BOLD_ITALIC_STAR, + BOLD_ITALIC_UNDERSCORE, + BOLD_STAR, + BOLD_UNDERSCORE, +} from './markdownTransformers' + +export const BoldFeature: FeatureProviderProviderServer = (props) => { + return { + dependenciesSoft: ['italic'], + feature: ({ featureProviderMap }) => { + const markdownTransformers = [BOLD_STAR, BOLD_UNDERSCORE] + if (featureProviderMap.get('italic')) { + markdownTransformers.push(BOLD_ITALIC_UNDERSCORE, BOLD_ITALIC_STAR) + } + + return { + ClientComponent: BoldFeatureClientComponent, + markdownTransformers, + serverFeatureProps: props, + } + }, + key: 'bold', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/features/format/inlinecode/index.ts b/packages/richtext-lexical/src/field/features/format/inlinecode/feature.client.tsx similarity index 64% rename from packages/richtext-lexical/src/field/features/format/inlinecode/index.ts rename to packages/richtext-lexical/src/field/features/format/inlinecode/feature.client.tsx index 502033ca0..92efc2aff 100644 --- a/packages/richtext-lexical/src/field/features/format/inlinecode/index.ts +++ b/packages/richtext-lexical/src/field/features/format/inlinecode/feature.client.tsx @@ -1,21 +1,25 @@ +'use client' + import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical' -import type { FeatureProvider } from '../../types' +import type { FeatureProviderProviderClient } from '../../types' +import { CodeIcon } from '../../../lexical/ui/icons/Code' +import { createClientComponent } from '../../createClientComponent' import { SectionWithEntries } from '../common/floatingSelectToolbarSection' import { INLINE_CODE } from './markdownTransformers' -export const InlineCodeTextFeature = (): FeatureProvider => { +const InlineCodeFeatureClient: FeatureProviderProviderClient = (props) => { return { + clientFeatureProps: props, feature: () => { return { + clientFeatureProps: props, floatingSelectToolbar: { sections: [ SectionWithEntries([ { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/Code').then((module) => module.CodeIcon), + ChildComponent: CodeIcon, isActive: ({ selection }) => { if ($isRangeSelection(selection)) { return selection.hasFormat('code') @@ -31,10 +35,11 @@ export const InlineCodeTextFeature = (): FeatureProvider => { ]), ], }, + markdownTransformers: [INLINE_CODE], - props: null, } }, - key: 'inlinecode', } } + +export const InlineCodeFeatureClientComponent = createClientComponent(InlineCodeFeatureClient) 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 new file mode 100644 index 000000000..3f0eabb1a --- /dev/null +++ b/packages/richtext-lexical/src/field/features/format/inlinecode/feature.server.ts @@ -0,0 +1,18 @@ +import type { FeatureProviderProviderServer } from '../../types' + +import { InlineCodeFeatureClientComponent } from './feature.client' +import { INLINE_CODE } from './markdownTransformers' + +export const InlineCodeFeature: FeatureProviderProviderServer = (props) => { + return { + feature: () => { + return { + ClientComponent: InlineCodeFeatureClientComponent, + markdownTransformers: [INLINE_CODE], + serverFeatureProps: props, + } + }, + key: 'inlinecode', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/features/format/italic/index.ts b/packages/richtext-lexical/src/field/features/format/italic/feature.client.tsx similarity index 65% rename from packages/richtext-lexical/src/field/features/format/italic/index.ts rename to packages/richtext-lexical/src/field/features/format/italic/feature.client.tsx index cdd15dcca..1c874f18f 100644 --- a/packages/richtext-lexical/src/field/features/format/italic/index.ts +++ b/packages/richtext-lexical/src/field/features/format/italic/feature.client.tsx @@ -1,21 +1,26 @@ +'use client' + import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical' -import type { FeatureProvider } from '../../types' +import type { FeatureProviderProviderClient } from '../../types' +import { ItalicIcon } from '../../../lexical/ui/icons/Italic' +import { createClientComponent } from '../../createClientComponent' import { SectionWithEntries } from '../common/floatingSelectToolbarSection' import { ITALIC_STAR, ITALIC_UNDERSCORE } from './markdownTransformers' -export const ItalicTextFeature = (): FeatureProvider => { +const ItalicFeatureClient: FeatureProviderProviderClient = (props) => { return { + clientFeatureProps: props, feature: () => { return { + clientFeatureProps: props, + floatingSelectToolbar: { sections: [ SectionWithEntries([ { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/Italic').then((module) => module.ItalicIcon), + ChildComponent: ItalicIcon, isActive: ({ selection }) => { if ($isRangeSelection(selection)) { return selection.hasFormat('italic') @@ -32,9 +37,9 @@ export const ItalicTextFeature = (): FeatureProvider => { ], }, markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE], - props: null, } }, - key: 'italic', } } + +export const ItalicFeatureClientComponent = createClientComponent(ItalicFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/format/italic/feature.server.ts b/packages/richtext-lexical/src/field/features/format/italic/feature.server.ts new file mode 100644 index 000000000..db69135e4 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/format/italic/feature.server.ts @@ -0,0 +1,18 @@ +import type { FeatureProviderProviderServer } from '../../types' + +import { ItalicFeatureClientComponent } from './feature.client' +import { ITALIC_STAR, ITALIC_UNDERSCORE } from './markdownTransformers' + +export const ItalicFeature: FeatureProviderProviderServer = (props) => { + return { + feature: () => { + return { + ClientComponent: ItalicFeatureClientComponent, + markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE], + serverFeatureProps: props, + } + }, + key: 'italic', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/features/format/strikethrough/index.ts b/packages/richtext-lexical/src/field/features/format/strikethrough/feature.client.tsx similarity index 63% rename from packages/richtext-lexical/src/field/features/format/strikethrough/index.ts rename to packages/richtext-lexical/src/field/features/format/strikethrough/feature.client.tsx index 949ec80b9..79cc2782c 100644 --- a/packages/richtext-lexical/src/field/features/format/strikethrough/index.ts +++ b/packages/richtext-lexical/src/field/features/format/strikethrough/feature.client.tsx @@ -1,23 +1,26 @@ +'use client' + import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical' -import type { FeatureProvider } from '../../types' +import type { FeatureProviderProviderClient } from '../../types' +import { StrikethroughIcon } from '../../../lexical/ui/icons/Strikethrough' +import { createClientComponent } from '../../createClientComponent' import { SectionWithEntries } from '../common/floatingSelectToolbarSection' import { STRIKETHROUGH } from './markdownTransformers' -export const StrikethroughTextFeature = (): FeatureProvider => { +const StrikethroughFeatureClient: FeatureProviderProviderClient = (props) => { return { + clientFeatureProps: props, feature: () => { return { + clientFeatureProps: props, + floatingSelectToolbar: { sections: [ SectionWithEntries([ { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/Strikethrough').then( - (module) => module.StrikethroughIcon, - ), + ChildComponent: StrikethroughIcon, isActive: ({ selection }) => { if ($isRangeSelection(selection)) { return selection.hasFormat('strikethrough') @@ -34,9 +37,9 @@ export const StrikethroughTextFeature = (): FeatureProvider => { ], }, markdownTransformers: [STRIKETHROUGH], - props: null, } }, - key: 'strikethrough', } } + +export const StrikethroughFeatureClientComponent = createClientComponent(StrikethroughFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/format/strikethrough/feature.server.ts b/packages/richtext-lexical/src/field/features/format/strikethrough/feature.server.ts new file mode 100644 index 000000000..5f98bdbbe --- /dev/null +++ b/packages/richtext-lexical/src/field/features/format/strikethrough/feature.server.ts @@ -0,0 +1,21 @@ +import type { FeatureProviderProviderServer } from '../../types' + +import { StrikethroughFeatureClientComponent } from './feature.client' +import { STRIKETHROUGH } from './markdownTransformers' + +export const StrikethroughFeature: FeatureProviderProviderServer = ( + props, +) => { + return { + feature: () => { + return { + ClientComponent: StrikethroughFeatureClientComponent, + + markdownTransformers: [STRIKETHROUGH], + serverFeatureProps: props, + } + }, + key: 'strikethrough', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/features/format/subscript/index.ts b/packages/richtext-lexical/src/field/features/format/subscript/feature.client.tsx similarity index 61% rename from packages/richtext-lexical/src/field/features/format/subscript/index.ts rename to packages/richtext-lexical/src/field/features/format/subscript/feature.client.tsx index 962e816f7..4c1904b0a 100644 --- a/packages/richtext-lexical/src/field/features/format/subscript/index.ts +++ b/packages/richtext-lexical/src/field/features/format/subscript/feature.client.tsx @@ -1,22 +1,24 @@ +'use client' + import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical' -import type { FeatureProvider } from '../../types' +import type { FeatureProviderProviderClient } from '../../types' +import { SubscriptIcon } from '../../../lexical/ui/icons/Subscript' +import { createClientComponent } from '../../createClientComponent' import { SectionWithEntries } from '../common/floatingSelectToolbarSection' -export const SubscriptTextFeature = (): FeatureProvider => { +const SubscriptFeatureClient: FeatureProviderProviderClient = (props) => { return { + clientFeatureProps: props, feature: () => { return { + clientFeatureProps: props, floatingSelectToolbar: { sections: [ SectionWithEntries([ { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/Subscript').then( - (module) => module.SubscriptIcon, - ), + ChildComponent: SubscriptIcon, isActive: ({ selection }) => { if ($isRangeSelection(selection)) { return selection.hasFormat('subscript') @@ -32,9 +34,9 @@ export const SubscriptTextFeature = (): FeatureProvider => { ]), ], }, - props: null, } }, - key: 'subscript', } } + +export const SubscriptFeatureClientComponent = createClientComponent(SubscriptFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/format/subscript/feature.server.ts b/packages/richtext-lexical/src/field/features/format/subscript/feature.server.ts new file mode 100644 index 000000000..a02f058d3 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/format/subscript/feature.server.ts @@ -0,0 +1,16 @@ +import type { FeatureProviderProviderServer } from '../../types' + +import { SubscriptFeatureClientComponent } from './feature.client' + +export const SubscriptFeature: FeatureProviderProviderServer = (props) => { + return { + feature: () => { + return { + ClientComponent: SubscriptFeatureClientComponent, + serverFeatureProps: props, + } + }, + key: 'subscript', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/features/format/superscript/index.ts b/packages/richtext-lexical/src/field/features/format/superscript/feature.client.tsx similarity index 60% rename from packages/richtext-lexical/src/field/features/format/superscript/index.ts rename to packages/richtext-lexical/src/field/features/format/superscript/feature.client.tsx index 0e2939c35..e59f48771 100644 --- a/packages/richtext-lexical/src/field/features/format/superscript/index.ts +++ b/packages/richtext-lexical/src/field/features/format/superscript/feature.client.tsx @@ -1,22 +1,24 @@ +'use client' + import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical' -import type { FeatureProvider } from '../../types' +import type { FeatureProviderProviderClient } from '../../types' +import { SuperscriptIcon } from '../../../lexical/ui/icons/Superscript' +import { createClientComponent } from '../../createClientComponent' import { SectionWithEntries } from '../common/floatingSelectToolbarSection' -export const SuperscriptTextFeature = (): FeatureProvider => { +const SuperscriptFeatureClient: FeatureProviderProviderClient = (props) => { return { + clientFeatureProps: props, feature: () => { return { + clientFeatureProps: props, floatingSelectToolbar: { sections: [ SectionWithEntries([ { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/Superscript').then( - (module) => module.SuperscriptIcon, - ), + ChildComponent: SuperscriptIcon, isActive: ({ selection }) => { if ($isRangeSelection(selection)) { return selection.hasFormat('superscript') @@ -32,9 +34,9 @@ export const SuperscriptTextFeature = (): FeatureProvider => { ]), ], }, - props: null, } }, - key: 'superscript', } } + +export const SuperscriptFeatureClientComponent = createClientComponent(SuperscriptFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/format/superscript/feature.server.ts b/packages/richtext-lexical/src/field/features/format/superscript/feature.server.ts new file mode 100644 index 000000000..080b91a5d --- /dev/null +++ b/packages/richtext-lexical/src/field/features/format/superscript/feature.server.ts @@ -0,0 +1,16 @@ +import type { FeatureProviderProviderServer } from '../../types' + +import { SuperscriptFeatureClientComponent } from './feature.client' + +export const SuperscriptFeature: FeatureProviderProviderServer = (props) => { + return { + feature: () => { + return { + ClientComponent: SuperscriptFeatureClientComponent, + serverFeatureProps: props, + } + }, + key: 'superscript', + serverFeatureProps: props, + } +} diff --git a/packages/richtext-lexical/src/field/features/format/underline/index.ts b/packages/richtext-lexical/src/field/features/format/underline/feature.client.tsx similarity index 61% rename from packages/richtext-lexical/src/field/features/format/underline/index.ts rename to packages/richtext-lexical/src/field/features/format/underline/feature.client.tsx index d5e4cf26b..e26bf06ba 100644 --- a/packages/richtext-lexical/src/field/features/format/underline/index.ts +++ b/packages/richtext-lexical/src/field/features/format/underline/feature.client.tsx @@ -1,22 +1,24 @@ +'use client' + import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical' -import type { FeatureProvider } from '../../types' +import type { FeatureProviderProviderClient } from '../../types' +import { UnderlineIcon } from '../../../lexical/ui/icons/Underline' +import { createClientComponent } from '../../createClientComponent' import { SectionWithEntries } from '../common/floatingSelectToolbarSection' -export const UnderlineTextFeature = (): FeatureProvider => { +const UnderlineFeatureClient: FeatureProviderProviderClient = (props) => { return { + clientFeatureProps: props, feature: () => { return { + clientFeatureProps: props, floatingSelectToolbar: { sections: [ SectionWithEntries([ { - ChildComponent: () => - // @ts-expect-error-next-line - import('../../../lexical/ui/icons/Underline').then( - (module) => module.UnderlineIcon, - ), + ChildComponent: UnderlineIcon, isActive: ({ selection }) => { if ($isRangeSelection(selection)) { return selection.hasFormat('underline') @@ -32,9 +34,9 @@ export const UnderlineTextFeature = (): FeatureProvider => { ]), ], }, - props: null, } }, - key: 'underline', } } + +export const UnderlineFeatureClientComponent = createClientComponent(UnderlineFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/format/underline/feature.server.ts b/packages/richtext-lexical/src/field/features/format/underline/feature.server.ts new file mode 100644 index 000000000..4f9fc087c --- /dev/null +++ b/packages/richtext-lexical/src/field/features/format/underline/feature.server.ts @@ -0,0 +1,16 @@ +import type { FeatureProviderProviderServer } from '../../types' + +import { UnderlineFeatureClientComponent } from './feature.client' + +export const UnderlineFeature: FeatureProviderProviderServer = (props) => { + return { + feature: () => { + return { + ClientComponent: UnderlineFeatureClientComponent, + serverFeatureProps: props, + } + }, + key: 'underline', + serverFeatureProps: props, + } +} 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 37f0998e4..2f6376714 100644 --- a/packages/richtext-lexical/src/field/lexical/config/server/default.ts +++ b/packages/richtext-lexical/src/field/lexical/config/server/default.ts @@ -5,13 +5,13 @@ import type { SanitizedServerEditorConfig, ServerEditorConfig } from '../types' import { AlignFeature } from '../../../features/align/feature.server' import { BlockQuoteFeature } from '../../../features/blockquote/feature.server' -import { BoldTextFeature } from '../../../features/format/bold' -import { InlineCodeTextFeature } from '../../../features/format/inlinecode' -import { ItalicTextFeature } from '../../../features/format/italic' -import { StrikethroughTextFeature } from '../../../features/format/strikethrough' -import { SubscriptTextFeature } from '../../../features/format/subscript' -import { SuperscriptTextFeature } from '../../../features/format/superscript' -import { UnderlineTextFeature } from '../../../features/format/underline' +import { BoldFeature } from '../../../features/format/bold/feature.server' +import { InlineCodeFeature } from '../../../features/format/inlinecode/feature.server' +import { ItalicFeature } from '../../../features/format/italic/feature.server' +import { StrikethroughFeature } from '../../../features/format/strikethrough/feature.server' +import { SubscriptFeature } from '../../../features/format/subscript/feature.server' +import { SuperscriptFeature } from '../../../features/format/superscript/feature.server' +import { UnderlineFeature } from '../../../features/format/underline/feature.server' import { HeadingFeature } from '../../../features/heading' import { IndentFeature } from '../../../features/indent' import { LinkFeature } from '../../../features/link/feature.server' @@ -30,13 +30,13 @@ export const defaultEditorLexicalConfig: LexicalEditorConfig = { } export const defaultEditorFeatures: FeatureProviderServer[] = [ - BoldTextFeature(), - ItalicTextFeature(), - UnderlineTextFeature(), - StrikethroughTextFeature(), - SubscriptTextFeature(), - SuperscriptTextFeature(), - InlineCodeTextFeature(), + BoldFeature(), + ItalicFeature(), + UnderlineFeature(), + StrikethroughFeature(), + SubscriptFeature(), + SuperscriptFeature(), + InlineCodeFeature(), ParagraphFeature(), HeadingFeature({}), AlignFeature(), diff --git a/packages/richtext-lexical/src/index.ts b/packages/richtext-lexical/src/index.ts index f7f1064ed..78165e5d7 100644 --- a/packages/richtext-lexical/src/index.ts +++ b/packages/richtext-lexical/src/index.ts @@ -262,14 +262,14 @@ export { consolidateHTMLConverters } from './field/features/converters/html/fiel export { lexicalHTML } from './field/features/converters/html/field' export { TestRecorderFeature } from './field/features/debug/testrecorder/feature.server' export { TreeViewFeature } from './field/features/debug/treeview/feature.server' -export { BoldTextFeature } from './field/features/format/bold' +export { BoldFeature } from './field/features/format/bold/feature.server' export { SectionWithEntries as FormatSectionWithEntries } from './field/features/format/common/floatingSelectToolbarSection' -export { InlineCodeTextFeature } from './field/features/format/inlinecode' -export { ItalicTextFeature } from './field/features/format/italic' -export { StrikethroughTextFeature } from './field/features/format/strikethrough' -export { SubscriptTextFeature } from './field/features/format/subscript' -export { SuperscriptTextFeature } from './field/features/format/superscript' -export { UnderlineTextFeature } from './field/features/format/underline' +export { InlineCodeFeature } from './field/features/format/inlinecode/feature.server' +export { ItalicFeature } from './field/features/format/italic/feature.server' +export { StrikethroughFeature } from './field/features/format/strikethrough/feature.server' +export { SubscriptFeature } from './field/features/format/subscript/feature.server' +export { SuperscriptFeature } from './field/features/format/superscript/feature.server' +export { UnderlineFeature } from './field/features/format/underline/feature.server' export { HeadingFeature } from './field/features/heading' export { IndentFeature } from './field/features/indent' diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index 466e99ff4..a1c347da8 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -2,7 +2,15 @@ import { AlignFeature, BlockQuoteFeature, BlocksFeature, + BoldFeature, + InlineCodeFeature, + ItalicFeature, LinkFeature, + StrikethroughFeature, + SubscriptFeature, + SuperscriptFeature, + TreeViewFeature, + UnderlineFeature, lexicalEditor, } from '@payloadcms/richtext-lexical' import path from 'path' @@ -83,6 +91,14 @@ export function buildConfigWithDefaults(testConfig?: Partial): Promise Date: Thu, 29 Feb 2024 22:39:37 -0500 Subject: [PATCH 10/15] chore: update @faceless-ui/modal in all packages --- examples/form-builder/next-pages/package.json | 2 +- examples/form-builder/payload/package.json | 2 +- packages/richtext-lexical/package.json | 2 +- packages/richtext-slate/package.json | 2 +- pnpm-lock.yaml | 46 +++++++------------ 5 files changed, 20 insertions(+), 34 deletions(-) diff --git a/examples/form-builder/next-pages/package.json b/examples/form-builder/next-pages/package.json index 757dc6c6d..94dddedad 100644 --- a/examples/form-builder/next-pages/package.json +++ b/examples/form-builder/next-pages/package.json @@ -11,7 +11,7 @@ "dependencies": { "@apollo/client": "^3.7.0", "@faceless-ui/css-grid": "^1.2.0", - "@faceless-ui/modal": "^2.0.1", + "@faceless-ui/modal": "^2.0.2", "escape-html": "^1.0.3", "graphql": "^16.8.1", "next": "^13.5.6", diff --git a/examples/form-builder/payload/package.json b/examples/form-builder/payload/package.json index 44de480b8..40ff3a7e6 100644 --- a/examples/form-builder/payload/package.json +++ b/examples/form-builder/payload/package.json @@ -18,7 +18,7 @@ "@payloadcms/bundler-webpack": "latest", "@payloadcms/db-mongodb": "latest", "@payloadcms/richtext-slate": "latest", - "@faceless-ui/modal": "^2.0.1", + "@faceless-ui/modal": "^2.0.2", "@payloadcms/plugin-form-builder": "^1.0.12", "@payloadcms/plugin-seo": "^1.0.8", "dotenv": "^8.2.0", diff --git a/packages/richtext-lexical/package.json b/packages/richtext-lexical/package.json index 1e6acf5f3..6ef4b1203 100644 --- a/packages/richtext-lexical/package.json +++ b/packages/richtext-lexical/package.json @@ -18,7 +18,7 @@ "prepublishOnly": "pnpm clean && pnpm turbo build" }, "dependencies": { - "@faceless-ui/modal": "2.0.1", + "@faceless-ui/modal": "2.0.2", "@lexical/headless": "0.13.1", "@lexical/link": "0.13.1", "@lexical/list": "0.13.1", diff --git a/packages/richtext-slate/package.json b/packages/richtext-slate/package.json index f87af68fa..a1fe79302 100644 --- a/packages/richtext-slate/package.json +++ b/packages/richtext-slate/package.json @@ -17,7 +17,7 @@ "prepublishOnly": "pnpm clean && pnpm turbo build" }, "dependencies": { - "@faceless-ui/modal": "2.0.1", + "@faceless-ui/modal": "2.0.2", "is-hotkey": "0.2.0", "react": "18.2.0", "slate": "0.91.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43f618ff6..6885de89a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -594,7 +594,7 @@ importers: packages/payload: dependencies: '@payloadcms/translations': - specifier: workspace:^ + specifier: workspace:* version: link:../translations bson-objectid: specifier: 2.0.4 @@ -906,7 +906,7 @@ importers: packages/plugin-form-builder: dependencies: '@payloadcms/ui': - specifier: workspace:^ + specifier: workspace:* version: link:../ui deepmerge: specifier: ^4.2.2 @@ -976,7 +976,7 @@ importers: packages/plugin-search: dependencies: '@payloadcms/ui': - specifier: workspace:^ + specifier: workspace:* version: link:../ui ts-deepmerge: specifier: ^2.0.1 @@ -1074,7 +1074,7 @@ importers: packages/plugin-stripe: dependencies: '@payloadcms/ui': - specifier: workspace:^ + specifier: workspace:* version: link:../ui lodash.get: specifier: ^4.4.2 @@ -1117,8 +1117,8 @@ importers: packages/richtext-lexical: dependencies: '@faceless-ui/modal': - specifier: 2.0.1 - version: 2.0.1(react-dom@18.2.0)(react@18.2.0) + specifier: 2.0.2 + version: 2.0.2(react-dom@18.2.0)(react@18.2.0) '@lexical/headless': specifier: 0.13.1 version: 0.13.1(lexical@0.13.1) @@ -1149,9 +1149,6 @@ importers: '@payloadcms/translations': specifier: workspace:* version: link:../translations - '@payloadcms/ui': - specifier: workspace:* - version: link:../ui bson-objectid: specifier: 2.0.4 version: 2.0.4 @@ -1186,6 +1183,9 @@ importers: '@payloadcms/eslint-config': specifier: workspace:* version: link:../eslint-config-payload + '@payloadcms/ui': + specifier: workspace:* + version: link:../ui '@types/json-schema': specifier: 7.0.15 version: 7.0.15 @@ -1205,14 +1205,11 @@ importers: packages/richtext-slate: dependencies: '@faceless-ui/modal': - specifier: 2.0.1 - version: 2.0.1(react-dom@18.2.0)(react@18.2.0) + specifier: 2.0.2 + version: 2.0.2(react-dom@18.2.0)(react@18.2.0) '@payloadcms/translations': - specifier: workspace:^ + specifier: workspace:* version: link:../translations - '@payloadcms/ui': - specifier: workspace:^ - version: link:../ui is-hotkey: specifier: 0.2.0 version: 0.2.0 @@ -1235,6 +1232,9 @@ importers: '@payloadcms/eslint-config': specifier: workspace:* version: link:../eslint-config-payload + '@payloadcms/ui': + specifier: workspace:* + version: link:../ui '@types/node': specifier: 20.5.7 version: 20.5.7 @@ -1281,7 +1281,7 @@ importers: specifier: 4.5.1 version: 4.5.1(monaco-editor@0.38.0)(react-dom@18.2.0)(react@18.2.0) '@payloadcms/translations': - specifier: workspace:^ + specifier: workspace:* version: link:../translations body-scroll-lock: specifier: 4.0.0-beta.0 @@ -3138,20 +3138,6 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false - /@faceless-ui/modal@2.0.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-z1PaaLxwuX+1In4vhUxODZndGKdCY+WIqzvtnas3CaYGGCVJBSJ4jfv9UEEGZzcahmSy+71bEL89cUT6d36j1Q==} - peerDependencies: - react: ^18.2.0 - react-dom: ^18.2.0 - dependencies: - body-scroll-lock: 3.1.5 - focus-trap: 6.9.4 - qs: 6.11.2 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) - dev: false - /@faceless-ui/modal@2.0.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-CtwUn+hHEaoYUjREzQKGRbEp55VzUx7sC+hxIxmCPwg7Yd5KXkQzSfoUfRAHqT/1MFfE1B2QCHVVbhtSnFL9BA==} peerDependencies: From 5b2b104e375641c4ea74b080a68858103f1e0df5 Mon Sep 17 00:00:00 2001 From: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com> Date: Fri, 1 Mar 2024 00:44:42 -0500 Subject: [PATCH 11/15] feat: upload images rendering properly in admin panel (#5215) --- .../next/src/pages/Edit/Default/index.tsx | 17 ++++--- .../List/Default/Cell/fields/File/index.tsx | 3 +- packages/payload/src/uploads/getBaseFields.ts | 26 +++++------ packages/payload/src/uploads/types.ts | 9 +++- .../field/features/upload/component/index.tsx | 2 +- .../field/elements/upload/Element/index.tsx | 2 +- .../ui/src/elements/FileDetails/index.tsx | 7 ++- packages/ui/src/elements/Thumbnail/index.tsx | 3 +- packages/ui/src/elements/Thumbnail/types.ts | 1 + packages/ui/src/elements/Upload/index.tsx | 7 ++- packages/ui/src/exports/hooks.ts | 1 + .../fields/Blocks/BlocksDrawer/index.tsx | 2 +- packages/ui/src/hooks/useAdminThumbnail.tsx | 8 ++++ packages/ui/src/hooks/useThumbnail.ts | 44 ++++++++++++------- .../src/utilities/buildComponentMap/index.tsx | 8 ++++ .../src/utilities/buildComponentMap/types.ts | 1 + test/_community/collections/Media/index.ts | 5 --- .../collections/Upload/index.ts | 2 +- test/field-error-states/tsconfig.json | 1 + .../admin-thumbnail/RegisterThumbnailFn.tsx | 35 +++++++++++++++ .../collections/admin-thumbnail/index.ts | 25 +---------- 21 files changed, 132 insertions(+), 77 deletions(-) create mode 100644 packages/ui/src/hooks/useAdminThumbnail.tsx create mode 100644 test/uploads/collections/admin-thumbnail/RegisterThumbnailFn.tsx diff --git a/packages/next/src/pages/Edit/Default/index.tsx b/packages/next/src/pages/Edit/Default/index.tsx index ef0c88eb0..0b9cd97e1 100644 --- a/packages/next/src/pages/Edit/Default/index.tsx +++ b/packages/next/src/pages/Edit/Default/index.tsx @@ -57,7 +57,7 @@ export const DefaultEditView: React.FC = () => { serverURL, } = config - const { getFieldMap } = useComponentMap() + const { componentMap, getFieldMap } = useComponentMap() const collectionConfig = collectionSlug && collections.find((collection) => collection.slug === collectionSlug) @@ -142,6 +142,8 @@ export const DefaultEditView: React.FC = () => { [serverURL, apiRoute, id, operation, schemaPath, collectionSlug, globalSlug], ) + const RegisterGetThumbnailFunction = componentMap?.[`${collectionSlug}.adminThumbnail`] + return (
@@ -220,11 +222,14 @@ export const DefaultEditView: React.FC = () => { /> )} {upload && ( - + + {RegisterGetThumbnailFunction && } + + )} ) diff --git a/packages/next/src/pages/List/Default/Cell/fields/File/index.tsx b/packages/next/src/pages/List/Default/Cell/fields/File/index.tsx index 09f006d6f..6c7871ee5 100644 --- a/packages/next/src/pages/List/Default/Cell/fields/File/index.tsx +++ b/packages/next/src/pages/List/Default/Cell/fields/File/index.tsx @@ -11,12 +11,13 @@ const baseClass = 'file' export interface FileCellProps extends CellComponentProps {} export const FileCell: React.FC = ({ cellData, customCellContext, rowData }) => { - const { uploadConfig } = customCellContext + const { collectionSlug, uploadConfig } = customCellContext return (
{ const mimeType: Field = { name: 'mimeType', + type: 'text', admin: { hidden: true, readOnly: true, }, label: 'MIME Type', - type: 'text', } const url: Field = { name: 'url', + type: 'text', admin: { hidden: true, readOnly: true, }, label: 'URL', - type: 'text', } const width: Field = { name: 'width', + type: 'number', admin: { hidden: true, readOnly: true, }, label: labels['upload:width'], - type: 'number', } const height: Field = { name: 'height', + type: 'number', admin: { hidden: true, readOnly: true, }, label: labels['upload:height'], - type: 'number', } const filesize: Field = { name: 'filesize', + type: 'number', admin: { hidden: true, readOnly: true, }, label: labels['upload:fileSize'], - type: 'number', } const filename: Field = { name: 'filename', + type: 'text', admin: { disableBulkEdit: true, hidden: true, @@ -82,7 +83,6 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => { }, index: true, label: labels['upload:fileName'], - type: 'text', unique: true, } @@ -93,10 +93,7 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => { afterRead: [ ({ data }) => { if (data?.filename) { - if (uploadOptions.staticURL.startsWith('/')) { - return `${config.serverURL}${uploadOptions.staticURL}/${data.filename}` - } - return `${uploadOptions.staticURL}/${data.filename}` + return `${config.serverURL}${config.routes.api}/${collection.slug}/file/${data.filename}` } return undefined @@ -119,11 +116,13 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => { uploadFields = uploadFields.concat([ { name: 'sizes', + type: 'group', admin: { hidden: true, }, fields: uploadOptions.imageSizes.map((size) => ({ name: size.name, + type: 'group', admin: { hidden: true, }, @@ -136,10 +135,7 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => { const sizeFilename = data?.sizes?.[size.name]?.filename if (sizeFilename) { - if (uploadOptions.staticURL.startsWith('/')) { - return `${config.serverURL}${uploadOptions.staticURL}/${sizeFilename}` - } - return `${uploadOptions.staticURL}/${sizeFilename}` + return `${config.serverURL}${config.routes.api}/${collection.slug}/file/${sizeFilename}` } return null @@ -157,10 +153,8 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => { }, ], label: size.name, - type: 'group', })), label: labels['upload:Sizes'], - type: 'group', }, ]) } diff --git a/packages/payload/src/uploads/types.ts b/packages/payload/src/uploads/types.ts index da722316a..6d2997e3f 100644 --- a/packages/payload/src/uploads/types.ts +++ b/packages/payload/src/uploads/types.ts @@ -70,7 +70,7 @@ export type ImageSize = Omit & { export type GetAdminThumbnail = (args: { doc: Record }) => false | null | string export type IncomingUploadType = { - adminThumbnail?: GetAdminThumbnail | string + adminThumbnail?: React.ComponentType | string crop?: boolean disableLocalStorage?: boolean filesRequiredOnCreate?: boolean @@ -88,7 +88,12 @@ export type IncomingUploadType = { } export type Upload = { - adminThumbnail?: GetAdminThumbnail | string + /** + * Represents an admin thumbnail, which can be either a React component or a string. + * - If a string, it should be one of the image size names. + * - If a React component, register a function that generates the thumbnail URL using the `useAdminThumbnail` hook. + **/ + adminThumbnail?: React.ComponentType | string crop?: boolean disableLocalStorage?: boolean filesRequiredOnCreate?: boolean diff --git a/packages/richtext-lexical/src/field/features/upload/component/index.tsx b/packages/richtext-lexical/src/field/features/upload/component/index.tsx index ccb986b1e..31eaae1d1 100644 --- a/packages/richtext-lexical/src/field/features/upload/component/index.tsx +++ b/packages/richtext-lexical/src/field/features/upload/component/index.tsx @@ -74,7 +74,7 @@ const Component: React.FC = (props) => { { initialParams }, ) - const thumbnailSRC = useThumbnail(relatedCollection, data) + const thumbnailSRC = useThumbnail(relatedCollection.slug, relatedCollection.upload, data) const removeUpload = useCallback(() => { editor.update(() => { diff --git a/packages/richtext-slate/src/field/elements/upload/Element/index.tsx b/packages/richtext-slate/src/field/elements/upload/Element/index.tsx index 07526d5b4..0cecff598 100644 --- a/packages/richtext-slate/src/field/elements/upload/Element/index.tsx +++ b/packages/richtext-slate/src/field/elements/upload/Element/index.tsx @@ -84,7 +84,7 @@ const Element: React.FC = ({ { initialParams }, ) - const thumbnailSRC = useThumbnail(relatedCollection.upload, data) + const thumbnailSRC = useThumbnail(relatedCollection.slug, relatedCollection.upload, data) const removeUpload = useCallback(() => { const elementPath = ReactEditor.findPath(editor, element) diff --git a/packages/ui/src/elements/FileDetails/index.tsx b/packages/ui/src/elements/FileDetails/index.tsx index f93868c8a..1e96b61b2 100644 --- a/packages/ui/src/elements/FileDetails/index.tsx +++ b/packages/ui/src/elements/FileDetails/index.tsx @@ -22,7 +22,12 @@ const FileDetails: React.FC = (props) => { return (
- +
= (props) => { const { className = '', + collectionSlug, doc: { filename } = {}, doc, fileSrc, @@ -20,7 +21,7 @@ const Thumbnail: React.FC = (props) => { uploadConfig, } = props - const thumbnailSRC = uploadConfig && doc ? useThumbnail(uploadConfig, doc) : fileSrc + const thumbnailSRC = useThumbnail(collectionSlug, uploadConfig, doc) || fileSrc const [src, setSrc] = useState(thumbnailSRC) const classes = [baseClass, `${baseClass}--size-${size || 'medium'}`, className].join(' ') diff --git a/packages/ui/src/elements/Thumbnail/types.ts b/packages/ui/src/elements/Thumbnail/types.ts index dd55ff795..0807ad442 100644 --- a/packages/ui/src/elements/Thumbnail/types.ts +++ b/packages/ui/src/elements/Thumbnail/types.ts @@ -2,6 +2,7 @@ import type { SanitizedCollectionConfig } from 'payload/types' export type Props = { className?: string + collectionSlug?: string doc?: Record fileSrc?: string imageCacheTag?: string diff --git a/packages/ui/src/elements/Upload/index.tsx b/packages/ui/src/elements/Upload/index.tsx index 77b2f9694..c800ad3d8 100644 --- a/packages/ui/src/elements/Upload/index.tsx +++ b/packages/ui/src/elements/Upload/index.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react' import type { Props } from './types' +import { useClientFunctions } from '../..' import Error from '../../forms/Error' import { useFormSubmitted } from '../../forms/Form/context' import reduceFieldsToValues from '../../forms/Form/reduceFieldsToValues' @@ -52,6 +53,7 @@ export const UploadActions = ({ canEdit, showSizePreviews }) => { export const Upload: React.FC = (props) => { const { collectionSlug, initialState, onChange, updatedAt, uploadConfig } = props + const clientFunctions = useClientFunctions() const submitted = useFormSubmitted() const [replacingFile, setReplacingFile] = useState(false) @@ -152,7 +154,10 @@ export const Upload: React.FC = (props) => { {value && (
- +
= (props) => {
    {filteredBlocks?.map((block, index) => { - const { imageAltText, imageURL, labels: blockLabels, slug } = block + const { slug, imageAltText, imageURL, labels: blockLabels } = block return (
  • diff --git a/packages/ui/src/hooks/useAdminThumbnail.tsx b/packages/ui/src/hooks/useAdminThumbnail.tsx new file mode 100644 index 000000000..f4088ed12 --- /dev/null +++ b/packages/ui/src/hooks/useAdminThumbnail.tsx @@ -0,0 +1,8 @@ +import type { GetAdminThumbnail } from 'payload/types' + +import { useAddClientFunction, useDocumentInfo } from '..' + +export const useAdminThumbnail = (func: GetAdminThumbnail) => { + const { collectionSlug } = useDocumentInfo() + useAddClientFunction(`${collectionSlug}.adminThumbnail`, func) +} diff --git a/packages/ui/src/hooks/useThumbnail.ts b/packages/ui/src/hooks/useThumbnail.ts index f80169aac..1affd9885 100644 --- a/packages/ui/src/hooks/useThumbnail.ts +++ b/packages/ui/src/hooks/useThumbnail.ts @@ -2,28 +2,34 @@ import type { SanitizedCollectionConfig } from 'payload/types' import { isImage } from 'payload/utilities' +import { useComponentMap } from '..' import { useConfig } from '../providers/Config' const absoluteURLPattern = new RegExp('^(?:[a-z]+:)?//', 'i') const base64Pattern = new RegExp(/^data:image\/[a-z]+;base64,/) +// /api/[collectionSlug]/file/[filename] + const useThumbnail = ( + collectionSlug: string, uploadConfig: SanitizedCollectionConfig['upload'], doc: Record, ): false | string => { - const { adminThumbnail, staticURL } = uploadConfig + const { + routes: { api: apiRoute }, + serverURL, + } = useConfig() + const { componentMap } = useComponentMap() + + if (!collectionSlug || !uploadConfig || !doc) return null + + const { adminThumbnail } = uploadConfig const { filename, mimeType, sizes, url } = doc + const thumbnailSrcFunction = componentMap?.[`${collectionSlug}.adminThumbnail`] - const { serverURL } = useConfig() - let pathURL = `${serverURL}${staticURL || ''}` - - if (absoluteURLPattern.test(staticURL)) { - pathURL = staticURL - } - - if (typeof adminThumbnail === 'function') { - const thumbnailURL = adminThumbnail({ doc }) + if (typeof thumbnailSrcFunction === 'function') { + const thumbnailURL = thumbnailSrcFunction({ doc }) if (!thumbnailURL) return false @@ -31,7 +37,7 @@ const useThumbnail = ( return thumbnailURL } - return `${pathURL}/${thumbnailURL}` + return `${serverURL}/${thumbnailURL}` } if (isImage(mimeType as string)) { @@ -39,19 +45,23 @@ const useThumbnail = ( return url as string } - if (sizes?.[adminThumbnail]?.url) { - return sizes[adminThumbnail].url - } + if (typeof adminThumbnail === 'string') { + if (sizes?.[adminThumbnail]?.url) { + return sizes[adminThumbnail].url + } - if (sizes?.[adminThumbnail]?.filename) { - return `${pathURL}/${sizes[adminThumbnail].filename}` + if (sizes?.[adminThumbnail]?.filename) { + return `${serverURL}${apiRoute}/${collectionSlug}/file/${sizes[adminThumbnail].filename}` + } } if (url) { return url as string } - return `${pathURL}/${filename}` + if (typeof filename === 'string') { + return `${serverURL}${apiRoute}/${collectionSlug}/file/${filename}` + } } return false diff --git a/packages/ui/src/utilities/buildComponentMap/index.tsx b/packages/ui/src/utilities/buildComponentMap/index.tsx index 870a1cf22..eb66edc59 100644 --- a/packages/ui/src/utilities/buildComponentMap/index.tsx +++ b/packages/ui/src/utilities/buildComponentMap/index.tsx @@ -97,7 +97,15 @@ export const buildComponentMap = (args: { readOnly: readOnlyOverride, }) + let AdminThumbnail = null + if (typeof collectionConfig?.upload?.adminThumbnail === 'function') { + AdminThumbnail = collectionConfig?.upload?.adminThumbnail + } else if (typeof collectionConfig?.upload?.adminThumbnail === 'string') { + AdminThumbnail = () => collectionConfig?.upload?.adminThumbnail + } + const componentMap: CollectionComponentMap = { + AdminThumbnail, AfterList, AfterListTable, BeforeList, diff --git a/packages/ui/src/utilities/buildComponentMap/types.ts b/packages/ui/src/utilities/buildComponentMap/types.ts index 06c1b2e17..06e951782 100644 --- a/packages/ui/src/utilities/buildComponentMap/types.ts +++ b/packages/ui/src/utilities/buildComponentMap/types.ts @@ -72,6 +72,7 @@ export type MappedField = { export type FieldMap = MappedField[] export type CollectionComponentMap = ConfigComponentMapBase & { + AdminThumbnail: React.ReactNode AfterList: React.ReactNode AfterListTable: React.ReactNode BeforeList: React.ReactNode diff --git a/test/_community/collections/Media/index.ts b/test/_community/collections/Media/index.ts index 408d73274..16dd434e7 100644 --- a/test/_community/collections/Media/index.ts +++ b/test/_community/collections/Media/index.ts @@ -1,14 +1,9 @@ -import path from 'path' - import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types' export const mediaSlug = 'media' export const MediaCollection: CollectionConfig = { slug: mediaSlug, - // upload: { - // staticDir: path.resolve(__dirname, './media'), - // }, upload: true, access: { read: () => true, diff --git a/test/field-error-states/collections/Upload/index.ts b/test/field-error-states/collections/Upload/index.ts index da72adcf6..c53d69d51 100644 --- a/test/field-error-states/collections/Upload/index.ts +++ b/test/field-error-states/collections/Upload/index.ts @@ -5,7 +5,7 @@ import type { CollectionConfig } from '../../../../packages/payload/src/collecti const Uploads: CollectionConfig = { slug: 'uploads', upload: { - staticDir: path.resolve(__dirname, './uploads'), + staticDir: path.resolve(process.cwd(), 'test/field-error-states/collections/Upload/uploads'), }, fields: [ { diff --git a/test/field-error-states/tsconfig.json b/test/field-error-states/tsconfig.json index 662b955ff..244de73b1 100644 --- a/test/field-error-states/tsconfig.json +++ b/test/field-error-states/tsconfig.json @@ -1,4 +1,5 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { "paths": { "payload/generated-types": ["./payload-types.ts"] diff --git a/test/uploads/collections/admin-thumbnail/RegisterThumbnailFn.tsx b/test/uploads/collections/admin-thumbnail/RegisterThumbnailFn.tsx new file mode 100644 index 000000000..120c95c84 --- /dev/null +++ b/test/uploads/collections/admin-thumbnail/RegisterThumbnailFn.tsx @@ -0,0 +1,35 @@ +'use client' +import type React from 'react' + +import { useAdminThumbnail } from '../../../../packages/ui/src/hooks/useAdminThumbnail' + +type TypeWithFile = { + filename: string + filesize: number + mimeType: string +} & Record + +function docHasFilename(doc: Record): doc is TypeWithFile { + if (typeof doc === 'object' && 'filename' in doc) { + return true + } + return false +} + +export const adminThumbnailSrc = '/media/image-640x480.png' + +function getThumbnailSrc({ doc }) { + if (docHasFilename(doc)) { + if (doc.mimeType.startsWith('image/')) { + return null // Fallback to default admin thumbnail if image + } + return adminThumbnailSrc // Use custom thumbnail if not image + } + return null +} + +export const RegisterAdminThumbnailFn: React.FC = () => { + void useAdminThumbnail(getThumbnailSrc) + + return null +} diff --git a/test/uploads/collections/admin-thumbnail/index.ts b/test/uploads/collections/admin-thumbnail/index.ts index 17bc29ed1..4114b4bf0 100644 --- a/test/uploads/collections/admin-thumbnail/index.ts +++ b/test/uploads/collections/admin-thumbnail/index.ts @@ -2,34 +2,13 @@ import path from 'path' import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types' -type TypeWithFile = { - filename: string - filesize: number - mimeType: string -} & Record - -function docHasFilename(doc: Record): doc is TypeWithFile { - if (typeof doc === 'object' && 'filename' in doc) { - return true - } - return false -} - -export const adminThumbnailSrc = '/media/image-640x480.png' +import { RegisterAdminThumbnailFn } from './RegisterThumbnailFn' export const AdminThumbnailCol: CollectionConfig = { slug: 'admin-thumbnail', upload: { staticDir: path.resolve(process.cwd(), 'test/uploads/media'), - // adminThumbnail: ({ doc }) => { - // if (docHasFilename(doc)) { - // if (doc.mimeType.startsWith('image/')) { - // return null // Fallback to default admin thumbnail if image - // } - // return adminThumbnailSrc // Use custom thumbnail if not image - // } - // return null - // }, + adminThumbnail: RegisterAdminThumbnailFn, }, fields: [], } From b288bcbc18977099f52f68071670720397643770 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Fri, 1 Mar 2024 01:40:00 -0500 Subject: [PATCH 12/15] chore(next): leave without saving (#5217) --- .../elements/LeaveWithoutSaving/index.scss | 2 +- .../src/elements/LeaveWithoutSaving/index.tsx | 52 +++--- .../LeaveWithoutSaving/usePreventLeave.tsx | 148 ++++++++++++++++++ .../next/src/pages/Edit/Default/index.tsx | 10 +- packages/next/src/pages/Edit/index.client.tsx | 22 ++- .../src/pages/LivePreview/index.client.tsx | 2 +- packages/ui/src/exports/elements.ts | 1 - .../ui/src/providers/DocumentInfo/types.ts | 2 +- 8 files changed, 207 insertions(+), 32 deletions(-) rename packages/{ui => next}/src/elements/LeaveWithoutSaving/index.scss (91%) rename packages/{ui => next}/src/elements/LeaveWithoutSaving/index.tsx (52%) create mode 100644 packages/next/src/elements/LeaveWithoutSaving/usePreventLeave.tsx diff --git a/packages/ui/src/elements/LeaveWithoutSaving/index.scss b/packages/next/src/elements/LeaveWithoutSaving/index.scss similarity index 91% rename from packages/ui/src/elements/LeaveWithoutSaving/index.scss rename to packages/next/src/elements/LeaveWithoutSaving/index.scss index 6cf46bd72..5088528a8 100644 --- a/packages/ui/src/elements/LeaveWithoutSaving/index.scss +++ b/packages/next/src/elements/LeaveWithoutSaving/index.scss @@ -1,4 +1,4 @@ -@import '../../scss/styles.scss'; +@import '../../../../ui/src/scss/styles.scss'; .leave-without-saving { @include blur-bg; diff --git a/packages/ui/src/elements/LeaveWithoutSaving/index.tsx b/packages/next/src/elements/LeaveWithoutSaving/index.tsx similarity index 52% rename from packages/ui/src/elements/LeaveWithoutSaving/index.tsx rename to packages/next/src/elements/LeaveWithoutSaving/index.tsx index fb8a83041..f2ead86b9 100644 --- a/packages/ui/src/elements/LeaveWithoutSaving/index.tsx +++ b/packages/next/src/elements/LeaveWithoutSaving/index.tsx @@ -1,12 +1,13 @@ 'use client' -import { Modal, useModal } from '@faceless-ui/modal' -import React, { useEffect } from 'react' +import { Modal, useModal } from '@payloadcms/ui' +import React, { useCallback, useEffect } from 'react' -import { Button } from '../../elements/Button' -import { useFormModified } from '../../forms/Form/context' -import { useAuth } from '../../providers/Auth' -import { useTranslation } from '../../providers/Translation' +import { Button } from '../../../../ui/src/elements/Button' +import { useFormModified } from '../../../../ui/src/forms/Form/context' +import { useAuth } from '../../../../ui/src/providers/Auth' +import { useTranslation } from '../../../../ui/src/providers/Translation' import './index.scss' +import { usePreventLeave } from './usePreventLeave' const modalSlug = 'leave-without-saving' @@ -17,15 +18,15 @@ const Component: React.FC<{ onCancel: () => void onConfirm: () => void }> = ({ isActive, onCancel, onConfirm }) => { - const { closeModal, openModal, modalState } = useModal() + const { closeModal, modalState, openModal } = useModal() const { t } = useTranslation() // Manually check for modal state as 'esc' key will not trigger the nav inactivity - useEffect(() => { - if (!modalState?.[modalSlug]?.isOpen && isActive) { - onCancel() - } - }, [modalState]) + // useEffect(() => { + // if (!modalState?.[modalSlug]?.isOpen && isActive) { + // onCancel() + // } + // }, [modalState, isActive, onCancel]) useEffect(() => { if (isActive) openModal(modalSlug) @@ -53,11 +54,26 @@ const Component: React.FC<{ export const LeaveWithoutSaving: React.FC = () => { const modified = useFormModified() const { user } = useAuth() + const [show, setShow] = React.useState(false) + const [hasAccepted, setHasAccepted] = React.useState(false) - return null - // - // {({ isActive, onCancel, onConfirm }) => ( - // - // )} - // + const prevent = Boolean(modified && user) + + const onPrevent = useCallback(() => { + setShow(true) + }, []) + + usePreventLeave({ hasAccepted, onPrevent, prevent }) + + return ( + { + setShow(false) + }} + onConfirm={() => { + setHasAccepted(true) + }} + /> + ) } diff --git a/packages/next/src/elements/LeaveWithoutSaving/usePreventLeave.tsx b/packages/next/src/elements/LeaveWithoutSaving/usePreventLeave.tsx new file mode 100644 index 000000000..340c9acb3 --- /dev/null +++ b/packages/next/src/elements/LeaveWithoutSaving/usePreventLeave.tsx @@ -0,0 +1,148 @@ +// Credit: @Taiki92777 +// - Source: https://github.com/vercel/next.js/discussions/32231#discussioncomment-7284386 +// Credit: `react-use` maintainers +// - Source: https://github.com/streamich/react-use/blob/ade8d3905f544305515d010737b4ae604cc51024/src/useBeforeUnload.ts#L2 +import { useRouter } from 'next/navigation' +import { useCallback, useEffect, useRef } from 'react' + +function on( + obj: T | null, + ...args: [string, Function | null, ...any] | Parameters +): void { + if (obj && obj.addEventListener) { + obj.addEventListener(...(args as Parameters)) + } +} + +function off( + obj: T | null, + ...args: [string, Function | null, ...any] | Parameters +): void { + if (obj && obj.removeEventListener) { + obj.removeEventListener(...(args as Parameters)) + } +} + +export const useBeforeUnload = (enabled: (() => boolean) | boolean = true, message?: string) => { + const handler = useCallback( + (event: BeforeUnloadEvent) => { + const finalEnabled = typeof enabled === 'function' ? enabled() : true + + if (!finalEnabled) { + return + } + + event.preventDefault() + + if (message) { + event.returnValue = message + } + + return message + }, + [enabled, message], + ) + + useEffect(() => { + if (!enabled) { + return + } + + on(window, 'beforeunload', handler) + + return () => off(window, 'beforeunload', handler) + }, [enabled, handler]) +} + +export const usePreventLeave = ({ + hasAccepted = false, + message = 'Are you sure want to leave this page?', + onPrevent, + prevent = true, +}: { + hasAccepted: boolean + // if no `onPrevent` is provided, the message will be displayed in a confirm dialog + message?: string + // to use a custom confirmation dialog, provide a function that returns a boolean + onPrevent?: () => void + prevent: boolean +}) => { + // check when page is about to be reloaded + useBeforeUnload(prevent, message) + + const router = useRouter() + const cancelledURL = useRef('') + + // check when page is about to be changed + useEffect(() => { + function isAnchorOfCurrentUrl(currentUrl: string, newUrl: string) { + const currentUrlObj = new URL(currentUrl) + const newUrlObj = new URL(newUrl) + // Compare hostname, pathname, and search parameters + if ( + currentUrlObj.hostname === newUrlObj.hostname && + currentUrlObj.pathname === newUrlObj.pathname && + currentUrlObj.search === newUrlObj.search + ) { + // Check if the new URL is just an anchor of the current URL page + const currentHash = currentUrlObj.hash + const newHash = newUrlObj.hash + return ( + currentHash !== newHash && + currentUrlObj.href.replace(currentHash, '') === newUrlObj.href.replace(newHash, '') + ) + } + return false + } + + function findClosestAnchor(element: HTMLElement | null): HTMLAnchorElement | null { + while (element && element.tagName.toLowerCase() !== 'a') { + element = element.parentElement + } + return element as HTMLAnchorElement + } + function handleClick(event: MouseEvent) { + try { + const target = event.target as HTMLElement + const anchor = findClosestAnchor(target) + if (anchor) { + const currentUrl = window.location.href + const newUrl = anchor.href + const isAnchor = isAnchorOfCurrentUrl(currentUrl, newUrl) + const isDownloadLink = anchor.download !== '' + + const isPageLeaving = !(newUrl === currentUrl || isAnchor || isDownloadLink) + + if (isPageLeaving && prevent && (!onPrevent ? !window.confirm(message) : true)) { + // Keep a reference of the href + cancelledURL.current = newUrl + + // Cancel the route change + event.preventDefault() + event.stopPropagation() + + if (typeof onPrevent === 'function') { + onPrevent() + } + } + } + } catch (err) { + alert(err) + } + } + + // Add the global click event listener + document.addEventListener('click', handleClick, true) + + // Clean up the global click event listener when the component is unmounted + return () => { + document.removeEventListener('click', handleClick, true) + } + }, [onPrevent, prevent, message]) + + useEffect(() => { + if (hasAccepted && cancelledURL.current) { + router.push(cancelledURL.current) + } + }, [hasAccepted, router]) +} diff --git a/packages/next/src/pages/Edit/Default/index.tsx b/packages/next/src/pages/Edit/Default/index.tsx index 0b9cd97e1..d67ab74f1 100644 --- a/packages/next/src/pages/Edit/Default/index.tsx +++ b/packages/next/src/pages/Edit/Default/index.tsx @@ -7,7 +7,6 @@ import { FieldPathProvider, Form, FormLoadingOverlayToggle, - LeaveWithoutSaving, OperationProvider, getFormState, useComponentMap, @@ -17,6 +16,7 @@ import { import React, { Fragment, useCallback } from 'react' import { Upload } from '../../../../../ui/src/elements/Upload' +import { LeaveWithoutSaving } from '../../../elements/LeaveWithoutSaving' // import { getTranslation } from '@payloadcms/translations' import Auth from './Auth' import { SetDocumentTitle } from './SetDocumentTitle' @@ -45,7 +45,7 @@ export const DefaultEditView: React.FC = () => { hasSavePermission, initialData: data, initialState, - onSave: onSaveFromProps, + onSave: onSaveFromContext, } = useDocumentInfo() const config = useConfig() @@ -95,8 +95,8 @@ export const DefaultEditView: React.FC = () => { // await refreshCookieAsync() // } - if (typeof onSaveFromProps === 'function') { - onSaveFromProps({ + if (typeof onSaveFromContext === 'function') { + onSaveFromContext({ ...json, operation: id ? 'update' : 'create', }) @@ -104,7 +104,7 @@ export const DefaultEditView: React.FC = () => { }, [ id, - onSaveFromProps, + onSaveFromContext, // refreshCookieAsync, // reportUpdate ], diff --git a/packages/next/src/pages/Edit/index.client.tsx b/packages/next/src/pages/Edit/index.client.tsx index 4b1124b96..fda4ac157 100644 --- a/packages/next/src/pages/Edit/index.client.tsx +++ b/packages/next/src/pages/Edit/index.client.tsx @@ -1,12 +1,17 @@ 'use client' import type { EditViewProps } from 'payload/config' -import { LoadingOverlay, useComponentMap, useDocumentInfo } from '@payloadcms/ui' -import React, { Fragment } from 'react' +import { LoadingOverlay, useComponentMap, useConfig, useDocumentInfo } from '@payloadcms/ui' +import { redirect } from 'next/navigation' +import React, { Fragment, useEffect } from 'react' import { useCallback } from 'react' export const EditViewClient: React.FC = () => { - const { id, collectionSlug, getDocPermissions, getVersions, globalSlug } = useDocumentInfo() + const { id, collectionSlug, getDocPermissions, getVersions, globalSlug, setDocumentInfo } = + useDocumentInfo() + const { + routes: { api: adminRoute }, + } = useConfig() const { componentMap } = useComponentMap() @@ -22,7 +27,7 @@ export const EditViewClient: React.FC = () => { getDocPermissions() if (!isEditing) { - // setRedirect(`${admin}/collections/${collection.slug}/${json?.doc?.id}`) + redirect(`${adminRoute}/collections/${collectionSlug}/${json?.doc?.id}`) } else { // buildState(json.doc, { // fieldSchema: collection.fields, @@ -33,9 +38,16 @@ export const EditViewClient: React.FC = () => { // })) } }, - [getVersions, isEditing, getDocPermissions, collectionSlug], + [getVersions, isEditing, getDocPermissions, collectionSlug, adminRoute], ) + useEffect(() => { + setDocumentInfo((current) => ({ + ...current, + onSave, + })) + }, [setDocumentInfo, onSave]) + // Allow the `DocumentInfoProvider` to hydrate if (!Edit || (!collectionSlug && !globalSlug)) { return diff --git a/packages/next/src/pages/LivePreview/index.client.tsx b/packages/next/src/pages/LivePreview/index.client.tsx index 8bf3cf531..226ec6686 100644 --- a/packages/next/src/pages/LivePreview/index.client.tsx +++ b/packages/next/src/pages/LivePreview/index.client.tsx @@ -8,7 +8,6 @@ import { DocumentFields, FieldPathProvider, Form, - LeaveWithoutSaving, LoadingOverlay, OperationProvider, getFormState, @@ -19,6 +18,7 @@ import { } from '@payloadcms/ui' import React, { Fragment, useCallback } from 'react' +import { LeaveWithoutSaving } from '../../elements/LeaveWithoutSaving' import { SetDocumentTitle } from '../Edit/Default/SetDocumentTitle' import { SetStepNav } from '../Edit/Default/SetStepNav' import { LivePreviewProvider } from './Context' diff --git a/packages/ui/src/exports/elements.ts b/packages/ui/src/exports/elements.ts index dee2d161b..7b58ff2d9 100644 --- a/packages/ui/src/exports/elements.ts +++ b/packages/ui/src/exports/elements.ts @@ -12,7 +12,6 @@ export { ErrorPill } from '../elements/ErrorPill' export { default as GenerateConfirmation } from '../elements/GenerateConfirmation' export { Gutter } from '../elements/Gutter' export { HydrateClientUser } from '../elements/HydrateClientUser' -export { LeaveWithoutSaving } from '../elements/LeaveWithoutSaving' export { ListControls } from '../elements/ListControls' export { useListDrawer } from '../elements/ListDrawer' export { ListSelection } from '../elements/ListSelection' diff --git a/packages/ui/src/providers/DocumentInfo/types.ts b/packages/ui/src/providers/DocumentInfo/types.ts index b0e22a375..62d9555c4 100644 --- a/packages/ui/src/providers/DocumentInfo/types.ts +++ b/packages/ui/src/providers/DocumentInfo/types.ts @@ -48,6 +48,6 @@ export type DocumentInfoContext = Omit & { getDocPreferences: () => Promise<{ [key: string]: unknown }> getVersions: () => Promise setDocFieldPreferences: (field: string, fieldPreferences: { [key: string]: unknown }) => void - setDocumentInfo?: (info: DocumentInfo) => void + setDocumentInfo?: React.Dispatch> setDocumentTitle: (title: string) => void } From e75917a4d37dd201bd4b021f67615171b47df963 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Fri, 1 Mar 2024 08:16:02 -0500 Subject: [PATCH 13/15] chore: sanitizes admin.preview from client config --- packages/payload/src/config/createClientConfig.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/payload/src/config/createClientConfig.ts b/packages/payload/src/config/createClientConfig.ts index 757698415..0e8222053 100644 --- a/packages/payload/src/config/createClientConfig.ts +++ b/packages/payload/src/config/createClientConfig.ts @@ -72,6 +72,10 @@ const sanitizeCollections = ( if ('hidden' in sanitized.admin) { delete sanitized.admin.hidden } + + if ('preview' in sanitized.admin) { + delete sanitized.admin.preview + } } return sanitized @@ -95,6 +99,10 @@ const sanitizeGlobals = (globals: SanitizedConfig['globals']): ClientConfig['glo if ('hidden' in sanitized.admin) { delete sanitized.admin.hidden } + + if ('preview' in sanitized.admin) { + delete sanitized.admin.preview + } } return sanitized From cd63722e5ec281cfe76a86eb012aa7bfa10602cc Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Fri, 1 Mar 2024 09:17:19 -0500 Subject: [PATCH 14/15] fix(ui): infinite rendering in document drawer --- packages/ui/src/elements/DocumentDrawer/DrawerContent.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/ui/src/elements/DocumentDrawer/DrawerContent.tsx b/packages/ui/src/elements/DocumentDrawer/DrawerContent.tsx index 8e18f4997..29a0162a9 100644 --- a/packages/ui/src/elements/DocumentDrawer/DrawerContent.tsx +++ b/packages/ui/src/elements/DocumentDrawer/DrawerContent.tsx @@ -20,7 +20,6 @@ import { DocumentInfoProvider } from '../../providers/DocumentInfo' import { useFormQueryParams } from '../../providers/FormQueryParams' import { useLocale } from '../../providers/Locale' import { useTranslation } from '../../providers/Translation' -import { formatFields } from '../../utilities/formatFields' import { getFormState } from '../../utilities/getFormState' import { Gutter } from '../Gutter' import IDLabel from '../IDLabel' @@ -58,8 +57,6 @@ const Content: React.FC = ({ const { Edit } = componentMap[`${collectionSlug ? 'collections' : 'globals'}`][collectionSlug] - const [fields, setFields] = useState(() => formatFields(fieldsFromConfig, true)) - // no need to an additional requests when creating new documents const initialID = useRef(id) @@ -68,10 +65,6 @@ const Content: React.FC = ({ { initialParams: { depth: 0, draft: 'true', 'fallback-locale': 'null' } }, ) - useEffect(() => { - setFields(formatFields(fields, true)) - }, [collectionSlug, collectionConfig, fields]) - useEffect(() => { setIsOpen(Boolean(modalState[drawerSlug]?.isOpen)) }, [modalState, drawerSlug]) From 486b3a1f448b833c17e6ed132fb3a68fda4f4d75 Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Fri, 1 Mar 2024 09:24:03 -0500 Subject: [PATCH 15/15] feat(richtext-lexical): headings --- .../heading/{index.ts => feature.client.tsx} | 73 +++++++------------ .../field/features/heading/feature.server.ts | 60 +++++++++++++++ .../field/lexical/config/client/sanitize.ts | 7 +- .../field/lexical/config/server/default.ts | 2 +- packages/richtext-lexical/src/index.ts | 2 +- test/buildConfigWithDefaults.ts | 2 + 6 files changed, 96 insertions(+), 50 deletions(-) rename packages/richtext-lexical/src/field/features/heading/{index.ts => feature.client.tsx} (51%) create mode 100644 packages/richtext-lexical/src/field/features/heading/feature.server.ts diff --git a/packages/richtext-lexical/src/field/features/heading/index.ts b/packages/richtext-lexical/src/field/features/heading/feature.client.tsx similarity index 51% rename from packages/richtext-lexical/src/field/features/heading/index.ts rename to packages/richtext-lexical/src/field/features/heading/feature.client.tsx index f4211d0b3..a910bc9ab 100644 --- a/packages/richtext-lexical/src/field/features/heading/index.ts +++ b/packages/richtext-lexical/src/field/features/heading/feature.client.tsx @@ -1,15 +1,24 @@ -import type { HeadingTagType, SerializedHeadingNode } from '@lexical/rich-text' +'use client' -import { $createHeadingNode, HeadingNode } from '@lexical/rich-text' +import type { HeadingTagType } from '@lexical/rich-text' + +import { HeadingNode } from '@lexical/rich-text' +import { $createHeadingNode } from '@lexical/rich-text' import { $setBlocksType } from '@lexical/selection' import { $getSelection } from 'lexical' -import type { HTMLConverter } from '../converters/html/converter/types' -import type { FeatureProvider } from '../types' +import type { FeatureProviderProviderClient } from '../types' +import type { HeadingFeatureProps } from './feature.server' import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types' +import { H1Icon } from '../../lexical/ui/icons/H1' +import { H2Icon } from '../../lexical/ui/icons/H2' +import { H3Icon } from '../../lexical/ui/icons/H3' +import { H4Icon } from '../../lexical/ui/icons/H4' +import { H5Icon } from '../../lexical/ui/icons/H5' +import { H6Icon } from '../../lexical/ui/icons/H6' import { TextDropdownSectionWithEntries } from '../common/floatingSelectToolbarTextDropdownSection' -import { convertLexicalNodesToHTML } from '../converters/html/converter' +import { createClientComponent } from '../createClientComponent' import { MarkdownTransformer } from './markdownTransformer' const setHeading = (headingSize: HeadingTagType) => { @@ -17,31 +26,23 @@ const setHeading = (headingSize: HeadingTagType) => { $setBlocksType(selection, () => $createHeadingNode(headingSize)) } -type Props = { - enabledHeadingSizes?: HeadingTagType[] -} - const iconImports = { - // @ts-expect-error-next-line - h1: () => import('../../lexical/ui/icons/H1').then((module) => module.H1Icon), - // @ts-expect-error-next-line - h2: () => import('../../lexical/ui/icons/H2').then((module) => module.H2Icon), - // @ts-expect-error-next-line - h3: () => import('../../lexical/ui/icons/H3').then((module) => module.H3Icon), - // @ts-expect-error-next-line - h4: () => import('../../lexical/ui/icons/H4').then((module) => module.H4Icon), - // @ts-expect-error-next-line - h5: () => import('../../lexical/ui/icons/H5').then((module) => module.H5Icon), - // @ts-expect-error-next-line - h6: () => import('../../lexical/ui/icons/H6').then((module) => module.H6Icon), + h1: H1Icon, + h2: H2Icon, + h3: H3Icon, + h4: H4Icon, + h5: H5Icon, + h6: H6Icon, } -export const HeadingFeature = (props: Props): FeatureProvider => { +const HeadingFeatureClient: FeatureProviderProviderClient = (props) => { const { enabledHeadingSizes = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] } = props return { + clientFeatureProps: props, feature: () => { return { + clientFeatureProps: props, floatingSelectToolbar: { sections: [ ...enabledHeadingSizes.map((headingSize, i) => @@ -63,30 +64,7 @@ export const HeadingFeature = (props: Props): FeatureProvider => { ], }, markdownTransformers: [MarkdownTransformer(enabledHeadingSizes)], - nodes: [ - { - type: HeadingNode.getType(), - converters: { - html: { - converter: async ({ converters, node, parent }) => { - const childrenText = await convertLexicalNodesToHTML({ - converters, - lexicalNodes: node.children, - parent: { - ...node, - parent, - }, - }) - - return '<' + node?.tag + '>' + childrenText + '' - }, - nodeTypes: [HeadingNode.getType()], - } as HTMLConverter, - }, - node: HeadingNode, - }, - ], - props, + nodes: [HeadingNode], slashMenu: { options: [ ...enabledHeadingSizes.map((headingSize) => { @@ -109,6 +87,7 @@ export const HeadingFeature = (props: Props): FeatureProvider => { }, } }, - key: 'heading', } } + +export const HeadingFeatureClientComponent = createClientComponent(HeadingFeatureClient) diff --git a/packages/richtext-lexical/src/field/features/heading/feature.server.ts b/packages/richtext-lexical/src/field/features/heading/feature.server.ts new file mode 100644 index 000000000..aabb20d33 --- /dev/null +++ b/packages/richtext-lexical/src/field/features/heading/feature.server.ts @@ -0,0 +1,60 @@ +import type { HeadingTagType } from '@lexical/rich-text' + +import { HeadingNode, type SerializedHeadingNode } from '@lexical/rich-text' + +import type { HTMLConverter } from '../converters/html/converter/types' +import type { FeatureProviderProviderServer } from '../types' + +import { convertLexicalNodesToHTML } from '../converters/html/converter' +import { HeadingFeatureClientComponent } from './feature.client' +import { MarkdownTransformer } from './markdownTransformer' + +export type HeadingFeatureProps = { + enabledHeadingSizes?: HeadingTagType[] +} + +export const HeadingFeature: FeatureProviderProviderServer< + HeadingFeatureProps, + HeadingFeatureProps +> = (props) => { + if (!props) { + props = {} + } + + const { enabledHeadingSizes = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] } = props + + return { + feature: () => { + return { + ClientComponent: HeadingFeatureClientComponent, + markdownTransformers: [MarkdownTransformer(enabledHeadingSizes)], + nodes: [ + { + type: HeadingNode.getType(), + converters: { + html: { + converter: async ({ converters, node, parent }) => { + const childrenText = await convertLexicalNodesToHTML({ + converters, + lexicalNodes: node.children, + parent: { + ...node, + parent, + }, + }) + + return '<' + node?.tag + '>' + childrenText + '' + }, + nodeTypes: [HeadingNode.getType()], + } as HTMLConverter, + }, + node: HeadingNode, + }, + ], + serverFeatureProps: props, + } + }, + key: 'heading', + serverFeatureProps: props, + } +} 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 ee881abc0..06d00a1de 100644 --- a/packages/richtext-lexical/src/field/lexical/config/client/sanitize.ts +++ b/packages/richtext-lexical/src/field/lexical/config/client/sanitize.ts @@ -13,11 +13,11 @@ export const sanitizeClientFeatures = ( floatingSelectToolbar: { sections: [], }, - hooks: { load: [], save: [], }, + markdownTransformers: [], nodes: [], plugins: [], slashMenu: { @@ -110,6 +110,11 @@ export const sanitizeClientFeatures = ( } } + if (feature.markdownTransformers?.length) { + sanitized.markdownTransformers = sanitized.markdownTransformers.concat( + feature.markdownTransformers, + ) + } sanitized.enabledFeatures.push(feature.key) }) 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 2f6376714..2c75cfb66 100644 --- a/packages/richtext-lexical/src/field/lexical/config/server/default.ts +++ b/packages/richtext-lexical/src/field/lexical/config/server/default.ts @@ -12,7 +12,7 @@ import { StrikethroughFeature } from '../../../features/format/strikethrough/fea import { SubscriptFeature } from '../../../features/format/subscript/feature.server' import { SuperscriptFeature } from '../../../features/format/superscript/feature.server' import { UnderlineFeature } from '../../../features/format/underline/feature.server' -import { HeadingFeature } from '../../../features/heading' +import { HeadingFeature } from '../../../features/heading/feature.server' import { IndentFeature } from '../../../features/indent' import { LinkFeature } from '../../../features/link/feature.server' import { CheckListFeature } from '../../../features/lists/checklist' diff --git a/packages/richtext-lexical/src/index.ts b/packages/richtext-lexical/src/index.ts index 78165e5d7..06d559d37 100644 --- a/packages/richtext-lexical/src/index.ts +++ b/packages/richtext-lexical/src/index.ts @@ -270,7 +270,7 @@ export { StrikethroughFeature } from './field/features/format/strikethrough/feat export { SubscriptFeature } from './field/features/format/subscript/feature.server' export { SuperscriptFeature } from './field/features/format/superscript/feature.server' export { UnderlineFeature } from './field/features/format/underline/feature.server' -export { HeadingFeature } from './field/features/heading' +export { HeadingFeature } from './field/features/heading/feature.server' export { IndentFeature } from './field/features/indent' export { LinkFeature, type LinkFeatureServerProps } from './field/features/link/feature.server' diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index a1c347da8..6f5d52e8c 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -3,6 +3,7 @@ import { BlockQuoteFeature, BlocksFeature, BoldFeature, + HeadingFeature, InlineCodeFeature, ItalicFeature, LinkFeature, @@ -99,6 +100,7 @@ export function buildConfigWithDefaults(testConfig?: Partial): Promise