fix: folder collection path (#549)

This commit is contained in:
Daniel Lautzenheiser
2023-02-16 13:34:35 -05:00
committed by GitHub
parent 93915dac35
commit 8f7237ab7c
53 changed files with 742 additions and 513 deletions

View File

@ -238,7 +238,7 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => {
const handleOpenMediaLibrary = useMediaInsert(
internalValue,
{ field, controlID },
{ collection, field, controlID, forImage },
handleOnChange,
);
@ -312,10 +312,11 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => {
replaceIndex: index,
allowMultiple: false,
config,
collection,
field,
});
},
[config, controlID, field, openMediaLibrary, internalValue],
[openMediaLibrary, controlID, internalValue, config, collection, field],
);
// TODO Readd when multiple uploads is supported

View File

@ -2,7 +2,7 @@ import { styled } from '@mui/material/styles';
import partial from 'lodash/partial';
import React, { useCallback, useMemo, useState } from 'react';
import EditorControl from '@staticcms/core/components/Editor/EditorControlPane/EditorControl';
import EditorControl from '@staticcms/core/components/editor/EditorControlPane/EditorControl';
import ListItemTopBar from '@staticcms/core/components/UI/ListItemTopBar';
import Outline from '@staticcms/core/components/UI/Outline';
import { colors } from '@staticcms/core/components/UI/styles';

View File

