@soul-refiner/design-system 0.1.24 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/actions/dropdown/dropdown.tsx +14 -11
- package/src/components/editor/constants.ts +288 -0
- package/src/components/editor/editor.tsx +56 -0
- package/src/components/editor/index.ts +2 -0
- package/src/components/editor/navbar/alignment_dropdown.tsx +94 -0
- package/src/components/editor/navbar/index.ts +2 -0
- package/src/components/editor/navbar/navbar.tsx +336 -0
- package/src/components/editor/plate.ts +17 -0
- package/src/components/editor/plugins/buttons.tsx +127 -0
- package/src/components/editor/plugins/exitBreak.ts +41 -0
- package/src/components/editor/plugins/index.ts +151 -0
- package/src/components/editor/plugins/resetBlockType.ts +29 -0
- package/src/components/editor/plugins/selectOnBackspace.ts +7 -0
- package/src/components/editor/plugins/softBreak.ts +19 -0
- package/src/components/editor/plugins/text.tsx +137 -0
- package/src/components/index.ts +1 -0
package/package.json
CHANGED
|
@@ -180,7 +180,8 @@ const Dropdown = ({
|
|
|
180
180
|
>
|
|
181
181
|
{React.cloneElement(
|
|
182
182
|
trigger,
|
|
183
|
-
typeof trigger.type === 'string'
|
|
183
|
+
typeof trigger.type === 'string' ||
|
|
184
|
+
(trigger.type as any)?.displayName !== 'Button'
|
|
184
185
|
? { onClick: handleOpen }
|
|
185
186
|
: { isDropdownOpen: open, onClick: handleOpen }
|
|
186
187
|
)}
|
|
@@ -254,7 +255,7 @@ export interface DropdownOptionProps {
|
|
|
254
255
|
color?: string;
|
|
255
256
|
disabled?: boolean;
|
|
256
257
|
icon?: React.ReactElement;
|
|
257
|
-
label
|
|
258
|
+
label?: React.ReactNode;
|
|
258
259
|
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void | undefined;
|
|
259
260
|
selected: boolean;
|
|
260
261
|
sublabel?: string;
|
|
@@ -285,15 +286,17 @@ const Option = ({
|
|
|
285
286
|
>
|
|
286
287
|
{checkbox ? <Checkbox selected={selected} /> : icon}
|
|
287
288
|
<Box $dir="column" $display="flex">
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
289
|
+
{label && (
|
|
290
|
+
<P
|
|
291
|
+
$color="grey-4"
|
|
292
|
+
$fontSize={14}
|
|
293
|
+
$lineHeight={20}
|
|
294
|
+
$mediumBold
|
|
295
|
+
$textClampLines={1}
|
|
296
|
+
>
|
|
297
|
+
{label}
|
|
298
|
+
</P>
|
|
299
|
+
)}
|
|
297
300
|
{sublabel && (
|
|
298
301
|
<P
|
|
299
302
|
$color="grey-4"
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ELEMENT_BLOCKQUOTE,
|
|
3
|
+
ELEMENT_CODE_BLOCK,
|
|
4
|
+
ELEMENT_CODE_LINE,
|
|
5
|
+
ELEMENT_CODE_SYNTAX,
|
|
6
|
+
ELEMENT_DEFAULT,
|
|
7
|
+
ELEMENT_H1,
|
|
8
|
+
ELEMENT_H2,
|
|
9
|
+
ELEMENT_H3,
|
|
10
|
+
ELEMENT_H4,
|
|
11
|
+
ELEMENT_H5,
|
|
12
|
+
ELEMENT_H6,
|
|
13
|
+
ELEMENT_HR,
|
|
14
|
+
ELEMENT_IMAGE,
|
|
15
|
+
ELEMENT_LI,
|
|
16
|
+
ELEMENT_LIC,
|
|
17
|
+
ELEMENT_LINK,
|
|
18
|
+
ELEMENT_MEDIA,
|
|
19
|
+
ELEMENT_MEDIA_EMBED,
|
|
20
|
+
ELEMENT_MENTION,
|
|
21
|
+
ELEMENT_MENTION_INPUT,
|
|
22
|
+
ELEMENT_OL,
|
|
23
|
+
ELEMENT_PARAGRAPH,
|
|
24
|
+
ELEMENT_TABLE,
|
|
25
|
+
ELEMENT_TD,
|
|
26
|
+
ELEMENT_TH,
|
|
27
|
+
ELEMENT_TODO_LI,
|
|
28
|
+
ELEMENT_TR,
|
|
29
|
+
ELEMENT_UL,
|
|
30
|
+
MARK_BG_COLOR,
|
|
31
|
+
MARK_BOLD,
|
|
32
|
+
MARK_CODE,
|
|
33
|
+
MARK_COLOR,
|
|
34
|
+
MARK_FONT_FAMILY,
|
|
35
|
+
MARK_FONT_SIZE,
|
|
36
|
+
MARK_FONT_WEIGHT,
|
|
37
|
+
MARK_HIGHLIGHT,
|
|
38
|
+
MARK_ITALIC,
|
|
39
|
+
MARK_KBD,
|
|
40
|
+
MARK_SEARCH_HIGHLIGHT,
|
|
41
|
+
MARK_STRIKETHROUGH,
|
|
42
|
+
MARK_SUBSCRIPT,
|
|
43
|
+
MARK_SUPERSCRIPT,
|
|
44
|
+
MARK_UNDERLINE,
|
|
45
|
+
} from '@udecode/plate';
|
|
46
|
+
|
|
47
|
+
export const ELEMENT_AUDIO = 'audio';
|
|
48
|
+
export const ELEMENT_BIBLE_QUOTE = 'bible-quote';
|
|
49
|
+
export const ELEMENT_BUTTON = 'button';
|
|
50
|
+
export const ELEMENT_EXAMPLE = 'example';
|
|
51
|
+
export const ELEMENT_FOOTNOTE = 'footnote';
|
|
52
|
+
export const ELEMENT_PAYWALL = 'paywall';
|
|
53
|
+
export const ELEMENT_POLL = 'poll';
|
|
54
|
+
export const ELEMENT_QA = 'q-n-a';
|
|
55
|
+
export const ELEMENT_RECIPE = 'recipe';
|
|
56
|
+
export const ELEMENT_VIDEO = 'video';
|
|
57
|
+
|
|
58
|
+
export const BUTTON_COMMENT = 'button-comment';
|
|
59
|
+
export const BUTTON_GIFT_SUBSCRIPTION = 'button-gift-subscription';
|
|
60
|
+
export const BUTTON_LINK = 'button-link';
|
|
61
|
+
export const BUTTON_SHARE = 'button-share';
|
|
62
|
+
export const BUTTON_SHARE_PAGE = 'button-share-page';
|
|
63
|
+
export const BUTTON_SIGNUP = 'signup';
|
|
64
|
+
export const BUTTON_SPECIAL_OFFER = 'button-special-offer';
|
|
65
|
+
export const BUTTON_SUBSCRIBE = 'button-subscribe';
|
|
66
|
+
|
|
67
|
+
export const COLORS = [
|
|
68
|
+
'var(--color-turquoise-5)',
|
|
69
|
+
'var(--color-green-5)',
|
|
70
|
+
'var(--color-blue-5)',
|
|
71
|
+
'var(--color-purple-5)',
|
|
72
|
+
'var(--color-yellow-5)',
|
|
73
|
+
'var(--color-orange-5)',
|
|
74
|
+
'var(--color-red-5)',
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
export const PLACEHOLDER = 'This is a placeholder';
|
|
78
|
+
|
|
79
|
+
export const DEFAULT_CONTENT = [
|
|
80
|
+
{
|
|
81
|
+
type: 'h1',
|
|
82
|
+
children: [
|
|
83
|
+
{
|
|
84
|
+
text: 'Module 1: The Warrior Identity',
|
|
85
|
+
bold: true,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
// {
|
|
90
|
+
// id: "video-234jlnlknsdlkfjwlkjaa",
|
|
91
|
+
// parentId: "course-1lk4j3k4jlkj2l4k2j34lkj",
|
|
92
|
+
// children: [
|
|
93
|
+
// {
|
|
94
|
+
// text: "The DNA of a Warrior",
|
|
95
|
+
// },
|
|
96
|
+
// ],
|
|
97
|
+
// captions: [],
|
|
98
|
+
// duration: 3765,
|
|
99
|
+
// source: "",
|
|
100
|
+
// synopsis:
|
|
101
|
+
// "Understanding the battlefield is the first key to winning the war for sexual purity. In this episode, you will discover hell's strategies against the 21st Century Church and be introduced to the four components of the noose of sexual bondage. Learn the first key steps to freedom and how to stay pure.",
|
|
102
|
+
// thumbnail: "video-thumbnail.jpg",
|
|
103
|
+
// title: "The DNA of a Warrior",
|
|
104
|
+
// type: "video",
|
|
105
|
+
// },
|
|
106
|
+
// {
|
|
107
|
+
// type: "p",
|
|
108
|
+
// children: [
|
|
109
|
+
// {
|
|
110
|
+
// text: "Welcome to Warpath. You’re about to embark on a journey towards lifelong healing, freedom and victory. This is a season of training. It’s sanctification on steroids, not for the faint-hearted, but for those who truly desire to live the rest of their lives for Jesus. No reserves, no retreats, no regrets.",
|
|
111
|
+
// },
|
|
112
|
+
// {
|
|
113
|
+
// type: "footnote",
|
|
114
|
+
// children: [
|
|
115
|
+
// {
|
|
116
|
+
// text: "This is a footnote",
|
|
117
|
+
// },
|
|
118
|
+
// ],
|
|
119
|
+
// },
|
|
120
|
+
// {
|
|
121
|
+
// text: " What was once your deepest, darkest secret will now become the word of your testimony that heals and liberates others.",
|
|
122
|
+
// },
|
|
123
|
+
// ],
|
|
124
|
+
// },
|
|
125
|
+
// {
|
|
126
|
+
// type: "q-n-a",
|
|
127
|
+
// children: [
|
|
128
|
+
// {
|
|
129
|
+
// type: "p",
|
|
130
|
+
// children: [
|
|
131
|
+
// {
|
|
132
|
+
// text: "Ask your question...",
|
|
133
|
+
// },
|
|
134
|
+
// ],
|
|
135
|
+
// },
|
|
136
|
+
// ],
|
|
137
|
+
// },
|
|
138
|
+
{
|
|
139
|
+
type: 'p',
|
|
140
|
+
children: [
|
|
141
|
+
{
|
|
142
|
+
text: 'In this new season, God is calling you up. He’s calling you to a higher level, a place where you’re no longer part of the problem but are becoming a part of the solution.',
|
|
143
|
+
},
|
|
144
|
+
// {
|
|
145
|
+
// type: "footnote",
|
|
146
|
+
// children: [
|
|
147
|
+
// {
|
|
148
|
+
// text: "This is another footnote",
|
|
149
|
+
// },
|
|
150
|
+
// ],
|
|
151
|
+
// },
|
|
152
|
+
{
|
|
153
|
+
text: '',
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'blockquote',
|
|
159
|
+
children: [
|
|
160
|
+
{
|
|
161
|
+
text: '"Warpath isn’t about just winning for you; that’s the beginning. Warpath is about you taking the battle and winning for others so they can have their families, and their marriages, and their destinies." \n— ',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
text: 'Dr. Doug Weiss',
|
|
165
|
+
italic: true,
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
type: 'hr',
|
|
171
|
+
children: [
|
|
172
|
+
{
|
|
173
|
+
text: '',
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
type: 'h2',
|
|
179
|
+
children: [
|
|
180
|
+
{
|
|
181
|
+
text: 'Lesson Overview',
|
|
182
|
+
bold: true,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: 'h3',
|
|
188
|
+
align: 'center',
|
|
189
|
+
children: [
|
|
190
|
+
{
|
|
191
|
+
text: 'THE WARRIOR IDENTITY',
|
|
192
|
+
bold: true,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
type: 'hr',
|
|
198
|
+
children: [
|
|
199
|
+
{
|
|
200
|
+
text: '',
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
type: 'img',
|
|
206
|
+
url: 'https://source.unsplash.com/1600x900/?christian',
|
|
207
|
+
children: [
|
|
208
|
+
{
|
|
209
|
+
text: '',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
type: 'p',
|
|
215
|
+
children: [
|
|
216
|
+
{
|
|
217
|
+
text: 'Identity: You will have to grasp your true identity in Christ. You cannot look at this as a “what”, but instead about a “who”. We must become more like Christ, because He is a warrior.',
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
type: 'p',
|
|
223
|
+
children: [
|
|
224
|
+
{
|
|
225
|
+
text: 'A different DNA: As a warrior, your spiritual genetic code will be different from those who haven’t tasted the victory and grace you’ve experienced from this battle. You’ve experienced truth as a person: Jesus; and it has forever changed you. You’ve been set apart as a “fighting man”, no longer a civilian idly consumed by the cares of this world, but a warrior who’s passionate about the Kingdom and craving some sweet revenge.',
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
type: 'code_block',
|
|
231
|
+
lang: 'typescript',
|
|
232
|
+
children: [
|
|
233
|
+
{
|
|
234
|
+
type: 'code_line',
|
|
235
|
+
children: [
|
|
236
|
+
{
|
|
237
|
+
text: 'const variable = "DO_NOT_USE"',
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
// {
|
|
244
|
+
// type: "paywall",
|
|
245
|
+
// children: [
|
|
246
|
+
// {
|
|
247
|
+
// type: "h2",
|
|
248
|
+
// children: [
|
|
249
|
+
// {
|
|
250
|
+
// text: "Keep reading with a paid subscription",
|
|
251
|
+
// },
|
|
252
|
+
// ],
|
|
253
|
+
// },
|
|
254
|
+
// {
|
|
255
|
+
// type: "p",
|
|
256
|
+
// children: [
|
|
257
|
+
// {
|
|
258
|
+
// text: "Subscribe to ",
|
|
259
|
+
// },
|
|
260
|
+
// {
|
|
261
|
+
// text: "Soul Refiner",
|
|
262
|
+
// italic: true,
|
|
263
|
+
// },
|
|
264
|
+
// {
|
|
265
|
+
// text: " to keep reading this content.",
|
|
266
|
+
// },
|
|
267
|
+
// ],
|
|
268
|
+
// },
|
|
269
|
+
// {
|
|
270
|
+
// type: "button",
|
|
271
|
+
// buttonType: "button-subscribe",
|
|
272
|
+
// children: [
|
|
273
|
+
// {
|
|
274
|
+
// text: "Get Access",
|
|
275
|
+
// },
|
|
276
|
+
// ],
|
|
277
|
+
// },
|
|
278
|
+
// ],
|
|
279
|
+
// },
|
|
280
|
+
{
|
|
281
|
+
type: 'p',
|
|
282
|
+
children: [
|
|
283
|
+
{
|
|
284
|
+
text: '',
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import { createPlugins, Plate, PlateProvider, TElement } from '@udecode/plate';
|
|
4
|
+
import { DEFAULT_CONTENT, PLACEHOLDER } from './constants';
|
|
5
|
+
import { EditorBar } from './navbar';
|
|
6
|
+
import { plateUI } from './plate';
|
|
7
|
+
import { blocks, inline, marks, utils } from './plugins';
|
|
8
|
+
|
|
9
|
+
const plugins = createPlugins([...blocks, ...inline, ...marks, ...utils], {
|
|
10
|
+
components: plateUI,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export interface EditorProps {
|
|
14
|
+
communityName: string;
|
|
15
|
+
editable: boolean;
|
|
16
|
+
onChange: (newNodes: TElement[]) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Editor = ({ communityName, editable, onChange }: EditorProps) => {
|
|
20
|
+
return (
|
|
21
|
+
<PlateProvider
|
|
22
|
+
id="main"
|
|
23
|
+
initialValue={DEFAULT_CONTENT}
|
|
24
|
+
onChange={onChange}
|
|
25
|
+
plugins={plugins}
|
|
26
|
+
>
|
|
27
|
+
<Wrapper>
|
|
28
|
+
<EditorBar communityName={communityName} />
|
|
29
|
+
<Plate
|
|
30
|
+
id="main"
|
|
31
|
+
editableProps={{
|
|
32
|
+
readOnly: !editable,
|
|
33
|
+
spellCheck: true,
|
|
34
|
+
autoFocus: true,
|
|
35
|
+
placeholder: PLACEHOLDER,
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
</Wrapper>
|
|
39
|
+
</PlateProvider>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const Wrapper = styled.div`
|
|
44
|
+
* {
|
|
45
|
+
font-family: Inter;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
& > [data-slate-editor='true'] > * {
|
|
49
|
+
margin: 24px 0px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
position: relative;
|
|
53
|
+
padding: 24px;
|
|
54
|
+
max-width: 1200px;
|
|
55
|
+
margin: 98px auto 0px;
|
|
56
|
+
`;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Alignment,
|
|
4
|
+
AlignToolbarButton,
|
|
5
|
+
isCollapsed,
|
|
6
|
+
PlateEditor,
|
|
7
|
+
setAlign,
|
|
8
|
+
someNode,
|
|
9
|
+
} from '@udecode/plate';
|
|
10
|
+
import { Dropdown } from 'components';
|
|
11
|
+
import {
|
|
12
|
+
PlayerListPlay,
|
|
13
|
+
AlignCenter,
|
|
14
|
+
AlignJustified,
|
|
15
|
+
AlignLeft,
|
|
16
|
+
AlignRight,
|
|
17
|
+
} from 'icons';
|
|
18
|
+
|
|
19
|
+
export interface AlignmentDropdownProps {
|
|
20
|
+
editorRef: PlateEditor;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const AlignmentDropdown = ({ editorRef }: AlignmentDropdownProps) => {
|
|
24
|
+
const handleAlign = (event: React.FormEvent) => {
|
|
25
|
+
const value = (event.target as HTMLElement)?.getAttribute(
|
|
26
|
+
'data-value'
|
|
27
|
+
) as Alignment | null;
|
|
28
|
+
|
|
29
|
+
if (!value) return;
|
|
30
|
+
|
|
31
|
+
setAlign(editorRef, {
|
|
32
|
+
value: value,
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const alignment = (() => {
|
|
37
|
+
if (!isCollapsed(editorRef?.selection)) {
|
|
38
|
+
return 'multiple-lines';
|
|
39
|
+
} else if (
|
|
40
|
+
someNode(editorRef, { match: { align: 'center' } }) ? true : undefined
|
|
41
|
+
) {
|
|
42
|
+
return 'center';
|
|
43
|
+
} else if (
|
|
44
|
+
someNode(editorRef, { match: { align: 'right' } }) ? true : undefined
|
|
45
|
+
) {
|
|
46
|
+
return 'right';
|
|
47
|
+
} else if (
|
|
48
|
+
someNode(editorRef, { match: { align: 'justify' } }) ? true : undefined
|
|
49
|
+
) {
|
|
50
|
+
return 'justify';
|
|
51
|
+
} else {
|
|
52
|
+
return 'left';
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Dropdown
|
|
58
|
+
closeOnBlur={false}
|
|
59
|
+
closeOnChange={false}
|
|
60
|
+
onChange={handleAlign}
|
|
61
|
+
maintainState={false}
|
|
62
|
+
trigger={<PlayerListPlay size={24} color="grey-5" />}
|
|
63
|
+
>
|
|
64
|
+
<AlignToolbarButton
|
|
65
|
+
as="div"
|
|
66
|
+
active={alignment === 'left' ? true : undefined}
|
|
67
|
+
data-value="left"
|
|
68
|
+
value="left"
|
|
69
|
+
icon={<AlignLeft size={24} color="grey-5" />}
|
|
70
|
+
/>
|
|
71
|
+
<AlignToolbarButton
|
|
72
|
+
as="div"
|
|
73
|
+
active={alignment === 'center' ? true : undefined}
|
|
74
|
+
data-value="center"
|
|
75
|
+
value="center"
|
|
76
|
+
icon={<AlignCenter size={24} color="grey-5" />}
|
|
77
|
+
/>
|
|
78
|
+
<AlignToolbarButton
|
|
79
|
+
as="div"
|
|
80
|
+
active={alignment === 'right' ? true : undefined}
|
|
81
|
+
data-value="right"
|
|
82
|
+
value="right"
|
|
83
|
+
icon={<AlignRight size={24} color="grey-5" />}
|
|
84
|
+
/>
|
|
85
|
+
<AlignToolbarButton
|
|
86
|
+
as="div"
|
|
87
|
+
active={alignment === 'justify' ? true : undefined}
|
|
88
|
+
data-value="justify"
|
|
89
|
+
value="justify"
|
|
90
|
+
icon={<AlignJustified size={24} color="grey-5" />}
|
|
91
|
+
/>
|
|
92
|
+
</Dropdown>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import {
|
|
4
|
+
Alignment,
|
|
5
|
+
AlignToolbarButton,
|
|
6
|
+
focusEditor,
|
|
7
|
+
getPluginType,
|
|
8
|
+
// getPreventDefaultHandler,
|
|
9
|
+
ImageToolbarButton,
|
|
10
|
+
insertImage,
|
|
11
|
+
insertNodes,
|
|
12
|
+
isCollapsed,
|
|
13
|
+
LinkToolbarButton,
|
|
14
|
+
MarkToolbarButton,
|
|
15
|
+
PlateEditor,
|
|
16
|
+
setMarks,
|
|
17
|
+
setAlign,
|
|
18
|
+
someNode,
|
|
19
|
+
toggleIndentList,
|
|
20
|
+
toggleNodeType,
|
|
21
|
+
ToolbarButton,
|
|
22
|
+
usePlateEditorRef,
|
|
23
|
+
usePlateEditorState,
|
|
24
|
+
} from '@udecode/plate';
|
|
25
|
+
|
|
26
|
+
import { Tooltip } from 'components';
|
|
27
|
+
import {
|
|
28
|
+
Bold,
|
|
29
|
+
Dollar,
|
|
30
|
+
FileCode,
|
|
31
|
+
Italic,
|
|
32
|
+
Link,
|
|
33
|
+
ListOrdered,
|
|
34
|
+
ListUnordered,
|
|
35
|
+
Quote,
|
|
36
|
+
Redo,
|
|
37
|
+
Strikethrough,
|
|
38
|
+
Underline,
|
|
39
|
+
Undo,
|
|
40
|
+
} from 'icons';
|
|
41
|
+
import {
|
|
42
|
+
BUTTON_SUBSCRIBE,
|
|
43
|
+
ELEMENT_BLOCKQUOTE,
|
|
44
|
+
ELEMENT_BUTTON,
|
|
45
|
+
ELEMENT_DEFAULT,
|
|
46
|
+
ELEMENT_H2,
|
|
47
|
+
ELEMENT_PARAGRAPH,
|
|
48
|
+
ELEMENT_PAYWALL,
|
|
49
|
+
ELEMENT_QA,
|
|
50
|
+
MARK_BOLD,
|
|
51
|
+
MARK_CODE,
|
|
52
|
+
MARK_ITALIC,
|
|
53
|
+
MARK_HIGHLIGHT,
|
|
54
|
+
MARK_STRIKETHROUGH,
|
|
55
|
+
MARK_SUBSCRIPT,
|
|
56
|
+
MARK_SUPERSCRIPT,
|
|
57
|
+
MARK_UNDERLINE,
|
|
58
|
+
} from '../constants';
|
|
59
|
+
import { AlignmentDropdown } from './alignment_dropdown';
|
|
60
|
+
|
|
61
|
+
export interface EditorBarProps {
|
|
62
|
+
communityName: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const EditorBar = ({ communityName }: EditorBarProps) => {
|
|
66
|
+
const editorRef = usePlateEditorRef('main');
|
|
67
|
+
const stateEditor = usePlateEditorState();
|
|
68
|
+
const audioRef = React.useRef<HTMLInputElement>(null);
|
|
69
|
+
const imageRef = React.useRef<HTMLInputElement>(null);
|
|
70
|
+
const videoRef = React.useRef<HTMLInputElement>(null);
|
|
71
|
+
|
|
72
|
+
const handleAudioChange = async (event: React.FormEvent) => {
|
|
73
|
+
const file = (event.target as HTMLInputElement)?.files?.[0];
|
|
74
|
+
if (!file) return;
|
|
75
|
+
/**
|
|
76
|
+
* TODO: Insert audio component
|
|
77
|
+
*/
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleBlockquote = (event: React.MouseEvent) => {
|
|
81
|
+
toggleNodeType(stateEditor, { activeType: ELEMENT_BLOCKQUOTE });
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleImageChange = async (event: React.FormEvent) => {
|
|
85
|
+
const file = (event.target as HTMLInputElement)?.files?.[0];
|
|
86
|
+
if (!file) return;
|
|
87
|
+
// const imageurl = await handleUploadImage(file);
|
|
88
|
+
// if (imageurl) {
|
|
89
|
+
// insertImage(editorRef, imageurl);
|
|
90
|
+
// }
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleFont = (value: string) => {
|
|
94
|
+
const type = getPluginType(editorRef, 'fontSize');
|
|
95
|
+
focusEditor(editorRef);
|
|
96
|
+
setMarks(stateEditor, { [type]: `${value}px` });
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const handleOrderedList = (event: any) => {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
toggleIndentList(editorRef, {
|
|
102
|
+
listStyleType: 'decimals',
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const handlePaywall = () => {
|
|
107
|
+
insertNodes(editorRef, [
|
|
108
|
+
{
|
|
109
|
+
type: ELEMENT_PAYWALL,
|
|
110
|
+
children: [
|
|
111
|
+
{
|
|
112
|
+
type: ELEMENT_H2,
|
|
113
|
+
children: [
|
|
114
|
+
{
|
|
115
|
+
text: 'Keep reading with a paid subscription',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: ELEMENT_PARAGRAPH,
|
|
121
|
+
children: [
|
|
122
|
+
{
|
|
123
|
+
text: 'Subscribe to ',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
text: communityName,
|
|
127
|
+
italic: true,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
text: ' to keep reading this content.',
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: ELEMENT_BUTTON,
|
|
136
|
+
buttonType: BUTTON_SUBSCRIBE,
|
|
137
|
+
children: [
|
|
138
|
+
{
|
|
139
|
+
text: 'Get Access',
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: ELEMENT_DEFAULT,
|
|
147
|
+
children: [
|
|
148
|
+
{
|
|
149
|
+
text: '',
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
]);
|
|
154
|
+
focusEditor(editorRef);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const handleQA = () => {
|
|
158
|
+
insertNodes(editorRef, {
|
|
159
|
+
type: ELEMENT_QA,
|
|
160
|
+
children: [
|
|
161
|
+
{
|
|
162
|
+
type: ELEMENT_PARAGRAPH,
|
|
163
|
+
children: [
|
|
164
|
+
{
|
|
165
|
+
text: 'Ask your question here...',
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const handleRedo = () => editorRef.redo();
|
|
174
|
+
|
|
175
|
+
const handleUndo = () => editorRef.undo();
|
|
176
|
+
|
|
177
|
+
const handleUnorderedList = (event: any) => {
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
toggleIndentList(editorRef, {
|
|
180
|
+
listStyleType: 'disc',
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const handleVideoChange = async (event: React.FormEvent) => {
|
|
185
|
+
const file = (event.target as HTMLInputElement)?.files?.[0];
|
|
186
|
+
if (!file) return;
|
|
187
|
+
/**
|
|
188
|
+
* TODO: Insert video component
|
|
189
|
+
*/
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<Wrapper>
|
|
194
|
+
<Menu aria-label="editor-navigation">
|
|
195
|
+
<MenuSection>
|
|
196
|
+
<Tooltip content="Undo" place="top">
|
|
197
|
+
<MenuButton $active={false} onClick={handleUndo}>
|
|
198
|
+
<Undo size={24} color="grey-5" />
|
|
199
|
+
</MenuButton>
|
|
200
|
+
</Tooltip>
|
|
201
|
+
<Tooltip content="Redo" place="top">
|
|
202
|
+
<MenuButton $active={false} onClick={handleRedo}>
|
|
203
|
+
<Redo size={24} color="grey-5" />
|
|
204
|
+
</MenuButton>
|
|
205
|
+
</Tooltip>
|
|
206
|
+
</MenuSection>
|
|
207
|
+
<Divider />
|
|
208
|
+
<MenuSection>
|
|
209
|
+
<Tooltip content="Bold" place="top">
|
|
210
|
+
<MarkToolbarButton
|
|
211
|
+
as="div"
|
|
212
|
+
type={getPluginType(editorRef, MARK_BOLD)}
|
|
213
|
+
icon={
|
|
214
|
+
<MenuButton $active={true}>
|
|
215
|
+
<Bold size={24} color="grey-5" />
|
|
216
|
+
</MenuButton>
|
|
217
|
+
}
|
|
218
|
+
/>
|
|
219
|
+
</Tooltip>
|
|
220
|
+
<Tooltip content="Italic" place="top">
|
|
221
|
+
<MarkToolbarButton
|
|
222
|
+
as="div"
|
|
223
|
+
type={getPluginType(editorRef, MARK_ITALIC)}
|
|
224
|
+
icon={
|
|
225
|
+
<MenuButton $active={false}>
|
|
226
|
+
<Italic size={24} color="grey-5" />
|
|
227
|
+
</MenuButton>
|
|
228
|
+
}
|
|
229
|
+
/>
|
|
230
|
+
</Tooltip>
|
|
231
|
+
<Tooltip content="Underline" place="top">
|
|
232
|
+
<MarkToolbarButton
|
|
233
|
+
as="div"
|
|
234
|
+
type={getPluginType(editorRef, MARK_UNDERLINE)}
|
|
235
|
+
icon={<Underline size={24} color="grey-5" />}
|
|
236
|
+
/>
|
|
237
|
+
</Tooltip>
|
|
238
|
+
<Tooltip content="Strikethrough" place="top">
|
|
239
|
+
<MarkToolbarButton
|
|
240
|
+
as="div"
|
|
241
|
+
type={getPluginType(editorRef, MARK_STRIKETHROUGH)}
|
|
242
|
+
icon={
|
|
243
|
+
<MenuButton $active={false}>
|
|
244
|
+
<Strikethrough size={24} color="grey-5" />
|
|
245
|
+
</MenuButton>
|
|
246
|
+
}
|
|
247
|
+
/>
|
|
248
|
+
</Tooltip>
|
|
249
|
+
<Tooltip content="Inline code" place="top">
|
|
250
|
+
<MarkToolbarButton
|
|
251
|
+
as="div"
|
|
252
|
+
type={getPluginType(editorRef, MARK_CODE)}
|
|
253
|
+
icon={
|
|
254
|
+
<MenuButton $active={false}>
|
|
255
|
+
<FileCode size={24} color="grey-5" />
|
|
256
|
+
</MenuButton>
|
|
257
|
+
}
|
|
258
|
+
/>
|
|
259
|
+
</Tooltip>
|
|
260
|
+
</MenuSection>
|
|
261
|
+
<Divider />
|
|
262
|
+
<MenuSection>
|
|
263
|
+
<MenuButton $active={false} as="div">
|
|
264
|
+
<AlignmentDropdown editorRef={editorRef} />
|
|
265
|
+
</MenuButton>
|
|
266
|
+
</MenuSection>
|
|
267
|
+
</Menu>
|
|
268
|
+
</Wrapper>
|
|
269
|
+
);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const Wrapper = styled.div`
|
|
273
|
+
position: sticky;
|
|
274
|
+
z-index: 2;
|
|
275
|
+
left: 0;
|
|
276
|
+
right: 0;
|
|
277
|
+
top: 0;
|
|
278
|
+
`;
|
|
279
|
+
|
|
280
|
+
const Menu = styled.nav`
|
|
281
|
+
display: flex;
|
|
282
|
+
gap: 12px;
|
|
283
|
+
justify-content: center;
|
|
284
|
+
max-width: 1200px;
|
|
285
|
+
width: 100%;
|
|
286
|
+
margin: 0 auto;
|
|
287
|
+
padding: 14px 24px;
|
|
288
|
+
background-color: var(--color-white);
|
|
289
|
+
`;
|
|
290
|
+
|
|
291
|
+
const MenuSection = styled.div`
|
|
292
|
+
display: flex;
|
|
293
|
+
gap: 16px;
|
|
294
|
+
align-items: center;
|
|
295
|
+
width: fit-content;
|
|
296
|
+
flex-shrink: 0;
|
|
297
|
+
`;
|
|
298
|
+
|
|
299
|
+
export const MenuButton = styled.button<{
|
|
300
|
+
$active?: boolean;
|
|
301
|
+
}>`
|
|
302
|
+
padding: 2px;
|
|
303
|
+
border: none;
|
|
304
|
+
outline: none;
|
|
305
|
+
cursor: pointer;
|
|
306
|
+
box-sizing: border-box;
|
|
307
|
+
height: 28px;
|
|
308
|
+
width: 28px;
|
|
309
|
+
border-radius: 2px;
|
|
310
|
+
|
|
311
|
+
&:hover:not(:disabled) {
|
|
312
|
+
padding: 1px;
|
|
313
|
+
border: 1px solid var(--color-grey-8);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
&:active:not(:disabled) {
|
|
317
|
+
background-color: var(--color-grey-9);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
&:disabled {
|
|
321
|
+
background-color: var(--color-white) !important;
|
|
322
|
+
cursor: initial;
|
|
323
|
+
svg {
|
|
324
|
+
fill: var(--color-grey-7) !important;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
background-color: ${({ $active }) =>
|
|
329
|
+
$active ? 'background-color: var(--color-grey-9)' : 'transparent'};
|
|
330
|
+
`;
|
|
331
|
+
|
|
332
|
+
const Divider = styled.div`
|
|
333
|
+
width: 1px;
|
|
334
|
+
height: 28px;
|
|
335
|
+
background-color: var(--color-grey-9);
|
|
336
|
+
`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CodeBlockElement,
|
|
3
|
+
BlockquoteElement,
|
|
4
|
+
CodeLineElement,
|
|
5
|
+
CodeSyntaxLeaf,
|
|
6
|
+
ImageElement,
|
|
7
|
+
createPlateUI,
|
|
8
|
+
} from '@udecode/plate';
|
|
9
|
+
import * as Elements from './constants';
|
|
10
|
+
|
|
11
|
+
export const plateUI = createPlateUI({
|
|
12
|
+
[Elements.ELEMENT_BLOCKQUOTE]: BlockquoteElement,
|
|
13
|
+
[Elements.ELEMENT_CODE_BLOCK]: CodeBlockElement,
|
|
14
|
+
[Elements.ELEMENT_CODE_LINE]: CodeLineElement,
|
|
15
|
+
[Elements.ELEMENT_CODE_SYNTAX]: CodeSyntaxLeaf,
|
|
16
|
+
[Elements.ELEMENT_IMAGE]: ImageElement,
|
|
17
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import {
|
|
4
|
+
createPluginFactory,
|
|
5
|
+
ELEMENT_LINK,
|
|
6
|
+
getAboveNode,
|
|
7
|
+
insertLink,
|
|
8
|
+
isEditorReadOnly,
|
|
9
|
+
setNodes,
|
|
10
|
+
PlateRenderElementProps,
|
|
11
|
+
} from '@udecode/plate';
|
|
12
|
+
import { Button as UIButton, P } from 'components';
|
|
13
|
+
import {
|
|
14
|
+
BUTTON_COMMENT,
|
|
15
|
+
BUTTON_GIFT_SUBSCRIPTION,
|
|
16
|
+
BUTTON_LINK,
|
|
17
|
+
BUTTON_SHARE,
|
|
18
|
+
BUTTON_SHARE_PAGE,
|
|
19
|
+
BUTTON_SPECIAL_OFFER,
|
|
20
|
+
BUTTON_SUBSCRIBE,
|
|
21
|
+
BUTTON_SIGNUP,
|
|
22
|
+
COLORS,
|
|
23
|
+
ELEMENT_BUTTON,
|
|
24
|
+
ELEMENT_PARAGRAPH,
|
|
25
|
+
} from '../constants';
|
|
26
|
+
|
|
27
|
+
export const Button = ({
|
|
28
|
+
attributes,
|
|
29
|
+
className,
|
|
30
|
+
children,
|
|
31
|
+
editor,
|
|
32
|
+
element,
|
|
33
|
+
}: PlateRenderElementProps) => {
|
|
34
|
+
const [state, setState] = React.useState('');
|
|
35
|
+
const { ref: _, ...htmlAttributes } = attributes;
|
|
36
|
+
const { buttonType, color } = element;
|
|
37
|
+
|
|
38
|
+
const currAboveNode = getAboveNode(editor, {
|
|
39
|
+
/**
|
|
40
|
+
* @todo possibly wrong
|
|
41
|
+
*/
|
|
42
|
+
at: editor.prevSelection?.focus,
|
|
43
|
+
match: {
|
|
44
|
+
type: ELEMENT_BUTTON,
|
|
45
|
+
buttonType: buttonType,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const editable = !isEditorReadOnly(editor);
|
|
50
|
+
|
|
51
|
+
const handleColorChange = (color: string) => {
|
|
52
|
+
if (currAboveNode) {
|
|
53
|
+
setNodes(
|
|
54
|
+
editor,
|
|
55
|
+
{
|
|
56
|
+
color,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
at: currAboveNode ? currAboveNode[1] : undefined,
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const ColorMenu = () =>
|
|
66
|
+
!currAboveNode?.[1] || !editable ? null : (
|
|
67
|
+
<ColorMenuComponent contentEditable={false}>
|
|
68
|
+
{COLORS.map((color) => (
|
|
69
|
+
<ColorItem
|
|
70
|
+
$color={color}
|
|
71
|
+
contentEditable={false}
|
|
72
|
+
key={color}
|
|
73
|
+
onClick={(event: React.MouseEvent) => {
|
|
74
|
+
event.stopPropagation();
|
|
75
|
+
handleColorChange(color);
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
))}
|
|
79
|
+
</ColorMenuComponent>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<ButtonWrapper>
|
|
84
|
+
<ColorMenu />
|
|
85
|
+
<UIButton>{children}</UIButton>
|
|
86
|
+
</ButtonWrapper>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const ColorMenuComponent = styled.div`
|
|
91
|
+
display: flex;
|
|
92
|
+
justify-content: center;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 16px;
|
|
95
|
+
z-index: 5;
|
|
96
|
+
position: absolute;
|
|
97
|
+
left: calc(50% - 148px);
|
|
98
|
+
border-radius: 16px;
|
|
99
|
+
padding: 8px 16px;
|
|
100
|
+
box-shadow: var(--elevation-1);
|
|
101
|
+
background-color: var(--color-white);
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
const ColorItem = styled.div<{ $color: string }>`
|
|
105
|
+
width: 24px;
|
|
106
|
+
height: 24px;
|
|
107
|
+
border-radius: 50%;
|
|
108
|
+
background-color: ${({ $color }) => $color};
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const ButtonWrapper = styled.div``;
|
|
113
|
+
|
|
114
|
+
export const createButtonsPlugin = createPluginFactory({
|
|
115
|
+
key: ELEMENT_BUTTON,
|
|
116
|
+
type: ELEMENT_BUTTON,
|
|
117
|
+
isElement: true,
|
|
118
|
+
component: Button,
|
|
119
|
+
then: (editor) => ({
|
|
120
|
+
inject: {
|
|
121
|
+
props: {
|
|
122
|
+
classNames: undefined,
|
|
123
|
+
validTypes: [ELEMENT_PARAGRAPH],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { KEYS_HEADING } from '@udecode/plate';
|
|
2
|
+
import {
|
|
3
|
+
ELEMENT_BLOCKQUOTE,
|
|
4
|
+
ELEMENT_BUTTON,
|
|
5
|
+
ELEMENT_CODE_BLOCK,
|
|
6
|
+
ELEMENT_MEDIA,
|
|
7
|
+
ELEMENT_MEDIA_EMBED,
|
|
8
|
+
ELEMENT_OL,
|
|
9
|
+
ELEMENT_UL,
|
|
10
|
+
} from '../constants';
|
|
11
|
+
|
|
12
|
+
export const exitBreakPlugin = {
|
|
13
|
+
options: {
|
|
14
|
+
rules: [
|
|
15
|
+
{
|
|
16
|
+
hotkey: 'mod+enter',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
hotkey: 'mod+shift+enter',
|
|
20
|
+
before: true,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
hotkey: 'enter',
|
|
24
|
+
query: {
|
|
25
|
+
start: true,
|
|
26
|
+
end: true,
|
|
27
|
+
allow: [
|
|
28
|
+
ELEMENT_BLOCKQUOTE,
|
|
29
|
+
ELEMENT_BUTTON,
|
|
30
|
+
ELEMENT_CODE_BLOCK,
|
|
31
|
+
ELEMENT_MEDIA,
|
|
32
|
+
ELEMENT_MEDIA_EMBED,
|
|
33
|
+
ELEMENT_OL,
|
|
34
|
+
ELEMENT_UL,
|
|
35
|
+
...KEYS_HEADING,
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBlockquotePlugin,
|
|
3
|
+
createCodeBlockPlugin,
|
|
4
|
+
createHeadingPlugin,
|
|
5
|
+
createHorizontalRulePlugin,
|
|
6
|
+
createIndentListPlugin,
|
|
7
|
+
createImagePlugin,
|
|
8
|
+
createMediaEmbedPlugin,
|
|
9
|
+
createResetNodePlugin,
|
|
10
|
+
createTablePlugin,
|
|
11
|
+
createBoldPlugin,
|
|
12
|
+
createCodePlugin,
|
|
13
|
+
createFontBackgroundColorPlugin,
|
|
14
|
+
createFontSizePlugin,
|
|
15
|
+
createFontFamilyPlugin,
|
|
16
|
+
createItalicPlugin,
|
|
17
|
+
createStrikethroughPlugin,
|
|
18
|
+
createSubscriptPlugin,
|
|
19
|
+
createSuperscriptPlugin,
|
|
20
|
+
createUnderlinePlugin,
|
|
21
|
+
createAlignPlugin,
|
|
22
|
+
createIndentPlugin,
|
|
23
|
+
createLineHeightPlugin,
|
|
24
|
+
createLinkPlugin,
|
|
25
|
+
PlateFloatingLink,
|
|
26
|
+
createExitBreakPlugin,
|
|
27
|
+
createHistoryPlugin,
|
|
28
|
+
createSelectOnBackspacePlugin,
|
|
29
|
+
createSoftBreakPlugin,
|
|
30
|
+
} from '@udecode/plate';
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
ELEMENT_PARAGRAPH,
|
|
34
|
+
ELEMENT_H1,
|
|
35
|
+
ELEMENT_H2,
|
|
36
|
+
ELEMENT_H3,
|
|
37
|
+
ELEMENT_H4,
|
|
38
|
+
ELEMENT_H5,
|
|
39
|
+
ELEMENT_H6,
|
|
40
|
+
ELEMENT_CODE_BLOCK,
|
|
41
|
+
} from '../constants';
|
|
42
|
+
|
|
43
|
+
import { createButtonsPlugin } from './buttons';
|
|
44
|
+
import { exitBreakPlugin } from './exitBreak';
|
|
45
|
+
import { resetBlockTypePlugin } from './resetBlockType';
|
|
46
|
+
import { selectOnBackspace } from './selectOnBackspace';
|
|
47
|
+
import { softBreakPlugin } from './softBreak';
|
|
48
|
+
import {
|
|
49
|
+
createParagraphPlugin,
|
|
50
|
+
createHOnePlugin,
|
|
51
|
+
createHTwoPlugin,
|
|
52
|
+
createHThreePlugin,
|
|
53
|
+
createHFourPlugin,
|
|
54
|
+
createHFivePlugin,
|
|
55
|
+
createHSixPlugin,
|
|
56
|
+
} from './text';
|
|
57
|
+
|
|
58
|
+
export const blocks = [
|
|
59
|
+
createButtonsPlugin(),
|
|
60
|
+
createBlockquotePlugin(),
|
|
61
|
+
createCodeBlockPlugin(),
|
|
62
|
+
createHeadingPlugin(),
|
|
63
|
+
createHorizontalRulePlugin(),
|
|
64
|
+
createImagePlugin({
|
|
65
|
+
options: {
|
|
66
|
+
disableUploadInsert: true,
|
|
67
|
+
disableCaption: true,
|
|
68
|
+
disableEmbedInsert: true,
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
createIndentListPlugin({
|
|
72
|
+
inject: {
|
|
73
|
+
props: {
|
|
74
|
+
validTypes: [
|
|
75
|
+
ELEMENT_PARAGRAPH,
|
|
76
|
+
ELEMENT_H1,
|
|
77
|
+
ELEMENT_H2,
|
|
78
|
+
ELEMENT_H3,
|
|
79
|
+
ELEMENT_H4,
|
|
80
|
+
ELEMENT_H5,
|
|
81
|
+
ELEMENT_H6,
|
|
82
|
+
ELEMENT_CODE_BLOCK,
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
// createFootnotePlugin(),
|
|
88
|
+
createMediaEmbedPlugin(),
|
|
89
|
+
createParagraphPlugin(),
|
|
90
|
+
createHOnePlugin(),
|
|
91
|
+
createHTwoPlugin(),
|
|
92
|
+
createHThreePlugin(),
|
|
93
|
+
createHFourPlugin(),
|
|
94
|
+
createHFivePlugin(),
|
|
95
|
+
createHSixPlugin(),
|
|
96
|
+
// createPaywallPlugin(),
|
|
97
|
+
// createQAPlugin(),
|
|
98
|
+
createResetNodePlugin(),
|
|
99
|
+
createTablePlugin(),
|
|
100
|
+
// createVideoPlugin(),
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
export const marks = [
|
|
104
|
+
createBoldPlugin(),
|
|
105
|
+
createCodePlugin(),
|
|
106
|
+
createFontBackgroundColorPlugin(),
|
|
107
|
+
createFontSizePlugin(),
|
|
108
|
+
createFontFamilyPlugin(),
|
|
109
|
+
createItalicPlugin(),
|
|
110
|
+
createStrikethroughPlugin(),
|
|
111
|
+
createSubscriptPlugin(),
|
|
112
|
+
createSuperscriptPlugin(),
|
|
113
|
+
createUnderlinePlugin(),
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
export const inline = [
|
|
117
|
+
createAlignPlugin({
|
|
118
|
+
inject: {
|
|
119
|
+
props: {
|
|
120
|
+
validTypes: [
|
|
121
|
+
ELEMENT_PARAGRAPH,
|
|
122
|
+
ELEMENT_H1,
|
|
123
|
+
ELEMENT_H2,
|
|
124
|
+
ELEMENT_H3,
|
|
125
|
+
ELEMENT_H4,
|
|
126
|
+
ELEMENT_H5,
|
|
127
|
+
ELEMENT_H6,
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
createIndentPlugin({
|
|
133
|
+
inject: {
|
|
134
|
+
props: {
|
|
135
|
+
validTypes: [ELEMENT_PARAGRAPH, ELEMENT_H1],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
createLineHeightPlugin(),
|
|
140
|
+
createLinkPlugin({
|
|
141
|
+
renderAfterEditable: PlateFloatingLink,
|
|
142
|
+
}),
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
export const utils = [
|
|
146
|
+
createExitBreakPlugin(exitBreakPlugin),
|
|
147
|
+
createHistoryPlugin(),
|
|
148
|
+
createResetNodePlugin(resetBlockTypePlugin),
|
|
149
|
+
createSelectOnBackspacePlugin(selectOnBackspace),
|
|
150
|
+
createSoftBreakPlugin(softBreakPlugin),
|
|
151
|
+
];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isBlockAboveEmpty, isSelectionAtBlockStart } from '@udecode/plate';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ELEMENT_BLOCKQUOTE,
|
|
5
|
+
ELEMENT_PARAGRAPH,
|
|
6
|
+
ELEMENT_TODO_LI,
|
|
7
|
+
} from '../constants';
|
|
8
|
+
|
|
9
|
+
const resetBlockTypesCommonRule = {
|
|
10
|
+
types: [ELEMENT_BLOCKQUOTE, ELEMENT_TODO_LI],
|
|
11
|
+
defaultType: ELEMENT_PARAGRAPH,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const resetBlockTypePlugin = {
|
|
15
|
+
options: {
|
|
16
|
+
rules: [
|
|
17
|
+
{
|
|
18
|
+
...resetBlockTypesCommonRule,
|
|
19
|
+
hotkey: 'Enter',
|
|
20
|
+
predicate: isBlockAboveEmpty,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
...resetBlockTypesCommonRule,
|
|
24
|
+
hotkey: 'Backspace',
|
|
25
|
+
predicate: isSelectionAtBlockStart,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ELEMENT_BLOCKQUOTE,
|
|
3
|
+
ELEMENT_CODE_BLOCK,
|
|
4
|
+
ELEMENT_TD,
|
|
5
|
+
} from '@udecode/plate';
|
|
6
|
+
|
|
7
|
+
export const softBreakPlugin = {
|
|
8
|
+
options: {
|
|
9
|
+
rules: [
|
|
10
|
+
{ hotkey: 'shift+enter' },
|
|
11
|
+
{
|
|
12
|
+
hotkey: 'enter',
|
|
13
|
+
query: {
|
|
14
|
+
allow: [ELEMENT_CODE_BLOCK, ELEMENT_BLOCKQUOTE, ELEMENT_TD],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PlateRenderElementProps, createPluginFactory } from '@udecode/plate';
|
|
3
|
+
import { H, P } from 'components';
|
|
4
|
+
import * as Elements from '../constants';
|
|
5
|
+
|
|
6
|
+
const ParagraphElement = ({
|
|
7
|
+
attributes,
|
|
8
|
+
nodeProps,
|
|
9
|
+
children,
|
|
10
|
+
}: PlateRenderElementProps) => {
|
|
11
|
+
return (
|
|
12
|
+
<P {...attributes} {...nodeProps}>
|
|
13
|
+
{children}
|
|
14
|
+
</P>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const createParagraphPlugin = createPluginFactory({
|
|
19
|
+
key: Elements.ELEMENT_PARAGRAPH,
|
|
20
|
+
type: Elements.ELEMENT_PARAGRAPH,
|
|
21
|
+
isElement: true,
|
|
22
|
+
component: ParagraphElement,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const HOneElement = ({
|
|
26
|
+
attributes,
|
|
27
|
+
nodeProps,
|
|
28
|
+
children,
|
|
29
|
+
}: PlateRenderElementProps) => {
|
|
30
|
+
return (
|
|
31
|
+
<H.One {...attributes} {...nodeProps}>
|
|
32
|
+
{children}
|
|
33
|
+
</H.One>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const createHOnePlugin = createPluginFactory({
|
|
38
|
+
key: Elements.ELEMENT_H1,
|
|
39
|
+
type: Elements.ELEMENT_H1,
|
|
40
|
+
isElement: true,
|
|
41
|
+
component: HOneElement,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const HTwoElement = ({
|
|
45
|
+
attributes,
|
|
46
|
+
nodeProps,
|
|
47
|
+
children,
|
|
48
|
+
}: PlateRenderElementProps) => {
|
|
49
|
+
return (
|
|
50
|
+
<H.Two {...attributes} {...nodeProps}>
|
|
51
|
+
{children}
|
|
52
|
+
</H.Two>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const createHTwoPlugin = createPluginFactory({
|
|
57
|
+
key: Elements.ELEMENT_H2,
|
|
58
|
+
type: Elements.ELEMENT_H2,
|
|
59
|
+
isElement: true,
|
|
60
|
+
component: HTwoElement,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const HThreeElement = ({
|
|
64
|
+
attributes,
|
|
65
|
+
nodeProps,
|
|
66
|
+
children,
|
|
67
|
+
}: PlateRenderElementProps) => {
|
|
68
|
+
return (
|
|
69
|
+
<H.Three {...attributes} {...nodeProps}>
|
|
70
|
+
{children}
|
|
71
|
+
</H.Three>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const createHThreePlugin = createPluginFactory({
|
|
76
|
+
key: Elements.ELEMENT_H3,
|
|
77
|
+
type: Elements.ELEMENT_H3,
|
|
78
|
+
isElement: true,
|
|
79
|
+
component: HThreeElement,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const HFourElement = ({
|
|
83
|
+
attributes,
|
|
84
|
+
nodeProps,
|
|
85
|
+
children,
|
|
86
|
+
}: PlateRenderElementProps) => {
|
|
87
|
+
return (
|
|
88
|
+
<H.Four {...attributes} {...nodeProps}>
|
|
89
|
+
{children}
|
|
90
|
+
</H.Four>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const createHFourPlugin = createPluginFactory({
|
|
95
|
+
key: Elements.ELEMENT_H4,
|
|
96
|
+
type: Elements.ELEMENT_H4,
|
|
97
|
+
isElement: true,
|
|
98
|
+
component: HFourElement,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const HFiveElement = ({
|
|
102
|
+
attributes,
|
|
103
|
+
nodeProps,
|
|
104
|
+
children,
|
|
105
|
+
}: PlateRenderElementProps) => {
|
|
106
|
+
return (
|
|
107
|
+
<H.Five {...attributes} {...nodeProps}>
|
|
108
|
+
{children}
|
|
109
|
+
</H.Five>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const createHFivePlugin = createPluginFactory({
|
|
114
|
+
key: Elements.ELEMENT_H5,
|
|
115
|
+
type: Elements.ELEMENT_H5,
|
|
116
|
+
isElement: true,
|
|
117
|
+
component: HFiveElement,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const HSixElement = ({
|
|
121
|
+
attributes,
|
|
122
|
+
nodeProps,
|
|
123
|
+
children,
|
|
124
|
+
}: PlateRenderElementProps) => {
|
|
125
|
+
return (
|
|
126
|
+
<H.Six {...attributes} {...nodeProps}>
|
|
127
|
+
{children}
|
|
128
|
+
</H.Six>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const createHSixPlugin = createPluginFactory({
|
|
133
|
+
key: Elements.ELEMENT_H6,
|
|
134
|
+
type: Elements.ELEMENT_H6,
|
|
135
|
+
isElement: true,
|
|
136
|
+
component: HSixElement,
|
|
137
|
+
});
|