@mirrormedia/lilith-draft-editor 1.0.0-beta

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.
Files changed (105) hide show
  1. package/lib/draft-js/block-renderer/background-image-block.tsx +113 -0
  2. package/lib/draft-js/block-renderer/background-video-block.tsx +120 -0
  3. package/lib/draft-js/block-renderer/color-box-block.tsx +85 -0
  4. package/lib/draft-js/block-renderer/divider-block.tsx +12 -0
  5. package/lib/draft-js/block-renderer/embedded-code-block.tsx +65 -0
  6. package/lib/draft-js/block-renderer/image-block.tsx +41 -0
  7. package/lib/draft-js/block-renderer/info-box-block.tsx +85 -0
  8. package/lib/draft-js/block-renderer/media-block.tsx +36 -0
  9. package/lib/draft-js/block-renderer/related-post-block.tsx +47 -0
  10. package/lib/draft-js/block-renderer/side-index-block.tsx +113 -0
  11. package/lib/draft-js/block-renderer/slideshow-block.tsx +62 -0
  12. package/lib/draft-js/block-renderer/table-block.tsx +488 -0
  13. package/lib/draft-js/buttons/annotation.tsx +113 -0
  14. package/lib/draft-js/buttons/background-color.tsx +125 -0
  15. package/lib/draft-js/buttons/background-image.tsx +276 -0
  16. package/lib/draft-js/buttons/background-video.tsx +275 -0
  17. package/lib/draft-js/buttons/color-box.tsx +207 -0
  18. package/lib/draft-js/buttons/divider.tsx +56 -0
  19. package/lib/draft-js/buttons/embedded-code.tsx +126 -0
  20. package/lib/draft-js/buttons/enlarge.tsx +11 -0
  21. package/lib/draft-js/buttons/font-color.tsx +113 -0
  22. package/lib/draft-js/buttons/image.tsx +71 -0
  23. package/lib/draft-js/buttons/info-box.tsx +170 -0
  24. package/lib/draft-js/buttons/link.tsx +103 -0
  25. package/lib/draft-js/buttons/media.tsx +120 -0
  26. package/lib/draft-js/buttons/related-post.tsx +81 -0
  27. package/lib/draft-js/buttons/selector/align-selector.tsx +65 -0
  28. package/lib/draft-js/buttons/selector/image-selector.tsx +485 -0
  29. package/lib/draft-js/buttons/selector/pagination.tsx +83 -0
  30. package/lib/draft-js/buttons/selector/post-selector.tsx +367 -0
  31. package/lib/draft-js/buttons/selector/search-box.tsx +39 -0
  32. package/lib/draft-js/buttons/selector/video-selector.tsx +312 -0
  33. package/lib/draft-js/buttons/side-index.tsx +257 -0
  34. package/lib/draft-js/buttons/slideshow.tsx +81 -0
  35. package/lib/draft-js/buttons/table.tsx +63 -0
  36. package/lib/draft-js/buttons/text-align.tsx +88 -0
  37. package/lib/draft-js/editor/basic-editor.tsx +384 -0
  38. package/lib/draft-js/editor/block-redender-fn.tsx +77 -0
  39. package/lib/draft-js/editor/draft-converter/api-data-instance.js +58 -0
  40. package/lib/draft-js/editor/draft-converter/atomic-block-processor.js +233 -0
  41. package/lib/draft-js/editor/draft-converter/entities.js +76 -0
  42. package/lib/draft-js/editor/draft-converter/index.js +201 -0
  43. package/lib/draft-js/editor/draft-converter/inline-styles-processor.js +238 -0
  44. package/lib/draft-js/editor/entity-decorator.tsx +7 -0
  45. package/lib/draft-js/editor/modifier.tsx +71 -0
  46. package/lib/draft-js/entity-decorator/annotation-decorator.tsx +81 -0
  47. package/lib/draft-js/entity-decorator/link-decorator.tsx +27 -0
  48. package/lib/index.js +31 -0
  49. package/lib/website/mirrormedia/custom/block-renderer/background-image-block.tsx +128 -0
  50. package/lib/website/mirrormedia/custom/block-renderer/background-video-block.tsx +135 -0
  51. package/lib/website/mirrormedia/custom/block-renderer/color-box-block.tsx +98 -0
  52. package/lib/website/mirrormedia/custom/block-renderer/divider-block.tsx +12 -0
  53. package/lib/website/mirrormedia/custom/block-renderer/embedded-code-block.tsx +65 -0
  54. package/lib/website/mirrormedia/custom/block-renderer/image-block.tsx +41 -0
  55. package/lib/website/mirrormedia/custom/block-renderer/info-box-block.tsx +98 -0
  56. package/lib/website/mirrormedia/custom/block-renderer/media-block.tsx +36 -0
  57. package/lib/website/mirrormedia/custom/block-renderer/related-post-block.tsx +47 -0
  58. package/lib/website/mirrormedia/custom/block-renderer/side-index-block.tsx +125 -0
  59. package/lib/website/mirrormedia/custom/block-renderer/slideshow-block.tsx +62 -0
  60. package/lib/website/mirrormedia/custom/block-renderer/table-block.tsx +537 -0
  61. package/lib/website/mirrormedia/custom/entity-decorator/annotation-decorator.tsx +81 -0
  62. package/lib/website/mirrormedia/custom/entity-decorator/link-decorator.tsx +27 -0
  63. package/lib/website/mirrormedia/custom/selector/align-selector.tsx +65 -0
  64. package/lib/website/mirrormedia/custom/selector/image-selector.tsx +485 -0
  65. package/lib/website/mirrormedia/custom/selector/pagination.tsx +83 -0
  66. package/lib/website/mirrormedia/custom/selector/post-selector.tsx +367 -0
  67. package/lib/website/mirrormedia/custom/selector/search-box.tsx +39 -0
  68. package/lib/website/mirrormedia/custom/selector/video-selector.tsx +310 -0
  69. package/lib/website/mirrormedia/draft-editor/block-redender-fn.tsx +77 -0
  70. package/lib/website/mirrormedia/draft-editor/entity-decorator.tsx +7 -0
  71. package/lib/website/mirrormedia/draft-editor/index.tsx +909 -0
  72. package/lib/website/mirrormedia/draft-renderer/block-redender-fn.tsx +77 -0
  73. package/lib/website/mirrormedia/draft-renderer/entity-decorator.tsx +7 -0
  74. package/lib/website/mirrormedia/draft-renderer/index-deprecated.tsx +43 -0
  75. package/lib/website/mirrormedia/draft-renderer/index.tsx +150 -0
  76. package/lib/website/mirrormedia/index.js +19 -0
  77. package/lib/website/readr/custom/block-renderer/background-image-block.tsx +128 -0
  78. package/lib/website/readr/custom/block-renderer/background-video-block.tsx +135 -0
  79. package/lib/website/readr/custom/block-renderer/color-box-block.tsx +98 -0
  80. package/lib/website/readr/custom/block-renderer/divider-block.tsx +12 -0
  81. package/lib/website/readr/custom/block-renderer/embedded-code-block.tsx +65 -0
  82. package/lib/website/readr/custom/block-renderer/image-block.tsx +41 -0
  83. package/lib/website/readr/custom/block-renderer/info-box-block.tsx +98 -0
  84. package/lib/website/readr/custom/block-renderer/media-block.tsx +36 -0
  85. package/lib/website/readr/custom/block-renderer/related-post-block.tsx +47 -0
  86. package/lib/website/readr/custom/block-renderer/side-index-block.tsx +125 -0
  87. package/lib/website/readr/custom/block-renderer/slideshow-block.tsx +62 -0
  88. package/lib/website/readr/custom/block-renderer/table-block.tsx +537 -0
  89. package/lib/website/readr/custom/entity-decorator/annotation-decorator.tsx +81 -0
  90. package/lib/website/readr/custom/entity-decorator/link-decorator.tsx +27 -0
  91. package/lib/website/readr/custom/selector/align-selector.tsx +65 -0
  92. package/lib/website/readr/custom/selector/image-selector.tsx +485 -0
  93. package/lib/website/readr/custom/selector/pagination.tsx +83 -0
  94. package/lib/website/readr/custom/selector/post-selector.tsx +367 -0
  95. package/lib/website/readr/custom/selector/search-box.tsx +39 -0
  96. package/lib/website/readr/custom/selector/video-selector.tsx +310 -0
  97. package/lib/website/readr/draft-editor/block-redender-fn.tsx +77 -0
  98. package/lib/website/readr/draft-editor/entity-decorator.tsx +7 -0
  99. package/lib/website/readr/draft-editor/index.tsx +909 -0
  100. package/lib/website/readr/draft-renderer/block-redender-fn.tsx +77 -0
  101. package/lib/website/readr/draft-renderer/entity-decorator.tsx +7 -0
  102. package/lib/website/readr/draft-renderer/index-deprecated.tsx +43 -0
  103. package/lib/website/readr/draft-renderer/index.tsx +150 -0
  104. package/lib/website/readr/index.js +19 -0
  105. package/package.json +39 -0
