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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 199 additions and 119 deletions

View File

@ -63,5 +63,6 @@ export const isSelectionExpanded = jest.fn();
export const isText = jest.fn(); export const isText = jest.fn();
export const someNode = jest.fn(); export const someNode = jest.fn();
export const usePlateSelection = jest.fn(); export const usePlateSelection = jest.fn();
export const isMarkActive = jest.fn();
export default {}; export default {};

View File

@ -157,6 +157,7 @@ const Field: FC<FieldProps> = ({
{renderedHint} {renderedHint}
{renderedErrorMessage} {renderedErrorMessage}
</div> </div>
{endAdornment ? (
<div <div
className={classNames( className={classNames(
` `
@ -167,6 +168,7 @@ const Field: FC<FieldProps> = ({
> >
{endAdornment} {endAdornment}
</div> </div>
) : null}
</div> </div>
); );
}; };

View File

@ -1,9 +1,11 @@
import React from 'react'; import React from 'react';
import { Image as ImageIcon } from '@styled-icons/material-outlined/Image';
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset'; import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
import classNames from '@staticcms/core/lib/util/classNames.util'; import classNames from '@staticcms/core/lib/util/classNames.util';
import { selectEditingDraft } from '@staticcms/core/reducers/selectors/entryDraft'; import { selectEditingDraft } from '@staticcms/core/reducers/selectors/entryDraft';
import { useAppSelector } from '@staticcms/core/store/hooks'; 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'; import type { BaseField, Collection, MediaField, UnknownField } from '@staticcms/core/interface';
@ -28,6 +30,20 @@ const Image = <EF extends BaseField = UnknownField>({
const assetSource = useMediaAsset(src, collection, field, entry); const assetSource = useMediaAsset(src, collection, field, entry);
if (isEmpty(src)) {
return (
<ImageIcon
className="
p-10
rounded-md
border
border-gray-200/75
dark:border-slate-600/75
"
/>
);
}
return ( return (
<img <img
key="image" key="image"

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import classNames from '@staticcms/core/lib/util/classNames.util';
import { isEmpty } from '../../../lib/util/string.util'; import { isEmpty } from '../../../lib/util/string.util';
import Image from '../../common/image/Image'; import Image from '../../common/image/Image';
import InlineEditTextField from './InlineEditTextField'; import InlineEditTextField from './InlineEditTextField';
@ -14,6 +15,7 @@ interface CurrentMediaDetailsProps {
url?: string | string[]; url?: string | string[];
alt?: string; alt?: string;
insertOptions?: MediaLibrarInsertOptions; insertOptions?: MediaLibrarInsertOptions;
forImage: boolean;
onUrlChange: (url: string) => void; onUrlChange: (url: string) => void;
onAltChange: (alt: string) => void; onAltChange: (alt: string) => void;
} }
@ -25,27 +27,46 @@ const CurrentMediaDetails: FC<CurrentMediaDetailsProps> = ({
url, url,
alt, alt,
insertOptions, insertOptions,
forImage,
onUrlChange, onUrlChange,
onAltChange, 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 null;
} }
return ( return (
<div <div
className=" className={classNames(
grid `
grid-cols-media-preview
items-center items-center
px-5 px-5
py-4 py-4
border-b border-b
border-gray-200/75 border-gray-200/75
dark:border-slate-500/75 dark:border-slate-500/75
" `,
forImage
? `
grid
grid-cols-media-preview
`
: `
flex
w-full
`,
)}
> >
{forImage ? (
<Image <Image
key="image-preview"
src={url} src={url}
collection={collection} collection={collection}
field={field} field={field}
@ -64,15 +85,25 @@ const CurrentMediaDetails: FC<CurrentMediaDetailsProps> = ({
object-cover object-cover
" "
/> />
) : null}
<div <div
className=" className={classNames(
`
flex flex
flex-col flex-col
h-full h-full
p-0 p-0
pl-4
gap-2 gap-2
" `,
forImage
? `
pl-4
`
: `
w-full
pl-1.5
`,
)}
> >
<InlineEditTextField <InlineEditTextField
label="URL" label="URL"
@ -80,7 +111,11 @@ const CurrentMediaDetails: FC<CurrentMediaDetailsProps> = ({
onChange={insertOptions?.chooseUrl ? onUrlChange : undefined} onChange={insertOptions?.chooseUrl ? onUrlChange : undefined}
/> />
{insertOptions?.showAlt ? ( {insertOptions?.showAlt ? (
<InlineEditTextField label="Alt" value={alt} onChange={onAltChange} /> <InlineEditTextField
label={forImage ? 'Alt' : 'Text'}
value={alt}
onChange={onAltChange}
/>
) : null} ) : null}
</div> </div>
</div> </div>

View File

@ -96,6 +96,7 @@ const InlineEditTextField: FC<InlineEditTextFieldProps> = ({
{!editing || !onChange ? ( {!editing || !onChange ? (
<div <div
key="value" key="value"
tabIndex={0}
className={classNames( className={classNames(
` `
flex flex
@ -110,7 +111,6 @@ const InlineEditTextField: FC<InlineEditTextFieldProps> = ({
rounded-md rounded-md
border border
text-slate-600 text-slate-600
dark:font-semibold
dark:text-gray-100 dark:text-gray-100
`, `,
onChange onChange
@ -125,6 +125,7 @@ const InlineEditTextField: FC<InlineEditTextFieldProps> = ({
`, `,
)} )}
onClick={handleValueClick} onClick={handleValueClick}
onFocus={handleValueClick}
> >
{internalValue} {internalValue}
</div> </div>

View File

@ -484,6 +484,7 @@ const MediaLibrary: FC<TranslatedProps<MediaLibraryProps>> = ({
url={url} url={url}
alt={alt} alt={alt}
insertOptions={insertOptions} insertOptions={insertOptions}
forImage={forImage}
onUrlChange={handleURLChange} onUrlChange={handleURLChange}
onAltChange={handleAltChange} onAltChange={handleAltChange}
/> />

View File

@ -253,7 +253,7 @@ const PlateEditor: FC<PlateEditorProps> = ({
return useMemo( 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}> <DndProvider backend={HTML5Backend}>
<PlateProvider<MdValue> <PlateProvider<MdValue>
id={id} id={id}
@ -272,7 +272,7 @@ const PlateEditor: FC<PlateEditorProps> = ({
disabled={disabled} disabled={disabled}
/> />
<div key="editor-wrapper" ref={editorContainerRef} className="w-full overflow-hidden"> <div key="editor-wrapper" ref={editorContainerRef} className="w-full">
<Plate <Plate
key="editor" key="editor"
id={id} id={id}

View File

@ -2,9 +2,7 @@ import PopperUnstyled from '@mui/base/PopperUnstyled';
import { import {
ELEMENT_LINK, ELEMENT_LINK,
ELEMENT_TD, ELEMENT_TD,
findNodePath,
getNode, getNode,
getParentNode,
getSelectionBoundingClientRect, getSelectionBoundingClientRect,
getSelectionText, getSelectionText,
isElement, isElement,
@ -20,7 +18,6 @@ import { useFocused } from 'slate-react';
import useDebounce from '@staticcms/core/lib/hooks/useDebounce'; import useDebounce from '@staticcms/core/lib/hooks/useDebounce';
import { isEmpty } from '@staticcms/core/lib/util/string.util'; import { isEmpty } from '@staticcms/core/lib/util/string.util';
import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes'; import { useMdPlateEditorState } from '@staticcms/markdown/plate/plateTypes';
import { VOID_ELEMENTS } from '../../serialization/slate/ast-types';
import BasicElementToolbarButtons from '../buttons/BasicElementToolbarButtons'; import BasicElementToolbarButtons from '../buttons/BasicElementToolbarButtons';
import BasicMarkToolbarButtons from '../buttons/BasicMarkToolbarButtons'; import BasicMarkToolbarButtons from '../buttons/BasicMarkToolbarButtons';
import MediaToolbarButtons from '../buttons/MediaToolbarButtons'; import MediaToolbarButtons from '../buttons/MediaToolbarButtons';
@ -156,30 +153,15 @@ const BalloonToolbar: FC<BalloonToolbarProps> = ({
!useMdx ? <ShortcodeToolbarButton key="shortcode-button" disabled={disabled} /> : null, !useMdx ? <ShortcodeToolbarButton key="shortcode-button" disabled={disabled} /> : null,
].filter(Boolean); ].filter(Boolean);
// if (isInTableCell) { // Empty table cell
// return allButtons;
// }
// Empty paragraph, not first line
if ( if (
isInTableCell &&
editor.children.length > 1 && editor.children.length > 1 &&
node && node &&
((isElement(node) && isElementEmpty(editor, node)) || (isText(node) && isEmpty(node.text))) ((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 []; return [];
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -4,6 +4,7 @@
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { import {
ELEMENT_LINK,
findNodePath, findNodePath,
getNode, getNode,
getParentNode, getParentNode,
@ -11,6 +12,7 @@ import {
isElementEmpty, isElementEmpty,
someNode, someNode,
usePlateEditorState, usePlateEditorState,
usePlateSelection,
} from '@udecode/plate'; } from '@udecode/plate';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { useFocused } from 'slate-react'; import { useFocused } from 'slate-react';
@ -25,6 +27,7 @@ import BalloonToolbar from '../BalloonToolbar';
import type { Config, MarkdownField } from '@staticcms/core/interface'; import type { Config, MarkdownField } from '@staticcms/core/interface';
import type { MdEditor } from '@staticcms/markdown/plate/plateTypes'; import type { MdEditor } from '@staticcms/markdown/plate/plateTypes';
import type { TRange } from '@udecode/plate';
import type { FC } from 'react'; import type { FC } from 'react';
interface BalloonToolbarWrapperProps { interface BalloonToolbarWrapperProps {
@ -54,6 +57,7 @@ const config = createMockConfig({
describe(BalloonToolbar.name, () => { describe(BalloonToolbar.name, () => {
const mockUseEditor = usePlateEditorState as jest.Mock; const mockUseEditor = usePlateEditorState as jest.Mock;
const mockUsePlateSelection = usePlateSelection as jest.Mock;
let mockEditor: MdEditor; let mockEditor: MdEditor;
const mockGetNode = getNode as jest.Mock; const mockGetNode = getNode as jest.Mock;
@ -67,26 +71,6 @@ describe(BalloonToolbar.name, () => {
beforeEach(() => { beforeEach(() => {
store.dispatch(configLoaded(config as unknown as Config)); 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 = { mockEditor = {
selection: undefined, selection: undefined,
} as unknown as MdEditor; } as unknown as MdEditor;
@ -99,45 +83,57 @@ describe(BalloonToolbar.name, () => {
expect(screen.queryAllByRole('button').length).toBe(0); expect(screen.queryAllByRole('button').length).toBe(0);
}); });
describe('empty node toolbar', () => { describe('empty node toolbar inside table', () => {
interface EmptyNodeToolbarSetupOptions { interface EmptyNodeToolbarSetupOptions {
useMdx?: boolean; useMdx?: boolean;
} }
const emptyNodeToolbarSetup = ({ useMdx }: EmptyNodeToolbarSetupOptions = {}) => { const emptyNodeToolbarSetup = ({ useMdx }: EmptyNodeToolbarSetupOptions = {}) => {
mockEditor = { mockEditor = {
selection: undefined, selection: {
anchor: {
path: [1, 0],
offset: 0,
},
focus: {
path: [1, 0],
offset: 0,
},
} as TRange,
children: [ children: [
{ {
type: 'p', type: 'p',
children: [{ text: '' }], children: [{ text: '' }],
}, },
{ {
type: 'p', type: 'td',
children: [{ text: '' }], children: [{ text: '' }],
}, },
], ],
} as unknown as MdEditor; } as unknown as MdEditor;
mockUseEditor.mockReturnValue(mockEditor); mockUseEditor.mockReturnValue(mockEditor);
mockUsePlateSelection.mockReturnValue(mockEditor.selection);
mockGetNode.mockReturnValue({ text: '' }); mockGetNode.mockReturnValue({ text: '' });
mockIsElement.mockReturnValue(true); mockIsElement.mockReturnValue(true);
mockIsElementEmpty.mockReturnValue(true); mockIsElementEmpty.mockReturnValue(true);
mockSomeNode.mockReturnValue(false); mockSomeNode.mockImplementation((_editor, { match: { type } }) => type !== ELEMENT_LINK);
mockUseFocused.mockReturnValue(true); mockUseFocused.mockReturnValue(true);
mockFindNodePath.mockReturnValue([1, 0]); mockFindNodePath.mockReturnValue([1, 0]);
mockGetParentNode.mockReturnValue([ mockGetParentNode.mockReturnValue([
{ {
type: 'p', type: 'td',
children: [{ text: '' }], 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', () => { 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-code')).toBeInTheDocument();
expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument(); expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument();
expect(screen.queryByTestId('font-type-select')).toBeInTheDocument(); expect(screen.queryByTestId('font-type-select')).not.toBeInTheDocument();
expect(screen.queryByTestId('toolbar-button-add-table')).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-link')).toBeInTheDocument();
expect(screen.queryByTestId('toolbar-button-insert-image')).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-code')).toBeInTheDocument();
expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument(); expect(screen.queryByTestId('toolbar-button-strikethrough')).toBeInTheDocument();
expect(screen.queryByTestId('font-type-select')).toBeInTheDocument(); expect(screen.queryByTestId('font-type-select')).not.toBeInTheDocument();
expect(screen.queryByTestId('toolbar-button-add-table')).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-link')).toBeInTheDocument();
expect(screen.queryByTestId('toolbar-button-insert-image')).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 { Add as AddIcon } from '@styled-icons/material/Add';
import { Code as CodeIcon } from '@styled-icons/material/Code'; import { Code as CodeIcon } from '@styled-icons/material/Code';
import { FormatQuote as FormatQuoteIcon } from '@styled-icons/material/FormatQuote'; import { FormatQuote as FormatQuoteIcon } from '@styled-icons/material/FormatQuote';
@ -6,7 +7,9 @@ import {
ELEMENT_CODE_BLOCK, ELEMENT_CODE_BLOCK,
ELEMENT_IMAGE, ELEMENT_IMAGE,
ELEMENT_LINK, ELEMENT_LINK,
ELEMENT_TABLE,
insertEmptyCodeBlock, insertEmptyCodeBlock,
insertTable,
toggleNodeType, toggleNodeType,
} from '@udecode/plate'; } from '@udecode/plate';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
@ -43,6 +46,13 @@ const AddButtons: FC<AddButtonsProps> = ({ collection, field, disabled }) => {
}); });
}, [editor]); }, [editor]);
const handleTableAdd = useCallback(() => {
insertTable(editor, {
rowCount: 2,
colCount: 2,
});
}, [editor]);
return ( return (
<Menu <Menu
label={<AddIcon className="h-5 w-5" aria-hidden="true" />} label={<AddIcon className="h-5 w-5" aria-hidden="true" />}
@ -74,6 +84,11 @@ const AddButtons: FC<AddButtonsProps> = ({ collection, field, disabled }) => {
Code Block Code Block
</MenuItemButton> </MenuItemButton>
</MenuGroup> </MenuGroup>
<MenuGroup>
<MenuItemButton key={ELEMENT_TABLE} onClick={handleTableAdd} startIcon={TableAdd}>
Table
</MenuItemButton>
</MenuGroup>
<MenuGroup> <MenuGroup>
<ImageToolbarButton <ImageToolbarButton
key={ELEMENT_IMAGE} key={ELEMENT_IMAGE}

View File

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

View File

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

View File

@ -45,7 +45,7 @@ const MediaPopover = <T extends FileOrImageField | MarkdownField>({
onMediaToggle?.(false); 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(() => { const handleFocus = useCallback(() => {
onFocus?.(); onFocus?.();

View File

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

View File

@ -27,7 +27,19 @@ const TableElement: FC<PlateRenderElementProps<MdValue, MdTableElement>> = ({
> >
{children ? ( {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> <tbody key="tbody">{children.slice(1)}</tbody>
</> </>
) : null} ) : null}

View File

@ -22,7 +22,7 @@ For common options, see [Common widget options](/docs/widgets#common-widget-opti
| media_folder | string | | _Optional_. Specifies the folder path where uploaded files should be saved, relative to the base of the repo | | media_folder | string | | _Optional_. Specifies the folder path where uploaded files should be saved, relative to the base of the repo |
| public_folder | string | | _Optional_. Specifies the folder path where the files uploaded by the media library will be accessed, relative to the base of the built site | | public_folder | string | | _Optional_. Specifies the folder path where the files uploaded by the media library will be accessed, relative to the base of the built site |
| media_library | Media Library Options | `{}` | _Optional_. Media library settings to apply when the media library is opened by the current widget. See [Media Library](/docs/configuration-options#media-library) | | media_library | Media Library Options | `{}` | _Optional_. Media library settings to apply when the media library is opened by the current widget. See [Media Library](/docs/configuration-options#media-library) |
| choose_url | boolean | `false` | _Optional_. When set to `false`, the "Insert from URL" button will be hidden | | choose_url | boolean | `true` | _Optional_. When set to `false`, the "Insert from URL" button will be hidden |
## Example ## Example
@ -231,9 +231,13 @@ const ImageControl = ({ src, onChange, controlProps }) => {
onChange({ src: path }); onChange({ src: path });
}; };
const handleOpenMediaLibrary = useMediaInsert(src, { collection: collection, field }, handleChange); const handleOpenMediaLibrary = useMediaInsert(
src,
{ collection: collection, field },
handleChange,
);
const assetSource = useMediaAsset(src , collection, field, entry); const assetSource = useMediaAsset(src, collection, field, entry);
return [ return [
h('button', { type: 'button', onClick: handleOpenMediaLibrary }, 'Upload'), h('button', { type: 'button', onClick: handleOpenMediaLibrary }, 'Upload'),
@ -253,9 +257,13 @@ const ImageControl = ({ src, onChange, controlProps }) => {
onChange({ src: path }); onChange({ src: path });
}; };
const handleOpenMediaLibrary = useMediaInsert(src ,{ collection: collection, field }, handleChange); const handleOpenMediaLibrary = useMediaInsert(
src,
{ collection: collection, field },
handleChange,
);
const assetSource = useMediaAsset(src , collection, field, entry); const assetSource = useMediaAsset(src, collection, field, entry);
return ( return (
<> <>
@ -275,11 +283,7 @@ import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
import type { WidgetControlProps, MediaPath } from '@staticcms/core/interface'; import type { WidgetControlProps, MediaPath } from '@staticcms/core/interface';
import type { FC } from 'react'; import type { FC } from 'react';
const FileControl: FC<WidgetControlProps<string, MyField>> = ({ const FileControl: FC<WidgetControlProps<string, MyField>> = ({ src, onChange, controlProps }) => {
src,
onChange,
controlProps
}) => {
const { collection, field, entry } = controlProps; const { collection, field, entry } = controlProps;
const handleChange = ({ path }: MediaPath) => { const handleChange = ({ path }: MediaPath) => {