refactor(richtext-lexical): new upload node design (#13901)
This changes the design of lexical upload nodes to better show the actual media instead of the metadata. ## Updated Design https://github.com/user-attachments/assets/49096378-35c2-4eb0-b4b6-5f138d49bdad Light mode: <img width="780" height="962" alt="Screenshot 2025-09-24 at 10 11 32@2x" src="https://github.com/user-attachments/assets/7611e659-3914-46e9-9c8c-db88c180227b" /> ## Previous Design > Before: > > <img width="1358" height="860" alt="Screenshot 2025-09-22 at 16 01 16@2x" src="https://github.com/user-attachments/assets/7831761c-6c3c-4072-82ed-68b88e3842b7" /> > > After: > > <img width="1776" height="1632" alt="Screenshot 2025-09-22 at 16 01 00@2x" src="https://github.com/user-attachments/assets/b434b6d5-a965-4c2b-adba-c1bf2a3be4bc" /> > > > https://github.com/user-attachments/assets/f2749a38-c191-4b50-a521-8f722ed42a8f > --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1211429812808983
This commit is contained in:
@@ -4,24 +4,45 @@
|
|||||||
.lexical-upload {
|
.lexical-upload {
|
||||||
@extend %body;
|
@extend %body;
|
||||||
@include shadow-sm;
|
@include shadow-sm;
|
||||||
max-width: calc(var(--base) * 15);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--theme-input-bg);
|
|
||||||
border-radius: $style-radius-m;
|
border-radius: $style-radius-m;
|
||||||
border: 1px solid var(--theme-elevation-100);
|
border: 1px solid var(--theme-elevation-100);
|
||||||
position: relative;
|
position: relative;
|
||||||
font-family: var(--font-body);
|
font-family: var(--font-body);
|
||||||
margin-block: base(0.5);
|
margin-block: base(0.5);
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid var(--theme-elevation-150);
|
border: 1px solid var(--theme-elevation-150);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
border-radius: $style-radius-s;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--landscape {
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
max-width: 450px;
|
||||||
|
min-width: 450px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&--portrait {
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
max-height: 450px;
|
||||||
|
min-height: 450px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
&__card {
|
&__card {
|
||||||
@include soft-shadow-bottom;
|
@include soft-shadow-bottom;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -29,118 +50,117 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__topRow {
|
&__floater {
|
||||||
display: flex;
|
@include shadow-lg;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
/* hidden by default */
|
||||||
|
opacity: 0;
|
||||||
|
transition:
|
||||||
|
opacity 0.15s ease,
|
||||||
|
transform 0.15s ease;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__thumbnail {
|
&:hover .lexical-upload__floater,
|
||||||
width: calc(var(--base) * 3.25);
|
&__media:focus-within .lexical-upload__floater {
|
||||||
height: auto;
|
opacity: 1;
|
||||||
position: relative;
|
pointer-events: auto;
|
||||||
overflow: hidden;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-top-left-radius: $style-radius-m;
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg {
|
|
||||||
position: absolute;
|
|
||||||
object-fit: cover;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: var(--theme-elevation-800);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__topRowRightPanel {
|
/* --- Floating Action Buttons (top-right) ------------------------------------- */
|
||||||
flex-grow: 1;
|
&__overlay {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
top: calc(var(--base) * 0.5);
|
||||||
padding: calc(var(--base) * 0.75);
|
right: calc(var(--base) * 0.5);
|
||||||
justify-content: space-between;
|
padding: calc(var(--base) * 0.2) calc(var(--base) * 0.2);
|
||||||
max-width: calc(100% - #{calc(var(--base) * 3.25)});
|
|
||||||
|
background: var(--theme-elevation-50);
|
||||||
|
border-radius: $style-radius-m;
|
||||||
|
transform: translateY(-6px);
|
||||||
|
}
|
||||||
|
&:hover .lexical-upload__overlay,
|
||||||
|
&__media:focus-within .lexical-upload__overlay {
|
||||||
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-shrink: 0;
|
flex-wrap: nowrap;
|
||||||
margin-left: calc(var(--base) * 0.5);
|
gap: calc(var(--base) * 0.3);
|
||||||
|
|
||||||
.lexical-upload__doc-drawer-toggler {
|
.btn:hover {
|
||||||
pointer-events: all;
|
background: var(--theme-elevation-100);
|
||||||
}
|
|
||||||
|
|
||||||
& > *:not(:last-child) {
|
|
||||||
margin-right: calc(var(--base) * 0.25);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* --- Floating Metadata (bottom-center) ------------------------------------- */
|
||||||
|
&__metaOverlay {
|
||||||
|
display: inline-flex;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: calc(var(--base) * 0.5) calc(var(--base) * 0.75);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
&__removeButton {
|
flex-wrap: wrap;
|
||||||
margin: 0;
|
gap: calc(var(--base) * 0.5);
|
||||||
|
row-gap: 0;
|
||||||
|
|
||||||
line {
|
background: color-mix(in oklab, var(--theme-elevation-50) 55%, transparent);
|
||||||
stroke-width: $style-stroke-width-m;
|
border-radius: 0 0 $style-radius-s $style-radius-s;
|
||||||
|
backdrop-filter: saturate(1.2) blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='light'] & {
|
||||||
|
&__metaOverlay {
|
||||||
|
background: color-mix(in oklab, var(--theme-elevation-800) 55%, transparent);
|
||||||
|
color: var(--theme-elevation-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&__collectionLabel {
|
||||||
color: var(--theme-elevation-300);
|
color: var(--theme-elevation-300);
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__upload-drawer-toggler {
|
&__filename {
|
||||||
background-color: transparent;
|
white-space: nowrap;
|
||||||
border: none;
|
text-overflow: ellipsis;
|
||||||
padding: 0;
|
overflow: hidden;
|
||||||
margin: 0;
|
|
||||||
outline: none;
|
|
||||||
line-height: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__doc-drawer-toggler {
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
cursor: pointer;
|
||||||
|
|
||||||
&__doc-drawer-toggler,
|
|
||||||
&__list-drawer-toggler,
|
|
||||||
&__upload-drawer-toggler {
|
|
||||||
& > * {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
color: var(--theme-elevation-300);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__collectionLabel {
|
&__collectionLabel {
|
||||||
|
color: var(--theme-elevation-500);
|
||||||
|
font-size: 0.9em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bottomRow {
|
|
||||||
padding: calc(var(--base) * 0.5);
|
|
||||||
border-top: 1px solid var(--theme-elevation-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__wrap {
|
|
||||||
padding: calc(var(--base) * 0.5) calc(var(--base) * 0.5) calc(var(--base) * 0.5) var(--base);
|
|
||||||
text-align: left;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include small-break {
|
@include small-break {
|
||||||
&__topRowRightPanel {
|
img,
|
||||||
padding: calc(var(--base) * 0.75) calc(var(--base) * 0.5);
|
svg {
|
||||||
|
// Allow images to shrink < 450px on small screens to
|
||||||
|
// maintain aspect ratio
|
||||||
|
min-width: unset;
|
||||||
|
min-height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__metaOverlay {
|
||||||
|
gap: 0;
|
||||||
|
padding: calc(var(--base) * 0.5) calc(var(--base) * 0.6);
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__collectionLabel {
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
usePayloadAPI,
|
usePayloadAPI,
|
||||||
useTranslation,
|
useTranslation,
|
||||||
} from '@payloadcms/ui'
|
} from '@payloadcms/ui'
|
||||||
import { $getNodeByKey } from 'lexical'
|
import { $getNodeByKey, type ElementFormatType } from 'lexical'
|
||||||
import { isImage } from 'payload/shared'
|
import { isImage } from 'payload/shared'
|
||||||
import React, { useCallback, useId, useReducer, useRef, useState } from 'react'
|
import React, { useCallback, useId, useReducer, useRef, useState } from 'react'
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ const initialParams = {
|
|||||||
|
|
||||||
export type ElementProps = {
|
export type ElementProps = {
|
||||||
data: UploadData
|
data: UploadData
|
||||||
|
format?: ElementFormatType
|
||||||
nodeKey: string
|
nodeKey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,22 +140,32 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
[editor, nodeKey],
|
[editor, nodeKey],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const aspectRatio =
|
||||||
|
thumbnailSRC && data?.width && data?.height
|
||||||
|
? data.width > data.height
|
||||||
|
? 'landscape'
|
||||||
|
: 'portrait'
|
||||||
|
: 'landscape'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={baseClass} contentEditable={false} ref={uploadRef}>
|
<div
|
||||||
|
className={`${baseClass} ${baseClass}--${aspectRatio}`}
|
||||||
|
data-filename={data?.filename}
|
||||||
|
ref={uploadRef}
|
||||||
|
>
|
||||||
<div className={`${baseClass}__card`}>
|
<div className={`${baseClass}__card`}>
|
||||||
<div className={`${baseClass}__topRow`}>
|
<div className={`${baseClass}__media`}>
|
||||||
<div className={`${baseClass}__thumbnail`}>
|
<Thumbnail
|
||||||
<Thumbnail
|
collectionSlug={relationTo}
|
||||||
collectionSlug={relationTo}
|
fileSrc={isImage(data?.mimeType) ? thumbnailSRC : null}
|
||||||
fileSrc={isImage(data?.mimeType) ? thumbnailSRC : null}
|
height={data?.height}
|
||||||
/>
|
size="none"
|
||||||
</div>
|
width={data?.width}
|
||||||
<div className={`${baseClass}__topRowRightPanel`}>
|
/>
|
||||||
<div className={`${baseClass}__collectionLabel`}>
|
|
||||||
{getTranslation(relatedCollection.labels.singular, i18n)}
|
{editor.isEditable() && (
|
||||||
</div>
|
<div className={`${baseClass}__overlay ${baseClass}__floater`}>
|
||||||
{editor.isEditable() && (
|
<div className={`${baseClass}__actions`} role="toolbar">
|
||||||
<div className={`${baseClass}__actions`}>
|
|
||||||
{hasExtraFields ? (
|
{hasExtraFields ? (
|
||||||
<Button
|
<Button
|
||||||
buttonStyle="icon-label"
|
buttonStyle="icon-label"
|
||||||
@@ -162,10 +173,9 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
el="button"
|
el="button"
|
||||||
icon="edit"
|
icon="edit"
|
||||||
onClick={() => {
|
onClick={toggleDrawer}
|
||||||
toggleDrawer()
|
|
||||||
}}
|
|
||||||
round
|
round
|
||||||
|
size="medium"
|
||||||
tooltip={t('fields:editRelationship')}
|
tooltip={t('fields:editRelationship')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -182,8 +192,10 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
round
|
round
|
||||||
|
size="medium"
|
||||||
tooltip={t('fields:swapUpload')}
|
tooltip={t('fields:swapUpload')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
buttonStyle="icon-label"
|
buttonStyle="icon-label"
|
||||||
className={`${baseClass}__removeButton`}
|
className={`${baseClass}__removeButton`}
|
||||||
@@ -194,18 +206,26 @@ const Component: React.FC<ElementProps> = (props) => {
|
|||||||
removeUpload()
|
removeUpload()
|
||||||
}}
|
}}
|
||||||
round
|
round
|
||||||
|
size="medium"
|
||||||
tooltip={t('fields:removeUpload')}
|
tooltip={t('fields:removeUpload')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`${baseClass}__metaOverlay ${baseClass}__floater`}>
|
||||||
|
<DocumentDrawerToggler className={`${baseClass}__doc-drawer-toggler`}>
|
||||||
|
<strong className={`${baseClass}__filename`}>
|
||||||
|
{data?.filename || t('general:untitled')}
|
||||||
|
</strong>
|
||||||
|
</DocumentDrawerToggler>
|
||||||
|
<div className={`${baseClass}__collectionLabel`}>
|
||||||
|
{getTranslation(relatedCollection.labels.singular, i18n)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${baseClass}__bottomRow`}>
|
|
||||||
<DocumentDrawerToggler className={`${baseClass}__doc-drawer-toggler`}>
|
|
||||||
<strong>{data?.filename}</strong>
|
|
||||||
</DocumentDrawerToggler>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{value ? <DocumentDrawer onSave={updateUpload} /> : null}
|
{value ? <DocumentDrawer onSave={updateUpload} /> : null}
|
||||||
{hasExtraFields ? (
|
{hasExtraFields ? (
|
||||||
<FieldsDrawer
|
<FieldsDrawer
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class UploadNode extends UploadServerNode {
|
|||||||
if ((this.__data as Internal_UploadData).pending) {
|
if ((this.__data as Internal_UploadData).pending) {
|
||||||
return <PendingUploadComponent />
|
return <PendingUploadComponent />
|
||||||
}
|
}
|
||||||
return <RawUploadComponent data={this.__data} nodeKey={this.getKey()} />
|
return <RawUploadComponent data={this.__data} format={this.__format} nodeKey={this.getKey()} />
|
||||||
}
|
}
|
||||||
|
|
||||||
override exportJSON(): SerializedUploadNode {
|
override exportJSON(): SerializedUploadNode {
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
@layer payload-default {
|
@layer payload-default {
|
||||||
.thumbnail {
|
.thumbnail {
|
||||||
min-height: 100%;
|
&:not(.thumbnail--size-none) {
|
||||||
flex-shrink: 0;
|
min-height: 100%;
|
||||||
align-self: stretch;
|
flex-shrink: 0;
|
||||||
overflow: hidden;
|
align-self: stretch;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
img,
|
img,
|
||||||
svg {
|
svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--size-expand {
|
&--size-expand {
|
||||||
|
|||||||
@@ -15,13 +15,23 @@ export type ThumbnailProps = {
|
|||||||
collectionSlug?: string
|
collectionSlug?: string
|
||||||
doc?: Record<string, unknown>
|
doc?: Record<string, unknown>
|
||||||
fileSrc?: string
|
fileSrc?: string
|
||||||
|
height?: number
|
||||||
imageCacheTag?: string
|
imageCacheTag?: string
|
||||||
size?: 'expand' | 'large' | 'medium' | 'small'
|
size?: 'expand' | 'large' | 'medium' | 'none' | 'small'
|
||||||
uploadConfig?: SanitizedCollectionConfig['upload']
|
uploadConfig?: SanitizedCollectionConfig['upload']
|
||||||
|
width?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Thumbnail: React.FC<ThumbnailProps> = (props) => {
|
export const Thumbnail: React.FC<ThumbnailProps> = (props) => {
|
||||||
const { className = '', doc: { filename } = {}, fileSrc, imageCacheTag, size } = props
|
const {
|
||||||
|
className = '',
|
||||||
|
doc: { filename } = {},
|
||||||
|
fileSrc,
|
||||||
|
height,
|
||||||
|
imageCacheTag,
|
||||||
|
size,
|
||||||
|
width,
|
||||||
|
} = props
|
||||||
const [fileExists, setFileExists] = React.useState(undefined)
|
const [fileExists, setFileExists] = React.useState(undefined)
|
||||||
|
|
||||||
const classNames = [baseClass, `${baseClass}--size-${size || 'medium'}`, className].join(' ')
|
const classNames = [baseClass, `${baseClass}--size-${size || 'medium'}`, className].join(' ')
|
||||||
@@ -57,7 +67,7 @@ export const Thumbnail: React.FC<ThumbnailProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div className={classNames}>
|
<div className={classNames}>
|
||||||
{fileExists === undefined && <ShimmerEffect height="100%" />}
|
{fileExists === undefined && <ShimmerEffect height="100%" />}
|
||||||
{fileExists && <img alt={filename as string} src={src} />}
|
{fileExists && <img alt={filename as string} height={height} src={src} width={width} />}
|
||||||
{fileExists === false && <File />}
|
{fileExists === false && <File />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -69,7 +79,7 @@ type ThumbnailComponentProps = {
|
|||||||
readonly filename: string
|
readonly filename: string
|
||||||
readonly fileSrc: string
|
readonly fileSrc: string
|
||||||
readonly imageCacheTag?: string
|
readonly imageCacheTag?: string
|
||||||
readonly size?: 'expand' | 'large' | 'medium' | 'small'
|
readonly size?: 'expand' | 'large' | 'medium' | 'none' | 'small'
|
||||||
}
|
}
|
||||||
export function ThumbnailComponent(props: ThumbnailComponentProps) {
|
export function ThumbnailComponent(props: ThumbnailComponentProps) {
|
||||||
const { alt, className = '', filename, fileSrc, imageCacheTag, size } = props
|
const { alt, className = '', filename, fileSrc, imageCacheTag, size } = props
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import type {
|
|||||||
import type { BrowserContext, Locator, Page } from '@playwright/test'
|
import type { BrowserContext, Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
import { expect, test } from '@playwright/test'
|
import { expect, test } from '@playwright/test'
|
||||||
import { except } from 'drizzle-orm/mysql-core'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { wait } from 'payload/shared'
|
import { wait } from 'payload/shared'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@@ -537,13 +536,11 @@ describe('lexicalMain', () => {
|
|||||||
const secondUploadNode = richTextField.locator('.lexical-upload').nth(1)
|
const secondUploadNode = richTextField.locator('.lexical-upload').nth(1)
|
||||||
await secondUploadNode.scrollIntoViewIfNeeded()
|
await secondUploadNode.scrollIntoViewIfNeeded()
|
||||||
await expect(secondUploadNode).toBeVisible()
|
await expect(secondUploadNode).toBeVisible()
|
||||||
|
// Focus the upload node
|
||||||
|
await secondUploadNode.click()
|
||||||
|
|
||||||
await expect(secondUploadNode.locator('.lexical-upload__bottomRow')).toContainText(
|
await expect(secondUploadNode.locator('.lexical-upload__filename')).toHaveText('payload-1.jpg')
|
||||||
'payload-1.jpg',
|
await expect(secondUploadNode.locator('.lexical-upload__collectionLabel')).toHaveText('Upload')
|
||||||
)
|
|
||||||
await expect(secondUploadNode.locator('.lexical-upload__collectionLabel')).toContainText(
|
|
||||||
'Upload',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// This reproduces https://github.com/payloadcms/payload/issues/7128
|
// This reproduces https://github.com/payloadcms/payload/issues/7128
|
||||||
@@ -590,8 +587,10 @@ describe('lexicalMain', () => {
|
|||||||
const newUploadNode = richTextField.locator('.lexical-upload').nth(1)
|
const newUploadNode = richTextField.locator('.lexical-upload').nth(1)
|
||||||
await newUploadNode.scrollIntoViewIfNeeded()
|
await newUploadNode.scrollIntoViewIfNeeded()
|
||||||
await expect(newUploadNode).toBeVisible()
|
await expect(newUploadNode).toBeVisible()
|
||||||
|
await newUploadNode.click() // Focus the upload node
|
||||||
|
await newUploadNode.hover()
|
||||||
|
|
||||||
await expect(newUploadNode.locator('.lexical-upload__bottomRow')).toContainText('payload.jpg')
|
await expect(newUploadNode.locator('.lexical-upload__filename')).toContainText('payload.jpg')
|
||||||
|
|
||||||
// Click on button with class lexical-upload__upload-drawer-toggler
|
// Click on button with class lexical-upload__upload-drawer-toggler
|
||||||
await newUploadNode.locator('.lexical-upload__upload-drawer-toggler').first().click()
|
await newUploadNode.locator('.lexical-upload__upload-drawer-toggler').first().click()
|
||||||
@@ -630,6 +629,9 @@ describe('lexicalMain', () => {
|
|||||||
.nth(1)
|
.nth(1)
|
||||||
await reloadedUploadNode.scrollIntoViewIfNeeded()
|
await reloadedUploadNode.scrollIntoViewIfNeeded()
|
||||||
await expect(reloadedUploadNode).toBeVisible()
|
await expect(reloadedUploadNode).toBeVisible()
|
||||||
|
await reloadedUploadNode.click() // Focus the upload node
|
||||||
|
await reloadedUploadNode.hover()
|
||||||
|
|
||||||
await reloadedUploadNode.locator('.lexical-upload__upload-drawer-toggler').first().click()
|
await reloadedUploadNode.locator('.lexical-upload__upload-drawer-toggler').first().click()
|
||||||
const reloadedUploadExtraFieldsDrawer = page
|
const reloadedUploadExtraFieldsDrawer = page
|
||||||
.locator('dialog[id^=drawer_1_lexical-upload-drawer-]')
|
.locator('dialog[id^=drawer_1_lexical-upload-drawer-]')
|
||||||
@@ -1209,7 +1211,9 @@ describe('lexicalMain', () => {
|
|||||||
|
|
||||||
await expect(slashMenuPopover).toBeHidden()
|
await expect(slashMenuPopover).toBeHidden()
|
||||||
|
|
||||||
await expect(newUploadNode.locator('.lexical-upload__bottomRow')).toContainText('payload.png')
|
await newUploadNode.hover()
|
||||||
|
|
||||||
|
await expect(newUploadNode.locator('.lexical-upload__filename')).toHaveText('payload.png')
|
||||||
|
|
||||||
await page.keyboard.press('Enter') // floating toolbar needs to appear with enough distance to the upload node, otherwise clicking may fail
|
await page.keyboard.press('Enter') // floating toolbar needs to appear with enough distance to the upload node, otherwise clicking may fail
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
@@ -1557,12 +1561,9 @@ describe('lexicalMain', () => {
|
|||||||
|
|
||||||
// test
|
// test
|
||||||
await navigateToLexicalFields()
|
await navigateToLexicalFields()
|
||||||
const bottomOfUploadNode = page
|
const uploadNode = page.locator('.lexical-upload[data-filename="payload.jpg"]').first()
|
||||||
.locator('.lexical-upload div')
|
await uploadNode.click()
|
||||||
.filter({ hasText: /^payload\.jpg$/ })
|
await expectInsideSelectedDecorator(uploadNode)
|
||||||
.first()
|
|
||||||
await bottomOfUploadNode.click()
|
|
||||||
await expectInsideSelectedDecorator(bottomOfUploadNode)
|
|
||||||
|
|
||||||
const textNode = page.getByText('Upload Node:', { exact: true })
|
const textNode = page.getByText('Upload Node:', { exact: true })
|
||||||
await textNode.click()
|
await textNode.click()
|
||||||
|
|||||||
Reference in New Issue
Block a user