@@ -0,0 +1,909 @@
1
+ import React from 'react'
2
+ import styled, { css } from 'styled-components'
3
+
4
+ import {
5
+ Editor,
6
+ EditorState,
7
+ KeyBindingUtil,
8
+ RichUtils,
9
+ getDefaultKeyBinding,
10
+ } from 'draft-js'
11
+
12
+ import { atomicBlockRenderer } from './block-redender-fn'
13
+ import decorators from './entity-decorator'
14
+ import { AnnotationButton } from '../../../draft-js/buttons/annotation'
15
+ import { EmbeddedCodeButton } from '../../../draft-js/buttons/embedded-code'
16
+ import { EnlargeButton } from '../../../draft-js/buttons/enlarge'
17
+ import { ImageButton } from '../../../draft-js/buttons/image'
18
+ import { InfoBoxButton } from '../../../draft-js/buttons/info-box'
19
+ import { LinkButton } from '../../../draft-js/buttons/link'
20
+ import { SlideshowButton } from '../../../draft-js/buttons/slideshow'
21
+ import { TableButton } from '../../../draft-js/buttons/table'
22
+ import { DividerButton } from '../../../draft-js/buttons/divider'
23
+ import { ColorBoxButton } from '../../../draft-js/buttons/color-box'
24
+ import { BGImageButton } from '../../../draft-js/buttons/background-image'
25
+ import { BGVideoButton } from '../../../draft-js/buttons/background-video'
26
+ import { RelatedPostButton } from '../../../draft-js/buttons/related-post'
27
+ import { SideIndexButton } from '../../../draft-js/buttons/side-index'
28
+ import {
29
+ AlignCenterButton,
30
+ AlignLeftButton,
31
+ } from '../../../draft-js/buttons/text-align'
32
+ import { FontColorButton } from '../../../draft-js/buttons/font-color'
33
+ import { BackgroundColorButton } from '../../../draft-js/buttons/background-color'
34
+
35
+ import { CUSTOM_STYLE_PREFIX_FONT_COLOR } from '../../../draft-js/buttons/font-color'
36
+ import { CUSTOM_STYLE_PREFIX_BACKGROUND_COLOR } from '../../../draft-js/buttons/background-color'
37
+ import { getSelectionBlockData } from '../../../draft-js/buttons/text-align'
38
+ import { ImageSelector } from '../custom/selector/image-selector'
39
+ import { VideoSelector } from '../custom/selector/video-selector'
40
+ import { PostSelector } from '../custom/selector/post-selector'
41
+
42
+ const buttonStyle = css`
43
+ border-radius: 6px;
44
+ text-align: center;
45
+ font-size: 1rem;
46
+ padding: 0 12px;
47
+ margin: 0px 0px 10px 0px;
48
+ background-color: #fff;
49
+ border: solid 1px rgb(193, 199, 208);
50
+ display: inline-flex;
51
+ align-items: center;
52
+ height: 40px;
53
+
54
+ cursor: ${(props) => {
55
+ if (props.readOnly) {
56
+ return 'not-allowed'
57
+ }
58
+ return 'pointer'
59
+ }};
60
+ color: ${(props) => {
61
+ if (props.readOnly) {
62
+ return '#c1c7d0'
63
+ }
64
+ if (props.isActive) {
65
+ return '#3b82f6'
66
+ }
67
+ return '#6b7280'
68
+ }};
69
+ border: solid 1px
70
+ ${(props) => {
71
+ if (props.readOnly) {
72
+ return '#c1c7d0'
73
+ }
74
+ if (props.isActive) {
75
+ return '#3b82f6'
76
+ }
77
+ return '#6b7280'
78
+ }};
79
+ box-shadow: ${(props) => {
80
+ if (props.readOnly) {
81
+ return 'unset'
82
+ }
83
+ if (props.isActive) {
84
+ return 'rgba(38, 132, 255, 20%) 0px 1px 4px '
85
+ }
86
+ return 'unset'
87
+ }};
88
+ transition: box-shadow 100ms linear;
89
+ `
90
+
91
+ const longFormButtonStyle = css`
92
+ ${buttonStyle}
93
+ color: ${(props) => {
94
+ if (props.readOnly) {
95
+ return '#c1c7d0'
96
+ }
97
+ if (props.isActive) {
98
+ return '#ED8B00'
99
+ }
100
+ return '#6b7280'
101
+ }};
102
+ border: solid 1px
103
+ ${(props) => {
104
+ if (props.readOnly) {
105
+ return '#c1c7d0'
106
+ }
107
+ if (props.isActive) {
108
+ return '#ED8B00'
109
+ }
110
+ return '#FECC85'
111
+ }};
112
+ box-shadow: ${(props) => {
113
+ if (props.readOnly) {
114
+ return 'unset'
115
+ }
116
+ if (props.isActive) {
117
+ return 'rgba(237, 139, 0, 0.5) 0px 1px 4px'
118
+ }
119
+ return 'unset'
120
+ }};
121
+ `
122
+
123
+ const CustomFontColorButton = styled(FontColorButton)<{
124
+ isActive: boolean
125
+ readOnly: boolean
126
+ }>`
127
+ ${buttonStyle}
128
+ `
129
+
130
+ const CustomBackgroundColorButton = styled(BackgroundColorButton)<{
131
+ isActive: boolean
132
+ readOnly: boolean
133
+ }>`
134
+ ${longFormButtonStyle}
135
+ `
136
+
137
+ const CustomButton = styled.div<{ isActive: boolean; readOnly: boolean }>`
138
+ ${buttonStyle}
139
+ `
140
+
141
+ const CustomAnnotationButton = styled(AnnotationButton)<{
142
+ isActive: boolean
143
+ readOnly: boolean
144
+ }>`
145
+ ${buttonStyle}
146
+ `
147
+
148
+ const CustomLinkButton = styled(LinkButton)<{
149
+ isActive: boolean
150
+ readOnly: boolean
151
+ }>`
152
+ ${buttonStyle}
153
+ `
154
+
155
+ function createButtonWithoutProps(
156
+ referenceComponent,
157
+ isLongForm = false,
158
+ additionalCSS = ``
159
+ ) {
160
+ return isLongForm
161
+ ? styled(referenceComponent)`
162
+ ${longFormButtonStyle}
163
+ ${additionalCSS}
164
+ `
165
+ : styled(referenceComponent)`
166
+ ${buttonStyle}
167
+ ${additionalCSS}
168
+ `
169
+ }
170
+ const CustomEnlargeButton = createButtonWithoutProps(
171
+ EnlargeButton,
172
+ false,
173
+ `color: #999`
174
+ )
175
+ const CustomImageButton = createButtonWithoutProps(ImageButton)
176
+ const CustomSlideshowButton = createButtonWithoutProps(SlideshowButton)
177
+ const CustomEmbeddedCodeButton = createButtonWithoutProps(EmbeddedCodeButton)
178
+ const CustomTableButton = createButtonWithoutProps(TableButton)
179
+ const CustomInfoBoxButton = createButtonWithoutProps(InfoBoxButton)
180
+ const CustomDividerButton = createButtonWithoutProps(DividerButton)
181
+ const CustomColorBoxButton = createButtonWithoutProps(ColorBoxButton, true)
182
+ const CustomBGImageButton = createButtonWithoutProps(BGImageButton, true)
183
+ const CustomBGVideoButton = createButtonWithoutProps(BGVideoButton, true)
184
+ const CustomRelatedPostButton = createButtonWithoutProps(
185
+ RelatedPostButton,
186
+ true
187
+ )
188
+ const CustomSideIndexButton = createButtonWithoutProps(SideIndexButton, true)
189
+ const CustomAlignCenterButton = createButtonWithoutProps(
190
+ AlignCenterButton,
191
+ true
192
+ )
193
+ const CustomAlignLeftButton = createButtonWithoutProps(AlignLeftButton, true)
194
+
195
+ const DraftEditorWrapper = styled.div`
196
+ /* Rich-editor default setting (.RichEditor-root)*/
197
+ background: #fff;
198
+ border: 1px solid #ddd;
199
+ font-family: 'Georgia', serif;
200
+ font-size: 14px;
201
+ padding: 15px;
202
+
203
+ /* Custom setting */
204
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
205
+ 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
206
+ 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
207
+ width: 100%;
208
+ height: 100%;
209
+ background: rgb(255, 255, 255);
210
+ border-radius: 6px;
211
+ padding: 0 1rem 1rem;
212
+
213
+ /* Draft built-in buttons' style */
214
+ .public-DraftStyleDefault-header-two {
215
+ }
216
+ .public-DraftStyleDefault-header-three {
217
+ }
218
+ .public-DraftStyleDefault-header-four {
219
+ }
220
+ .public-DraftStyleDefault-blockquote {
221
+ }
222
+ .public-DraftStyleDefault-ul {
223
+ }
224
+ .public-DraftStyleDefault-unorderedListItem {
225
+ }
226
+ .public-DraftStyleDefault-ol {
227
+ }
228
+ .public-DraftStyleDefault-orderedListItem {
229
+ }
230
+ /* code-block */
231
+ .public-DraftStyleDefault-pre {
232
+ }
233
+ .alignCenter * {
234
+ text-align: center;
235
+ }
236
+ .alignLeft * {
237
+ text-align: left;
238
+ }
239
+ `
240
+
241
+ const DraftEditorControls = styled.div`
242
+ padding-top: 1rem;
243
+ width: 100%;
244
+ background: rgb(255, 255, 255);
245
+ `
246
+
247
+ const DraftEditorControlsWrapper = styled.div`
248
+ width: 100%;
249
+ position: relative;
250
+ display: flex;
251
+ flex-direction: row;
252
+ flex-wrap: wrap;
253
+ padding-right: 45px;
254
+ `
255
+
256
+ const TextEditorWrapper = styled.div`
257
+ /* Rich-editor default setting (.RichEditor-editor)*/
258
+ border-top: 1px solid #ddd;
259
+ cursor: text;
260
+ font-size: 16px;
261
+ margin-top: 10px;
262
+ /* Custom setting */
263
+ h2 {
264
+ font-size: 22px;
265
+ }
266
+ h3 {
267
+ font-size: 17.5px;
268
+ }
269
+ font-weight: normal;
270
+ max-width: 800px;
271
+
272
+ // atimoic block float setting
273
+ display: flow-root;
274
+ figure {
275
+ clear: both;
276
+ margin: 0;
277
+ }
278
+ figure.left {
279
+ float: left;
280
+ width: 33%;
281
+ }
282
+ figure.right {
283
+ float: right;
284
+ width: 33%;
285
+ }
286
+ `
287
+
288
+ const DraftEditorContainer = styled.div<{ isEnlarged: boolean }>`
289
+ position: relative;
290
+ margin-top: 4px;
291
+ ${(props) =>
292
+ props.isEnlarged
293
+ ? css`
294
+ position: fixed;
295
+ width: 100%;
296
+ height: 100%;
297
+ top: 0;
298
+ left: 0;
299
+ z-index: 30;
300
+ padding-left: 3em;
301
+ padding-right: 3em;
302
+ background: rgba(0, 0, 0, 0.5);
303
+ `
304
+ : ''}
305
+ ${DraftEditorWrapper} {
306
+ ${(props) =>
307
+ props.isEnlarged
308
+ ? css`
309
+ width: 100%;
310
+ height: 100%;
311
+ padding: 0 1rem 0;
312
+ overflow: scroll;
313
+ `
314
+ : ''}
315
+ }
316
+ ${DraftEditorControls} {
317
+ ${(props) =>
318
+ props.isEnlarged
319
+ ? css`
320
+ position: sticky;
321
+ top: 0;
322
+ z-index: 12;
323
+ `
324
+ : ''}
325
+ }
326
+ ${DraftEditorControlsWrapper} {
327
+ ${(props) =>
328
+ props.isEnlarged
329
+ ? css`
330
+ overflow: auto;
331
+ padding-bottom: 0;
332
+ `
333
+ : ''}
334
+ }
335
+ ${TextEditorWrapper} {
336
+ ${(props) =>
337
+ props.isEnlarged
338
+ ? css`
339
+ max-width: 100%;
340
+ min-height: 100vh;
341
+ padding-bottom: 0;
342
+ `
343
+ : ''}
344
+ }
345
+ `
346
+
347
+ const ButtonGroup = styled.div`
348
+ margin: 0 10px 0 0;
349
+ `
350
+ const EnlargeButtonWrapper = styled.div`
351
+ position: absolute;
352
+ top: 0;
353
+ right: 0;
354
+ margin: 0;
355
+ `
356
+
357
+ type RichTextEditorProps = {
358
+ onChange: (editorState) => void
359
+ editorState: EditorState
360
+ }
361
+ type State = {
362
+ isEnlarged?: boolean
363
+ readOnly?: boolean
364
+ }
365
+
366
+ class RichTextEditor extends React.Component<RichTextEditorProps, State> {
367
+ customStyleMap: {
368
+ CODE: {
369
+ backgroundColor: string
370
+ fontFamily: string
371
+ fontSize: number
372
+ padding: number
373
+ }
374
+ }
375
+ constructor(props) {
376
+ super(props)
377
+ this.state = {
378
+ isEnlarged: false,
379
+ readOnly: false,
380
+ }
381
+ // Custom overrides for "code" style.
382
+ this.customStyleMap = {
383
+ CODE: {
384
+ backgroundColor: 'rgba(0, 0, 0, 0.05)',
385
+ fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
386
+ fontSize: 16,
387
+ padding: 2,
388
+ },
389
+ }
390
+ }
391
+ onChange = (editorState) => {
392
+ this.props.onChange(editorState)
393
+ }
394
+
395
+ handleKeyCommand = (command, editorState) => {
396
+ const newState = RichUtils.handleKeyCommand(editorState, command)
397
+ if (newState) {
398
+ this.onChange(newState)
399
+ return true
400
+ }
401
+ return false
402
+ }
403
+
404
+ handleReturn = (event) => {
405
+ if (KeyBindingUtil.isSoftNewlineEvent(event)) {
406
+ const { onChange, editorState } = this.props
407
+ onChange(RichUtils.insertSoftNewline(editorState))
408
+ return 'handled'
409
+ }
410
+
411
+ return 'not-handled'
412
+ }
413
+
414
+ mapKeyToEditorCommand = (e) => {
415
+ if (e.keyCode === 9 /* TAB */) {
416
+ const newEditorState = RichUtils.onTab(
417
+ e,
418
+ this.props.editorState,
419
+ 4 /* maxDepth */
420
+ )
421
+ if (newEditorState !== this.props.editorState) {
422
+ this.onChange(newEditorState)
423
+ }
424
+ return
425
+ }
426
+ return getDefaultKeyBinding(e)
427
+ }
428
+
429
+ toggleBlockType = (blockType) => {
430
+ this.onChange(RichUtils.toggleBlockType(this.props.editorState, blockType))
431
+ }
432
+
433
+ toggleInlineStyle = (inlineStyle) => {
434
+ this.onChange(
435
+ RichUtils.toggleInlineStyle(this.props.editorState, inlineStyle)
436
+ )
437
+ }
438
+
439
+ getEntityType = (editorState) => {
440
+ const contentState = editorState.getCurrentContent()
441
+ const selection = editorState.getSelection()
442
+ const startOffset = selection.getStartOffset()
443
+ const startBlock = editorState
444
+ .getCurrentContent()
445
+ .getBlockForKey(selection.getStartKey())
446
+
447
+ const endOffset = selection.getEndOffset()
448
+ let entityInstance
449
+ let entityKey
450
+
451
+ if (!selection.isCollapsed()) {
452
+ entityKey = startBlock.getEntityAt(startOffset)
453
+ } else {
454
+ entityKey = startBlock.getEntityAt(endOffset)
455
+ }
456
+
457
+ if (entityKey !== null) {
458
+ entityInstance = contentState.getEntity(entityKey)
459
+ }
460
+
461
+ let entityType = ''
462
+ if (entityInstance) {
463
+ entityType = entityInstance.getType()
464
+ }
465
+
466
+ return entityType
467
+ }
468
+
469
+ getCustomStyle = (style) => {
470
+ return style.reduce((styles, styleName) => {
471
+ if (styleName?.startsWith(CUSTOM_STYLE_PREFIX_FONT_COLOR)) {
472
+ styles['color'] = styleName.split(CUSTOM_STYLE_PREFIX_FONT_COLOR)[1]
473
+ }
474
+ if (styleName?.startsWith(CUSTOM_STYLE_PREFIX_BACKGROUND_COLOR)) {
475
+ styles['backgroundColor'] = styleName.split(
476
+ CUSTOM_STYLE_PREFIX_BACKGROUND_COLOR
477
+ )[1]
478
+ }
479
+ return styles
480
+ }, {})
481
+ }
482
+
483
+ toggleEnlarge = () => {
484
+ this.setState({ isEnlarged: !this.state.isEnlarged })
485
+ }
486
+
487
+ customStyleFn = (style) => {
488
+ return this.getCustomStyle(style)
489
+ }
490
+
491
+ blockStyleFn(block) {
492
+ const { editorState } = this.props
493
+
494
+ const entityKey = block.getEntityAt(0)
495
+ const entity = entityKey
496
+ ? editorState.getCurrentContent().getEntity(entityKey)
497
+ : null
498
+
499
+ let result = ''
500
+ const blockData = block.getData()
501
+ if (blockData) {
502
+ const textAlign = blockData?.get('textAlign')
503
+ if (textAlign === 'center') {
504
+ result += 'alignCenter '
505
+ } else if (textAlign === 'left') {
506
+ result += 'alignLeft '
507
+ }
508
+ }
509
+
510
+ switch (block.getType()) {
511
+ case 'header-two':
512
+ case 'header-three':
513
+ case 'header-four':
514
+ case 'blockquote':
515
+ result += 'public-DraftStyleDefault-' + block.getType()
516
+ break
517
+ case 'atomic':
518
+ if (entity.getData()?.alignment) {
519
+ // support all atomic block to set alignment
520
+ result += ' ' + entity.getData().alignment
521
+ }
522
+ break
523
+ default:
524
+ break
525
+ }
526
+ return result
527
+ }
528
+
529
+ blockRendererFn = (block) => {
530
+ const atomicBlockObj = atomicBlockRenderer(block)
531
+ if (atomicBlockObj) {
532
+ const onEditStart = () => {
533
+ this.setState({
534
+ // If custom block renderer requires mouse interaction,
535
+ // [Draft.js document](https://draftjs.org/docs/advanced-topics-block-components#recommendations-and-other-notes)
536
+ // suggests that we should temporarily set Editor
537
+ // to readOnly={true} during the interaction.
538
+ // In readOnly={true} condition, the user does not
539
+ // trigger any selection changes within the editor
540
+ // while interacting with custom block.
541
+ // If we don't set readOnly={true},
542
+ // it will cause some subtle bugs in InfoBox button.
543
+ readOnly: true,
544
+ })
545
+ }
546
+ const onEditFinish = ({
547
+ entityKey,
548
+ entityData,
549
+ }: {
550
+ entityKey?: string
551
+ entityData?: Record<string, unknown>
552
+ }) => {
553
+ if (entityKey) {
554
+ const oldContentState = this.props.editorState.getCurrentContent()
555
+ const newContentState = oldContentState.replaceEntityData(
556
+ entityKey,
557
+ entityData
558
+ )
559
+ this.onChange(
560
+ EditorState.set(this.props.editorState, {
561
+ currentContent: newContentState,
562
+ })
563
+ )
564
+ }
565
+
566
+ // Custom block interaction is finished.
567
+ // Therefore, we set readOnly={false} to
568
+ // make editor editable.
569
+ this.setState({
570
+ readOnly: false,
571
+ })
572
+ }
573
+
574
+ // `onEditStart` and `onEditFinish` will be passed
575
+ // into custom block component.
576
+ // We can get them via `props.blockProps.onEditStart`
577
+ // and `props.blockProps.onEditFinish` in the custom block components.
578
+ atomicBlockObj['props'] = {
579
+ onEditStart,
580
+ onEditFinish,
581
+ getMainEditorReadOnly: () => this.state.readOnly,
582
+ }
583
+ }
584
+ return atomicBlockObj
585
+ }
586
+
587
+ render() {
588
+ let { editorState } = this.props
589
+
590
+ if (!(editorState instanceof EditorState)) {
591
+ editorState = EditorState.createEmpty(decorators)
592
+ }
593
+ const { isEnlarged, readOnly } = this.state
594
+
595
+ const entityType = this.getEntityType(editorState)
596
+ const customStyle = this.getCustomStyle(editorState.getCurrentInlineStyle())
597
+ return (
598
+ <DraftEditorContainer isEnlarged={isEnlarged}>
599
+ <DraftEditorWrapper>
600
+ <link
601
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
602
+ rel="stylesheet"
603
+ type="text/css"
604
+ />
605
+ <link
606
+ href="https://storage.googleapis.com/static-readr-tw-dev/cdn/draft-js/rich-editor.css"
607
+ rel="stylesheet"
608
+ type="text/css"
609
+ />
610
+ <link
611
+ href="https://cdnjs.cloudflare.com/ajax/libs/draft-js/0.11.7/Draft.css"
612
+ rel="stylesheet"
613
+ type="text/css"
614
+ />
615
+ <link
616
+ href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
617
+ rel="stylesheet"
618
+ type="text/css"
619
+ />
620
+ <DraftEditorControls>
621
+ <DraftEditorControlsWrapper>
622
+ <ButtonGroup>
623
+ <BlockStyleControls
624
+ editorState={editorState}
625
+ onToggle={this.toggleBlockType}
626
+ readOnly={this.state.readOnly}
627
+ />
628
+ </ButtonGroup>
629
+ <ButtonGroup>
630
+ <InlineStyleControls
631
+ editorState={editorState}
632
+ onToggle={this.toggleInlineStyle}
633
+ readOnly={this.state.readOnly}
634
+ />
635
+ </ButtonGroup>
636
+
637
+ <EnlargeButtonWrapper>
638
+ <CustomEnlargeButton
639
+ onToggle={this.toggleEnlarge}
640
+ isEnlarged={isEnlarged}
641
+ ></CustomEnlargeButton>
642
+ </EnlargeButtonWrapper>
643
+ </DraftEditorControlsWrapper>
644
+ <DraftEditorControlsWrapper>
645
+ <ButtonGroup>
646
+ <CustomLinkButton
647
+ isActive={entityType === 'LINK'}
648
+ editorState={editorState}
649
+ onChange={this.onChange}
650
+ readOnly={this.state.readOnly}
651
+ />
652
+ </ButtonGroup>
653
+ <ButtonGroup>
654
+ <CustomFontColorButton
655
+ isActive={Object.prototype.hasOwnProperty.call(
656
+ customStyle,
657
+ 'color'
658
+ )}
659
+ editorState={editorState}
660
+ onChange={this.onChange}
661
+ readOnly={this.state.readOnly}
662
+ />
663
+ </ButtonGroup>
664
+ <ButtonGroup>
665
+ <CustomDividerButton
666
+ editorState={editorState}
667
+ onChange={this.onChange}
668
+ ></CustomDividerButton>
669
+ </ButtonGroup>
670
+ <ButtonGroup>
671
+ <CustomAnnotationButton
672
+ isActive={entityType === 'ANNOTATION'}
673
+ editorState={editorState}
674
+ onChange={this.onChange}
675
+ readOnly={this.state.readOnly}
676
+ />
677
+ </ButtonGroup>
678
+ <ButtonGroup>
679
+ <CustomImageButton
680
+ editorState={editorState}
681
+ onChange={this.onChange}
682
+ readOnly={this.state.readOnly}
683
+ ImageSelector={ImageSelector}
684
+ />
685
+ </ButtonGroup>
686
+ <ButtonGroup>
687
+ <CustomSlideshowButton
688
+ editorState={editorState}
689
+ onChange={this.onChange}
690
+ readOnly={this.state.readOnly}
691
+ ImageSelector={ImageSelector}
692
+ />
693
+ </ButtonGroup>
694
+ <ButtonGroup>
695
+ <CustomInfoBoxButton
696
+ editorState={editorState}
697
+ onChange={this.onChange}
698
+ readOnly={this.state.readOnly}
699
+ />
700
+ </ButtonGroup>
701
+ <ButtonGroup>
702
+ <CustomEmbeddedCodeButton
703
+ editorState={editorState}
704
+ onChange={this.onChange}
705
+ readOnly={this.state.readOnly}
706
+ ></CustomEmbeddedCodeButton>
707
+ </ButtonGroup>
708
+ <ButtonGroup>
709
+ <CustomTableButton
710
+ editorState={editorState}
711
+ onChange={this.onChange}
712
+ readOnly={this.state.readOnly}
713
+ ></CustomTableButton>
714
+ </ButtonGroup>
715
+ </DraftEditorControlsWrapper>
716
+ <DraftEditorControlsWrapper>
717
+ <ButtonGroup>
718
+ <CustomAlignLeftButton
719
+ isActive={
720
+ getSelectionBlockData(editorState, 'textAlign') === 'left'
721
+ }
722
+ editorState={editorState}
723
+ onChange={this.onChange}
724
+ readOnly={this.state.readOnly}
725
+ />
726
+ </ButtonGroup>
727
+ <ButtonGroup>
728
+ <CustomAlignCenterButton
729
+ isActive={
730
+ getSelectionBlockData(editorState, 'textAlign') === 'center'
731
+ }
732
+ editorState={editorState}
733
+ onChange={this.onChange}
734
+ readOnly={this.state.readOnly}
735
+ />
736
+ </ButtonGroup>
737
+ <ButtonGroup>
738
+ <CustomColorBoxButton
739
+ editorState={editorState}
740
+ onChange={this.onChange}
741
+ readOnly={this.state.readOnly}
742
+ />
743
+ </ButtonGroup>
744
+ <ButtonGroup>
745
+ <CustomBackgroundColorButton
746
+ isActive={Object.prototype.hasOwnProperty.call(
747
+ customStyle,
748
+ 'backgroundColor'
749
+ )}
750
+ editorState={editorState}
751
+ onChange={this.onChange}
752
+ readOnly={this.state.readOnly}
753
+ />
754
+ </ButtonGroup>
755
+ <ButtonGroup>
756
+ <CustomBGImageButton
757
+ editorState={editorState}
758
+ onChange={this.onChange}
759
+ readOnly={this.state.readOnly}
760
+ ImageSelector={ImageSelector}
761
+ />
762
+ </ButtonGroup>
763
+ <ButtonGroup>
764
+ <CustomBGVideoButton
765
+ editorState={editorState}
766
+ onChange={this.onChange}
767
+ readOnly={this.state.readOnly}
768
+ VideoSelector={VideoSelector}
769
+ />
770
+ </ButtonGroup>
771
+ <ButtonGroup>
772
+ <CustomRelatedPostButton
773
+ editorState={editorState}
774
+ onChange={this.onChange}
775
+ readOnly={this.state.readOnly}
776
+ PostSelector={PostSelector}
777
+ />
778
+ </ButtonGroup>
779
+ <ButtonGroup>
780
+ <CustomSideIndexButton
781
+ editorState={editorState}
782
+ onChange={this.onChange}
783
+ readOnly={this.state.readOnly}
784
+ />
785
+ </ButtonGroup>
786
+ </DraftEditorControlsWrapper>
787
+ </DraftEditorControls>
788
+ <TextEditorWrapper
789
+ onClick={() => {
790
+ // eslint-disable-next-line prettier/prettier
791
+ (this.refs.editor as HTMLElement)?.focus()
792
+ }}
793
+ >
794
+ <Editor
795
+ blockStyleFn={this.blockStyleFn.bind(this)}
796
+ blockRendererFn={this.blockRendererFn}
797
+ customStyleMap={this.customStyleMap}
798
+ customStyleFn={this.customStyleFn}
799
+ editorState={editorState}
800
+ handleKeyCommand={this.handleKeyCommand}
801
+ handleReturn={this.handleReturn}
802
+ keyBindingFn={this.mapKeyToEditorCommand}
803
+ onChange={this.onChange}
804
+ placeholder="Tell a story..."
805
+ ref="editor"
806
+ spellCheck={true}
807
+ readOnly={readOnly}
808
+ />
809
+ </TextEditorWrapper>
810
+ </DraftEditorWrapper>
811
+ </DraftEditorContainer>
812
+ )
813
+ }
814
+ }
815
+
816
+ type StyleButtonProps = {
817
+ active: boolean
818
+ label: string
819
+ onToggle: (string) => void
820
+ style: string
821
+ icon: string
822
+ readOnly: boolean
823
+ }
824
+
825
+ class StyleButton extends React.Component<StyleButtonProps> {
826
+ onToggle = (e) => {
827
+ e.preventDefault()
828
+ this.props.onToggle(this.props.style)
829
+ }
830
+
831
+ render() {
832
+ return (
833
+ <CustomButton
834
+ isActive={this.props.active}
835
+ onMouseDown={this.onToggle}
836
+ readOnly={this.props.readOnly}
837
+ >
838
+ {this.props.icon && <i className={this.props.icon}></i>}
839
+ <span>{!this.props.icon ? this.props.label : ''}</span>
840
+ </CustomButton>
841
+ )
842
+ }
843
+ }
844
+
845
+ const blockStyles = [
846
+ { label: 'H2', style: 'header-two', icon: '' },
847
+ { label: 'H3', style: 'header-three', icon: '' },
848
+ { label: 'H4', style: 'header-four', icon: '' },
849
+ { label: 'Blockquote', style: 'blockquote', icon: 'fas fa-quote-right' },
850
+ { label: 'UL', style: 'unordered-list-item', icon: 'fas fa-list-ul' },
851
+ { label: 'OL', style: 'ordered-list-item', icon: 'fas fa-list-ol' },
852
+ { label: 'Code Block', style: 'code-block', icon: 'fas fa-code' },
853
+ ]
854
+
855
+ const BlockStyleControls = (props) => {
856
+ const { editorState } = props
857
+ const selection = editorState.getSelection()
858
+ const blockType = editorState
859
+ .getCurrentContent()
860
+ .getBlockForKey(selection.getStartKey())
861
+ .getType()
862
+ return (
863
+ <React.Fragment>
864
+ {blockStyles.map((type) => (
865
+ <StyleButton
866
+ key={type.label}
867
+ active={type.style === blockType}
868
+ label={type.label}
869
+ onToggle={props.onToggle}
870
+ style={type.style}
871
+ icon={type.icon}
872
+ readOnly={props.readOnly}
873
+ />
874
+ ))}
875
+ </React.Fragment>
876
+ )
877
+ }
878
+
879
+ const inlineStyles = [
880
+ { label: 'Bold', style: 'BOLD', icon: 'fas fa-bold' },
881
+ { label: 'Italic', style: 'ITALIC', icon: 'fas fa-italic' },
882
+ { label: 'Underline', style: 'UNDERLINE', icon: 'fas fa-underline' },
883
+ { label: 'Monospace', style: 'CODE', icon: 'fas fa-terminal' },
884
+ ]
885
+
886
+ const InlineStyleControls = (props) => {
887
+ const currentStyle = props.editorState.getCurrentInlineStyle()
888
+ return (
889
+ <React.Fragment>
890
+ {inlineStyles.map((type) => (
891
+ <StyleButton
892
+ key={type.label}
893
+ active={currentStyle.has(type.style)}
894
+ label={type.label}
895
+ onToggle={props.onToggle}
896
+ style={type.style}
897
+ icon={type.icon}
898
+ readOnly={props.readOnly}
899
+ />
900
+ ))}
901
+ </React.Fragment>
902
+ )
903
+ }
904
+
905
+ const DraftEditor = {
906
+ RichTextEditor,
907
+ decorators,
908
+ }
909
+ export default DraftEditor