@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.
- package/lib/draft-js/block-renderer/background-image-block.tsx +113 -0
- package/lib/draft-js/block-renderer/background-video-block.tsx +120 -0
- package/lib/draft-js/block-renderer/color-box-block.tsx +85 -0
- package/lib/draft-js/block-renderer/divider-block.tsx +12 -0
- package/lib/draft-js/block-renderer/embedded-code-block.tsx +65 -0
- package/lib/draft-js/block-renderer/image-block.tsx +41 -0
- package/lib/draft-js/block-renderer/info-box-block.tsx +85 -0
- package/lib/draft-js/block-renderer/media-block.tsx +36 -0
- package/lib/draft-js/block-renderer/related-post-block.tsx +47 -0
- package/lib/draft-js/block-renderer/side-index-block.tsx +113 -0
- package/lib/draft-js/block-renderer/slideshow-block.tsx +62 -0
- package/lib/draft-js/block-renderer/table-block.tsx +488 -0
- package/lib/draft-js/buttons/annotation.tsx +113 -0
- package/lib/draft-js/buttons/background-color.tsx +125 -0
- package/lib/draft-js/buttons/background-image.tsx +276 -0
- package/lib/draft-js/buttons/background-video.tsx +275 -0
- package/lib/draft-js/buttons/color-box.tsx +207 -0
- package/lib/draft-js/buttons/divider.tsx +56 -0
- package/lib/draft-js/buttons/embedded-code.tsx +126 -0
- package/lib/draft-js/buttons/enlarge.tsx +11 -0
- package/lib/draft-js/buttons/font-color.tsx +113 -0
- package/lib/draft-js/buttons/image.tsx +71 -0
- package/lib/draft-js/buttons/info-box.tsx +170 -0
- package/lib/draft-js/buttons/link.tsx +103 -0
- package/lib/draft-js/buttons/media.tsx +120 -0
- package/lib/draft-js/buttons/related-post.tsx +81 -0
- package/lib/draft-js/buttons/selector/align-selector.tsx +65 -0
- package/lib/draft-js/buttons/selector/image-selector.tsx +485 -0
- package/lib/draft-js/buttons/selector/pagination.tsx +83 -0
- package/lib/draft-js/buttons/selector/post-selector.tsx +367 -0
- package/lib/draft-js/buttons/selector/search-box.tsx +39 -0
- package/lib/draft-js/buttons/selector/video-selector.tsx +312 -0
- package/lib/draft-js/buttons/side-index.tsx +257 -0
- package/lib/draft-js/buttons/slideshow.tsx +81 -0
- package/lib/draft-js/buttons/table.tsx +63 -0
- package/lib/draft-js/buttons/text-align.tsx +88 -0
- package/lib/draft-js/editor/basic-editor.tsx +384 -0
- package/lib/draft-js/editor/block-redender-fn.tsx +77 -0
- package/lib/draft-js/editor/draft-converter/api-data-instance.js +58 -0
- package/lib/draft-js/editor/draft-converter/atomic-block-processor.js +233 -0
- package/lib/draft-js/editor/draft-converter/entities.js +76 -0
- package/lib/draft-js/editor/draft-converter/index.js +201 -0
- package/lib/draft-js/editor/draft-converter/inline-styles-processor.js +238 -0
- package/lib/draft-js/editor/entity-decorator.tsx +7 -0
- package/lib/draft-js/editor/modifier.tsx +71 -0
- package/lib/draft-js/entity-decorator/annotation-decorator.tsx +81 -0
- package/lib/draft-js/entity-decorator/link-decorator.tsx +27 -0
- package/lib/index.js +31 -0
- package/lib/website/mirrormedia/custom/block-renderer/background-image-block.tsx +128 -0
- package/lib/website/mirrormedia/custom/block-renderer/background-video-block.tsx +135 -0
- package/lib/website/mirrormedia/custom/block-renderer/color-box-block.tsx +98 -0
- package/lib/website/mirrormedia/custom/block-renderer/divider-block.tsx +12 -0
- package/lib/website/mirrormedia/custom/block-renderer/embedded-code-block.tsx +65 -0
- package/lib/website/mirrormedia/custom/block-renderer/image-block.tsx +41 -0
- package/lib/website/mirrormedia/custom/block-renderer/info-box-block.tsx +98 -0
- package/lib/website/mirrormedia/custom/block-renderer/media-block.tsx +36 -0
- package/lib/website/mirrormedia/custom/block-renderer/related-post-block.tsx +47 -0
- package/lib/website/mirrormedia/custom/block-renderer/side-index-block.tsx +125 -0
- package/lib/website/mirrormedia/custom/block-renderer/slideshow-block.tsx +62 -0
- package/lib/website/mirrormedia/custom/block-renderer/table-block.tsx +537 -0
- package/lib/website/mirrormedia/custom/entity-decorator/annotation-decorator.tsx +81 -0
- package/lib/website/mirrormedia/custom/entity-decorator/link-decorator.tsx +27 -0
- package/lib/website/mirrormedia/custom/selector/align-selector.tsx +65 -0
- package/lib/website/mirrormedia/custom/selector/image-selector.tsx +485 -0
- package/lib/website/mirrormedia/custom/selector/pagination.tsx +83 -0
- package/lib/website/mirrormedia/custom/selector/post-selector.tsx +367 -0
- package/lib/website/mirrormedia/custom/selector/search-box.tsx +39 -0
- package/lib/website/mirrormedia/custom/selector/video-selector.tsx +310 -0
- package/lib/website/mirrormedia/draft-editor/block-redender-fn.tsx +77 -0
- package/lib/website/mirrormedia/draft-editor/entity-decorator.tsx +7 -0
- package/lib/website/mirrormedia/draft-editor/index.tsx +909 -0
- package/lib/website/mirrormedia/draft-renderer/block-redender-fn.tsx +77 -0
- package/lib/website/mirrormedia/draft-renderer/entity-decorator.tsx +7 -0
- package/lib/website/mirrormedia/draft-renderer/index-deprecated.tsx +43 -0
- package/lib/website/mirrormedia/draft-renderer/index.tsx +150 -0
- package/lib/website/mirrormedia/index.js +19 -0
- package/lib/website/readr/custom/block-renderer/background-image-block.tsx +128 -0
- package/lib/website/readr/custom/block-renderer/background-video-block.tsx +135 -0
- package/lib/website/readr/custom/block-renderer/color-box-block.tsx +98 -0
- package/lib/website/readr/custom/block-renderer/divider-block.tsx +12 -0
- package/lib/website/readr/custom/block-renderer/embedded-code-block.tsx +65 -0
- package/lib/website/readr/custom/block-renderer/image-block.tsx +41 -0
- package/lib/website/readr/custom/block-renderer/info-box-block.tsx +98 -0
- package/lib/website/readr/custom/block-renderer/media-block.tsx +36 -0
- package/lib/website/readr/custom/block-renderer/related-post-block.tsx +47 -0
- package/lib/website/readr/custom/block-renderer/side-index-block.tsx +125 -0
- package/lib/website/readr/custom/block-renderer/slideshow-block.tsx +62 -0
- package/lib/website/readr/custom/block-renderer/table-block.tsx +537 -0
- package/lib/website/readr/custom/entity-decorator/annotation-decorator.tsx +81 -0
- package/lib/website/readr/custom/entity-decorator/link-decorator.tsx +27 -0
- package/lib/website/readr/custom/selector/align-selector.tsx +65 -0
- package/lib/website/readr/custom/selector/image-selector.tsx +485 -0
- package/lib/website/readr/custom/selector/pagination.tsx +83 -0
- package/lib/website/readr/custom/selector/post-selector.tsx +367 -0
- package/lib/website/readr/custom/selector/search-box.tsx +39 -0
- package/lib/website/readr/custom/selector/video-selector.tsx +310 -0
- package/lib/website/readr/draft-editor/block-redender-fn.tsx +77 -0
- package/lib/website/readr/draft-editor/entity-decorator.tsx +7 -0
- package/lib/website/readr/draft-editor/index.tsx +909 -0
- package/lib/website/readr/draft-renderer/block-redender-fn.tsx +77 -0
- package/lib/website/readr/draft-renderer/entity-decorator.tsx +7 -0
- package/lib/website/readr/draft-renderer/index-deprecated.tsx +43 -0
- package/lib/website/readr/draft-renderer/index.tsx +150 -0
- package/lib/website/readr/index.js +19 -0
- package/package.json +39 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import { ContentBlock, ContentState } from 'draft-js'
|
|
4
|
+
import draftConverter from '../editor/draft-converter'
|
|
5
|
+
import { BGImageInput } from '../buttons/background-image'
|
|
6
|
+
|
|
7
|
+
const BGImageRenderWrapper = styled.div`
|
|
8
|
+
padding: 30px;
|
|
9
|
+
position: relative;
|
|
10
|
+
width: 100%;
|
|
11
|
+
height: 100%;
|
|
12
|
+
background-image: url(${({ image }) => image});
|
|
13
|
+
background-size: cover;
|
|
14
|
+
background-position: center center;
|
|
15
|
+
${({ textBlockAlign }) => {
|
|
16
|
+
if (textBlockAlign === 'left') {
|
|
17
|
+
return `padding-right: 50%;`
|
|
18
|
+
} else if (textBlockAlign === 'right') {
|
|
19
|
+
return `padding-left: 50%;`
|
|
20
|
+
} else if (textBlockAlign === 'bottom') {
|
|
21
|
+
return `padding-top: 50%;`
|
|
22
|
+
}
|
|
23
|
+
}}
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
const BGImageRenderBody = styled.div`
|
|
27
|
+
background: rgba(0, 0, 0, 0.5);
|
|
28
|
+
padding: 4px 20px;
|
|
29
|
+
margin-bottom: 10px;
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
const BGImageRenderButton = styled.span`
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
background-color: white;
|
|
35
|
+
padding: 6px;
|
|
36
|
+
border-radius: 6px;
|
|
37
|
+
`
|
|
38
|
+
|
|
39
|
+
type BGImageBlockProps = {
|
|
40
|
+
block: ContentBlock
|
|
41
|
+
blockProps: {
|
|
42
|
+
onEditStart: () => void
|
|
43
|
+
onEditFinish: ({
|
|
44
|
+
entityKey,
|
|
45
|
+
entityData,
|
|
46
|
+
}: {
|
|
47
|
+
entityKey?: string
|
|
48
|
+
entityData?: Record<string, unknown>
|
|
49
|
+
}) => void
|
|
50
|
+
}
|
|
51
|
+
contentState: ContentState
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function BGImageBlock(props: BGImageBlockProps) {
|
|
55
|
+
const [toShowInput, setToShowInput] = useState(false)
|
|
56
|
+
const { block, blockProps, contentState } = props
|
|
57
|
+
const { onEditStart, onEditFinish } = blockProps
|
|
58
|
+
const entityKey = block.getEntityAt(0)
|
|
59
|
+
const entity = contentState.getEntity(entityKey)
|
|
60
|
+
const { textBlockAlign, image, body, rawContentState } = entity.getData()
|
|
61
|
+
const onChange = ({
|
|
62
|
+
textBlockAlign: newTextBlockAlign,
|
|
63
|
+
image: newImage,
|
|
64
|
+
rawContentState: newRawContentState,
|
|
65
|
+
}) => {
|
|
66
|
+
// close `BGImageInput`
|
|
67
|
+
setToShowInput(false)
|
|
68
|
+
onEditFinish({
|
|
69
|
+
entityKey,
|
|
70
|
+
entityData: {
|
|
71
|
+
textBlockAlign: newTextBlockAlign,
|
|
72
|
+
image: newImage,
|
|
73
|
+
body: draftConverter.convertToHtml(newRawContentState),
|
|
74
|
+
rawContentState: newRawContentState,
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<React.Fragment>
|
|
81
|
+
<BGImageInput
|
|
82
|
+
textBlockAlign={textBlockAlign}
|
|
83
|
+
image={image}
|
|
84
|
+
rawContentStateForBGImageEditor={rawContentState}
|
|
85
|
+
onChange={onChange}
|
|
86
|
+
onCancel={() => {
|
|
87
|
+
onEditFinish({})
|
|
88
|
+
setToShowInput(false)
|
|
89
|
+
}}
|
|
90
|
+
isOpen={toShowInput}
|
|
91
|
+
/>
|
|
92
|
+
<BGImageRenderWrapper
|
|
93
|
+
image={image?.imageFile?.url}
|
|
94
|
+
textBlockAlign={textBlockAlign}
|
|
95
|
+
>
|
|
96
|
+
<BGImageRenderBody dangerouslySetInnerHTML={{ __html: body }} />
|
|
97
|
+
<div>
|
|
98
|
+
<BGImageRenderButton
|
|
99
|
+
onClick={() => {
|
|
100
|
+
// call `onEditStart` prop as we are trying to update the BGImage entity
|
|
101
|
+
onEditStart()
|
|
102
|
+
// open `BGImageInput`
|
|
103
|
+
setToShowInput(true)
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
<i className="fa-solid fa-pen"></i>
|
|
107
|
+
<span>Modify</span>
|
|
108
|
+
</BGImageRenderButton>
|
|
109
|
+
</div>
|
|
110
|
+
</BGImageRenderWrapper>
|
|
111
|
+
</React.Fragment>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import { ContentBlock, ContentState } from 'draft-js'
|
|
4
|
+
import draftConverter from '../editor/draft-converter'
|
|
5
|
+
import { BGVideoInput } from '../buttons/background-video'
|
|
6
|
+
|
|
7
|
+
const BGVideoRenderWrapper = styled.div`
|
|
8
|
+
position: relative;
|
|
9
|
+
padding: 30px;
|
|
10
|
+
width: 100%;
|
|
11
|
+
${({ textBlockAlign }) => {
|
|
12
|
+
if (textBlockAlign === 'left') {
|
|
13
|
+
return `padding-right: 50%;`
|
|
14
|
+
} else if (textBlockAlign === 'right') {
|
|
15
|
+
return `padding-left: 50%;`
|
|
16
|
+
} else if (textBlockAlign === 'bottom') {
|
|
17
|
+
return `padding-top: 50%;`
|
|
18
|
+
}
|
|
19
|
+
}}
|
|
20
|
+
`
|
|
21
|
+
|
|
22
|
+
const BGVideoRednerVideo = styled.video`
|
|
23
|
+
position: absolute;
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 100%;
|
|
26
|
+
top: 0;
|
|
27
|
+
left: 0;
|
|
28
|
+
z-index: -1;
|
|
29
|
+
background-color: black;
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
const BGVideoRenderBody = styled.div`
|
|
33
|
+
background: rgba(0, 0, 0, 0.5);
|
|
34
|
+
padding: 4px 20px;
|
|
35
|
+
margin-bottom: 10px;
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
const BGVideoRenderButton = styled.span`
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
background-color: white;
|
|
41
|
+
padding: 6px;
|
|
42
|
+
border-radius: 6px;
|
|
43
|
+
`
|
|
44
|
+
|
|
45
|
+
type BGVideoBlockProps = {
|
|
46
|
+
block: ContentBlock
|
|
47
|
+
blockProps: {
|
|
48
|
+
onEditStart: () => void
|
|
49
|
+
onEditFinish: ({
|
|
50
|
+
entityKey,
|
|
51
|
+
entityData,
|
|
52
|
+
}: {
|
|
53
|
+
entityKey?: string
|
|
54
|
+
entityData?: Record<string, unknown>
|
|
55
|
+
}) => void
|
|
56
|
+
}
|
|
57
|
+
contentState: ContentState
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function BGVideoBlock(props: BGVideoBlockProps) {
|
|
61
|
+
const [toShowInput, setToShowInput] = useState(false)
|
|
62
|
+
const { block, blockProps, contentState } = props
|
|
63
|
+
const { onEditStart, onEditFinish } = blockProps
|
|
64
|
+
const entityKey = block.getEntityAt(0)
|
|
65
|
+
const entity = contentState.getEntity(entityKey)
|
|
66
|
+
const { textBlockAlign, video, body, rawContentState } = entity.getData()
|
|
67
|
+
const onChange = ({
|
|
68
|
+
textBlockAlign: newTextBlockAlign,
|
|
69
|
+
video: newVideo,
|
|
70
|
+
rawContentState: newRawContentState,
|
|
71
|
+
}) => {
|
|
72
|
+
// close `BGVideoInput`
|
|
73
|
+
setToShowInput(false)
|
|
74
|
+
onEditFinish({
|
|
75
|
+
entityKey,
|
|
76
|
+
entityData: {
|
|
77
|
+
textBlockAlign: newTextBlockAlign,
|
|
78
|
+
video: newVideo,
|
|
79
|
+
body: draftConverter.convertToHtml(newRawContentState),
|
|
80
|
+
rawContentState: newRawContentState,
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<React.Fragment>
|
|
87
|
+
<BGVideoInput
|
|
88
|
+
textBlockAlign={textBlockAlign}
|
|
89
|
+
video={video}
|
|
90
|
+
rawContentStateForBGVideoEditor={rawContentState}
|
|
91
|
+
onChange={onChange}
|
|
92
|
+
onCancel={() => {
|
|
93
|
+
onEditFinish({})
|
|
94
|
+
setToShowInput(false)
|
|
95
|
+
}}
|
|
96
|
+
isOpen={toShowInput}
|
|
97
|
+
/>
|
|
98
|
+
<BGVideoRenderWrapper textBlockAlign={textBlockAlign}>
|
|
99
|
+
<BGVideoRednerVideo muted autoPlay loop>
|
|
100
|
+
<source src={video?.videoSrc} />
|
|
101
|
+
<source src={video?.videoFile?.url} />
|
|
102
|
+
</BGVideoRednerVideo>
|
|
103
|
+
<BGVideoRenderBody dangerouslySetInnerHTML={{ __html: body }} />
|
|
104
|
+
<div>
|
|
105
|
+
<BGVideoRenderButton
|
|
106
|
+
onClick={() => {
|
|
107
|
+
// call `onEditStart` prop as we are trying to update the BGVideo entity
|
|
108
|
+
onEditStart()
|
|
109
|
+
// open `BGVideoInput`
|
|
110
|
+
setToShowInput(true)
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<i className="fa-solid fa-pen"></i>
|
|
114
|
+
<span>Modify</span>
|
|
115
|
+
</BGVideoRenderButton>
|
|
116
|
+
</div>
|
|
117
|
+
</BGVideoRenderWrapper>
|
|
118
|
+
</React.Fragment>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import { ContentBlock, ContentState } from 'draft-js'
|
|
4
|
+
import draftConverter from '../editor/draft-converter'
|
|
5
|
+
import { ColorBoxInput } from '../buttons/color-box'
|
|
6
|
+
|
|
7
|
+
const ColorBoxRenderWrapper = styled.div`
|
|
8
|
+
background-color: ${(props) => (props.color ? props.color : '#F5F4F3')};
|
|
9
|
+
padding: 30px;
|
|
10
|
+
position: relative;
|
|
11
|
+
color: white;
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
const ColorBoxRenderButton = styled.div`
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
type ColorBoxBlockProps = {
|
|
19
|
+
block: ContentBlock
|
|
20
|
+
blockProps: {
|
|
21
|
+
onEditStart: () => void
|
|
22
|
+
onEditFinish: ({
|
|
23
|
+
entityKey,
|
|
24
|
+
entityData,
|
|
25
|
+
}: {
|
|
26
|
+
entityKey?: string
|
|
27
|
+
entityData?: Record<string, unknown>
|
|
28
|
+
}) => void
|
|
29
|
+
}
|
|
30
|
+
contentState: ContentState
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ColorBoxBlock(props: ColorBoxBlockProps) {
|
|
34
|
+
const [toShowInput, setToShowInput] = useState(false)
|
|
35
|
+
const { block, blockProps, contentState } = props
|
|
36
|
+
const { onEditStart, onEditFinish } = blockProps
|
|
37
|
+
const entityKey = block.getEntityAt(0)
|
|
38
|
+
const entity = contentState.getEntity(entityKey)
|
|
39
|
+
const { color, body, rawContentState } = entity.getData()
|
|
40
|
+
const onChange = ({
|
|
41
|
+
color: newColor,
|
|
42
|
+
rawContentState: newRawContentState,
|
|
43
|
+
}) => {
|
|
44
|
+
// close `ColorBoxInput`
|
|
45
|
+
setToShowInput(false)
|
|
46
|
+
|
|
47
|
+
onEditFinish({
|
|
48
|
+
entityKey,
|
|
49
|
+
entityData: {
|
|
50
|
+
color: newColor,
|
|
51
|
+
body: draftConverter.convertToHtml(newRawContentState),
|
|
52
|
+
rawContentState: newRawContentState,
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<React.Fragment>
|
|
59
|
+
<ColorBoxInput
|
|
60
|
+
color={color}
|
|
61
|
+
rawContentStateForColorBoxEditor={rawContentState}
|
|
62
|
+
onChange={onChange}
|
|
63
|
+
onCancel={() => {
|
|
64
|
+
onEditFinish({})
|
|
65
|
+
setToShowInput(false)
|
|
66
|
+
}}
|
|
67
|
+
isOpen={toShowInput}
|
|
68
|
+
/>
|
|
69
|
+
<ColorBoxRenderWrapper color={color}>
|
|
70
|
+
<div dangerouslySetInnerHTML={{ __html: body }} />
|
|
71
|
+
<ColorBoxRenderButton
|
|
72
|
+
onClick={() => {
|
|
73
|
+
// call `onEditStart` prop as we are trying to update the ColorBox entity
|
|
74
|
+
onEditStart()
|
|
75
|
+
// open `ColorBoxInput`
|
|
76
|
+
setToShowInput(true)
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<i className="fa-solid fa-pen"></i>
|
|
80
|
+
<span>Modify</span>
|
|
81
|
+
</ColorBoxRenderButton>
|
|
82
|
+
</ColorBoxRenderWrapper>
|
|
83
|
+
</React.Fragment>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
|
2
|
+
import { DraftEntityInstance } from 'draft-js'
|
|
3
|
+
import { Block, Caption } from '../buttons/embedded-code'
|
|
4
|
+
|
|
5
|
+
export const EmbeddedCodeBlock = (entity: DraftEntityInstance) => {
|
|
6
|
+
const { caption, embeddedCode } = entity.getData()
|
|
7
|
+
const embedded = useRef(null)
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!embedded.current) return
|
|
11
|
+
const node: HTMLElement = embedded.current
|
|
12
|
+
|
|
13
|
+
const fragment = document.createDocumentFragment()
|
|
14
|
+
|
|
15
|
+
// `embeddedCode` is a string, which may includes
|
|
16
|
+
// multiple '<script>' tags and other html tags.
|
|
17
|
+
// For executing '<script>' tags on the browser,
|
|
18
|
+
// we need to extract '<script>' tags from `embeddedCode` string first.
|
|
19
|
+
//
|
|
20
|
+
// The approach we have here is to parse html string into elements,
|
|
21
|
+
// and we could use DOM element built-in functions,
|
|
22
|
+
// such as `querySelectorAll` method, to query '<script>' elements,
|
|
23
|
+
// and other non '<script>' elements.
|
|
24
|
+
const parser = new DOMParser()
|
|
25
|
+
const ele = parser.parseFromString(
|
|
26
|
+
`<div id="draft-embed">${embeddedCode}</div>`,
|
|
27
|
+
'text/html'
|
|
28
|
+
)
|
|
29
|
+
const scripts = ele.querySelectorAll('script')
|
|
30
|
+
const nonScripts = ele.querySelectorAll('div#draft-embed > :not(script)')
|
|
31
|
+
|
|
32
|
+
nonScripts.forEach((ele) => {
|
|
33
|
+
fragment.appendChild(ele)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
scripts.forEach((s) => {
|
|
37
|
+
const scriptEle = document.createElement('script')
|
|
38
|
+
const attrs = s.attributes
|
|
39
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
40
|
+
scriptEle.setAttribute(attrs[i].name, attrs[i].value)
|
|
41
|
+
}
|
|
42
|
+
scriptEle.text = s.text || ''
|
|
43
|
+
fragment.appendChild(scriptEle)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
node.appendChild(fragment)
|
|
47
|
+
}, [embeddedCode])
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div>
|
|
51
|
+
{
|
|
52
|
+
// WORKAROUND:
|
|
53
|
+
// The following `<input>` is to solve [issue 153](https://github.com/mirror-media/openwarehouse-k6/issues/153).
|
|
54
|
+
// If the emebed code generates `<input>` or `<textarea>` and appends them onto DOM,
|
|
55
|
+
// and then the generated `<input>` or `<textarea>` will hijack the users' cursors.
|
|
56
|
+
// It will cause that users could not edit the DraftJS Editor anymore.
|
|
57
|
+
// The following phony `<input>` is used to prevent the generated `<input>` or `<textare>` from
|
|
58
|
+
// hijacking the users' cursors.
|
|
59
|
+
}
|
|
60
|
+
<input hidden disabled />
|
|
61
|
+
<Block ref={embedded} />
|
|
62
|
+
{caption ? <Caption>{caption}</Caption> : null}
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import { DraftEntityInstance } from 'draft-js'
|
|
4
|
+
|
|
5
|
+
const Image = styled.img`
|
|
6
|
+
width: 100%;
|
|
7
|
+
`
|
|
8
|
+
|
|
9
|
+
const Figure = styled.figure`
|
|
10
|
+
margin-block: unset;
|
|
11
|
+
margin-inline: unset;
|
|
12
|
+
margin: 0 10px;
|
|
13
|
+
`
|
|
14
|
+
|
|
15
|
+
const Anchor = styled.a`
|
|
16
|
+
text-decoration: none;
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
export function ImageBlock(entity: DraftEntityInstance) {
|
|
20
|
+
const { desc, imageFile, resized, url } = entity.getData()
|
|
21
|
+
|
|
22
|
+
let imgBlock = (
|
|
23
|
+
<Figure>
|
|
24
|
+
<Image
|
|
25
|
+
src={resized?.w800}
|
|
26
|
+
onError={(e) => (e.currentTarget.src = imageFile?.url)}
|
|
27
|
+
/>
|
|
28
|
+
<figcaption>{desc}</figcaption>
|
|
29
|
+
</Figure>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if (url) {
|
|
33
|
+
imgBlock = (
|
|
34
|
+
<Anchor href={url} target="_blank">
|
|
35
|
+
{imgBlock}
|
|
36
|
+
</Anchor>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return imgBlock
|
|
41
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { ContentBlock, ContentState } from 'draft-js'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import draftConverter from '../editor/draft-converter'
|
|
5
|
+
import { InfoBoxInput } from '../buttons/info-box'
|
|
6
|
+
|
|
7
|
+
const InfoBoxRenderWrapper = styled.div`
|
|
8
|
+
background-color: #f5f4f3;
|
|
9
|
+
padding: 30px;
|
|
10
|
+
position: relative;
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
const InfoBoxRenderButton = styled.div`
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
type InfoBoxBlockProps = {
|
|
18
|
+
block: ContentBlock
|
|
19
|
+
blockProps: {
|
|
20
|
+
onEditStart: () => void
|
|
21
|
+
onEditFinish: ({
|
|
22
|
+
entityKey,
|
|
23
|
+
entityData,
|
|
24
|
+
}: {
|
|
25
|
+
entityKey?: string
|
|
26
|
+
entityData?: Record<string, unknown>
|
|
27
|
+
}) => void
|
|
28
|
+
}
|
|
29
|
+
contentState: ContentState
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function InfoBoxBlock(props: InfoBoxBlockProps) {
|
|
33
|
+
const [toShowInput, setToShowInput] = useState(false)
|
|
34
|
+
const { block, blockProps, contentState } = props
|
|
35
|
+
const { onEditStart, onEditFinish } = blockProps
|
|
36
|
+
const entityKey = block.getEntityAt(0)
|
|
37
|
+
const entity = contentState.getEntity(entityKey)
|
|
38
|
+
const { title, body, rawContentState } = entity.getData()
|
|
39
|
+
const onChange = ({
|
|
40
|
+
title: newTitle,
|
|
41
|
+
rawContentState: newRawContentState,
|
|
42
|
+
}) => {
|
|
43
|
+
// close `InfoBoxInput`
|
|
44
|
+
setToShowInput(false)
|
|
45
|
+
|
|
46
|
+
onEditFinish({
|
|
47
|
+
entityKey,
|
|
48
|
+
entityData: {
|
|
49
|
+
title: newTitle,
|
|
50
|
+
body: draftConverter.convertToHtml(newRawContentState),
|
|
51
|
+
rawContentState: newRawContentState,
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<React.Fragment>
|
|
58
|
+
<InfoBoxInput
|
|
59
|
+
title={title}
|
|
60
|
+
rawContentStateForInfoBoxEditor={rawContentState}
|
|
61
|
+
onChange={onChange}
|
|
62
|
+
onCancel={() => {
|
|
63
|
+
onEditFinish({})
|
|
64
|
+
setToShowInput(false)
|
|
65
|
+
}}
|
|
66
|
+
isOpen={toShowInput}
|
|
67
|
+
/>
|
|
68
|
+
<InfoBoxRenderWrapper>
|
|
69
|
+
<h2>{title}</h2>
|
|
70
|
+
<div dangerouslySetInnerHTML={{ __html: body }} />
|
|
71
|
+
<InfoBoxRenderButton
|
|
72
|
+
onClick={() => {
|
|
73
|
+
// call `onEditStart` prop as we are trying to update the InfoBox entity
|
|
74
|
+
onEditStart()
|
|
75
|
+
// open `InfoBoxInput`
|
|
76
|
+
setToShowInput(true)
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<i className="fa-solid fa-pen"></i>
|
|
80
|
+
<span>Modify</span>
|
|
81
|
+
</InfoBoxRenderButton>
|
|
82
|
+
</InfoBoxRenderWrapper>
|
|
83
|
+
</React.Fragment>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { DraftEntityInstance } from 'draft-js'
|
|
3
|
+
|
|
4
|
+
const styles = {
|
|
5
|
+
media: {
|
|
6
|
+
width: '100%',
|
|
7
|
+
},
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Audio = (props) => {
|
|
11
|
+
return <audio controls src={props.src} style={styles.media} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const Image = (props) => {
|
|
15
|
+
return <img src={props.src} style={styles.media} />
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Video = (props) => {
|
|
19
|
+
return <video controls src={props.src} style={styles.media} />
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const MediaBlock = (entity: DraftEntityInstance) => {
|
|
23
|
+
const { src } = entity.getData()
|
|
24
|
+
const type = entity.getType()
|
|
25
|
+
|
|
26
|
+
let media
|
|
27
|
+
if (type === 'audioLink') {
|
|
28
|
+
media = <Audio src={src} />
|
|
29
|
+
} else if (type === 'imageLink') {
|
|
30
|
+
media = <Image src={src} />
|
|
31
|
+
} else if (type === 'videoLink') {
|
|
32
|
+
media = <Video src={src} />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return media
|
|
36
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import { DraftEntityInstance } from 'draft-js'
|
|
4
|
+
|
|
5
|
+
const RelatedPostRenderWrapper = styled.div`
|
|
6
|
+
display: flex;
|
|
7
|
+
width: 100%;
|
|
8
|
+
`
|
|
9
|
+
|
|
10
|
+
const RelatedPostItem = styled.div`
|
|
11
|
+
flex: 0 0 33.3333%;
|
|
12
|
+
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
13
|
+
`
|
|
14
|
+
|
|
15
|
+
const RelatedPostImage = styled.img`
|
|
16
|
+
display: block;
|
|
17
|
+
width: 100%;
|
|
18
|
+
aspect-ratio: 2;
|
|
19
|
+
object-fit: cover;
|
|
20
|
+
`
|
|
21
|
+
|
|
22
|
+
const RelatedPostTitle = styled.p`
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 12px;
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
export function RelatedPostBlock(entity: DraftEntityInstance) {
|
|
28
|
+
const { posts } = entity.getData()
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<React.Fragment>
|
|
32
|
+
<RelatedPostRenderWrapper>
|
|
33
|
+
{posts.map((post) => (
|
|
34
|
+
<RelatedPostItem key={post.id}>
|
|
35
|
+
<RelatedPostImage
|
|
36
|
+
src={post.heroImage?.resized?.original}
|
|
37
|
+
onError={(e) =>
|
|
38
|
+
(e.currentTarget.src = post.heroImage?.imageFile?.url)
|
|
39
|
+
}
|
|
40
|
+
/>
|
|
41
|
+
<RelatedPostTitle>{post.name}</RelatedPostTitle>
|
|
42
|
+
</RelatedPostItem>
|
|
43
|
+
))}
|
|
44
|
+
</RelatedPostRenderWrapper>
|
|
45
|
+
</React.Fragment>
|
|
46
|
+
)
|
|
47
|
+
}
|