Skip to content

Commit ffea45c

Browse files
authored
Merge pull request #574 from prezly/feature/dev-13852-refactor-gallerybookmarkplaceholderelement
[DEV-13852] Refactoring - Allow custom rendering of placeholders
2 parents 5f22370 + 0df51e0 commit ffea45c

14 files changed

+313
-1093
lines changed

packages/slate-editor/src/extensions/placeholders/PlaceholdersExtension.tsx

+6-56
Original file line numberDiff line numberDiff line change
@@ -35,80 +35,30 @@ export interface Parameters {
3535
format?: FrameProps['format'];
3636
removable?: RemovableFlagConfig;
3737
withAttachmentPlaceholders?: boolean;
38-
withContactPlaceholders?:
39-
| false
40-
| Pick<
41-
ContactPlaceholderElement.Props,
42-
| 'getSuggestions'
43-
| 'invalidateSuggestions'
44-
| 'renderAddon'
45-
| 'renderEmpty'
46-
| 'renderSuggestion'
47-
| 'renderSuggestionsFooter'
48-
>;
38+
withContactPlaceholders?: false | Pick<ContactPlaceholderElement.Props, 'renderPlaceholder'>;
4939
withCoveragePlaceholders?:
5040
| false
51-
| Pick<
52-
CoveragePlaceholderElement.Props,
53-
| 'getSuggestions'
54-
| 'invalidateSuggestions'
55-
| 'renderEmpty'
56-
| 'renderSuggestion'
57-
| 'renderSuggestionsFooter'
58-
| 'onCreateCoverage'
59-
>;
41+
| Pick<CoveragePlaceholderElement.Props, 'onCreateCoverage' | 'renderPlaceholder'>;
6042
withEmbedPlaceholders?: false | { fetchOembed: FetchOEmbedFn };
6143
withGalleryPlaceholders?: boolean | { withMediaGalleryTab: WithMediaGalleryTab };
6244
withGalleryBookmarkPlaceholders?:
6345
| false
64-
| Pick<
65-
GalleryBookmarkPlaceholderElement.Props,
66-
| 'fetchOembed'
67-
| 'getSuggestions'
68-
| 'invalidateSuggestions'
69-
| 'renderAddon'
70-
| 'renderEmpty'
71-
| 'renderSuggestion'
72-
| 'renderSuggestionsFooter'
73-
>;
46+
| Pick<GalleryBookmarkPlaceholderElement.Props, 'renderPlaceholder'>;
7447
withImagePlaceholders?:
7548
| boolean
7649
| { withCaptions: boolean; withMediaGalleryTab: WithMediaGalleryTab };
7750
withInlineContactPlaceholders?:
7851
| false
79-
| Pick<
80-
InlineContactPlaceholderElement.Props,
81-
| 'getSuggestions'
82-
| 'invalidateSuggestions'
83-
| 'renderEmpty'
84-
| 'renderSuggestion'
85-
| 'renderSuggestionsFooter'
86-
>;
52+
| Pick<InlineContactPlaceholderElement.Props, 'renderPlaceholder'>;
8753
withMediaPlaceholders?:
8854
| boolean
8955
| { withCaptions: boolean; withMediaGalleryTab: WithMediaGalleryTab };
9056
withStoryBookmarkPlaceholders?:
9157
| false
92-
| Pick<
93-
StoryBookmarkPlaceholderElement.Props,
94-
| 'getSuggestions'
95-
| 'invalidateSuggestions'
96-
| 'renderAddon'
97-
| 'renderEmpty'
98-
| 'renderSuggestion'
99-
| 'renderSuggestionsFooter'
100-
>;
58+
| Pick<StoryBookmarkPlaceholderElement.Props, 'renderPlaceholder'>;
10159
withStoryEmbedPlaceholders?:
10260
| false
103-
| Pick<
104-
StoryEmbedPlaceholderElement.Props,
105-
| 'getSuggestions'
106-
| 'invalidateSuggestions'
107-
| 'renderAddon'
108-
| 'renderEmpty'
109-
| 'renderSuggestion'
110-
| 'renderSuggestionsFooter'
111-
>;
61+
| Pick<StoryEmbedPlaceholderElement.Props, 'renderPlaceholder'>;
11262
withSocialPostPlaceholders?: false | { fetchOembed: FetchOEmbedFn };
11363
withVideoPlaceholders?: false | { fetchOembed: FetchOEmbedFn };
11464
withWebBookmarkPlaceholders?: false | { fetchOembed: FetchOEmbedFn };

packages/slate-editor/src/extensions/placeholders/components/PlaceholderElement.tsx

+32-22
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ReactNode } from 'react';
12
import React, { type MouseEvent, useState } from 'react';
23
import { Transforms } from 'slate';
34
import { ReactEditor, type RenderElementProps, useSlateStatic } from 'slate-react';
@@ -14,7 +15,9 @@ import { type Props as BaseProps, Placeholder } from './Placeholder';
1415
export type Props = RenderElementProps &
1516
Pick<BaseProps, 'icon' | 'title' | 'description' | 'format' | 'onClick' | 'onDrop'> & {
1617
element: PlaceholderNode;
18+
overflow?: 'visible' | 'hidden' | 'auto';
1719
removable: RemovableFlagConfig;
20+
renderFrame?: () => ReactNode;
1821
};
1922

