chore: merge
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.7.0",
|
"@apollo/client": "^3.7.0",
|
||||||
"@faceless-ui/css-grid": "^1.2.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",
|
"escape-html": "^1.0.3",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
"next": "^13.5.6",
|
"next": "^13.5.6",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"@payloadcms/bundler-webpack": "latest",
|
"@payloadcms/bundler-webpack": "latest",
|
||||||
"@payloadcms/db-mongodb": "latest",
|
"@payloadcms/db-mongodb": "latest",
|
||||||
"@payloadcms/richtext-slate": "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-form-builder": "^1.0.12",
|
||||||
"@payloadcms/plugin-seo": "^1.0.8",
|
"@payloadcms/plugin-seo": "^1.0.8",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import '../../scss/styles.scss';
|
@import '../../../../ui/src/scss/styles.scss';
|
||||||
|
|
||||||
.leave-without-saving {
|
.leave-without-saving {
|
||||||
@include blur-bg;
|
@include blur-bg;
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { Modal, useModal } from '@faceless-ui/modal'
|
import { Modal, useModal } from '@payloadcms/ui'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useCallback, useEffect } from 'react'
|
||||||
|
|
||||||
import { Button } from '../../elements/Button'
|
import { Button } from '../../../../ui/src/elements/Button'
|
||||||
import { useFormModified } from '../../forms/Form/context'
|
import { useFormModified } from '../../../../ui/src/forms/Form/context'
|
||||||
import { useAuth } from '../../providers/Auth'
|
import { useAuth } from '../../../../ui/src/providers/Auth'
|
||||||
import { useTranslation } from '../../providers/Translation'
|
import { useTranslation } from '../../../../ui/src/providers/Translation'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
import { usePreventLeave } from './usePreventLeave'
|
||||||
|
|
||||||
const modalSlug = 'leave-without-saving'
|
const modalSlug = 'leave-without-saving'
|
||||||
|
|
||||||
@@ -17,15 +18,15 @@ const Component: React.FC<{
|
|||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
onConfirm: () => void
|
onConfirm: () => void
|
||||||
}> = ({ isActive, onCancel, onConfirm }) => {
|
}> = ({ isActive, onCancel, onConfirm }) => {
|
||||||
const { closeModal, openModal, modalState } = useModal()
|
const { closeModal, modalState, openModal } = useModal()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
// Manually check for modal state as 'esc' key will not trigger the nav inactivity
|
// Manually check for modal state as 'esc' key will not trigger the nav inactivity
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (!modalState?.[modalSlug]?.isOpen && isActive) {
|
// if (!modalState?.[modalSlug]?.isOpen && isActive) {
|
||||||
onCancel()
|
// onCancel()
|
||||||
}
|
// }
|
||||||
}, [modalState])
|
// }, [modalState, isActive, onCancel])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isActive) openModal(modalSlug)
|
if (isActive) openModal(modalSlug)
|
||||||
@@ -53,11 +54,26 @@ const Component: React.FC<{
|
|||||||
export const LeaveWithoutSaving: React.FC = () => {
|
export const LeaveWithoutSaving: React.FC = () => {
|
||||||
const modified = useFormModified()
|
const modified = useFormModified()
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
|
const [show, setShow] = React.useState(false)
|
||||||
|
const [hasAccepted, setHasAccepted] = React.useState(false)
|
||||||
|
|
||||||
return null
|
const prevent = Boolean(modified && user)
|
||||||
// <NavigationPrompt renderIfNotActive when={Boolean(modified && user)}>
|
|
||||||
// {({ isActive, onCancel, onConfirm }) => (
|
const onPrevent = useCallback(() => {
|
||||||
// <Component isActive={isActive} onCancel={onCancel} onConfirm={onConfirm} />
|
setShow(true)
|
||||||
// )}
|
}, [])
|
||||||
// </NavigationPrompt>
|
|
||||||
|
usePreventLeave({ hasAccepted, onPrevent, prevent })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
isActive={show}
|
||||||
|
onCancel={() => {
|
||||||
|
setShow(false)
|
||||||
|
}}
|
||||||
|
onConfirm={() => {
|
||||||
|
setHasAccepted(true)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -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<T extends Document | EventTarget | HTMLElement | Window>(
|
||||||
|
obj: T | null,
|
||||||
|
...args: [string, Function | null, ...any] | Parameters<T['addEventListener']>
|
||||||
|
): void {
|
||||||
|
if (obj && obj.addEventListener) {
|
||||||
|
obj.addEventListener(...(args as Parameters<HTMLElement['addEventListener']>))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function off<T extends Document | EventTarget | HTMLElement | Window>(
|
||||||
|
obj: T | null,
|
||||||
|
...args: [string, Function | null, ...any] | Parameters<T['removeEventListener']>
|
||||||
|
): void {
|
||||||
|
if (obj && obj.removeEventListener) {
|
||||||
|
obj.removeEventListener(...(args as Parameters<HTMLElement['removeEventListener']>))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string>('')
|
||||||
|
|
||||||
|
// 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])
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
FieldPathProvider,
|
FieldPathProvider,
|
||||||
Form,
|
Form,
|
||||||
FormLoadingOverlayToggle,
|
FormLoadingOverlayToggle,
|
||||||
LeaveWithoutSaving,
|
|
||||||
OperationProvider,
|
OperationProvider,
|
||||||
getFormState,
|
getFormState,
|
||||||
useComponentMap,
|
useComponentMap,
|
||||||
@@ -17,6 +16,7 @@ import {
|
|||||||
import React, { Fragment, useCallback } from 'react'
|
import React, { Fragment, useCallback } from 'react'
|
||||||
|
|
||||||
import { Upload } from '../../../../../ui/src/elements/Upload'
|
import { Upload } from '../../../../../ui/src/elements/Upload'
|
||||||
|
import { LeaveWithoutSaving } from '../../../elements/LeaveWithoutSaving'
|
||||||
// import { getTranslation } from '@payloadcms/translations'
|
// import { getTranslation } from '@payloadcms/translations'
|
||||||
import Auth from './Auth'
|
import Auth from './Auth'
|
||||||
import { SetDocumentTitle } from './SetDocumentTitle'
|
import { SetDocumentTitle } from './SetDocumentTitle'
|
||||||
@@ -45,7 +45,7 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
hasSavePermission,
|
hasSavePermission,
|
||||||
initialData: data,
|
initialData: data,
|
||||||
initialState,
|
initialState,
|
||||||
onSave: onSaveFromProps,
|
onSave: onSaveFromContext,
|
||||||
} = useDocumentInfo()
|
} = useDocumentInfo()
|
||||||
|
|
||||||
const config = useConfig()
|
const config = useConfig()
|
||||||
@@ -57,7 +57,7 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
serverURL,
|
serverURL,
|
||||||
} = config
|
} = config
|
||||||
|
|
||||||
const { getFieldMap } = useComponentMap()
|
const { componentMap, getFieldMap } = useComponentMap()
|
||||||
|
|
||||||
const collectionConfig =
|
const collectionConfig =
|
||||||
collectionSlug && collections.find((collection) => collection.slug === collectionSlug)
|
collectionSlug && collections.find((collection) => collection.slug === collectionSlug)
|
||||||
@@ -95,8 +95,8 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
// await refreshCookieAsync()
|
// await refreshCookieAsync()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (typeof onSaveFromProps === 'function') {
|
if (typeof onSaveFromContext === 'function') {
|
||||||
onSaveFromProps({
|
onSaveFromContext({
|
||||||
...json,
|
...json,
|
||||||
operation: id ? 'update' : 'create',
|
operation: id ? 'update' : 'create',
|
||||||
})
|
})
|
||||||
@@ -104,7 +104,7 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
id,
|
id,
|
||||||
onSaveFromProps,
|
onSaveFromContext,
|
||||||
// refreshCookieAsync,
|
// refreshCookieAsync,
|
||||||
// reportUpdate
|
// reportUpdate
|
||||||
],
|
],
|
||||||
@@ -142,6 +142,8 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
[serverURL, apiRoute, id, operation, schemaPath, collectionSlug, globalSlug],
|
[serverURL, apiRoute, id, operation, schemaPath, collectionSlug, globalSlug],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const RegisterGetThumbnailFunction = componentMap?.[`${collectionSlug}.adminThumbnail`]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className={classes}>
|
<main className={classes}>
|
||||||
<FieldPathProvider path="" schemaPath={schemaPath}>
|
<FieldPathProvider path="" schemaPath={schemaPath}>
|
||||||
@@ -220,11 +222,14 @@ export const DefaultEditView: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{upload && (
|
{upload && (
|
||||||
|
<React.Fragment>
|
||||||
|
{RegisterGetThumbnailFunction && <RegisterGetThumbnailFunction />}
|
||||||
<Upload
|
<Upload
|
||||||
collectionSlug={collectionConfig.slug}
|
collectionSlug={collectionConfig.slug}
|
||||||
initialState={initialState}
|
initialState={initialState}
|
||||||
uploadConfig={upload}
|
uploadConfig={upload}
|
||||||
/>
|
/>
|
||||||
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { EditViewProps } from 'payload/config'
|
import type { EditViewProps } from 'payload/config'
|
||||||
|
|
||||||
import { LoadingOverlay, useComponentMap, useDocumentInfo } from '@payloadcms/ui'
|
import { LoadingOverlay, useComponentMap, useConfig, useDocumentInfo } from '@payloadcms/ui'
|
||||||
import React, { Fragment } from 'react'
|
import { redirect } from 'next/navigation'
|
||||||
|
import React, { Fragment, useEffect } from 'react'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
export const EditViewClient: React.FC<EditViewProps> = () => {
|
export const EditViewClient: React.FC<EditViewProps> = () => {
|
||||||
const { id, collectionSlug, getDocPermissions, getVersions, globalSlug } = useDocumentInfo()
|
const { id, collectionSlug, getDocPermissions, getVersions, globalSlug, setDocumentInfo } =
|
||||||
|
useDocumentInfo()
|
||||||
|
const {
|
||||||
|
routes: { api: adminRoute },
|
||||||
|
} = useConfig()
|
||||||
|
|
||||||
const { componentMap } = useComponentMap()
|
const { componentMap } = useComponentMap()
|
||||||
|
|
||||||
@@ -22,7 +27,7 @@ export const EditViewClient: React.FC<EditViewProps> = () => {
|
|||||||
getDocPermissions()
|
getDocPermissions()
|
||||||
|
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
// setRedirect(`${admin}/collections/${collection.slug}/${json?.doc?.id}`)
|
redirect(`${adminRoute}/collections/${collectionSlug}/${json?.doc?.id}`)
|
||||||
} else {
|
} else {
|
||||||
// buildState(json.doc, {
|
// buildState(json.doc, {
|
||||||
// fieldSchema: collection.fields,
|
// fieldSchema: collection.fields,
|
||||||
@@ -33,9 +38,16 @@ export const EditViewClient: React.FC<EditViewProps> = () => {
|
|||||||
// }))
|
// }))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[getVersions, isEditing, getDocPermissions, collectionSlug],
|
[getVersions, isEditing, getDocPermissions, collectionSlug, adminRoute],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDocumentInfo((current) => ({
|
||||||
|
...current,
|
||||||
|
onSave,
|
||||||
|
}))
|
||||||
|
}, [setDocumentInfo, onSave])
|
||||||
|
|
||||||
// Allow the `DocumentInfoProvider` to hydrate
|
// Allow the `DocumentInfoProvider` to hydrate
|
||||||
if (!Edit || (!collectionSlug && !globalSlug)) {
|
if (!Edit || (!collectionSlug && !globalSlug)) {
|
||||||
return <LoadingOverlay />
|
return <LoadingOverlay />
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ const baseClass = 'file'
|
|||||||
export interface FileCellProps extends CellComponentProps<any> {}
|
export interface FileCellProps extends CellComponentProps<any> {}
|
||||||
|
|
||||||
export const FileCell: React.FC<FileCellProps> = ({ cellData, customCellContext, rowData }) => {
|
export const FileCell: React.FC<FileCellProps> = ({ cellData, customCellContext, rowData }) => {
|
||||||
const { uploadConfig } = customCellContext
|
const { collectionSlug, uploadConfig } = customCellContext
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={baseClass}>
|
<div className={baseClass}>
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
className={`${baseClass}__thumbnail`}
|
className={`${baseClass}__thumbnail`}
|
||||||
|
collectionSlug={collectionSlug}
|
||||||
doc={{
|
doc={{
|
||||||
...rowData,
|
...rowData,
|
||||||
filename: cellData,
|
filename: cellData,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
DocumentFields,
|
DocumentFields,
|
||||||
FieldPathProvider,
|
FieldPathProvider,
|
||||||
Form,
|
Form,
|
||||||
LeaveWithoutSaving,
|
|
||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
OperationProvider,
|
OperationProvider,
|
||||||
getFormState,
|
getFormState,
|
||||||
@@ -19,6 +18,7 @@ import {
|
|||||||
} from '@payloadcms/ui'
|
} from '@payloadcms/ui'
|
||||||
import React, { Fragment, useCallback } from 'react'
|
import React, { Fragment, useCallback } from 'react'
|
||||||
|
|
||||||
|
import { LeaveWithoutSaving } from '../../elements/LeaveWithoutSaving'
|
||||||
import { SetDocumentTitle } from '../Edit/Default/SetDocumentTitle'
|
import { SetDocumentTitle } from '../Edit/Default/SetDocumentTitle'
|
||||||
import { SetStepNav } from '../Edit/Default/SetStepNav'
|
import { SetStepNav } from '../Edit/Default/SetStepNav'
|
||||||
import { LivePreviewProvider } from './Context'
|
import { LivePreviewProvider } from './Context'
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"translateNewKeys": "ts-node -T ./scripts/translateNewKeys.ts"
|
"translateNewKeys": "ts-node -T ./scripts/translateNewKeys.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@payloadcms/translations": "workspace:^",
|
"@payloadcms/translations": "workspace:*",
|
||||||
"bson-objectid": "2.0.4",
|
"bson-objectid": "2.0.4",
|
||||||
"conf": "10.2.0",
|
"conf": "10.2.0",
|
||||||
"console-table-printer": "2.11.2",
|
"console-table-printer": "2.11.2",
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ const sanitizeCollections = (
|
|||||||
if ('hidden' in sanitized.admin) {
|
if ('hidden' in sanitized.admin) {
|
||||||
delete sanitized.admin.hidden
|
delete sanitized.admin.hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('preview' in sanitized.admin) {
|
||||||
|
delete sanitized.admin.preview
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sanitized
|
return sanitized
|
||||||
@@ -95,6 +99,10 @@ const sanitizeGlobals = (globals: SanitizedConfig['globals']): ClientConfig['glo
|
|||||||
if ('hidden' in sanitized.admin) {
|
if ('hidden' in sanitized.admin) {
|
||||||
delete sanitized.admin.hidden
|
delete sanitized.admin.hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('preview' in sanitized.admin) {
|
||||||
|
delete sanitized.admin.preview
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sanitized
|
return sanitized
|
||||||
|
|||||||
@@ -25,56 +25,57 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
|||||||
|
|
||||||
const mimeType: Field = {
|
const mimeType: Field = {
|
||||||
name: 'mimeType',
|
name: 'mimeType',
|
||||||
|
type: 'text',
|
||||||
admin: {
|
admin: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
},
|
},
|
||||||
label: 'MIME Type',
|
label: 'MIME Type',
|
||||||
type: 'text',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const url: Field = {
|
const url: Field = {
|
||||||
name: 'url',
|
name: 'url',
|
||||||
|
type: 'text',
|
||||||
admin: {
|
admin: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
},
|
},
|
||||||
label: 'URL',
|
label: 'URL',
|
||||||
type: 'text',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const width: Field = {
|
const width: Field = {
|
||||||
name: 'width',
|
name: 'width',
|
||||||
|
type: 'number',
|
||||||
admin: {
|
admin: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
},
|
},
|
||||||
label: labels['upload:width'],
|
label: labels['upload:width'],
|
||||||
type: 'number',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const height: Field = {
|
const height: Field = {
|
||||||
name: 'height',
|
name: 'height',
|
||||||
|
type: 'number',
|
||||||
admin: {
|
admin: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
},
|
},
|
||||||
label: labels['upload:height'],
|
label: labels['upload:height'],
|
||||||
type: 'number',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesize: Field = {
|
const filesize: Field = {
|
||||||
name: 'filesize',
|
name: 'filesize',
|
||||||
|
type: 'number',
|
||||||
admin: {
|
admin: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
},
|
},
|
||||||
label: labels['upload:fileSize'],
|
label: labels['upload:fileSize'],
|
||||||
type: 'number',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filename: Field = {
|
const filename: Field = {
|
||||||
name: 'filename',
|
name: 'filename',
|
||||||
|
type: 'text',
|
||||||
admin: {
|
admin: {
|
||||||
disableBulkEdit: true,
|
disableBulkEdit: true,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
@@ -82,7 +83,6 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
|||||||
},
|
},
|
||||||
index: true,
|
index: true,
|
||||||
label: labels['upload:fileName'],
|
label: labels['upload:fileName'],
|
||||||
type: 'text',
|
|
||||||
unique: true,
|
unique: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,10 +93,7 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
|||||||
afterRead: [
|
afterRead: [
|
||||||
({ data }) => {
|
({ data }) => {
|
||||||
if (data?.filename) {
|
if (data?.filename) {
|
||||||
if (uploadOptions.staticURL.startsWith('/')) {
|
return `${config.serverURL}${config.routes.api}/${collection.slug}/file/${data.filename}`
|
||||||
return `${config.serverURL}${uploadOptions.staticURL}/${data.filename}`
|
|
||||||
}
|
|
||||||
return `${uploadOptions.staticURL}/${data.filename}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
@@ -119,11 +116,13 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
|||||||
uploadFields = uploadFields.concat([
|
uploadFields = uploadFields.concat([
|
||||||
{
|
{
|
||||||
name: 'sizes',
|
name: 'sizes',
|
||||||
|
type: 'group',
|
||||||
admin: {
|
admin: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
fields: uploadOptions.imageSizes.map((size) => ({
|
fields: uploadOptions.imageSizes.map((size) => ({
|
||||||
name: size.name,
|
name: size.name,
|
||||||
|
type: 'group',
|
||||||
admin: {
|
admin: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
},
|
},
|
||||||
@@ -136,10 +135,7 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
|||||||
const sizeFilename = data?.sizes?.[size.name]?.filename
|
const sizeFilename = data?.sizes?.[size.name]?.filename
|
||||||
|
|
||||||
if (sizeFilename) {
|
if (sizeFilename) {
|
||||||
if (uploadOptions.staticURL.startsWith('/')) {
|
return `${config.serverURL}${config.routes.api}/${collection.slug}/file/${sizeFilename}`
|
||||||
return `${config.serverURL}${uploadOptions.staticURL}/${sizeFilename}`
|
|
||||||
}
|
|
||||||
return `${uploadOptions.staticURL}/${sizeFilename}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@@ -157,10 +153,8 @@ const getBaseUploadFields = ({ collection, config }: Options): Field[] => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
label: size.name,
|
label: size.name,
|
||||||
type: 'group',
|
|
||||||
})),
|
})),
|
||||||
label: labels['upload:Sizes'],
|
label: labels['upload:Sizes'],
|
||||||
type: 'group',
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export type ImageSize = Omit<ResizeOptions, 'withoutEnlargement'> & {
|
|||||||
export type GetAdminThumbnail = (args: { doc: Record<string, unknown> }) => false | null | string
|
export type GetAdminThumbnail = (args: { doc: Record<string, unknown> }) => false | null | string
|
||||||
|
|
||||||
export type IncomingUploadType = {
|
export type IncomingUploadType = {
|
||||||
adminThumbnail?: GetAdminThumbnail | string
|
adminThumbnail?: React.ComponentType | string
|
||||||
crop?: boolean
|
crop?: boolean
|
||||||
disableLocalStorage?: boolean
|
disableLocalStorage?: boolean
|
||||||
filesRequiredOnCreate?: boolean
|
filesRequiredOnCreate?: boolean
|
||||||
@@ -88,7 +88,12 @@ export type IncomingUploadType = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Upload = {
|
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
|
crop?: boolean
|
||||||
disableLocalStorage?: boolean
|
disableLocalStorage?: boolean
|
||||||
filesRequiredOnCreate?: boolean
|
filesRequiredOnCreate?: boolean
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||||
"build": "pnpm build:swc && pnpm build:types",
|
"build": "echo \"Build temporarily disabled.\" && exit 0",
|
||||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||||
"prepublishOnly": "pnpm clean && pnpm turbo build",
|
"prepublishOnly": "pnpm clean && pnpm turbo build",
|
||||||
"test": "echo \"No tests available.\""
|
"test": "echo \"No tests available.\""
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@payloadcms/ui": "workspace:^",
|
"@payloadcms/ui": "workspace:*",
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
"escape-html": "^1.0.3"
|
"escape-html": "^1.0.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@payloadcms/ui": "workspace:^",
|
"@payloadcms/ui": "workspace:*",
|
||||||
"ts-deepmerge": "^2.0.1"
|
"ts-deepmerge": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"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:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"payload": "^1.1.8 || ^2.0.0"
|
"payload": "^1.1.8 || ^2.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@payloadcms/ui": "workspace:^",
|
"@payloadcms/ui": "workspace:*",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"stripe": "^10.2.0",
|
"stripe": "^10.2.0",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faceless-ui/modal": "2.0.1",
|
"@faceless-ui/modal": "2.0.2",
|
||||||
"@lexical/headless": "0.13.1",
|
"@lexical/headless": "0.13.1",
|
||||||
"@lexical/link": "0.13.1",
|
"@lexical/link": "0.13.1",
|
||||||
"@lexical/list": "0.13.1",
|
"@lexical/list": "0.13.1",
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
"@lexical/selection": "0.13.1",
|
"@lexical/selection": "0.13.1",
|
||||||
"@lexical/utils": "0.13.1",
|
"@lexical/utils": "0.13.1",
|
||||||
"@payloadcms/translations": "workspace:*",
|
"@payloadcms/translations": "workspace:*",
|
||||||
"@payloadcms/ui": "workspace:*",
|
|
||||||
"bson-objectid": "2.0.4",
|
"bson-objectid": "2.0.4",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"deep-equal": "2.2.3",
|
"deep-equal": "2.2.3",
|
||||||
@@ -43,6 +42,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@payloadcms/eslint-config": "workspace:*",
|
"@payloadcms/eslint-config": "workspace:*",
|
||||||
|
"@payloadcms/ui": "workspace:*",
|
||||||
"@types/json-schema": "7.0.15",
|
"@types/json-schema": "7.0.15",
|
||||||
"@types/node": "20.6.2",
|
"@types/node": "20.6.2",
|
||||||
"@types/react": "18.2.15",
|
"@types/react": "18.2.15",
|
||||||
@@ -50,8 +50,8 @@
|
|||||||
"payload": "workspace:*"
|
"payload": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@payloadcms/translations": "workspace:^",
|
"@payloadcms/translations": "workspace:*",
|
||||||
"@payloadcms/ui": "workspace:^",
|
"@payloadcms/ui": "workspace:*",
|
||||||
"payload": "^2.4.0"
|
"payload": "^2.4.0"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* Wraps the input formData in a blockFieldWrapperName, so that it can be read by the RenderFields component
|
|
||||||
* which requires it to be wrapped in a group field
|
|
||||||
*/
|
|
||||||
export function transformInputFormData(data: any, blockFieldWrapperName: string) {
|
|
||||||
const dataCopy = JSON.parse(JSON.stringify(data))
|
|
||||||
|
|
||||||
const fieldDataWithoutBlockFields = { ...dataCopy }
|
|
||||||
delete fieldDataWithoutBlockFields['id']
|
|
||||||
delete fieldDataWithoutBlockFields['blockName']
|
|
||||||
delete fieldDataWithoutBlockFields['blockType']
|
|
||||||
|
|
||||||
// Wrap all fields inside blockFieldWrapperName.
|
|
||||||
// This is necessary, because blockFieldWrapperName is set as the 'base' path for all fields in the block (in the RenderFields component).
|
|
||||||
// Thus, in order for the data to be read, it has to be wrapped in this blockFieldWrapperName, as it's expected to be there.
|
|
||||||
|
|
||||||
// Why are we doing this? Because that way, all rendered fields of the blocks have different paths and names, and thus don't conflict with each other.
|
|
||||||
// They have different paths and names, because they are wrapped in the blockFieldWrapperName, which has a name that is unique for each block.
|
|
||||||
return {
|
|
||||||
id: dataCopy.id,
|
|
||||||
[blockFieldWrapperName]: fieldDataWithoutBlockFields,
|
|
||||||
blockName: dataCopy.blockName,
|
|
||||||
blockType: dataCopy.blockType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import type { Field } from 'payload/types'
|
|
||||||
|
|
||||||
export function transformInputFormSchema(formSchema: any, blockFieldWrapperName: string): Field[] {
|
|
||||||
const formSchemaCopy = [...formSchema]
|
|
||||||
|
|
||||||
// First, check if it needs wrapping
|
|
||||||
const hasBlockFieldWrapper = formSchemaCopy.some(
|
|
||||||
(field) => 'name' in field && field.name === blockFieldWrapperName,
|
|
||||||
)
|
|
||||||
if (hasBlockFieldWrapper) {
|
|
||||||
return formSchemaCopy
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a group in the field schema, which represents all values saved in the blockFieldWrapperName
|
|
||||||
return [
|
|
||||||
...formSchemaCopy.filter(
|
|
||||||
(field) => 'name' in field && ['blockName', 'blockType', 'id'].includes(field.name),
|
|
||||||
),
|
|
||||||
{
|
|
||||||
name: blockFieldWrapperName,
|
|
||||||
type: 'group',
|
|
||||||
admin: {
|
|
||||||
hideGutter: true,
|
|
||||||
},
|
|
||||||
fields: formSchemaCopy.filter(
|
|
||||||
(field) => !('name' in field) || !['blockName', 'blockType', 'id'].includes(field.name),
|
|
||||||
),
|
|
||||||
label: '',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
ErrorPill,
|
ErrorPill,
|
||||||
Pill,
|
Pill,
|
||||||
SectionTitle,
|
SectionTitle,
|
||||||
createNestedFieldPath,
|
|
||||||
useDocumentInfo,
|
useDocumentInfo,
|
||||||
useFormSubmitted,
|
useFormSubmitted,
|
||||||
useTranslation,
|
useTranslation,
|
||||||
@@ -21,7 +20,6 @@ import { $getNodeByKey } from 'lexical'
|
|||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
|
|
||||||
import type { ReducedBlock } from '../../../../../../ui/src/utilities/buildComponentMap/types'
|
import type { ReducedBlock } from '../../../../../../ui/src/utilities/buildComponentMap/types'
|
||||||
import type { FieldProps } from '../../../../types'
|
|
||||||
import type { BlockFields, BlockNode } from '../nodes/BlocksNode'
|
import type { BlockFields, BlockNode } from '../nodes/BlocksNode'
|
||||||
|
|
||||||
import { FormSavePlugin } from './FormSavePlugin'
|
import { FormSavePlugin } from './FormSavePlugin'
|
||||||
@@ -89,8 +87,6 @@ export const BlockContent: React.FC<Props> = (props) => {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ')
|
.join(' ')
|
||||||
|
|
||||||
const path = '' as const
|
|
||||||
|
|
||||||
const onFormChange = useCallback(
|
const onFormChange = useCallback(
|
||||||
({
|
({
|
||||||
fullFieldsWithValues,
|
fullFieldsWithValues,
|
||||||
@@ -101,9 +97,9 @@ export const BlockContent: React.FC<Props> = (props) => {
|
|||||||
}) => {
|
}) => {
|
||||||
newFormData = {
|
newFormData = {
|
||||||
...newFormData,
|
...newFormData,
|
||||||
id: formData.id, // TODO: Why does form updatee not include theeeeem
|
id: formData.id,
|
||||||
blockName: formData.blockName, // TODO: Why does form updatee not include theeeeem
|
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, // TODO: Why does form updatee not include theeeeem
|
blockType: formData.blockType,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively remove all undefined values from even being present in formData, as they will
|
// Recursively remove all undefined values from even being present in formData, as they will
|
||||||
@@ -123,8 +119,6 @@ export const BlockContent: React.FC<Props> = (props) => {
|
|||||||
removeUndefinedAndNullRecursively(newFormData)
|
removeUndefinedAndNullRecursively(newFormData)
|
||||||
removeUndefinedAndNullRecursively(formData)
|
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,
|
// 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
|
// which would trigger the "Leave without saving" dialog unnecessarily
|
||||||
if (!isDeepEqual(formData, newFormData)) {
|
if (!isDeepEqual(formData, newFormData)) {
|
||||||
@@ -136,7 +130,6 @@ export const BlockContent: React.FC<Props> = (props) => {
|
|||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
const node: BlockNode = $getNodeByKey(nodeKey)
|
const node: BlockNode = $getNodeByKey(nodeKey)
|
||||||
if (node) {
|
if (node) {
|
||||||
console.log('saving node data...', newFormData)
|
|
||||||
node.setFields(newFormData as BlockFields)
|
node.setFields(newFormData as BlockFields)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -197,14 +190,14 @@ export const BlockContent: React.FC<Props> = (props) => {
|
|||||||
? getTranslation(labels.singular, i18n)
|
? getTranslation(labels.singular, i18n)
|
||||||
: '[Singular Label]'}
|
: '[Singular Label]'}
|
||||||
</Pill>
|
</Pill>
|
||||||
<SectionTitle path={`${path}blockName`} readOnly={field?.admin?.readOnly} />
|
<SectionTitle path="blockName2" readOnly={field?.readOnly} />
|
||||||
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
|
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
|
||||||
</div>
|
</div>
|
||||||
{editor.isEditable() && (
|
{editor.isEditable() && (
|
||||||
<Button
|
<Button
|
||||||
buttonStyle="icon-label"
|
buttonStyle="icon-label"
|
||||||
className={`${baseClass}__removeButton`}
|
className={`${baseClass}__removeButton`}
|
||||||
disabled={field?.admin?.readOnly}
|
disabled={field?.readOnly}
|
||||||
icon="x"
|
icon="x"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -25,7 +25,6 @@ export const FormSavePlugin: React.FC<Props> = (props) => {
|
|||||||
const newFormData = reduceFieldsToValues(fields, true)
|
const newFormData = reduceFieldsToValues(fields, true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('FormSavePlugin', newFormData)
|
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange({ fullFieldsWithValues: fields, newFormData })
|
onChange({ fullFieldsWithValues: fields, newFormData })
|
||||||
}
|
}
|
||||||
@@ -4,15 +4,11 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
type FormProps,
|
type FormProps,
|
||||||
type FormState,
|
type FormState,
|
||||||
buildInitialState,
|
|
||||||
buildStateFromSchema,
|
|
||||||
getFormState,
|
getFormState,
|
||||||
useConfig,
|
useConfig,
|
||||||
useDocumentInfo,
|
useDocumentInfo,
|
||||||
useFieldPath,
|
useFieldPath,
|
||||||
useFormSubmitted,
|
useFormSubmitted,
|
||||||
useLocale,
|
|
||||||
useTranslation,
|
|
||||||
} from '@payloadcms/ui'
|
} from '@payloadcms/ui'
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
@@ -21,6 +17,8 @@ const baseClass = 'lexical-block'
|
|||||||
|
|
||||||
import type { Data } from 'payload/types'
|
import type { Data } from 'payload/types'
|
||||||
|
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
import type { ReducedBlock } from '../../../../../../ui/src/utilities/buildComponentMap/types'
|
import type { ReducedBlock } from '../../../../../../ui/src/utilities/buildComponentMap/types'
|
||||||
import type { ClientComponentProps } from '../../types'
|
import type { ClientComponentProps } from '../../types'
|
||||||
import type { BlocksFeatureClientProps } from '../feature.client'
|
import type { BlocksFeatureClientProps } from '../feature.client'
|
||||||
@@ -54,8 +52,6 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
field: { richTextComponentMap },
|
field: { richTextComponentMap },
|
||||||
} = useEditorConfigContext()
|
} = useEditorConfigContext()
|
||||||
|
|
||||||
console.log('1. Loading node data', formData)
|
|
||||||
|
|
||||||
const componentMapRenderedFieldsPath = `feature.blocks.fields.${formData?.blockType}`
|
const componentMapRenderedFieldsPath = `feature.blocks.fields.${formData?.blockType}`
|
||||||
const schemaFieldsPath = `${schemaPath}.feature.blocks.${formData?.blockType}`
|
const schemaFieldsPath = `${schemaPath}.feature.blocks.${formData?.blockType}`
|
||||||
|
|
||||||
@@ -64,7 +60,8 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
?.clientFeatureProps as ClientComponentProps<BlocksFeatureClientProps>
|
?.clientFeatureProps as ClientComponentProps<BlocksFeatureClientProps>
|
||||||
)?.reducedBlocks?.find((block) => block.slug === formData?.blockType)
|
)?.reducedBlocks?.find((block) => block.slug === formData?.blockType)
|
||||||
|
|
||||||
const fieldMap = richTextComponentMap.get(componentMapRenderedFieldsPath) // Field Schema
|
const fieldMap = richTextComponentMap.get(componentMapRenderedFieldsPath)
|
||||||
|
// Field Schema
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const awaitInitialState = async () => {
|
const awaitInitialState = async () => {
|
||||||
const state = await getFormState({
|
const state = await getFormState({
|
||||||
@@ -79,7 +76,15 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
}) // Form State
|
}) // Form State
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
setInitialState(state)
|
setInitialState({
|
||||||
|
...state,
|
||||||
|
blockName2: {
|
||||||
|
initialValue: '',
|
||||||
|
passesCondition: true,
|
||||||
|
valid: true,
|
||||||
|
value: formData.blockName,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +95,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const onChange: FormProps['onChange'][0] = useCallback(
|
const onChange: FormProps['onChange'][0] = useCallback(
|
||||||
async ({ formState: prevFormState }) => {
|
async ({ formState: prevFormState }) => {
|
||||||
return await getFormState({
|
const formState = await getFormState({
|
||||||
apiRoute: config.routes.api,
|
apiRoute: config.routes.api,
|
||||||
body: {
|
body: {
|
||||||
id,
|
id,
|
||||||
@@ -100,18 +105,21 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
},
|
},
|
||||||
serverURL: config.serverURL,
|
serverURL: config.serverURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...formState,
|
||||||
|
blockName2: {
|
||||||
|
initialValue: '',
|
||||||
|
passesCondition: true,
|
||||||
|
valid: true,
|
||||||
|
value: formData.blockName,
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
[config.routes.api, config.serverURL, schemaFieldsPath, id],
|
[config.routes.api, config.serverURL, schemaFieldsPath, id, formData.blockName],
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sanitize block's fields here. This is done here and not in the feature, because the payload config is available here
|
|
||||||
//const formSchema = transformInputFormSchema(fieldMap, blockFieldWrapperName)
|
|
||||||
|
|
||||||
const initialStateRef = React.useRef<Data>(null) // Store initial value in a ref, so it doesn't change on re-render and only gets initialized once
|
|
||||||
|
|
||||||
console.log('Bloocks initialState', initialState)
|
|
||||||
|
|
||||||
// Memoized Form JSX
|
// Memoized Form JSX
|
||||||
const formContent = useMemo(() => {
|
const formContent = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@@ -123,6 +131,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
initialState={initialState}
|
initialState={initialState}
|
||||||
onChange={[onChange]}
|
onChange={[onChange]}
|
||||||
submitted={submitted}
|
submitted={submitted}
|
||||||
|
uuid={uuid()}
|
||||||
>
|
>
|
||||||
<BlockContent
|
<BlockContent
|
||||||
baseClass={baseClass}
|
baseClass={baseClass}
|
||||||
@@ -136,7 +145,16 @@ export const BlockComponent: React.FC<Props> = (props) => {
|
|||||||
</FieldPathProvider>
|
</FieldPathProvider>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}, [fieldMap, parentLexicalRichTextField, nodeKey, submitted, initialState, reducedBlock])
|
}, [
|
||||||
|
fieldMap,
|
||||||
|
parentLexicalRichTextField,
|
||||||
|
nodeKey,
|
||||||
|
submitted,
|
||||||
|
initialState,
|
||||||
|
reducedBlock,
|
||||||
|
blockFieldWrapperName,
|
||||||
|
onChange,
|
||||||
|
])
|
||||||
|
|
||||||
return <div className={baseClass}>{formContent}</div>
|
return <div className={baseClass}>{formContent}</div>
|
||||||
}
|
}
|
||||||
@@ -14,8 +14,6 @@ import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode'
|
|||||||
import ObjectID from 'bson-objectid'
|
import ObjectID from 'bson-objectid'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { transformInputFormData } from '../utils/transformInputFormData'
|
|
||||||
|
|
||||||
export type BlockFields = {
|
export type BlockFields = {
|
||||||
/** Block form data */
|
/** Block form data */
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
@@ -90,17 +88,7 @@ export class BlockNode extends DecoratorBlockNode {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element {
|
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element {
|
||||||
const blockFieldWrapperName = this.getFields().blockType + '-' + this.getFields().id
|
return <BlockComponent formData={this.getFields()} nodeKey={this.getKey()} />
|
||||||
const transformedFormData = transformInputFormData(this.getFields(), blockFieldWrapperName)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BlockComponent
|
|
||||||
blockFieldWrapperName={blockFieldWrapperName}
|
|
||||||
formData={this.getFields()}
|
|
||||||
nodeKey={this.getKey()}
|
|
||||||
transformedFormData={transformedFormData}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exportDOM(): DOMExportOutput {
|
exportDOM(): DOMExportOutput {
|
||||||
@@ -129,18 +117,7 @@ export class BlockNode extends DecoratorBlockNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFields(fields: BlockFields): void {
|
setFields(fields: BlockFields): void {
|
||||||
let fieldsCopy = JSON.parse(JSON.stringify(fields)) as BlockFields
|
const fieldsCopy = JSON.parse(JSON.stringify(fields)) as BlockFields
|
||||||
// Possibly transform fields
|
|
||||||
const blockFieldWrapperName = fieldsCopy.blockType + '-' + fieldsCopy.id
|
|
||||||
if (fieldsCopy[blockFieldWrapperName]) {
|
|
||||||
fieldsCopy = {
|
|
||||||
id: fieldsCopy.id,
|
|
||||||
blockName: fieldsCopy.blockName,
|
|
||||||
blockType: fieldsCopy.blockType,
|
|
||||||
...fieldsCopy[blockFieldWrapperName],
|
|
||||||
}
|
|
||||||
delete fieldsCopy[blockFieldWrapperName]
|
|
||||||
}
|
|
||||||
|
|
||||||
const writable = this.getWritable()
|
const writable = this.getWritable()
|
||||||
writable.__fields = fieldsCopy
|
writable.__fields = fieldsCopy
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import type { HTMLConverter } from '@payloadcms/richtext-lexical'
|
||||||
|
|
||||||
|
import type { FeatureProviderProviderServer } from '../../types'
|
||||||
|
|
||||||
|
export type HTMLConverterFeatureProps = {
|
||||||
|
converters?:
|
||||||
|
| (({ defaultConverters }: { defaultConverters: HTMLConverter[] }) => HTMLConverter[])
|
||||||
|
| HTMLConverter[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HTMLConverterFeature: FeatureProviderProviderServer<
|
||||||
|
HTMLConverterFeatureProps,
|
||||||
|
undefined
|
||||||
|
> = (props) => {
|
||||||
|
return {
|
||||||
|
feature: () => {
|
||||||
|
return {
|
||||||
|
clientFeatureProps: null,
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'htmlConverter',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { SerializedEditorState } from 'lexical'
|
import type { SerializedEditorState } from 'lexical'
|
||||||
import type { Field, RichTextField, TextField } from 'payload/types'
|
import type { Field, RichTextField, TextField } from 'payload/types'
|
||||||
|
|
||||||
import type { LexicalRichTextAdapter, SanitizedEditorConfig } from '../../../../../index'
|
import type { LexicalRichTextAdapter, SanitizedServerEditorConfig } from '../../../../../index'
|
||||||
import type { AdapterProps } from '../../../../../types'
|
import type { AdapterProps } from '../../../../../types'
|
||||||
import type { HTMLConverter } from '../converter/types'
|
import type { HTMLConverter } from '../converter/types'
|
||||||
import type { HTMLConverterFeatureProps } from '../index'
|
import type { HTMLConverterFeatureProps } from '../feature.server'
|
||||||
|
|
||||||
import { convertLexicalToHTML } from '../converter'
|
import { convertLexicalToHTML } from '../converter'
|
||||||
import { defaultHTMLConverters } from '../converter/defaultConverters'
|
import { defaultHTMLConverters } from '../converter/defaultConverters'
|
||||||
@@ -21,10 +21,11 @@ type Props = {
|
|||||||
export const consolidateHTMLConverters = ({
|
export const consolidateHTMLConverters = ({
|
||||||
editorConfig,
|
editorConfig,
|
||||||
}: {
|
}: {
|
||||||
editorConfig: SanitizedEditorConfig
|
editorConfig: SanitizedServerEditorConfig
|
||||||
}) => {
|
}) => {
|
||||||
const htmlConverterFeature = editorConfig.resolvedFeatureMap.get('htmlConverter')
|
const htmlConverterFeature = editorConfig.resolvedFeatureMap.get('htmlConverter')
|
||||||
const htmlConverterFeatureProps: HTMLConverterFeatureProps = htmlConverterFeature?.props
|
const htmlConverterFeatureProps: HTMLConverterFeatureProps =
|
||||||
|
htmlConverterFeature?.serverFeatureProps
|
||||||
|
|
||||||
const defaultConvertersWithConvertersFromFeatures = defaultHTMLConverters
|
const defaultConvertersWithConvertersFromFeatures = defaultHTMLConverters
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ export const lexicalHTML: (
|
|||||||
) => TextField = (lexicalFieldName, props) => {
|
) => TextField = (lexicalFieldName, props) => {
|
||||||
const { name = 'lexicalHTML' } = props
|
const { name = 'lexicalHTML' } = props
|
||||||
return {
|
return {
|
||||||
name: name,
|
name,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
admin: {
|
admin: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import type { FeatureProvider } from '../../types'
|
|
||||||
import type { HTMLConverter } from './converter/types'
|
|
||||||
|
|
||||||
export type HTMLConverterFeatureProps = {
|
|
||||||
converters?:
|
|
||||||
| (({ defaultConverters }: { defaultConverters: HTMLConverter[] }) => HTMLConverter[])
|
|
||||||
| HTMLConverter[]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This feature only manages the converters. They are read and actually run / executed by the
|
|
||||||
* Lexical field.
|
|
||||||
*/
|
|
||||||
export const HTMLConverterFeature = (props?: HTMLConverterFeatureProps): FeatureProvider => {
|
|
||||||
if (!props) {
|
|
||||||
props = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
feature: () => {
|
|
||||||
return {
|
|
||||||
props,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
key: 'htmlConverter',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import type { FeatureProvider } from '../../types'
|
|
||||||
|
|
||||||
export const TestRecorderFeature = (): FeatureProvider => {
|
|
||||||
return {
|
|
||||||
feature: () => {
|
|
||||||
return {
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
Component: () =>
|
|
||||||
// @ts-expect-error-next-line
|
|
||||||
import('./plugin').then((module) => module.TestRecorderPlugin),
|
|
||||||
position: 'bottom',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
props: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
key: 'debug-testrecorder',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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: 'debug-treeview',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FeatureProviderProviderClient } from '../../types'
|
||||||
|
|
||||||
|
import { createClientComponent } from '../../createClientComponent'
|
||||||
|
import { TestRecorderPlugin } from './plugin'
|
||||||
|
|
||||||
|
const TestRecorderFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||||
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
|
feature: () => ({
|
||||||
|
clientFeatureProps: props,
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
Component: TestRecorderPlugin,
|
||||||
|
position: 'bottom',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TestRecorderFeatureClientComponent = createClientComponent(TestRecorderFeatureClient)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { FeatureProviderProviderServer } from '../../types'
|
||||||
|
|
||||||
|
import { TestRecorderFeatureClientComponent } from './feature.client'
|
||||||
|
|
||||||
|
export const TestRecorderFeature: FeatureProviderProviderServer<undefined, undefined> = (props) => {
|
||||||
|
return {
|
||||||
|
feature: () => {
|
||||||
|
return {
|
||||||
|
ClientComponent: TestRecorderFeatureClientComponent,
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'testrecorder',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -389,6 +389,7 @@ ${steps.map(formatStep).join(`\n`)}
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}}
|
}}
|
||||||
title={isRecording ? 'Disable test recorder' : 'Enable test recorder'}
|
title={isRecording ? 'Disable test recorder' : 'Enable test recorder'}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
{isRecording ? 'Disable test recorder' : 'Enable test recorder'}
|
{isRecording ? 'Disable test recorder' : 'Enable test recorder'}
|
||||||
</button>
|
</button>
|
||||||
@@ -404,6 +405,7 @@ ${steps.map(formatStep).join(`\n`)}
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}}
|
}}
|
||||||
title="Insert snapshot"
|
title="Insert snapshot"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Insert Snapshot
|
Insert Snapshot
|
||||||
</button>
|
</button>
|
||||||
@@ -415,6 +417,7 @@ ${steps.map(formatStep).join(`\n`)}
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}}
|
}}
|
||||||
title="Copy to clipboard"
|
title="Copy to clipboard"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Copy
|
Copy
|
||||||
</button>
|
</button>
|
||||||
@@ -426,6 +429,7 @@ ${steps.map(formatStep).join(`\n`)}
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}}
|
}}
|
||||||
title="Download as a file"
|
title="Download as a file"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Download
|
Download
|
||||||
</button>
|
</button>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FeatureProviderProviderClient } from '../../types'
|
||||||
|
|
||||||
|
import { createClientComponent } from '../../createClientComponent'
|
||||||
|
import { TreeViewPlugin } from './plugin'
|
||||||
|
|
||||||
|
const TreeViewFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||||
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
|
feature: () => ({
|
||||||
|
clientFeatureProps: props,
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
Component: TreeViewPlugin,
|
||||||
|
position: 'bottom',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TreeViewFeatureClientComponent = createClientComponent(TreeViewFeatureClient)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { FeatureProviderProviderServer } from '../../types'
|
||||||
|
|
||||||
|
import { TreeViewFeatureClientComponent } from './feature.client'
|
||||||
|
|
||||||
|
export const TreeViewFeature: FeatureProviderProviderServer<undefined, undefined> = (props) => {
|
||||||
|
return {
|
||||||
|
feature: () => {
|
||||||
|
return {
|
||||||
|
ClientComponent: TreeViewFeatureClientComponent,
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'treeview',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import * as React from 'react'
|
|||||||
|
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
export function TreeViewPlugin(): JSX.Element {
|
export function TreeViewPlugin(): React.ReactNode {
|
||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
return (
|
return (
|
||||||
<TreeView
|
<TreeView
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
|
'use client'
|
||||||
import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical'
|
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 { SectionWithEntries } from '../common/floatingSelectToolbarSection'
|
||||||
import {
|
import {
|
||||||
BOLD_ITALIC_STAR,
|
BOLD_ITALIC_STAR,
|
||||||
@@ -10,9 +13,9 @@ import {
|
|||||||
BOLD_UNDERSCORE,
|
BOLD_UNDERSCORE,
|
||||||
} from './markdownTransformers'
|
} from './markdownTransformers'
|
||||||
|
|
||||||
export const BoldTextFeature = (): FeatureProvider => {
|
const BoldFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||||
return {
|
return {
|
||||||
dependenciesSoft: ['italic'],
|
clientFeatureProps: props,
|
||||||
feature: ({ featureProviderMap }) => {
|
feature: ({ featureProviderMap }) => {
|
||||||
const markdownTransformers = [BOLD_STAR, BOLD_UNDERSCORE]
|
const markdownTransformers = [BOLD_STAR, BOLD_UNDERSCORE]
|
||||||
if (featureProviderMap.get('italic')) {
|
if (featureProviderMap.get('italic')) {
|
||||||
@@ -20,13 +23,12 @@ export const BoldTextFeature = (): FeatureProvider => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
floatingSelectToolbar: {
|
floatingSelectToolbar: {
|
||||||
sections: [
|
sections: [
|
||||||
SectionWithEntries([
|
SectionWithEntries([
|
||||||
{
|
{
|
||||||
ChildComponent: () =>
|
ChildComponent: BoldIcon,
|
||||||
// @ts-expect-error-next-line
|
|
||||||
import('../../../lexical/ui/icons/Bold').then((module) => module.BoldIcon),
|
|
||||||
isActive: ({ selection }) => {
|
isActive: ({ selection }) => {
|
||||||
if ($isRangeSelection(selection)) {
|
if ($isRangeSelection(selection)) {
|
||||||
return selection.hasFormat('bold')
|
return selection.hasFormat('bold')
|
||||||
@@ -42,10 +44,10 @@ export const BoldTextFeature = (): FeatureProvider => {
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
markdownTransformers: markdownTransformers,
|
markdownTransformers,
|
||||||
props: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'bold',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BoldFeatureClientComponent = createClientComponent(BoldFeatureClient)
|
||||||
@@ -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<undefined, undefined> = (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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical'
|
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 { SectionWithEntries } from '../common/floatingSelectToolbarSection'
|
||||||
import { INLINE_CODE } from './markdownTransformers'
|
import { INLINE_CODE } from './markdownTransformers'
|
||||||
|
|
||||||
export const InlineCodeTextFeature = (): FeatureProvider => {
|
const InlineCodeFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
feature: () => {
|
feature: () => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
floatingSelectToolbar: {
|
floatingSelectToolbar: {
|
||||||
sections: [
|
sections: [
|
||||||
SectionWithEntries([
|
SectionWithEntries([
|
||||||
{
|
{
|
||||||
ChildComponent: () =>
|
ChildComponent: CodeIcon,
|
||||||
// @ts-expect-error-next-line
|
|
||||||
import('../../../lexical/ui/icons/Code').then((module) => module.CodeIcon),
|
|
||||||
isActive: ({ selection }) => {
|
isActive: ({ selection }) => {
|
||||||
if ($isRangeSelection(selection)) {
|
if ($isRangeSelection(selection)) {
|
||||||
return selection.hasFormat('code')
|
return selection.hasFormat('code')
|
||||||
@@ -31,10 +35,11 @@ export const InlineCodeTextFeature = (): FeatureProvider => {
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
markdownTransformers: [INLINE_CODE],
|
markdownTransformers: [INLINE_CODE],
|
||||||
props: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'inlineCode',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const InlineCodeFeatureClientComponent = createClientComponent(InlineCodeFeatureClient)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import type { FeatureProviderProviderServer } from '../../types'
|
||||||
|
|
||||||
|
import { InlineCodeFeatureClientComponent } from './feature.client'
|
||||||
|
import { INLINE_CODE } from './markdownTransformers'
|
||||||
|
|
||||||
|
export const InlineCodeFeature: FeatureProviderProviderServer<undefined, undefined> = (props) => {
|
||||||
|
return {
|
||||||
|
feature: () => {
|
||||||
|
return {
|
||||||
|
ClientComponent: InlineCodeFeatureClientComponent,
|
||||||
|
markdownTransformers: [INLINE_CODE],
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'inlinecode',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,26 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical'
|
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 { SectionWithEntries } from '../common/floatingSelectToolbarSection'
|
||||||
import { ITALIC_STAR, ITALIC_UNDERSCORE } from './markdownTransformers'
|
import { ITALIC_STAR, ITALIC_UNDERSCORE } from './markdownTransformers'
|
||||||
|
|
||||||
export const ItalicTextFeature = (): FeatureProvider => {
|
const ItalicFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
feature: () => {
|
feature: () => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
|
|
||||||
floatingSelectToolbar: {
|
floatingSelectToolbar: {
|
||||||
sections: [
|
sections: [
|
||||||
SectionWithEntries([
|
SectionWithEntries([
|
||||||
{
|
{
|
||||||
ChildComponent: () =>
|
ChildComponent: ItalicIcon,
|
||||||
// @ts-expect-error-next-line
|
|
||||||
import('../../../lexical/ui/icons/Italic').then((module) => module.ItalicIcon),
|
|
||||||
isActive: ({ selection }) => {
|
isActive: ({ selection }) => {
|
||||||
if ($isRangeSelection(selection)) {
|
if ($isRangeSelection(selection)) {
|
||||||
return selection.hasFormat('italic')
|
return selection.hasFormat('italic')
|
||||||
@@ -32,9 +37,9 @@ export const ItalicTextFeature = (): FeatureProvider => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE],
|
markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE],
|
||||||
props: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'italic',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ItalicFeatureClientComponent = createClientComponent(ItalicFeatureClient)
|
||||||
@@ -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<undefined, undefined> = (props) => {
|
||||||
|
return {
|
||||||
|
feature: () => {
|
||||||
|
return {
|
||||||
|
ClientComponent: ItalicFeatureClientComponent,
|
||||||
|
markdownTransformers: [ITALIC_STAR, ITALIC_UNDERSCORE],
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'italic',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,26 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical'
|
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 { SectionWithEntries } from '../common/floatingSelectToolbarSection'
|
||||||
import { STRIKETHROUGH } from './markdownTransformers'
|
import { STRIKETHROUGH } from './markdownTransformers'
|
||||||
|
|
||||||
export const StrikethroughTextFeature = (): FeatureProvider => {
|
const StrikethroughFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
feature: () => {
|
feature: () => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
|
|
||||||
floatingSelectToolbar: {
|
floatingSelectToolbar: {
|
||||||
sections: [
|
sections: [
|
||||||
SectionWithEntries([
|
SectionWithEntries([
|
||||||
{
|
{
|
||||||
ChildComponent: () =>
|
ChildComponent: StrikethroughIcon,
|
||||||
// @ts-expect-error-next-line
|
|
||||||
import('../../../lexical/ui/icons/Strikethrough').then(
|
|
||||||
(module) => module.StrikethroughIcon,
|
|
||||||
),
|
|
||||||
isActive: ({ selection }) => {
|
isActive: ({ selection }) => {
|
||||||
if ($isRangeSelection(selection)) {
|
if ($isRangeSelection(selection)) {
|
||||||
return selection.hasFormat('strikethrough')
|
return selection.hasFormat('strikethrough')
|
||||||
@@ -34,9 +37,9 @@ export const StrikethroughTextFeature = (): FeatureProvider => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
markdownTransformers: [STRIKETHROUGH],
|
markdownTransformers: [STRIKETHROUGH],
|
||||||
props: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'strikethrough',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const StrikethroughFeatureClientComponent = createClientComponent(StrikethroughFeatureClient)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { FeatureProviderProviderServer } from '../../types'
|
||||||
|
|
||||||
|
import { StrikethroughFeatureClientComponent } from './feature.client'
|
||||||
|
import { STRIKETHROUGH } from './markdownTransformers'
|
||||||
|
|
||||||
|
export const StrikethroughFeature: FeatureProviderProviderServer<undefined, undefined> = (
|
||||||
|
props,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
feature: () => {
|
||||||
|
return {
|
||||||
|
ClientComponent: StrikethroughFeatureClientComponent,
|
||||||
|
|
||||||
|
markdownTransformers: [STRIKETHROUGH],
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'strikethrough',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical'
|
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'
|
import { SectionWithEntries } from '../common/floatingSelectToolbarSection'
|
||||||
|
|
||||||
export const SubscriptTextFeature = (): FeatureProvider => {
|
const SubscriptFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
feature: () => {
|
feature: () => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
floatingSelectToolbar: {
|
floatingSelectToolbar: {
|
||||||
sections: [
|
sections: [
|
||||||
SectionWithEntries([
|
SectionWithEntries([
|
||||||
{
|
{
|
||||||
ChildComponent: () =>
|
ChildComponent: SubscriptIcon,
|
||||||
// @ts-expect-error-next-line
|
|
||||||
import('../../../lexical/ui/icons/Subscript').then(
|
|
||||||
(module) => module.SubscriptIcon,
|
|
||||||
),
|
|
||||||
isActive: ({ selection }) => {
|
isActive: ({ selection }) => {
|
||||||
if ($isRangeSelection(selection)) {
|
if ($isRangeSelection(selection)) {
|
||||||
return selection.hasFormat('subscript')
|
return selection.hasFormat('subscript')
|
||||||
@@ -32,9 +34,9 @@ export const SubscriptTextFeature = (): FeatureProvider => {
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
props: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'subscript',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const SubscriptFeatureClientComponent = createClientComponent(SubscriptFeatureClient)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { FeatureProviderProviderServer } from '../../types'
|
||||||
|
|
||||||
|
import { SubscriptFeatureClientComponent } from './feature.client'
|
||||||
|
|
||||||
|
export const SubscriptFeature: FeatureProviderProviderServer<undefined, undefined> = (props) => {
|
||||||
|
return {
|
||||||
|
feature: () => {
|
||||||
|
return {
|
||||||
|
ClientComponent: SubscriptFeatureClientComponent,
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'subscript',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical'
|
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'
|
import { SectionWithEntries } from '../common/floatingSelectToolbarSection'
|
||||||
|
|
||||||
export const SuperscriptTextFeature = (): FeatureProvider => {
|
const SuperscriptFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
feature: () => {
|
feature: () => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
floatingSelectToolbar: {
|
floatingSelectToolbar: {
|
||||||
sections: [
|
sections: [
|
||||||
SectionWithEntries([
|
SectionWithEntries([
|
||||||
{
|
{
|
||||||
ChildComponent: () =>
|
ChildComponent: SuperscriptIcon,
|
||||||
// @ts-expect-error-next-line
|
|
||||||
import('../../../lexical/ui/icons/Superscript').then(
|
|
||||||
(module) => module.SuperscriptIcon,
|
|
||||||
),
|
|
||||||
isActive: ({ selection }) => {
|
isActive: ({ selection }) => {
|
||||||
if ($isRangeSelection(selection)) {
|
if ($isRangeSelection(selection)) {
|
||||||
return selection.hasFormat('superscript')
|
return selection.hasFormat('superscript')
|
||||||
@@ -32,9 +34,9 @@ export const SuperscriptTextFeature = (): FeatureProvider => {
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
props: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'superscript',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const SuperscriptFeatureClientComponent = createClientComponent(SuperscriptFeatureClient)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { FeatureProviderProviderServer } from '../../types'
|
||||||
|
|
||||||
|
import { SuperscriptFeatureClientComponent } from './feature.client'
|
||||||
|
|
||||||
|
export const SuperscriptFeature: FeatureProviderProviderServer<undefined, undefined> = (props) => {
|
||||||
|
return {
|
||||||
|
feature: () => {
|
||||||
|
return {
|
||||||
|
ClientComponent: SuperscriptFeatureClientComponent,
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'superscript',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
import { $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical'
|
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'
|
import { SectionWithEntries } from '../common/floatingSelectToolbarSection'
|
||||||
|
|
||||||
export const UnderlineTextFeature = (): FeatureProvider => {
|
const UnderlineFeatureClient: FeatureProviderProviderClient<undefined> = (props) => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
feature: () => {
|
feature: () => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
floatingSelectToolbar: {
|
floatingSelectToolbar: {
|
||||||
sections: [
|
sections: [
|
||||||
SectionWithEntries([
|
SectionWithEntries([
|
||||||
{
|
{
|
||||||
ChildComponent: () =>
|
ChildComponent: UnderlineIcon,
|
||||||
// @ts-expect-error-next-line
|
|
||||||
import('../../../lexical/ui/icons/Underline').then(
|
|
||||||
(module) => module.UnderlineIcon,
|
|
||||||
),
|
|
||||||
isActive: ({ selection }) => {
|
isActive: ({ selection }) => {
|
||||||
if ($isRangeSelection(selection)) {
|
if ($isRangeSelection(selection)) {
|
||||||
return selection.hasFormat('underline')
|
return selection.hasFormat('underline')
|
||||||
@@ -32,9 +34,9 @@ export const UnderlineTextFeature = (): FeatureProvider => {
|
|||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
props: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'underline',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const UnderlineFeatureClientComponent = createClientComponent(UnderlineFeatureClient)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { FeatureProviderProviderServer } from '../../types'
|
||||||
|
|
||||||
|
import { UnderlineFeatureClientComponent } from './feature.client'
|
||||||
|
|
||||||
|
export const UnderlineFeature: FeatureProviderProviderServer<undefined, undefined> = (props) => {
|
||||||
|
return {
|
||||||
|
feature: () => {
|
||||||
|
return {
|
||||||
|
ClientComponent: UnderlineFeatureClientComponent,
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'underline',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 { $setBlocksType } from '@lexical/selection'
|
||||||
import { $getSelection } from 'lexical'
|
import { $getSelection } from 'lexical'
|
||||||
|
|
||||||
import type { HTMLConverter } from '../converters/html/converter/types'
|
import type { FeatureProviderProviderClient } from '../types'
|
||||||
import type { FeatureProvider } from '../types'
|
import type { HeadingFeatureProps } from './feature.server'
|
||||||
|
|
||||||
import { SlashMenuOption } from '../../lexical/plugins/SlashMenu/LexicalTypeaheadMenuPlugin/types'
|
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 { TextDropdownSectionWithEntries } from '../common/floatingSelectToolbarTextDropdownSection'
|
||||||
import { convertLexicalNodesToHTML } from '../converters/html/converter'
|
import { createClientComponent } from '../createClientComponent'
|
||||||
import { MarkdownTransformer } from './markdownTransformer'
|
import { MarkdownTransformer } from './markdownTransformer'
|
||||||
|
|
||||||
const setHeading = (headingSize: HeadingTagType) => {
|
const setHeading = (headingSize: HeadingTagType) => {
|
||||||
@@ -17,31 +26,23 @@ const setHeading = (headingSize: HeadingTagType) => {
|
|||||||
$setBlocksType(selection, () => $createHeadingNode(headingSize))
|
$setBlocksType(selection, () => $createHeadingNode(headingSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
|
||||||
enabledHeadingSizes?: HeadingTagType[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const iconImports = {
|
const iconImports = {
|
||||||
// @ts-expect-error-next-line
|
h1: H1Icon,
|
||||||
h1: () => import('../../lexical/ui/icons/H1').then((module) => module.H1Icon),
|
h2: H2Icon,
|
||||||
// @ts-expect-error-next-line
|
h3: H3Icon,
|
||||||
h2: () => import('../../lexical/ui/icons/H2').then((module) => module.H2Icon),
|
h4: H4Icon,
|
||||||
// @ts-expect-error-next-line
|
h5: H5Icon,
|
||||||
h3: () => import('../../lexical/ui/icons/H3').then((module) => module.H3Icon),
|
h6: H6Icon,
|
||||||
// @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),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeadingFeature = (props: Props): FeatureProvider => {
|
const HeadingFeatureClient: FeatureProviderProviderClient<HeadingFeatureProps> = (props) => {
|
||||||
const { enabledHeadingSizes = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] } = props
|
const { enabledHeadingSizes = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] } = props
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
feature: () => {
|
feature: () => {
|
||||||
return {
|
return {
|
||||||
|
clientFeatureProps: props,
|
||||||
floatingSelectToolbar: {
|
floatingSelectToolbar: {
|
||||||
sections: [
|
sections: [
|
||||||
...enabledHeadingSizes.map((headingSize, i) =>
|
...enabledHeadingSizes.map((headingSize, i) =>
|
||||||
@@ -63,30 +64,7 @@ export const HeadingFeature = (props: Props): FeatureProvider => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
markdownTransformers: [MarkdownTransformer(enabledHeadingSizes)],
|
markdownTransformers: [MarkdownTransformer(enabledHeadingSizes)],
|
||||||
nodes: [
|
nodes: [HeadingNode],
|
||||||
{
|
|
||||||
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 + '</' + node?.tag + '>'
|
|
||||||
},
|
|
||||||
nodeTypes: [HeadingNode.getType()],
|
|
||||||
} as HTMLConverter<SerializedHeadingNode>,
|
|
||||||
},
|
|
||||||
node: HeadingNode,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
props,
|
|
||||||
slashMenu: {
|
slashMenu: {
|
||||||
options: [
|
options: [
|
||||||
...enabledHeadingSizes.map((headingSize) => {
|
...enabledHeadingSizes.map((headingSize) => {
|
||||||
@@ -109,6 +87,7 @@ export const HeadingFeature = (props: Props): FeatureProvider => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'heading',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HeadingFeatureClientComponent = createClientComponent(HeadingFeatureClient)
|
||||||
@@ -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 + '</' + node?.tag + '>'
|
||||||
|
},
|
||||||
|
nodeTypes: [HeadingNode.getType()],
|
||||||
|
} as HTMLConverter<SerializedHeadingNode>,
|
||||||
|
},
|
||||||
|
node: HeadingNode,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: 'heading',
|
||||||
|
serverFeatureProps: props,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,6 @@ export const CheckListFeature = (): FeatureProvider => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'checkList',
|
key: 'checklist',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,6 +85,6 @@ export const OrderedListFeature = (): FeatureProvider => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'orderedList',
|
key: 'orderedlist',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,6 +81,6 @@ export const UnorderedListFeature = (): FeatureProvider => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
key: 'unorderedList',
|
key: 'unorderedlist',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user