From a7ab1a7c0d8af15651cb1ce709cd5ec7a5f53b03 Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Tue, 2 May 2023 16:34:59 -0400 Subject: [PATCH] fix: media library url entry (#775) --- packages/core/src/__mocks__/@udecode/plate.ts | 1 + .../src/components/common/field/Field.tsx | 18 +-- .../src/components/common/image/Image.tsx | 16 +++ .../common/CurrentMediaDetails.tsx | 113 ++++++++++++------ .../common/InlineEditTextField.tsx | 3 +- .../media-library/common/MediaLibrary.tsx | 1 + .../widgets/markdown/plate/PlateEditor.tsx | 4 +- .../balloon-toolbar/BalloonToolbar.tsx | 24 +--- .../__tests__/BalloonToolbar.spec.tsx | 70 ++++++----- .../plate/components/buttons/AddButtons.tsx | 15 +++ .../buttons/common/ImageToolbarButton.tsx | 6 +- .../buttons/common/LinkToolbarButton.tsx | 6 +- .../plate/components/common/MediaPopover.tsx | 2 +- .../TableHeaderCellElement.tsx | 1 - .../nodes/table/TableElement/TableElement.tsx | 14 ++- .../docs/content/docs/widget-markdown.mdx | 24 ++-- 16 files changed, 199 insertions(+), 119 deletions(-) diff --git a/packages/core/src/__mocks__/@udecode/plate.ts b/packages/core/src/__mocks__/@udecode/plate.ts index 900b3087..041048fc 100644 --- a/packages/core/src/__mocks__/@udecode/plate.ts +++ b/packages/core/src/__mocks__/@udecode/plate.ts @@ -63,5 +63,6 @@ export const isSelectionExpanded = jest.fn(); export const isText = jest.fn(); export const someNode = jest.fn(); export const usePlateSelection = jest.fn(); +export const isMarkActive = jest.fn(); export default {}; diff --git a/packages/core/src/components/common/field/Field.tsx b/packages/core/src/components/common/field/Field.tsx index 7026b964..a61c3a08 100644 --- a/packages/core/src/components/common/field/Field.tsx +++ b/packages/core/src/components/common/field/Field.tsx @@ -157,16 +157,18 @@ const Field: FC = ({ {renderedHint} {renderedErrorMessage} -
- {endAdornment} -
+ !noPadding && '-mb-3', + )} + > + {endAdornment} + + ) : null} ); }; diff --git a/packages/core/src/components/common/image/Image.tsx b/packages/core/src/components/common/image/Image.tsx index 98b2b911..96c1f318 100644 --- a/packages/core/src/components/common/image/Image.tsx +++ b/packages/core/src/components/common/image/Image.tsx @@ -1,9 +1,11 @@ import React from 'react'; +import { Image as ImageIcon } from '@styled-icons/material-outlined/Image'; import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset'; import classNames from '@staticcms/core/lib/util/classNames.util'; import { selectEditingDraft } from '@staticcms/core/reducers/selectors/entryDraft'; import { useAppSelector } from '@staticcms/core/store/hooks'; +import { isEmpty } from '@staticcms/core/lib/util/string.util'; import type { BaseField, Collection, MediaField, UnknownField } from '@staticcms/core/interface'; @@ -28,6 +30,20 @@ const Image = ({ const assetSource = useMediaAsset(src, collection, field, entry); + if (isEmpty(src)) { + return ( + + ); + } + return ( void; onAltChange: (alt: string) => void; } @@ -25,54 +27,83 @@ const CurrentMediaDetails: FC = ({ url, alt, insertOptions, + forImage, onUrlChange, onAltChange, }) => { - if (!field || !canInsert || typeof url !== 'string' || isEmpty(url)) { + if ( + !field || + !canInsert || + Array.isArray(url) || + (!insertOptions?.chooseUrl && + !insertOptions?.showAlt && + (typeof url !== 'string' || isEmpty(url))) + ) { return null; } return (
- + dark:border-slate-500/75 + `, + forImage + ? ` + grid + grid-cols-media-preview + ` + : ` + flex + w-full + `, + )} + > + {forImage ? ( + + ) : null}
= ({ onChange={insertOptions?.chooseUrl ? onUrlChange : undefined} /> {insertOptions?.showAlt ? ( - + ) : null}
diff --git a/packages/core/src/components/media-library/common/InlineEditTextField.tsx b/packages/core/src/components/media-library/common/InlineEditTextField.tsx index 034e2063..71b5aa81 100644 --- a/packages/core/src/components/media-library/common/InlineEditTextField.tsx +++ b/packages/core/src/components/media-library/common/InlineEditTextField.tsx @@ -96,6 +96,7 @@ const InlineEditTextField: FC = ({ {!editing || !onChange ? (
= ({ rounded-md border text-slate-600 - dark:font-semibold dark:text-gray-100 `, onChange @@ -125,6 +125,7 @@ const InlineEditTextField: FC = ({ `, )} onClick={handleValueClick} + onFocus={handleValueClick} > {internalValue}
diff --git a/packages/core/src/components/media-library/common/MediaLibrary.tsx b/packages/core/src/components/media-library/common/MediaLibrary.tsx index 2aa6a6e1..9a2ae0db 100644 --- a/packages/core/src/components/media-library/common/MediaLibrary.tsx +++ b/packages/core/src/components/media-library/common/MediaLibrary.tsx @@ -484,6 +484,7 @@ const MediaLibrary: FC> = ({ url={url} alt={alt} insertOptions={insertOptions} + forImage={forImage} onUrlChange={handleURLChange} onAltChange={handleAltChange} /> diff --git a/packages/core/src/widgets/markdown/plate/PlateEditor.tsx b/packages/core/src/widgets/markdown/plate/PlateEditor.tsx index 546c9247..45ecca65 100644 --- a/packages/core/src/widgets/markdown/plate/PlateEditor.tsx +++ b/packages/core/src/widgets/markdown/plate/PlateEditor.tsx @@ -253,7 +253,7 @@ const PlateEditor: FC = ({ return useMemo( () => ( -
+
id={id} @@ -272,7 +272,7 @@ const PlateEditor: FC = ({ disabled={disabled} /> -
+
= ({ !useMdx ? : null, ].filter(Boolean); - // if (isInTableCell) { - // return allButtons; - // } - - // Empty paragraph, not first line + // Empty table cell if ( + isInTableCell && editor.children.length > 1 && node && ((isElement(node) && isElementEmpty(editor, node)) || (isText(node) && isEmpty(node.text))) ) { - const path = findNodePath(editor, node) ?? []; - const parent = getParentNode(editor, path); - if ( - path.length > 0 && - path[0] !== 0 && - parent && - parent.length > 0 && - 'children' in parent[0] && - !VOID_ELEMENTS.includes(parent[0].type as string) && - parent[0].children.length === 1 - ) { - return allButtons; - } + return allButtons; } return []; diff --git a/packages/core/src/widgets/markdown/plate/components/balloon-toolbar/__tests__/BalloonToolbar.spec.tsx b/packages/core/src/widgets/markdown/plate/components/balloon-toolbar/__tests__/BalloonToolbar.spec.tsx index 748e307c..089f43e1 100644 --- a/packages/core/src/widgets/markdown/plate/components/balloon-toolbar/__tests__/BalloonToolbar.spec.tsx +++ b/packages/core/src/widgets/markdown/plate/components/balloon-toolbar/__tests__/BalloonToolbar.spec.tsx @@ -4,6 +4,7 @@ import '@testing-library/jest-dom'; import { screen } from '@testing-library/react'; import { + ELEMENT_LINK, findNodePath, getNode, getParentNode, @@ -11,6 +12,7 @@ import { isElementEmpty, someNode, usePlateEditorState, + usePlateSelection, } from '@udecode/plate'; import React, { useRef } from 'react'; import { useFocused } from 'slate-react'; @@ -25,6 +27,7 @@ import BalloonToolbar from '../BalloonToolbar'; import type { Config, MarkdownField } from '@staticcms/core/interface'; import type { MdEditor } from '@staticcms/markdown/plate/plateTypes'; +import type { TRange } from '@udecode/plate'; import type { FC } from 'react'; interface BalloonToolbarWrapperProps { @@ -54,6 +57,7 @@ const config = createMockConfig({ describe(BalloonToolbar.name, () => { const mockUseEditor = usePlateEditorState as jest.Mock; + const mockUsePlateSelection = usePlateSelection as jest.Mock; let mockEditor: MdEditor; const mockGetNode = getNode as jest.Mock; @@ -67,26 +71,6 @@ describe(BalloonToolbar.name, () => { beforeEach(() => { store.dispatch(configLoaded(config as unknown as Config)); - // entry = { - // collection: 'posts', - // slug: '2022-12-13-post-number-1', - // path: '_posts/2022-12-13-post-number-1.md', - // partial: false, - // raw: '--- title: "This is post # 1" draft: false date: 2022-12-13T00:00:00.000Z --- # The post is number 1\n\nAnd some text', - // label: '', - // author: '', - // mediaFiles: [], - // isModification: null, - // newRecord: false, - // updatedOn: '', - // data: { - // title: 'This is post # 1', - // draft: false, - // date: '2022-12-13T00:00:00.000Z', - // body: '# The post is number 1\n\nAnd some text', - // }, - // }; - mockEditor = { selection: undefined, } as unknown as MdEditor; @@ -99,45 +83,57 @@ describe(BalloonToolbar.name, () => { expect(screen.queryAllByRole('button').length).toBe(0); }); - describe('empty node toolbar', () => { + describe('empty node toolbar inside table', () => { interface EmptyNodeToolbarSetupOptions { useMdx?: boolean; } const emptyNodeToolbarSetup = ({ useMdx }: EmptyNodeToolbarSetupOptions = {}) => { mockEditor = { - selection: undefined, + selection: { + anchor: { + path: [1, 0], + offset: 0, + }, + focus: { + path: [1, 0], + offset: 0, + }, + } as TRange, children: [ { type: 'p', children: [{ text: '' }], }, { - type: 'p', + type: 'td', children: [{ text: '' }], }, ], } as unknown as MdEditor; mockUseEditor.mockReturnValue(mockEditor); + mockUsePlateSelection.mockReturnValue(mockEditor.selection); mockGetNode.mockReturnValue({ text: '' }); mockIsElement.mockReturnValue(true); mockIsElementEmpty.mockReturnValue(true); - mockSomeNode.mockReturnValue(false); + mockSomeNode.mockImplementation((_editor, { match: { type } }) => type !== ELEMENT_LINK); mockUseFocused.mockReturnValue(true); mockFindNodePath.mockReturnValue([1, 0]); mockGetParentNode.mockReturnValue([ { - type: 'p', + type: 'td', children: [{ text: '' }], }, ]); - const { rerender } = renderWithProviders(); + const result = renderWithProviders(); - rerender(); + result.rerender(); + + return result; }; it('renders empty node toolbar for markdown', () => { @@ -148,8 +144,14 @@ describe(BalloonToolbar.name, () => { expect(screen.queryByTestId('toolbar-button-code')).toBeInTheDocument(); expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument(); - expect(screen.queryByTestId('font-type-select')).toBeInTheDocument(); - expect(screen.queryByTestId('toolbar-button-add-table')).toBeInTheDocument(); + expect(screen.queryByTestId('font-type-select')).not.toBeInTheDocument(); + + expect(screen.queryByTestId('toolbar-button-insert-row')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-delete-row')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-insert-column')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-delete-column')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-delete-table')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-insert-link')).toBeInTheDocument(); expect(screen.queryByTestId('toolbar-button-insert-image')).toBeInTheDocument(); @@ -165,8 +167,14 @@ describe(BalloonToolbar.name, () => { expect(screen.queryByTestId('toolbar-button-code')).toBeInTheDocument(); expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument(); - expect(screen.queryByTestId('font-type-select')).toBeInTheDocument(); - expect(screen.queryByTestId('toolbar-button-add-table')).toBeInTheDocument(); + expect(screen.queryByTestId('font-type-select')).not.toBeInTheDocument(); + + expect(screen.queryByTestId('toolbar-button-insert-row')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-delete-row')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-insert-column')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-delete-column')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-delete-table')).toBeInTheDocument(); + expect(screen.queryByTestId('toolbar-button-insert-link')).toBeInTheDocument(); expect(screen.queryByTestId('toolbar-button-insert-image')).toBeInTheDocument(); diff --git a/packages/core/src/widgets/markdown/plate/components/buttons/AddButtons.tsx b/packages/core/src/widgets/markdown/plate/components/buttons/AddButtons.tsx index 5f9f50e7..0f719df0 100644 --- a/packages/core/src/widgets/markdown/plate/components/buttons/AddButtons.tsx +++ b/packages/core/src/widgets/markdown/plate/components/buttons/AddButtons.tsx @@ -1,3 +1,4 @@ +import { TableAdd } from '@styled-icons/fluentui-system-regular/TableAdd'; import { Add as AddIcon } from '@styled-icons/material/Add'; import { Code as CodeIcon } from '@styled-icons/material/Code'; import { FormatQuote as FormatQuoteIcon } from '@styled-icons/material/FormatQuote'; @@ -6,7 +7,9 @@ import { ELEMENT_CODE_BLOCK, ELEMENT_IMAGE, ELEMENT_LINK, + ELEMENT_TABLE, insertEmptyCodeBlock, + insertTable, toggleNodeType, } from '@udecode/plate'; import React, { useCallback } from 'react'; @@ -43,6 +46,13 @@ const AddButtons: FC = ({ collection, field, disabled }) => { }); }, [editor]); + const handleTableAdd = useCallback(() => { + insertTable(editor, { + rowCount: 2, + colCount: 2, + }); + }, [editor]); + return (