2023
export function PlaceholderElement({
@@ -27,7 +30,9 @@ export function PlaceholderElement({
2730
icon,
2831
title,
2932
description,
33+
overflow,
3034
removable,
35+
renderFrame,
3136
// Callbacks
3237
onClick,
3338
onDrop,
@@ -63,29 +68,34 @@ export function PlaceholderElement({
6368
<EditorBlock
6469
{...attributes}
6570
element={element}
71+
overflow={overflow}
6672
renderAboveFrame={children}
67-
renderReadOnlyFrame={({ isSelected }) => (
68-
<Placeholder
69-
// Core
70-
active={isActive}
71-
format={format}
72-
icon={icon}
73-
title={title}
74-
description={description}
75-
// Variations
76-
dragOver={onDrop ? dragOver : false}
77-
dropZone={Boolean(onDrop)}
78-
selected={isSelected}
79-
progress={progress ?? isLoading}
80-
// Callbacks
81-
onClick={isLoading ? undefined : onClick}
82-
onRemove={isRemovable ? handleRemove : undefined}
83-
onDragOver={handleDragOver}
84-
onDragLeave={handleDragLeave}
85-
onDrop={onDrop}
86-
onMouseOver={handleMouseOver}
87-
/>
88-
)}
73+
renderReadOnlyFrame={({ isSelected }) =>
74+
renderFrame ? (
75+
renderFrame()
76+
) : (
77+
<Placeholder
78+
// Core
79+
active={isActive}
80+
format={format}
81+
icon={icon}
82+
title={title}
83+
description={description}
84+
// Variations
85+
dragOver={onDrop ? dragOver : false}
86+
dropZone={Boolean(onDrop)}
87+
selected={isSelected}
88+
progress={progress ?? isLoading}
89+
// Callbacks
90+
onClick={isLoading ? undefined : onClick}
91+
onRemove={isRemovable ? handleRemove : undefined}
92+
onDragOver={handleDragOver}
93+
onDragLeave={handleDragLeave}
94+
onDrop={onDrop}
95+
onMouseOver={handleMouseOver}
96+
/>
97+
)
98+
}
8999
rounded
90100
void
91101
/>
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
import type { ContactInfo } from '@prezly/slate-types';
21
import * as React from 'react';
32
import { createEditor as createSlateEditor } from 'slate';
43
import { type RenderElementProps, Slate } from 'slate-react';
54

6-
import { SearchInput } from '#components';
7-
85
import { PlaceholdersExtension } from '#extensions/placeholders';
96
import { createEditor } from '#modules/editor';
107

11-
import type { Suggestion } from '../../../components/SearchInput/types';
128
import { PlaceholderNode } from '../PlaceholderNode';
139

1410
import { ContactPlaceholderElement } from './ContactPlaceholderElement';
@@ -28,42 +24,6 @@ const attributes: RenderElementProps['attributes'] = {
2824
ref: () => null,
2925
};
3026

31-
function contact(props: Partial<ContactInfo> & Pick<ContactInfo, 'name'>): ContactInfo {
32-
return {
33-
avatar_url: null,
34-
company: '',
35-
description: '',
36-
address: '',
37-
email: '',
38-
facebook: '',
39-
mobile: '',
40-
phone: '',
41-
twitter: '',
42-
website: '',
43-
...props,
44-
};
45-
}
46-
47-
const suggestions: Suggestion<ContactInfo>[] = [
48-
{ id: '00000000-00000000-00000000-00000001', value: contact({ name: 'Frodo Baggins' }) },
49-
{ id: '00000000-00000000-00000000-00000002', value: contact({ name: 'Aragorn' }) },
50-
{ id: '00000000-00000000-00000000-00000003', value: contact({ name: 'Samwise Gamgee' }) },
51-
{ id: '00000000-00000000-00000000-00000004', value: contact({ name: 'Merry Brandybuck' }) },
52-
{ id: '00000000-00000000-00000000-00000005', value: contact({ name: 'Legolas' }) },
53-
{ id: '00000000-00000000-00000000-00000006', value: contact({ name: 'Pippin Took' }) },
54-
{ id: '00000000-00000000-00000000-00000007', value: contact({ name: 'Gandalf' }) },
55-
{ id: '00000000-00000000-00000000-00000008', value: contact({ name: 'Boromir' }) },
56-
{ id: '00000000-00000000-00000000-00000009', value: contact({ name: 'Arwen' }) },
57-
{ id: '00000000-00000000-00000000-00000010', value: contact({ name: 'Galadriel' }) },
58-
];
59-
60-
async function getSuggestions(query: string) {
61-
await delay(500 + Math.random() * 500);
62-
return suggestions.filter(({ value }) =>
63-
value.name.toLowerCase().includes(query.toLowerCase()),
64-
);
65-
}
66-
6727
export default {
6828
title: 'Extensions/Placeholders/elements',
6929
decorators: [
@@ -82,55 +42,10 @@ export function ContactPlaceholder() {
8242
<ContactPlaceholderElement
8343
attributes={attributes}
8444
element={placeholder}
85-
getSuggestions={getSuggestions}
86-
renderSuggestion={({ ref, active, disabled, suggestion, onSelect }) => (
87-
<SearchInput.Option
88-
active={active}
89-
disabled={disabled}
90-
onClick={onSelect}
91-
forwardRef={ref}
92-
>
93-
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
94-
<div
95-
style={{
96-
width: 40,
97-
height: 40,
98-
textAlign: 'center',
99-
color: 'white',
100-
lineHeight: '40px',
101-
borderRadius: 6,
102-
backgroundColor: '#2FA4F9',
103-
marginRight: 16,
104-
flexGrow: 0,
105-
}}
106-
>
107-
{suggestion.value.name
108-
.split(/\s/g)
109-
.slice(0, 2)
110-
.map((word) => word.substring(0, 1))
111-
.join('')}
112-
</div>
113-
<div style={{ flexGrow: 1, fontWeight: 600, fontSize: 14 }}>
114-
{suggestion.value.name}
115-
</div>
116-
</div>
117-
</SearchInput.Option>
118-
)}
119-
renderSuggestionsFooter={() => (
120-
<div>
121-
<a href="#">+ Create a Site Contact</a>&nbsp;&nbsp;|&nbsp;&nbsp;
122-
<a href="#">Edit Site Contacts</a>
123-
</div>
124-
)}
12545
removable
46+
renderPlaceholder={() => null}
12647
>
12748
{''}
12849
</ContactPlaceholderElement>
12950
);
13051
}
131-
132-
function delay(ms: number): Promise<void> {
133-
return new Promise((resolve) => {
134-
setTimeout(resolve, ms);
135-
});
136-
}

0 commit comments

Comments
 (0)