fix: media library url entry (#775)

This commit is contained in:
Daniel Lautzenheiser
2023-05-02 16:34:59 -04:00
committed by GitHub
parent 7d0a705eee
commit a7ab1a7c0d
16 changed files with 199 additions and 119 deletions

View File

@ -253,7 +253,7 @@ const PlateEditor: FC<PlateEditorProps> = ({
return useMemo(
() => (
<div className="relative px-3 py-5 pb-0 mb-5">
<div className="relative px-3 py-5 pb-0">
<DndProvider backend={HTML5Backend}>
<PlateProvider<MdValue>
id={id}
@ -272,7 +272,7 @@ const PlateEditor: FC<PlateEditorProps> = ({
disabled={disabled}
/>
<div key="editor-wrapper" ref={editorContainerRef} className="w-full overflow-hidden">
<div key="editor-wrapper" ref={editorContainerRef} className="w-full">
<Plate
key="editor"
id={id}

View File

@ -2,9 +2,7 @@ import PopperUnstyled from '@mui/base/PopperUnstyled';
import {
ELEMENT_LINK,
ELEMENT_TD,
findNodePath,
getNode,
getParentNode,
getSelectionBoundingClientRect,
getSelectionText,
isElement,
@ -20,7 +18,6 @@ import { useFocused } from 'slate-react';
import useDebounce from '@staticcms/core/lib/hooks/useDebounce';
import { isEmpty } from '@staticcms/core/lib/util/string.util';
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
import { VOID_ELEMENTS } from '../../serialization/slate/ast-types';
import BasicElementToolbarButtons from '../buttons/BasicElementToolbarButtons';
import BasicMarkToolbarButtons from '../buttons/BasicMarkToolbarButtons';
import MediaToolbarButtons from '../buttons/MediaToolbarButtons';
@ -156,29 +153,14 @@ const BalloonToolbar: FC<BalloonToolbarProps> = ({
!useMdx ? <ShortcodeToolbarButton key="shortcode-button" disabled={disabled} /> : 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 [];

View File

@ -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(<BalloonToolbarWrapper />);
const result = renderWithProviders(<BalloonToolbarWrapper />);
rerender(<BalloonToolbarWrapper useMdx={useMdx} />);
result.rerender(<BalloonToolbarWrapper useMdx={useMdx} />);
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();

View File

@ -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<AddButtonsProps> = ({ collection, field, disabled }) => {
});
}, [editor]);
const handleTableAdd = useCallback(() => {
insertTable(editor, {
rowCount: 2,
colCount: 2,
});
}, [editor]);
return (
<Menu
label={<AddIcon className="h-5 w-5" aria-hidden="true" />}
@ -74,6 +84,11 @@ const AddButtons: FC<AddButtonsProps> = ({ collection, field, disabled }) => {
Code Block
</MenuItemButton>
</MenuGroup>
<MenuGroup>
<MenuItemButton key={ELEMENT_TABLE} onClick={handleTableAdd} startIcon={TableAdd}>
Table
</MenuItemButton>
</MenuGroup>
<MenuGroup>
<ImageToolbarButton
key={ELEMENT_IMAGE}

View File

@ -1,6 +1,6 @@
import { Image as ImageIcon } from '@styled-icons/material/Image';
import { ELEMENT_IMAGE, insertImage } from '@udecode/plate';
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import MenuItemButton from '@staticcms/core/components/common/menu/MenuItemButton';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
@ -36,12 +36,14 @@ const ImageToolbarButton: FC<ImageToolbarButtonProps> = ({
[editor],
);
const chooseUrl = useMemo(() => field.choose_url ?? true, [field.choose_url]);
const openMediaLibrary = useMediaInsert(
{
path: currentValue?.url ?? '',
alt: currentValue?.alt,
},
{ collection, field, forImage: true },
{ collection, field, forImage: true, insertOptions: { chooseUrl, showAlt: true } },
handleInsert,
);

View File

@ -1,6 +1,6 @@
import { Link as LinkIcon } from '@styled-icons/material/Link';
import { ELEMENT_LINK, insertLink, someNode } from '@udecode/plate';
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import MenuItemButton from '@staticcms/core/components/common/menu/MenuItemButton';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
@ -41,6 +41,8 @@ const LinkToolbarButton: FC<LinkToolbarButtonProps> = ({
[editor],
);
const chooseUrl = useMemo(() => field.choose_url ?? true, [field.choose_url]);
const isLink = !!editor?.selection && someNode(editor, { match: { type: ELEMENT_LINK } });
const controlID = useUUID();
@ -49,7 +51,7 @@ const LinkToolbarButton: FC<LinkToolbarButtonProps> = ({
path: currentValue?.url ?? '',
alt: currentValue?.alt,
},
{ collection, field, controlID, forImage: true },
{ collection, field, controlID, forImage: false, insertOptions: { chooseUrl, showAlt: true } },
handleInsert,
);

View File

@ -45,7 +45,7 @@ const MediaPopover = <T extends FileOrImageField | MarkdownField>({
onMediaToggle?.(false);
});
const chooseUrl = useMemo(() => field.choose_url ?? false, [field.choose_url]);
const chooseUrl = useMemo(() => field.choose_url ?? true, [field.choose_url]);
const handleFocus = useCallback(() => {
onFocus?.();

View File

@ -22,7 +22,6 @@ const TableHeaderCellElement: FC<PlateRenderElementProps<MdValue, MdTableCellEle
text-sm
border-r
border-gray-200
last:border-0
dark:bg-slate-700
dark:border-gray-800
"

View File

@ -27,7 +27,19 @@ const TableElement: FC<PlateRenderElementProps<MdValue, MdTableElement>> = ({
>
{children ? (
<>
<thead key="thead">{children[0]}</thead>
<thead
key="thead"
className="
border-r
border-b
bg-slate-300
border-gray-200
dark:bg-slate-700
dark:border-gray-800
"
>
{children[0]}
</thead>
<tbody key="tbody">{children.slice(1)}</tbody>
</>
) : null}