@ -52,7 +52,7 @@ const ListControlWrapper = createControlWrapper({
path: 'list',
});
jest.mock('@staticcms/core/components/Editor/EditorControlPane/EditorControl', () => {
jest.mock('@staticcms/core/components/editor/EditorControlPane/EditorControl', () => {
return jest.fn(props => {
const { parentPath, field, value } = props;
return (

View File

@ -7,6 +7,7 @@ import { getShortcodes } from '../../lib/registry';
import withShortcodeMdxComponent from './mdx/withShortcodeMdxComponent';
import useMdx from './plate/hooks/useMdx';
import { processShortcodeConfigToMdx } from './plate/serialization/slate/processShortcodeConfig';
import { withMdxImage } from '@staticcms/core/components/common/image/Image';
import type { MarkdownField, WidgetPreviewProps } from '@staticcms/core/interface';
import type { FC } from 'react';
@ -26,13 +27,14 @@ function FallbackComponent({ error }: FallbackComponentProps) {
}
const MarkdownPreview: FC<WidgetPreviewProps<string, MarkdownField>> = previewProps => {
const { value } = previewProps;
const { value, collection, field } = previewProps;
const components = useMemo(
() => ({
Shortcode: withShortcodeMdxComponent({ previewProps }),
img: withMdxImage({ collection, field }),
}),
[previewProps],
[collection, field, previewProps],
);
const [state, setValue] = useMdx(value ?? '');

View File

@ -5,11 +5,10 @@ import Popper from '@mui/material/Popper';
import { styled, useTheme } from '@mui/material/styles';
import TextField from '@mui/material/TextField';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useFocused } from 'slate-react';
import useDebounce from '@staticcms/core/lib/hooks/useDebounce';
import useIsMediaAsset from '@staticcms/core/lib/hooks/useIsMediaAsset';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
import { useWindowEvent } from '@staticcms/core/lib/util/window.util';
import type { Collection, Entry, FileOrImageField, MarkdownField } from '@staticcms/core/interface';
import type { ChangeEvent, KeyboardEvent } from 'react';
@ -65,7 +64,6 @@ export interface MediaPopoverProps<T extends FileOrImageField | MarkdownField> {
onUrlChange: (newValue: string) => void;
onTextChange?: (newValue: string) => void;
onClose: (shouldFocus: boolean) => void;
mediaOpen?: boolean;
onMediaToggle?: (open: boolean) => void;
onMediaChange: (newValue: string) => void;
onRemove?: () => void;
@ -87,7 +85,6 @@ const MediaPopover = <T extends FileOrImageField | MarkdownField>({
onUrlChange,
onTextChange,
onClose,
mediaOpen,
onMediaToggle,
onMediaChange,
onRemove,
@ -101,9 +98,9 @@ const MediaPopover = <T extends FileOrImageField | MarkdownField>({
const [editing, setEditing] = useState(inserting);
const hasEditorFocus = useFocused();
const [hasFocus, setHasFocus] = useState(false);
const debouncedHasFocus = useDebounce(hasFocus, 150);
useWindowEvent('mediaLibraryClose', () => {
onMediaToggle?.(false);
});
const handleClose = useCallback(
(shouldFocus: boolean) => {
@ -155,60 +152,11 @@ const MediaPopover = <T extends FileOrImageField | MarkdownField>({
}
}, [anchorEl, editing, inserting, urlDisabled]);
const [
{ prevAnchorEl, prevHasEditorFocus, prevHasFocus, prevDebouncedHasFocus },
setPrevFocusState,
] = useState<{
prevAnchorEl: HTMLElement | null;
prevHasEditorFocus: boolean;
prevHasFocus: boolean;
prevDebouncedHasFocus: boolean;
}>({
prevAnchorEl: anchorEl,
prevHasEditorFocus: hasEditorFocus,
prevHasFocus: hasFocus,
prevDebouncedHasFocus: debouncedHasFocus,
});
useEffect(() => {
if (mediaOpen) {
return;
}
if (anchorEl && !prevHasEditorFocus && hasEditorFocus) {
handleClose(false);
}
if (anchorEl && (prevHasFocus || prevDebouncedHasFocus) && !hasFocus && !debouncedHasFocus) {
handleClose(false);
}
setPrevFocusState({
prevAnchorEl: anchorEl,
prevHasEditorFocus: hasEditorFocus,
prevHasFocus: hasFocus,
prevDebouncedHasFocus: debouncedHasFocus,
});
}, [
anchorEl,
debouncedHasFocus,
handleClose,
hasEditorFocus,
hasFocus,
mediaOpen,
prevAnchorEl,
prevDebouncedHasFocus,
prevHasEditorFocus,
prevHasFocus,
]);
const handleFocus = useCallback(() => {
setHasFocus(true);
onFocus?.();
}, [onFocus]);
const handleBlur = useCallback(() => {
setHasFocus(false);
onBlur?.();
}, [onBlur]);
@ -220,7 +168,11 @@ const MediaPopover = <T extends FileOrImageField | MarkdownField>({
[onMediaChange, onMediaToggle],
);
const handleOpenMediaLibrary = useMediaInsert(url, { field, forImage }, handleMediaChange);
const handleOpenMediaLibrary = useMediaInsert(
url,
{ collection, field, forImage },
handleMediaChange,
);
const handleMediaOpen = useCallback(() => {
onMediaToggle?.(true);

View File

@ -12,6 +12,7 @@ import { useFocused } from 'slate-react';
import useMediaAsset from '@staticcms/core/lib/hooks/useMediaAsset';
import { isEmpty } from '@staticcms/core/lib/util/string.util';
import { MediaPopover } from '@staticcms/markdown';
import useDebounce from '@staticcms/core/lib/hooks/useDebounce';
import type { Collection, Entry, MarkdownField } from '@staticcms/core/interface';
import type { MdImageElement, MdValue } from '@staticcms/markdown';
@ -35,13 +36,32 @@ const withImageElement = ({ containerRef, collection, entry, field }: WithImageE
const { url, alt } = element;
const [internalUrl, setInternalUrl] = useState(url);
const [internalAlt, setInternalAlt] = useState(alt);
const [popoverHasFocus, setPopoverHasFocus] = useState(false);
const debouncedPopoverHasFocus = useDebounce(popoverHasFocus, 100);
const [mediaOpen, setMediaOpen] = useState(false);
const imageRef = useRef<HTMLImageElement | null>(null);
const [anchorEl, setAnchorEl] = useState<HTMLImageElement | null>(null);
const hasEditorFocus = useFocused();
const debouncedHasEditorFocus = useDebounce(hasEditorFocus, 100);
const handleBlur = useCallback(() => {
setAnchorEl(null);
if (!popoverHasFocus && !mediaOpen) {
setAnchorEl(null);
}
}, [mediaOpen, popoverHasFocus]);
const handlePopoverFocus = useCallback(() => {
setPopoverHasFocus(true);
}, []);
const handlePopoverBlur = useCallback(() => {
setPopoverHasFocus(false);
}, []);
const handleMediaToggle = useCallback(() => {
setMediaOpen(oldMediaOpen => !oldMediaOpen);
}, []);
const handleChange = useCallback(
@ -96,7 +116,28 @@ const withImageElement = ({ containerRef, collection, entry, field }: WithImageE
const selection = usePlateSelection();
useEffect(() => {
if (!hasEditorFocus || !selection) {
if (
hasEditorFocus ||
debouncedHasEditorFocus ||
mediaOpen ||
popoverHasFocus ||
debouncedPopoverHasFocus
) {
return;
}
handleClose();
}, [
debouncedHasEditorFocus,
debouncedPopoverHasFocus,
handleClose,
hasEditorFocus,
mediaOpen,
popoverHasFocus,
]);
useEffect(() => {
if (!hasEditorFocus || !selection || mediaOpen || popoverHasFocus) {
return;
}
@ -109,12 +150,24 @@ const withImageElement = ({ containerRef, collection, entry, field }: WithImageE
}
if (node !== element && node !== firstChild) {
handleClose();
if (anchorEl) {
handleClose();
}
return;
}
handleOpenPopover();
}, [handleClose, hasEditorFocus, element, selection, editor, handleOpenPopover]);
}, [
handleClose,
hasEditorFocus,
element,
selection,
editor,
handleOpenPopover,
mediaOpen,
popoverHasFocus,
anchorEl,
]);
return (
<span onBlur={handleBlur}>
@ -140,6 +193,9 @@ const withImageElement = ({ containerRef, collection, entry, field }: WithImageE
onMediaChange={handleMediaChange}
onRemove={handleRemove}
forImage
onFocus={handlePopoverFocus}
onBlur={handlePopoverBlur}
onMediaToggle={handleMediaToggle}
/>
{children}
</span>

View File

@ -2,17 +2,21 @@ import {
findNodePath,
focusEditor,
getEditorString,
getNode,
replaceNodeChildren,
setNodes,
unwrapLink,
upsertLink,
usePlateSelection,
} from '@udecode/plate';
import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useFocused } from 'slate-react';
import useDebounce from '@staticcms/core/lib/hooks/useDebounce';
import MediaPopover from '../../common/MediaPopover';
import type { Collection, Entry, MarkdownField } from '@staticcms/core/interface';
import type { MdLinkElement, MdValue } from '@staticcms/markdown';
import type { PlateRenderElementProps } from '@udecode/plate';
import type { PlateRenderElementProps, TText } from '@udecode/plate';
import type { FC, MouseEvent } from 'react';
export interface WithLinkElementProps {
@ -24,19 +28,49 @@ export interface WithLinkElementProps {
const withLinkElement = ({ containerRef, collection, field, entry }: WithLinkElementProps) => {
const LinkElement: FC<PlateRenderElementProps<MdValue, MdLinkElement>> = ({
attributes,
attributes: { ref: _ref, ...attributes },
children,
nodeProps,
element,
editor,
}) => {
const [anchorEl, setAnchorEl] = useState<HTMLAnchorElement | null>(null);
const urlRef = useRef<HTMLAnchorElement | null>(null);
const { url } = element;
const path = findNodePath(editor, element);
const [internalUrl, setInternalUrl] = useState(url);
const [internalText, setInternalText] = useState(getEditorString(editor, path));
const [popoverHasFocus, setPopoverHasFocus] = useState(false);
const debouncedPopoverHasFocus = useDebounce(popoverHasFocus, 100);
const [mediaOpen, setMediaOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<HTMLAnchorElement | null>(null);
const hasEditorFocus = useFocused();
const debouncedHasEditorFocus = useDebounce(hasEditorFocus, 100);
const handleOpenPopover = useCallback(() => {
setAnchorEl(urlRef.current);
}, []);
const handleBlur = useCallback(() => {
if (!popoverHasFocus && !mediaOpen) {
setAnchorEl(null);
}
}, [mediaOpen, popoverHasFocus]);
const handlePopoverFocus = useCallback(() => {
setPopoverHasFocus(true);
}, []);
const handlePopoverBlur = useCallback(() => {
setPopoverHasFocus(false);
}, []);
const handleMediaToggle = useCallback(() => {
setMediaOpen(oldMediaOpen => !oldMediaOpen);
}, []);
const handleClick = useCallback((event: MouseEvent<HTMLAnchorElement>) => {
setAnchorEl(event.currentTarget);
@ -50,11 +84,29 @@ const withLinkElement = ({ containerRef, collection, field, entry }: WithLinkEle
focusEditor(editor, editor.selection);
}, [editor]);
const selection = usePlateSelection();
const handleChange = useCallback(
(newUrl: string, newText: string) => {
const path = findNodePath(editor, element);
path && setNodes<MdLinkElement>(editor, { url: newUrl }, { at: path });
upsertLink(editor, { url: newUrl, text: newText });
if (path) {
setNodes(
editor,
{ ...element, url: newUrl, children: [{ text: newText }] },
{ at: path },
);
if (newText?.length && newText !== getEditorString(editor, path)) {
replaceNodeChildren<TText>(editor, {
at: path,
nodes: { text: newText },
insertOptions: {
select: true,
},
});
}
}
},
[editor, element],
);
@ -72,9 +124,83 @@ const withLinkElement = ({ containerRef, collection, field, entry }: WithLinkEle
handleChange(internalUrl, internalText);
}, [handleChange, internalText, internalUrl]);
useEffect(() => {
if (
hasEditorFocus ||
debouncedHasEditorFocus ||
mediaOpen ||
popoverHasFocus ||
debouncedPopoverHasFocus
) {
return;
}
handleClose();
}, [
debouncedHasEditorFocus,
debouncedPopoverHasFocus,
handleClose,
hasEditorFocus,
mediaOpen,
popoverHasFocus,
]);
useEffect(() => {
if (
hasEditorFocus ||
debouncedHasEditorFocus ||
mediaOpen ||
popoverHasFocus ||
debouncedPopoverHasFocus
) {
return;
}
handleClose();
}, [
debouncedHasEditorFocus,
debouncedPopoverHasFocus,
handleClose,
hasEditorFocus,
mediaOpen,
popoverHasFocus,
]);
useEffect(() => {
if (!hasEditorFocus || !selection || mediaOpen || popoverHasFocus) {
return;
}
const node = getNode(editor, selection.anchor.path);
const firstChild =
'children' in element && element.children.length > 0 ? element.children[0] : undefined;
if (!node) {
return;
}
if (node !== element && node !== firstChild) {
if (anchorEl) {
handleClose();
}
return;
}
handleOpenPopover();
}, [
handleClose,
hasEditorFocus,
element,
selection,
editor,
handleOpenPopover,
mediaOpen,
popoverHasFocus,
anchorEl,
]);
return (
<>
<a {...attributes} href={url} {...nodeProps} onClick={handleClick}>
<span onBlur={handleBlur}>
<a ref={urlRef} {...attributes} href={url} {...nodeProps} onClick={handleClick}>
{children}
</a>
<MediaPopover
@ -90,8 +216,11 @@ const withLinkElement = ({ containerRef, collection, field, entry }: WithLinkEle
onClose={handleClose}
onMediaChange={handleMediaChange}
onRemove={handleRemove}
onFocus={handlePopoverFocus}
onBlur={handlePopoverBlur}
onMediaToggle={handleMediaToggle}
/>
</>
</span>
);
};

View File

@ -1,7 +1,7 @@
import { styled } from '@mui/material/styles';
import React, { useCallback, useMemo, useState } from 'react';
import EditorControl from '@staticcms/core/components/Editor/EditorControlPane/EditorControl';
import EditorControl from '@staticcms/core/components/editor/EditorControlPane/EditorControl';
import ObjectWidgetTopBar from '@staticcms/core/components/UI/ObjectWidgetTopBar';
import Outline from '@staticcms/core/components/UI/Outline';
import { transientOptions } from '@staticcms/core/lib';

View File

@ -35,7 +35,7 @@ const ObjectControlWrapper = createControlWrapper({
path: 'object',
});
jest.mock('@staticcms/core/components/Editor/EditorControlPane/EditorControl', () => {
jest.mock('@staticcms/core/components/editor/EditorControlPane/EditorControl', () => {
return jest.fn(props => {
const { parentPath, fieldName, field } = props;
return (