diff --git a/packages/slate-editor/src/extensions/placeholders/PlaceholdersExtension.tsx b/packages/slate-editor/src/extensions/placeholders/PlaceholdersExtension.tsx index 0ce56cde6..b43e5b2ef 100644 --- a/packages/slate-editor/src/extensions/placeholders/PlaceholdersExtension.tsx +++ b/packages/slate-editor/src/extensions/placeholders/PlaceholdersExtension.tsx @@ -35,80 +35,30 @@ export interface Parameters { format?: FrameProps['format']; removable?: RemovableFlagConfig; withAttachmentPlaceholders?: boolean; - withContactPlaceholders?: - | false - | Pick< - ContactPlaceholderElement.Props, - | 'getSuggestions' - | 'invalidateSuggestions' - | 'renderAddon' - | 'renderEmpty' - | 'renderSuggestion' - | 'renderSuggestionsFooter' - >; + withContactPlaceholders?: false | Pick; withCoveragePlaceholders?: | false - | Pick< - CoveragePlaceholderElement.Props, - | 'getSuggestions' - | 'invalidateSuggestions' - | 'renderEmpty' - | 'renderSuggestion' - | 'renderSuggestionsFooter' - | 'onCreateCoverage' - >; + | Pick; withEmbedPlaceholders?: false | { fetchOembed: FetchOEmbedFn }; withGalleryPlaceholders?: boolean | { withMediaGalleryTab: WithMediaGalleryTab }; withGalleryBookmarkPlaceholders?: | false - | Pick< - GalleryBookmarkPlaceholderElement.Props, - | 'fetchOembed' - | 'getSuggestions' - | 'invalidateSuggestions' - | 'renderAddon' - | 'renderEmpty' - | 'renderSuggestion' - | 'renderSuggestionsFooter' - >; + | Pick; withImagePlaceholders?: | boolean | { withCaptions: boolean; withMediaGalleryTab: WithMediaGalleryTab }; withInlineContactPlaceholders?: | false - | Pick< - InlineContactPlaceholderElement.Props, - | 'getSuggestions' - | 'invalidateSuggestions' - | 'renderEmpty' - | 'renderSuggestion' - | 'renderSuggestionsFooter' - >; + | Pick; withMediaPlaceholders?: | boolean | { withCaptions: boolean; withMediaGalleryTab: WithMediaGalleryTab }; withStoryBookmarkPlaceholders?: | false - | Pick< - StoryBookmarkPlaceholderElement.Props, - | 'getSuggestions' - | 'invalidateSuggestions' - | 'renderAddon' - | 'renderEmpty' - | 'renderSuggestion' - | 'renderSuggestionsFooter' - >; + | Pick; withStoryEmbedPlaceholders?: | false - | Pick< - StoryEmbedPlaceholderElement.Props, - | 'getSuggestions' - | 'invalidateSuggestions' - | 'renderAddon' - | 'renderEmpty' - | 'renderSuggestion' - | 'renderSuggestionsFooter' - >; + | Pick; withSocialPostPlaceholders?: false | { fetchOembed: FetchOEmbedFn }; withVideoPlaceholders?: false | { fetchOembed: FetchOEmbedFn }; withWebBookmarkPlaceholders?: false | { fetchOembed: FetchOEmbedFn }; diff --git a/packages/slate-editor/src/extensions/placeholders/components/PlaceholderElement.tsx b/packages/slate-editor/src/extensions/placeholders/components/PlaceholderElement.tsx index e3e229121..b7477684c 100644 --- a/packages/slate-editor/src/extensions/placeholders/components/PlaceholderElement.tsx +++ b/packages/slate-editor/src/extensions/placeholders/components/PlaceholderElement.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react'; import React, { type MouseEvent, useState } from 'react'; import { Transforms } from 'slate'; import { ReactEditor, type RenderElementProps, useSlateStatic } from 'slate-react'; @@ -14,7 +15,9 @@ import { type Props as BaseProps, Placeholder } from './Placeholder'; export type Props = RenderElementProps & Pick & { element: PlaceholderNode; + overflow?: 'visible' | 'hidden' | 'auto'; removable: RemovableFlagConfig; + renderFrame?: () => ReactNode; }; export function PlaceholderElement({ @@ -27,7 +30,9 @@ export function PlaceholderElement({ icon, title, description, + overflow, removable, + renderFrame, // Callbacks onClick, onDrop, @@ -63,29 +68,34 @@ export function PlaceholderElement({ ( - - )} + renderReadOnlyFrame={({ isSelected }) => + renderFrame ? ( + renderFrame() + ) : ( + + ) + } rounded void /> diff --git a/packages/slate-editor/src/extensions/placeholders/elements/ContactPlaceholderElement.stories.tsx b/packages/slate-editor/src/extensions/placeholders/elements/ContactPlaceholderElement.stories.tsx index c534cd844..73c612c28 100644 --- a/packages/slate-editor/src/extensions/placeholders/elements/ContactPlaceholderElement.stories.tsx +++ b/packages/slate-editor/src/extensions/placeholders/elements/ContactPlaceholderElement.stories.tsx @@ -1,14 +1,10 @@ -import type { ContactInfo } from '@prezly/slate-types'; import * as React from 'react'; import { createEditor as createSlateEditor } from 'slate'; import { type RenderElementProps, Slate } from 'slate-react'; -import { SearchInput } from '#components'; - import { PlaceholdersExtension } from '#extensions/placeholders'; import { createEditor } from '#modules/editor'; -import type { Suggestion } from '../../../components/SearchInput/types'; import { PlaceholderNode } from '../PlaceholderNode'; import { ContactPlaceholderElement } from './ContactPlaceholderElement'; @@ -28,42 +24,6 @@ const attributes: RenderElementProps['attributes'] = { ref: () => null, }; -function contact(props: Partial & Pick): ContactInfo { - return { - avatar_url: null, - company: '', - description: '', - address: '', - email: '', - facebook: '', - mobile: '', - phone: '', - twitter: '', - website: '', - ...props, - }; -} - -const suggestions: Suggestion[] = [ - { id: '00000000-00000000-00000000-00000001', value: contact({ name: 'Frodo Baggins' }) }, - { id: '00000000-00000000-00000000-00000002', value: contact({ name: 'Aragorn' }) }, - { id: '00000000-00000000-00000000-00000003', value: contact({ name: 'Samwise Gamgee' }) }, - { id: '00000000-00000000-00000000-00000004', value: contact({ name: 'Merry Brandybuck' }) }, - { id: '00000000-00000000-00000000-00000005', value: contact({ name: 'Legolas' }) }, - { id: '00000000-00000000-00000000-00000006', value: contact({ name: 'Pippin Took' }) }, - { id: '00000000-00000000-00000000-00000007', value: contact({ name: 'Gandalf' }) }, - { id: '00000000-00000000-00000000-00000008', value: contact({ name: 'Boromir' }) }, - { id: '00000000-00000000-00000000-00000009', value: contact({ name: 'Arwen' }) }, - { id: '00000000-00000000-00000000-00000010', value: contact({ name: 'Galadriel' }) }, -]; - -async function getSuggestions(query: string) { - await delay(500 + Math.random() * 500); - return suggestions.filter(({ value }) => - value.name.toLowerCase().includes(query.toLowerCase()), - ); -} - export default { title: 'Extensions/Placeholders/elements', decorators: [ @@ -82,55 +42,10 @@ export function ContactPlaceholder() { ( - -
-
- {suggestion.value.name - .split(/\s/g) - .slice(0, 2) - .map((word) => word.substring(0, 1)) - .join('')} -
-
- {suggestion.value.name} -
-
-
- )} - renderSuggestionsFooter={() => ( - - )} removable + renderPlaceholder={() => null} > {''}
); } - -function delay(ms: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} diff --git a/packages/slate-editor/src/extensions/placeholders/elements/ContactPlaceholderElement.tsx b/packages/slate-editor/src/extensions/placeholders/elements/ContactPlaceholderElement.tsx index e892f71f5..5726f279d 100644 --- a/packages/slate-editor/src/extensions/placeholders/elements/ContactPlaceholderElement.tsx +++ b/packages/slate-editor/src/extensions/placeholders/elements/ContactPlaceholderElement.tsx @@ -1,43 +1,36 @@ import type { NewsroomContact } from '@prezly/sdk'; import type { ContactInfo } from '@prezly/slate-types'; -import React from 'react'; +import type { ReactNode } from 'react'; +import React, { useEffect, useState } from 'react'; +import { Transforms } from 'slate'; import { useSelected, useSlateStatic } from 'slate-react'; -import { SearchInput } from '#components'; import { PlaceholderContact } from '#icons'; import { useFunction } from '#lib'; import { EventsEditor } from '#modules/events'; import { createContactNode } from '../../press-contacts'; -import type { Props as PlaceholderElementProps } from '../components/PlaceholderElement'; import { - type Props as BaseProps, - SearchInputPlaceholderElement, -} from '../components/SearchInputPlaceholderElement'; + PlaceholderElement, + type Props as PlaceholderElementProps, +} from '../components/PlaceholderElement'; +import { type Props as BaseProps } from '../components/SearchInputPlaceholderElement'; import { replacePlaceholder } from '../lib'; import type { PlaceholderNode } from '../PlaceholderNode'; -import { PlaceholdersManager, usePlaceholderManagement } from '../PlaceholdersManager'; export function ContactPlaceholderElement({ + attributes, children, element, format = 'card', - getSuggestions, removable, - renderAddon, - renderEmpty, - renderSuggestion, - renderSuggestionsFooter, - ...props + renderPlaceholder, }: ContactPlaceholderElement.Props) { + const [isCustomRendered, setCustomRendered] = useState(true); const editor = useSlateStatic(); const isSelected = useSelected(); - const handleTrigger = useFunction(() => { - PlaceholdersManager.activate(element); - }); - const handleSelect = useFunction((uuid: NewsroomContact['uuid'], contact: ContactInfo) => { EventsEditor.dispatchEvent(editor, 'contact-placeholder-submitted', { contact: { uuid }, @@ -48,61 +41,52 @@ export function ContactPlaceholderElement({ }); }); - usePlaceholderManagement(element.type, element.uuid, { - onTrigger: handleTrigger, + const handleRemove = useFunction(() => { + Transforms.removeNodes(editor, { at: [], match: (node) => node === element }); }); + useEffect(() => { + if (!isSelected) { + setCustomRendered(false); + } + }, [isSelected]); + return ( - - {...props} + ( - - {props.children} - - )} - inputTitle="Site contacts" - inputDescription="Add a contact card to your stories, campaigns and pitches" - inputPlaceholder="Search site contacts" - onSelect={handleSelect} + onClick={() => setCustomRendered(true)} + overflow="visible" removable={removable} + renderFrame={ + isCustomRendered + ? () => + renderPlaceholder({ + onRemove: removable ? handleRemove : undefined, + onSelect: handleSelect, + placeholder: element, + }) + : undefined + } > {children} - + ); } export namespace ContactPlaceholderElement { export interface Props - extends Omit< - BaseProps, - | 'onSelect' - | 'icon' - | 'title' - | 'description' - | 'inputTitle' - | 'inputDescription' - | 'inputPlaceholder' - | 'renderSuggestions' - >, + extends Pick, 'attributes' | 'children' | 'format'>, Pick { element: PlaceholderNode; - renderSuggestionsFooter?: BaseProps['renderSuggestions']; + renderPlaceholder: (props: { + onRemove: (() => void) | undefined; + onSelect: (uuid: string, contactInfo: ContactInfo) => void; + placeholder: PlaceholderNode; + }) => ReactNode; } } diff --git a/packages/slate-editor/src/extensions/placeholders/elements/CoveragePlaceholderElement.module.scss b/packages/slate-editor/src/extensions/placeholders/elements/CoveragePlaceholderElement.module.scss deleted file mode 100644 index 20dd6420a..000000000 --- a/packages/slate-editor/src/extensions/placeholders/elements/CoveragePlaceholderElement.module.scss +++ /dev/null @@ -1,9 +0,0 @@ -@import "styles/variables"; - -.action { - margin: $spacing-2 0 0; -} - -.button { - text-decoration: none; -} diff --git a/packages/slate-editor/src/extensions/placeholders/elements/CoveragePlaceholderElement.tsx b/packages/slate-editor/src/extensions/placeholders/elements/CoveragePlaceholderElement.tsx index 39402795f..8afbf04f1 100644 --- a/packages/slate-editor/src/extensions/placeholders/elements/CoveragePlaceholderElement.tsx +++ b/packages/slate-editor/src/extensions/placeholders/elements/CoveragePlaceholderElement.tsx @@ -1,71 +1,54 @@ +import type { ProgressPromise } from '@prezly/progress-promise'; import type { CoverageNode } from '@prezly/slate-types'; import type { PrezlyFileInfo } from '@prezly/uploadcare'; import { toProgressPromise, UploadcareFile } from '@prezly/uploadcare'; +import type { UploadInfo } from '@prezly/uploadcare-widget'; import uploadcare from '@prezly/uploadcare-widget'; -import type { ReactElement } from 'react'; +import type { ReactNode } from 'react'; import React, { type DragEvent, useEffect, useState } from 'react'; import { Transforms } from 'slate'; import { useSelected, useSlateStatic } from 'slate-react'; -import { Button, SearchInput } from '#components'; -import { PlaceholderCoverage, Upload } from '#icons'; -import { URL_WITH_OPTIONAL_PROTOCOL_REGEXP, useFunction } from '#lib'; +import { PlaceholderCoverage } from '#icons'; +import { useFunction } from '#lib'; import { createCoverage } from '#extensions/coverage'; import { EventsEditor } from '#modules/events'; -import { UploadcareEditor } from '#modules/uploadcare'; -import { InputPlaceholder } from '../components/InputPlaceholder'; import { withLoadingDots } from '../components/LoadingDots'; import { - type Props as BaseProps, - SearchInputPlaceholderElement, -} from '../components/SearchInputPlaceholderElement'; + PlaceholderElement, + type Props as PlaceholderElementProps, +} from '../components/PlaceholderElement'; +import { type Props as BaseProps } from '../components/SearchInputPlaceholderElement'; import { replacePlaceholder } from '../lib'; import type { PlaceholderNode } from '../PlaceholderNode'; import { PlaceholdersManager, usePlaceholderManagement } from '../PlaceholdersManager'; -import styles from './CoveragePlaceholderElement.module.scss'; - -type Url = string; type CoverageRef = Pick; -type Mode = 'search' | 'create'; -type ModeSetter = (mode: Mode) => void; export function CoveragePlaceholderElement({ + attributes, children, element, format = 'card', - getSuggestions, - renderEmpty, - renderSuggestion, - renderSuggestionsFooter, onCreateCoverage, - ...props + removable, + renderPlaceholder, }: CoveragePlaceholderElement.Props) { const editor = useSlateStatic(); const isSelected = useSelected(); - const [mode, setMode] = useState('search'); - const onMode = setMode; - - const handleTrigger = useFunction(() => { - PlaceholdersManager.activate(element); - }); - - const handleUpload = useFunction(async () => { - const files = await UploadcareEditor.upload(editor, { multiple: false }); - if (!files) { - return; - } + const [isCustomRendered, setCustomRendered] = useState(true); - setMode('search'); - const uploading = toProgressPromise(files[0]).then(async (fileInfo: PrezlyFileInfo) => { - const file = UploadcareFile.createFromUploadcareWidgetPayload(fileInfo); - const ref = await onCreateCoverage(file); + const handleUpload = useFunction( + (promise: Promise | ProgressPromise) => { + setCustomRendered(false); + PlaceholdersManager.register(element.type, element.uuid, promise); + }, + ); - return { coverage: { id: ref.coverage.id } }; - }); - PlaceholdersManager.register(element.type, element.uuid, uploading); + const handleDragOver = useFunction(() => { + setCustomRendered(false); }); const handleDrop = useFunction((event: DragEvent) => { @@ -96,138 +79,64 @@ export function CoveragePlaceholderElement({ }); }); - const handleSubmitUrl = useFunction((input: string) => { - setMode('search'); - - const loading = (async () => { - const ref = await onCreateCoverage(input); - return { coverage: { id: ref.coverage.id } }; - })(); - - PlaceholdersManager.register(element.type, element.uuid, loading); - }); - - const handleDragOver = useFunction(() => { - setMode('search'); - }); - const handleRemove = useFunction(() => { Transforms.removeNodes(editor, { at: [], match: (node) => node === element }); }); - const { isActive } = usePlaceholderManagement(element.type, element.uuid, { + usePlaceholderManagement(element.type, element.uuid, { onResolve: handleSelect, - onTrigger: handleTrigger, }); useEffect(() => { - if (!isActive && mode === 'create') { - setMode('search'); + if (!isSelected) { + setCustomRendered(false); } - }, [isActive]); + }, [isSelected]); return ( - - {...props} + renderEmpty?.({ ...props, onMode }) ?? null} - renderSuggestion={(props) => renderSuggestion?.({ ...props, onMode }) ?? null} - renderSuggestions={(props) => ( - - {props.children} - - )} - renderFrame={() => - mode === 'create' ? ( - PlaceholdersManager.deactivate(element)} - onRemove={handleRemove} - onSubmit={handleSubmitUrl} - > -
- -
-
- ) : undefined - } - inputTitle="Coverage" - inputDescription="Select coverage to insert" - inputPlaceholder="Search for coverage" + onClick={() => setCustomRendered(true)} onDrop={handleDrop} - onSelect={(_, entry) => handleSelect(entry)} + removable={removable} + renderFrame={ + isCustomRendered + ? () => + renderPlaceholder({ + onDragOver: handleDragOver, + onRemove: removable ? handleRemove : undefined, + onSelect: handleSelect, + onUpload: handleUpload, + placeholder: element, + }) + : undefined + } > {children} - +
); } export namespace CoveragePlaceholderElement { export interface Props - extends Omit< - BaseProps, - | 'onSelect' - | 'icon' - | 'title' - | 'description' - | 'inputTitle' - | 'inputDescription' - | 'inputPlaceholder' - | 'renderEmpty' - | 'renderSuggestion' - | 'renderSuggestions' - > { + extends Pick, 'attributes' | 'children' | 'format'>, + Pick { element: PlaceholderNode; - - // SearchInput - renderEmpty?: ( - props: SearchInput.Props.Empty & { placeholder: PlaceholderNode; onMode: ModeSetter }, - ) => ReactElement | null; - - renderSuggestion?: ( - props: SearchInput.Props.Option & { - placeholder: PlaceholderNode; - onMode: ModeSetter; - }, - ) => ReactElement | null; - - renderSuggestionsFooter?: ( - props: SearchInput.Props.Suggestions & { - placeholder: PlaceholderNode; - onMode: ModeSetter; - }, - ) => ReactElement | null; - - onCreateCoverage(input: Url | UploadcareFile): Promise; + onCreateCoverage(input: UploadcareFile): Promise; + renderPlaceholder: (props: { + onDragOver: () => void; + onRemove: (() => void) | undefined; + onSelect: (data: CoverageRef) => void; + onUpload: ( + promise: Promise | ProgressPromise, + ) => void; + placeholder: PlaceholderNode; + }) => ReactNode; } } diff --git a/packages/slate-editor/src/extensions/placeholders/elements/GalleryBookmarkPlaceholderElement.tsx b/packages/slate-editor/src/extensions/placeholders/elements/GalleryBookmarkPlaceholderElement.tsx index dddbe8ac6..9da245526 100644 --- a/packages/slate-editor/src/extensions/placeholders/elements/GalleryBookmarkPlaceholderElement.tsx +++ b/packages/slate-editor/src/extensions/placeholders/elements/GalleryBookmarkPlaceholderElement.tsx @@ -1,57 +1,48 @@ -import type { NewsroomGallery } from '@prezly/sdk'; +import type { NewsroomGallery, OEmbedInfo } from '@prezly/sdk'; import type { BookmarkNode } from '@prezly/slate-types'; -import React from 'react'; +import type { ReactNode } from 'react'; +import React, { useEffect, useState } from 'react'; +import { Transforms } from 'slate'; import { useSelected, useSlateStatic } from 'slate-react'; -import { SearchInput } from '#components'; import { PlaceholderGallery } from '#icons'; import { useFunction } from '#lib'; import { EventsEditor } from '#modules/events'; import { createGalleryBookmark } from '../../gallery-bookmark'; -import type { Props as PlaceholderElementProps } from '../components/PlaceholderElement'; import { - type Props as BaseProps, - SearchInputPlaceholderElement, -} from '../components/SearchInputPlaceholderElement'; + PlaceholderElement, + type Props as PlaceholderElementProps, +} from '../components/PlaceholderElement'; +import { type Props as BaseProps } from '../components/SearchInputPlaceholderElement'; import { replacePlaceholder } from '../lib'; import type { PlaceholderNode } from '../PlaceholderNode'; import { PlaceholdersManager, usePlaceholderManagement } from '../PlaceholdersManager'; -import type { FetchOEmbedFn } from '../types'; export function GalleryBookmarkPlaceholderElement({ + attributes, children, element, - fetchOembed, format = 'card', - getSuggestions, removable, - renderAddon, - renderEmpty, - renderSuggestion, - renderSuggestionsFooter, - ...props + renderPlaceholder, }: GalleryBookmarkPlaceholderElement.Props) { + const [isCustomRendered, setCustomRendered] = useState(true); const editor = useSlateStatic(); const isSelected = useSelected(); - const handleTrigger = useFunction(() => { - PlaceholdersManager.activate(element); - }); - - const handleSelect = useFunction(async (uuid: string, { url }: NewsroomGallery) => { - EventsEditor.dispatchEvent(editor, 'gallery-bookmark-placeholder-submitted', { - gallery: { uuid }, - }); + const handleSelect = useFunction( + (promise: Promise<{ oembed?: BookmarkNode['oembed']; url: BookmarkNode['url'] }>) => { + setCustomRendered(false); - const loading = fetchOembed(url).then( - (oembed) => ({ oembed, url }), - () => ({ url }), // `oembed` is undefined if an error occurred - ); + PlaceholdersManager.register(element.type, element.uuid, promise); + PlaceholdersManager.deactivateAll(); + }, + ); - PlaceholdersManager.register(element.type, element.uuid, loading); - PlaceholdersManager.deactivateAll(); + const handleRemove = useFunction(() => { + Transforms.removeNodes(editor, { at: [], match: (node) => node === element }); }); const handleData = useFunction( @@ -71,62 +62,49 @@ export function GalleryBookmarkPlaceholderElement({ ); usePlaceholderManagement(element.type, element.uuid, { - onTrigger: handleTrigger, onResolve: handleData, }); + useEffect(() => { + if (!isSelected) { + setCustomRendered(false); + } + }, [isSelected]); + return ( - - {...props} + ( - - {props.children} - - )} - inputTitle="Media gallery bookmark" - inputDescription="Add a media gallery card to your stories, campaigns and pitches" - inputPlaceholder="Search for media galleries" - onSelect={handleSelect} + onClick={() => setCustomRendered(true)} + overflow="visible" + renderFrame={ + isCustomRendered + ? () => + renderPlaceholder({ + onRemove: removable ? handleRemove : undefined, + onSelect: handleSelect, + }) + : undefined + } removable={removable} > {children} - + ); } export namespace GalleryBookmarkPlaceholderElement { export interface Props - extends Omit< - BaseProps, - | 'onSelect' - | 'icon' - | 'title' - | 'description' - | 'inputTitle' - | 'inputDescription' - | 'inputPlaceholder' - | 'renderSuggestions' - >, + extends Pick, 'attributes' | 'children' | 'format'>, Pick { element: PlaceholderNode; - fetchOembed: FetchOEmbedFn; - renderSuggestionsFooter?: BaseProps['renderSuggestions']; + renderPlaceholder: (props: { + onRemove: (() => void) | undefined; + onSelect: (promise: Promise<{ oembed?: OEmbedInfo; url: string }>) => void; + }) => ReactNode; } } diff --git a/packages/slate-editor/src/extensions/placeholders/elements/InlineContactPlaceholderElement/InlineContactPlaceholderElement.tsx b/packages/slate-editor/src/extensions/placeholders/elements/InlineContactPlaceholderElement/InlineContactPlaceholderElement.tsx index bdd2af456..3dc9e0b0a 100644 --- a/packages/slate-editor/src/extensions/placeholders/elements/InlineContactPlaceholderElement/InlineContactPlaceholderElement.tsx +++ b/packages/slate-editor/src/extensions/placeholders/elements/InlineContactPlaceholderElement/InlineContactPlaceholderElement.tsx @@ -1,24 +1,23 @@ import type { ContactInfo } from '@prezly/slate-types'; -import type { ReactElement } from 'react'; -import { useState } from 'react'; +import type { ReactNode } from 'react'; +import { useEffect, useState } from 'react'; import React from 'react'; +import { Transforms } from 'slate'; import { useSelected, useSlateStatic } from 'slate-react'; -import { SearchInput } from '#components'; import { PlaceholderContact } from '#icons'; import { useFunction } from '#lib'; import { InlineContactForm } from '#modules/components'; import { createContactNode } from '../../../press-contacts'; -import type { Props as PlaceholderElementProps } from '../../components/PlaceholderElement'; import { - type Props as BaseProps, - SearchInputPlaceholderElement, -} from '../../components/SearchInputPlaceholderElement'; + PlaceholderElement, + type Props as PlaceholderElementProps, +} from '../../components/PlaceholderElement'; +import { type Props as BaseProps } from '../../components/SearchInputPlaceholderElement'; import { replacePlaceholder } from '../../lib'; import type { PlaceholderNode } from '../../PlaceholderNode'; -import { PlaceholdersManager, usePlaceholderManagement } from '../../PlaceholdersManager'; import { FormFrame } from './FormFrame'; @@ -28,114 +27,90 @@ enum Mode { } export function InlineContactPlaceholderElement({ + attributes, children, element, format = 'card', - getSuggestions, removable, - renderEmpty, - renderSuggestion, - renderSuggestionsFooter, - ...props + renderPlaceholder, }: InlineContactPlaceholderElement.Props) { + const [isCustomRendered, setCustomRendered] = useState(true); const editor = useSlateStatic(); const isSelected = useSelected(); const [mode, setMode] = useState(Mode.SEARCH); const [contact, setContact] = useState(null); - const handleTrigger = useFunction(() => { - PlaceholdersManager.activate(element); - }); - const handleSelect = useFunction((contact: ContactInfo | null) => { setMode(Mode.FORM); setContact(contact); }); + const handleRemove = useFunction(() => { + Transforms.removeNodes(editor, { at: [], match: (node) => node === element }); + }); + const handleSubmit = useFunction((contact: ContactInfo) => { replacePlaceholder(editor, element, createContactNode({ contact })); }); - usePlaceholderManagement(element.type, element.uuid, { - onTrigger: handleTrigger, + const renderFormFrame = useFunction(() => ( + + setMode(Mode.SEARCH)} + onSubmit={handleSubmit} + /> + + )); + + const renderFrame = useFunction(() => { + if (mode === Mode.FORM) { + return () => renderFormFrame(); + } + + if (isCustomRendered) { + return () => + renderPlaceholder({ + onRemove: removable ? handleRemove : undefined, + onSelect: handleSelect, + }); + } + + return undefined; }); + useEffect(() => { + if (!isSelected) { + setCustomRendered(false); + } + }, [isSelected]); + return ( - - {...props} + renderEmpty({ ...props, onCreate: () => handleSelect(null) })} - renderFrame={ - mode === Mode.FORM - ? () => ( - - setMode(Mode.SEARCH)} - onSubmit={handleSubmit} - /> - - ) - : undefined - } - renderSuggestion={ - renderSuggestion - ? (props) => - renderSuggestion({ - ...props, - onSelect: () => handleSelect(props.suggestion.value), - }) - : undefined - } - renderSuggestions={(props) => ( - - {props.children} - - )} - inputTitle="Contact" - inputDescription="Select a contact to insert or create a new one" - inputPlaceholder="Search for contacts" - onSelect={(_, contact) => handleSelect(contact)} + onClick={() => setCustomRendered(true)} removable={removable} + renderFrame={renderFrame()} > {children} - + ); } export namespace InlineContactPlaceholderElement { export interface Props - extends Omit< - BaseProps, - | 'onSelect' - | 'icon' - | 'title' - | 'description' - | 'inputTitle' - | 'inputDescription' - | 'inputPlaceholder' - | 'renderEmpty' - | 'renderSuggestions' - >, + extends Pick, 'attributes' | 'children' | 'format'>, Pick { element: PlaceholderNode; - renderEmpty: ( - props: SearchInput.Props.Empty & { onCreate: () => void }, - ) => ReactElement | null; - renderSuggestionsFooter?: BaseProps['renderSuggestions']; + renderPlaceholder: (props: { + onRemove: (() => void) | undefined; + onSelect: (contactInfo: ContactInfo) => void; + }) => ReactNode; } } diff --git a/packages/slate-editor/src/extensions/placeholders/elements/StoryBookmarkPlaceholderElement.stories.tsx b/packages/slate-editor/src/extensions/placeholders/elements/StoryBookmarkPlaceholderElement.stories.tsx index b9448397f..5cecfb741 100644 --- a/packages/slate-editor/src/extensions/placeholders/elements/StoryBookmarkPlaceholderElement.stories.tsx +++ b/packages/slate-editor/src/extensions/placeholders/elements/StoryBookmarkPlaceholderElement.stories.tsx @@ -1,16 +1,10 @@ -import type { CultureRef, NewsroomRef, StoryRef } from '@prezly/sdk'; -import { Culture, Newsroom, Story } from '@prezly/sdk'; import * as React from 'react'; import { createEditor as createSlateEditor } from 'slate'; import { type RenderElementProps, Slate } from 'slate-react'; -import * as uuid from 'uuid'; - -import { SearchInput } from '#components'; import { PlaceholdersExtension } from '#extensions/placeholders'; import { createEditor } from '#modules/editor'; -import type { Suggestion } from '../../../components/SearchInput/types'; import { PlaceholderNode } from '../PlaceholderNode'; import { StoryBookmarkPlaceholderElement } from './StoryBookmarkPlaceholderElement'; @@ -30,180 +24,6 @@ const attributes: RenderElementProps['attributes'] = { ref: () => null, }; -let lastStoryId = 0; -let lastNewsroomId = 0; - -const EN: CultureRef = { - name: 'English', - code: 'en', - locale: 'en', - direction: Culture.TextDirection.LTR, - native_name: 'English', - language_code: 'en', -}; - -const THUMBNAIL = [ - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-White.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Circus.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Mosque.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-FairyLights.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Goemetry.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Comb.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Floral.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Stripes.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Map.jpg', -]; - -const NEWSROOM = [ - { name: 'Apple' }, - { name: 'Banana' }, - { name: 'Cinnamon' }, - { name: 'Durian' }, - { name: 'Elderberry' }, -]; - -function newsroom(): NewsroomRef { - const id = ++lastNewsroomId; - return { - uuid: uuid.v4(), - /** - * @deprecated Please use `uuid` instead. - * @see uuid - */ - id, - display_name: NEWSROOM[id % NEWSROOM.length].name, - thumbnail_url: THUMBNAIL[(id + 5) % THUMBNAIL.length], - name: NEWSROOM[id % NEWSROOM.length].name, - subdomain: NEWSROOM[id % NEWSROOM.length].name.toLowerCase(), - status: Newsroom.Status.ACTIVE, - is_active: true, - is_archived: false, - is_multilingual: true, - is_indexable: true, - timezone: 'Europe/Brussels', - time_format: 'HH:mm', - date_format: 'DD.MM.YYYY', - is_offline: false, - is_online: true, - is_demo: false, - url: NEWSROOM[id % NEWSROOM.length].name.toLowerCase() + '.prezly.com', - /** - * @deprecated Do not rely on these. - */ - links: { - media_gallery_api: '', - analytics_and_visibility_settings: '', - categories_management: '', - company_info_settings: '', - contacts_management: '', - domain_settings: '', - edit: '', - gallery_management: '', - hub_settings: '', - languages: '', - look_and_feel_settings: '', - manual_subscription_management: '', - privacy_settings: '', - widget_settings: '', - }, - }; -} - -function story(props: Partial & Pick): StoryRef { - const { uuid, title, ...rest } = props; - const id = ++lastStoryId; - return { - uuid, - title, - /** - * @deprecated Please use `uuid` as an identifier instead. - * @see uuid - */ - id: lastStoryId++, - slug: title.toLowerCase(), - status: Story.Status.PUBLISHED, - lifecycle_status: Story.Status.PUBLISHED, - publication_status: Story.PublicationStatus.PUBLISHED, - visibility: Story.Visibility.PUBLIC, - thumbnail_url: THUMBNAIL[id % THUMBNAIL.length], - created_at: '2023-06-29T13:03:00Z', - updated_at: '2023-06-29T13:03:00Z', - published_at: '2023-06-29T13:03:00Z', - scheduled_at: null, - culture: EN, - author: null, - newsroom: newsroom(), - oembed: { - type: 'link', - version: '1.0', - url: '', - }, - links: { - edit: '', - newsroom_view: '', - report: '', - }, - - ...rest, - } as StoryRef; -} - -function suggestion( - props: Partial & Pick, -): Suggestion { - return { id: props.uuid, value: story(props) }; -} - -const suggestions: Suggestion[] = [ - suggestion({ - uuid: '00000000-00000000-00000000-00000001', - title: 'The "Made with Prezly" badge', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000002', - title: 'The new side navigation layout', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000003', - title: 'Story headers, titles and subtitles are now one with the editor', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000004', - title: 'Introducing: Improved Campaign click reporting', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000005', - title: 'Improvement: Site contacts and email signatures', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000006', - title: 'Prezly themes got an upgrade', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000007', - title: 'Recent improvements & fixes', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000008', - title: 'Introducing the revamped Contact preview', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000009', - title: 'In beta: The Site activity dashboard', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000010', - title: 'Revamping billing: A new Plans page & a self-upgrade option', - }), -]; - -async function getSuggestions(query: string) { - await delay(500 + Math.random() * 500); - return suggestions.filter(({ value }) => - value.title.toLowerCase().includes(query.toLowerCase()), - ); -} - export default { title: 'Extensions/Placeholders/elements', decorators: [ @@ -222,50 +42,10 @@ export function StoryBookmarkPlaceholder() { ( - -
- -
- {suggestion.value.title} -
-
-
- )} - renderSuggestionsFooter={() => ( - - )} + renderPlaceholder={() => null} removable > {''}
); } - -function delay(ms: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} diff --git a/packages/slate-editor/src/extensions/placeholders/elements/StoryBookmarkPlaceholderElement.tsx b/packages/slate-editor/src/extensions/placeholders/elements/StoryBookmarkPlaceholderElement.tsx index b28bc312e..6cc6a9e01 100644 --- a/packages/slate-editor/src/extensions/placeholders/elements/StoryBookmarkPlaceholderElement.tsx +++ b/packages/slate-editor/src/extensions/placeholders/elements/StoryBookmarkPlaceholderElement.tsx @@ -1,42 +1,35 @@ import type { StoryRef } from '@prezly/sdk'; -import React from 'react'; +import type { ReactNode } from 'react'; +import React, { useEffect, useState } from 'react'; +import { Transforms } from 'slate'; import { useSelected, useSlateStatic } from 'slate-react'; -import { SearchInput } from '#components'; import { PlaceholderStory } from '#icons'; import { useFunction } from '#lib'; import { EventsEditor } from '#modules/events'; import { createStoryBookmark } from '../../story-bookmark'; -import type { Props as PlaceholderElementProps } from '../components/PlaceholderElement'; import { - type Props as BaseProps, - SearchInputPlaceholderElement, -} from '../components/SearchInputPlaceholderElement'; + PlaceholderElement, + type Props as PlaceholderElementProps, +} from '../components/PlaceholderElement'; +import { type Props as BaseProps } from '../components/SearchInputPlaceholderElement'; import { replacePlaceholder } from '../lib'; import type { PlaceholderNode } from '../PlaceholderNode'; -import { PlaceholdersManager, usePlaceholderManagement } from '../PlaceholdersManager'; export function StoryBookmarkPlaceholderElement({ + attributes, children, element, format = 'card', - getSuggestions, removable, - renderAddon, - renderEmpty, - renderSuggestion, - renderSuggestionsFooter, - ...props + renderPlaceholder, }: StoryBookmarkPlaceholderElement.Props) { + const [isCustomRendered, setCustomRendered] = useState(true); const editor = useSlateStatic(); const isSelected = useSelected(); - const handleTrigger = useFunction(() => { - PlaceholdersManager.activate(element); - }); - const handleSelect = useFunction((uuid: StoryRef['uuid']) => { EventsEditor.dispatchEvent(editor, 'story-bookmark-placeholder-submitted', { story: { uuid }, @@ -47,61 +40,50 @@ export function StoryBookmarkPlaceholderElement({ }); }); - usePlaceholderManagement(element.type, element.uuid, { - onTrigger: handleTrigger, + const handleRemove = useFunction(() => { + Transforms.removeNodes(editor, { at: [], match: (node) => node === element }); }); + useEffect(() => { + if (!isSelected) { + setCustomRendered(false); + } + }, [isSelected]); + return ( - - {...props} + ( - - {props.children} - - )} - inputTitle="Story bookmark" - inputDescription="Add a story card to your stories, campaigns and pitches" - inputPlaceholder="Search Prezly stories" - onSelect={handleSelect} + onClick={() => setCustomRendered(true)} + overflow="visible" + renderFrame={ + isCustomRendered + ? () => + renderPlaceholder({ + onRemove: removable ? handleRemove : undefined, + onSelect: handleSelect, + }) + : undefined + } removable={removable} > {children} - + ); } export namespace StoryBookmarkPlaceholderElement { export interface Props - extends Omit< - BaseProps, - | 'onSelect' - | 'icon' - | 'title' - | 'description' - | 'inputTitle' - | 'inputDescription' - | 'inputPlaceholder' - | 'renderSuggestions' - >, + extends Pick, 'attributes' | 'children' | 'format'>, Pick { element: PlaceholderNode; - renderSuggestionsFooter?: BaseProps['renderSuggestions']; + renderPlaceholder: (props: { + onRemove: (() => void) | undefined; + onSelect: (uuid: string) => void; + }) => ReactNode; } } diff --git a/packages/slate-editor/src/extensions/placeholders/elements/StoryEmbedPlaceholderElement.stories.tsx b/packages/slate-editor/src/extensions/placeholders/elements/StoryEmbedPlaceholderElement.stories.tsx index 7bba65aa0..9a283410f 100644 --- a/packages/slate-editor/src/extensions/placeholders/elements/StoryEmbedPlaceholderElement.stories.tsx +++ b/packages/slate-editor/src/extensions/placeholders/elements/StoryEmbedPlaceholderElement.stories.tsx @@ -1,16 +1,10 @@ -import type { CultureRef, NewsroomRef, StoryRef } from '@prezly/sdk'; -import { Culture, Newsroom, Story } from '@prezly/sdk'; import * as React from 'react'; import { createEditor as createSlateEditor } from 'slate'; import { type RenderElementProps, Slate } from 'slate-react'; -import * as uuid from 'uuid'; - -import { SearchInput } from '#components'; import { PlaceholdersExtension } from '#extensions/placeholders'; import { createEditor } from '#modules/editor'; -import type { Suggestion } from '../../../components/SearchInput/types'; import { PlaceholderNode } from '../PlaceholderNode'; import { StoryEmbedPlaceholderElement } from './StoryEmbedPlaceholderElement'; @@ -30,180 +24,6 @@ const attributes: RenderElementProps['attributes'] = { ref: () => null, }; -let lastStoryId = 0; -let lastNewsroomId = 0; - -const EN: CultureRef = { - name: 'English', - code: 'en', - locale: 'en', - direction: Culture.TextDirection.LTR, - native_name: 'English', - language_code: 'en', -}; - -const THUMBNAIL = [ - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-White.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Circus.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Mosque.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-FairyLights.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Goemetry.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Comb.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Floral.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Stripes.jpg', - 'https://img.dummy-image-generator.com/abstract/dummy-48x48-Map.jpg', -]; - -const NEWSROOM = [ - { name: 'Apple' }, - { name: 'Banana' }, - { name: 'Cinnamon' }, - { name: 'Durian' }, - { name: 'Elderberry' }, -]; - -function newsroom(): NewsroomRef { - const id = ++lastNewsroomId; - return { - uuid: uuid.v4(), - /** - * @deprecated Please use `uuid` instead. - * @see uuid - */ - id, - display_name: NEWSROOM[id % NEWSROOM.length].name, - thumbnail_url: THUMBNAIL[(id + 5) % THUMBNAIL.length], - name: NEWSROOM[id % NEWSROOM.length].name, - subdomain: NEWSROOM[id % NEWSROOM.length].name.toLowerCase(), - status: Newsroom.Status.ACTIVE, - is_active: true, - is_archived: false, - is_multilingual: true, - is_indexable: true, - timezone: 'Europe/Brussels', - time_format: 'HH:mm', - date_format: 'DD.MM.YYYY', - is_offline: false, - is_online: true, - is_demo: false, - url: NEWSROOM[id % NEWSROOM.length].name.toLowerCase() + '.prezly.com', - /** - * @deprecated Do not rely on these. - */ - links: { - media_gallery_api: '', - analytics_and_visibility_settings: '', - categories_management: '', - company_info_settings: '', - contacts_management: '', - domain_settings: '', - edit: '', - gallery_management: '', - hub_settings: '', - languages: '', - look_and_feel_settings: '', - manual_subscription_management: '', - privacy_settings: '', - widget_settings: '', - }, - }; -} - -function story(props: Partial & Pick): StoryRef { - const { uuid, title, ...rest } = props; - const id = ++lastStoryId; - return { - uuid, - title, - /** - * @deprecated Please use `uuid` as an identifier instead. - * @see uuid - */ - id: lastStoryId++, - slug: title.toLowerCase(), - status: Story.Status.PUBLISHED, - lifecycle_status: Story.Status.PUBLISHED, - publication_status: Story.PublicationStatus.PUBLISHED, - visibility: Story.Visibility.PUBLIC, - thumbnail_url: THUMBNAIL[id % THUMBNAIL.length], - created_at: '2023-06-29T13:03:00Z', - updated_at: '2023-06-29T13:03:00Z', - published_at: '2023-06-29T13:03:00Z', - scheduled_at: null, - culture: EN, - author: null, - newsroom: newsroom(), - oembed: { - type: 'link', - version: '1.0', - url: '', - }, - links: { - edit: '', - newsroom_view: '', - report: '', - }, - - ...rest, - } as StoryRef; -} - -function suggestion( - props: Partial & Pick, -): Suggestion { - return { id: props.uuid, value: story(props) }; -} - -const suggestions: Suggestion[] = [ - suggestion({ - uuid: '00000000-00000000-00000000-00000001', - title: 'The "Made with Prezly" badge', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000002', - title: 'The new side navigation layout', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000003', - title: 'Story headers, titles and subtitles are now one with the editor', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000004', - title: 'Introducing: Improved Campaign click reporting', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000005', - title: 'Improvement: Site contacts and email signatures', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000006', - title: 'Prezly themes got an upgrade', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000007', - title: 'Recent improvements & fixes', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000008', - title: 'Introducing the revamped Contact preview', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000009', - title: 'In beta: The Site activity dashboard', - }), - suggestion({ - uuid: '00000000-00000000-00000000-00000010', - title: 'Revamping billing: A new Plans page & a self-upgrade option', - }), -]; - -async function getSuggestions(query: string) { - await delay(500 + Math.random() * 500); - return suggestions.filter(({ value }) => - value.title.toLowerCase().includes(query.toLowerCase()), - ); -} - export default { title: 'Extensions/Placeholders/elements', decorators: [ @@ -222,50 +42,10 @@ export function StoryEmbedPlaceholder() { ( - -
- -
- {suggestion.value.title} -
-
-
- )} - renderSuggestionsFooter={() => ( - - )} removable + renderPlaceholder={() => null} > {''}
); } - -function delay(ms: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} diff --git a/packages/slate-editor/src/extensions/placeholders/elements/StoryEmbedPlaceholderElement.tsx b/packages/slate-editor/src/extensions/placeholders/elements/StoryEmbedPlaceholderElement.tsx index d94d13ccf..0cb68fbcc 100644 --- a/packages/slate-editor/src/extensions/placeholders/elements/StoryEmbedPlaceholderElement.tsx +++ b/packages/slate-editor/src/extensions/placeholders/elements/StoryEmbedPlaceholderElement.tsx @@ -1,42 +1,35 @@ import type { StoryRef } from '@prezly/sdk'; -import React from 'react'; +import type { ReactNode } from 'react'; +import React, { useEffect, useState } from 'react'; +import { Transforms } from 'slate'; import { useSelected, useSlateStatic } from 'slate-react'; -import { SearchInput } from '#components'; import { PlaceholderStory } from '#icons'; import { useFunction } from '#lib'; import { createStoryEmbed } from '#extensions/story-embed'; import { EventsEditor } from '#modules/events'; -import type { Props as PlaceholderElementProps } from '../components/PlaceholderElement'; import { - type Props as BaseProps, - SearchInputPlaceholderElement, -} from '../components/SearchInputPlaceholderElement'; + PlaceholderElement, + type Props as PlaceholderElementProps, +} from '../components/PlaceholderElement'; +import { type Props as BaseProps } from '../components/SearchInputPlaceholderElement'; import { replacePlaceholder } from '../lib'; import type { PlaceholderNode } from '../PlaceholderNode'; -import { PlaceholdersManager, usePlaceholderManagement } from '../PlaceholdersManager'; export function StoryEmbedPlaceholderElement({ + attributes, children, element, format = 'card', - getSuggestions, removable, - renderAddon, - renderEmpty, - renderSuggestion, - renderSuggestionsFooter, - ...props + renderPlaceholder, }: StoryEmbedPlaceholderElement.Props) { + const [isCustomRendered, setCustomRendered] = useState(true); const editor = useSlateStatic(); const isSelected = useSelected(); - const handleTrigger = useFunction(() => { - PlaceholdersManager.activate(element); - }); - const handleSelect = useFunction((uuid: StoryRef['uuid']) => { EventsEditor.dispatchEvent(editor, 'story-embed-placeholder-submitted', { story: { uuid }, @@ -47,61 +40,50 @@ export function StoryEmbedPlaceholderElement({ }); }); - usePlaceholderManagement(element.type, element.uuid, { - onTrigger: handleTrigger, + const handleRemove = useFunction(() => { + Transforms.removeNodes(editor, { at: [], match: (node) => node === element }); }); + useEffect(() => { + if (!isSelected) { + setCustomRendered(false); + } + }, [isSelected]); + return ( - - {...props} + ( - - {props.children} - - )} - inputTitle="Story embed" - inputDescription="Add a story to your stories, campaigns and pitches" - inputPlaceholder="Search Prezly stories" - onSelect={handleSelect} + onClick={() => setCustomRendered(true)} + overflow="visible" + renderFrame={ + isCustomRendered + ? () => + renderPlaceholder({ + onRemove: removable ? handleRemove : undefined, + onSelect: handleSelect, + }) + : undefined + } removable={removable} > {children} - + ); } export namespace StoryEmbedPlaceholderElement { export interface Props - extends Omit< - BaseProps, - | 'onSelect' - | 'icon' - | 'title' - | 'description' - | 'inputTitle' - | 'inputDescription' - | 'inputPlaceholder' - | 'renderSuggestions' - >, + extends Pick, 'attributes' | 'children' | 'format'>, Pick { element: PlaceholderNode; - renderSuggestionsFooter?: BaseProps['renderSuggestions']; + renderPlaceholder: (props: { + onRemove: (() => void) | undefined; + onSelect: (uuid: string) => void; + }) => ReactNode; } } diff --git a/packages/slate-editor/src/modules/editor/test-utils.ts b/packages/slate-editor/src/modules/editor/test-utils.ts index a57ea9bf0..773fa866c 100644 --- a/packages/slate-editor/src/modules/editor/test-utils.ts +++ b/packages/slate-editor/src/modules/editor/test-utils.ts @@ -31,12 +31,9 @@ export function getAllExtensions() { withCoverage: { dateFormat: 'YYYY/MM/DD', fetchCoverage: createDelayedResolve(coverage), - getSuggestions: () => [], - renderEmpty: () => null, - renderSuggestion: () => null, - renderSuggestionsFooter: () => null, onCreateCoverage: createDelayedResolve({ coverage }), onEdit: () => {}, + renderPlaceholder: () => null, withLayoutOptions: true, }, withCustomNormalization: false, @@ -49,8 +46,7 @@ export function getAllExtensions() { withFloatingAddMenu: true, withGalleries: {}, withGalleryBookmarks: { - fetchOembed, - getSuggestions: () => [], + renderPlaceholder: () => null, }, withHeadings: true, withImages: { @@ -62,21 +58,18 @@ export function getAllExtensions() { withLists: true, withPlaceholders: {}, withPressContacts: { - getSuggestions: () => [], onEdit: () => {}, - renderEmpty: () => null, - renderSuggestion: () => null, - renderSuggestionsFooter: () => null, + renderPlaceholder: () => null, }, withStoryBookmarks: { loadStory: () => Promise.reject(), generateEditUrl: (story) => `/stories/${story.id}/edit`, generatePreviewUrl: (story) => `/stories/${story.id}/preview`, - getSuggestions: () => [], + renderPlaceholder: () => null, }, withStoryEmbeds: { render: () => null, - getSuggestions: () => [], + renderPlaceholder: () => null, }, withTextStyling: true, withTables: true, diff --git a/packages/slate-editor/src/modules/events/types.ts b/packages/slate-editor/src/modules/events/types.ts index 08df06555..1e1e3db71 100644 --- a/packages/slate-editor/src/modules/events/types.ts +++ b/packages/slate-editor/src/modules/events/types.ts @@ -1,11 +1,5 @@ import type { Listener } from '@prezly/events'; -import type { - CoverageEntry, - NewsroomContact, - NewsroomGallery, - OEmbedInfo, - Story, -} from '@prezly/sdk'; +import type { CoverageEntry, NewsroomContact, OEmbedInfo, Story } from '@prezly/sdk'; import type { GalleryImageSize, GalleryLayout, @@ -110,9 +104,6 @@ export type EditorEventMap = { 'gallery-layout-changed': { layout: GalleryLayout; }; - 'gallery-bookmark-placeholder-submitted': { - gallery: Pick; - }; 'image-added': { description: string; isPasted: boolean;