@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,275 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import decorators from '../editor/entity-decorator'
|
|
3
|
+
import {
|
|
4
|
+
AtomicBlockUtils,
|
|
5
|
+
EditorState,
|
|
6
|
+
RawDraftContentState,
|
|
7
|
+
convertToRaw,
|
|
8
|
+
convertFromRaw,
|
|
9
|
+
} from 'draft-js'
|
|
10
|
+
import { BasicEditor } from '../editor/basic-editor'
|
|
11
|
+
import { Drawer, DrawerController } from '@keystone-ui/modals'
|
|
12
|
+
import { Button } from '@keystone-ui/button'
|
|
13
|
+
import draftConverter from '../editor/draft-converter'
|
|
14
|
+
import styled from 'styled-components'
|
|
15
|
+
import {
|
|
16
|
+
VideoSelector as DefaultVideoSelector,
|
|
17
|
+
VideoEntity,
|
|
18
|
+
VideoEntityWithMeta,
|
|
19
|
+
} from './selector/video-selector'
|
|
20
|
+
import { AlignSelector } from './selector/align-selector'
|
|
21
|
+
|
|
22
|
+
const Label = styled.label`
|
|
23
|
+
display: block;
|
|
24
|
+
font-weight: 600;
|
|
25
|
+
margin: 10px 0;
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
const VideoInputText = styled.span`
|
|
29
|
+
display: inline-block;
|
|
30
|
+
margin-right: 10px;
|
|
31
|
+
`
|
|
32
|
+
|
|
33
|
+
type BGVideoInputOnChange = ({
|
|
34
|
+
textBlockAlign,
|
|
35
|
+
video,
|
|
36
|
+
rawContentState,
|
|
37
|
+
}: {
|
|
38
|
+
textBlockAlign: string
|
|
39
|
+
video?: VideoEntity
|
|
40
|
+
rawContentState: RawDraftContentState
|
|
41
|
+
}) => void
|
|
42
|
+
|
|
43
|
+
type BGVideoInputType = {
|
|
44
|
+
textBlockAlign?: string
|
|
45
|
+
video?: VideoEntity
|
|
46
|
+
rawContentStateForBGVideoEditor?: RawDraftContentState
|
|
47
|
+
isOpen: boolean
|
|
48
|
+
onChange: BGVideoInputOnChange
|
|
49
|
+
onCancel: () => void
|
|
50
|
+
VideoSelector: typeof DefaultVideoSelector
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function BGVideoInput(props: BGVideoInputType) {
|
|
54
|
+
const {
|
|
55
|
+
isOpen,
|
|
56
|
+
onChange,
|
|
57
|
+
onCancel,
|
|
58
|
+
textBlockAlign,
|
|
59
|
+
video,
|
|
60
|
+
rawContentStateForBGVideoEditor,
|
|
61
|
+
VideoSelector = DefaultVideoSelector,
|
|
62
|
+
} = props
|
|
63
|
+
const rawContentState = rawContentStateForBGVideoEditor || {
|
|
64
|
+
blocks: [],
|
|
65
|
+
entityMap: {},
|
|
66
|
+
}
|
|
67
|
+
const options = [
|
|
68
|
+
{ value: 'fixed', label: 'fixed (default)', isDisabled: false },
|
|
69
|
+
{ value: 'bottom', label: 'bottom', isDisabled: false },
|
|
70
|
+
{ value: 'left', label: 'left', isDisabled: false },
|
|
71
|
+
{ value: 'right', label: 'right', isDisabled: false },
|
|
72
|
+
]
|
|
73
|
+
const initialInputValue: {
|
|
74
|
+
textBlockAlign: string
|
|
75
|
+
video?: VideoEntity
|
|
76
|
+
editorStateOfBasicEditor: EditorState
|
|
77
|
+
} = {
|
|
78
|
+
textBlockAlign: textBlockAlign || 'fixed',
|
|
79
|
+
video: video || undefined,
|
|
80
|
+
// create an `editorState` from raw content state object
|
|
81
|
+
editorStateOfBasicEditor: EditorState.createWithContent(
|
|
82
|
+
convertFromRaw(rawContentState),
|
|
83
|
+
decorators
|
|
84
|
+
),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const [inputValue, setInputValue] = useState(initialInputValue)
|
|
88
|
+
const [toShowVideoSelector, setToShowVideoSelector] = useState(false)
|
|
89
|
+
|
|
90
|
+
const clearInputValue = () => {
|
|
91
|
+
setInputValue((oldInputValue) => ({
|
|
92
|
+
...oldInputValue,
|
|
93
|
+
editorStateOfBasicEditor: EditorState.createWithContent(
|
|
94
|
+
convertFromRaw({
|
|
95
|
+
blocks: [],
|
|
96
|
+
entityMap: {},
|
|
97
|
+
}),
|
|
98
|
+
decorators
|
|
99
|
+
),
|
|
100
|
+
}))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const onVideoSelectorChange = (
|
|
104
|
+
selectedVideosWithMeta: VideoEntityWithMeta[]
|
|
105
|
+
) => {
|
|
106
|
+
const video = selectedVideosWithMeta?.[0]?.video
|
|
107
|
+
if (!video) {
|
|
108
|
+
setToShowVideoSelector(false)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setInputValue((oldInputValue) => ({
|
|
113
|
+
...oldInputValue,
|
|
114
|
+
video: video,
|
|
115
|
+
}))
|
|
116
|
+
setToShowVideoSelector(false)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (isOpen) {
|
|
121
|
+
setInputValue(initialInputValue)
|
|
122
|
+
}
|
|
123
|
+
}, [isOpen])
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<>
|
|
127
|
+
{toShowVideoSelector && (
|
|
128
|
+
<VideoSelector onChange={onVideoSelectorChange} />
|
|
129
|
+
)}
|
|
130
|
+
<DrawerController isOpen={isOpen}>
|
|
131
|
+
<Drawer
|
|
132
|
+
title={`Insert Background Video`}
|
|
133
|
+
actions={{
|
|
134
|
+
cancel: {
|
|
135
|
+
label: 'Cancel',
|
|
136
|
+
action: () => {
|
|
137
|
+
clearInputValue()
|
|
138
|
+
onCancel()
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
confirm: {
|
|
142
|
+
label: 'Confirm',
|
|
143
|
+
action: () => {
|
|
144
|
+
onChange({
|
|
145
|
+
textBlockAlign: inputValue.textBlockAlign,
|
|
146
|
+
video: inputValue.video,
|
|
147
|
+
// convert `contentState` of the `editorState` into raw content state object
|
|
148
|
+
rawContentState: convertToRaw(
|
|
149
|
+
inputValue.editorStateOfBasicEditor.getCurrentContent()
|
|
150
|
+
),
|
|
151
|
+
})
|
|
152
|
+
clearInputValue()
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
}}
|
|
156
|
+
>
|
|
157
|
+
<Label>影片</Label>
|
|
158
|
+
<div>
|
|
159
|
+
<VideoInputText>
|
|
160
|
+
{inputValue.video?.name ? inputValue.video.name : '尚未選取影片'}
|
|
161
|
+
</VideoInputText>
|
|
162
|
+
<Button
|
|
163
|
+
type="button"
|
|
164
|
+
onClick={() => setToShowVideoSelector(true)}
|
|
165
|
+
tone="passive"
|
|
166
|
+
>
|
|
167
|
+
添加影片
|
|
168
|
+
</Button>
|
|
169
|
+
</div>
|
|
170
|
+
<AlignSelector
|
|
171
|
+
align={inputValue.textBlockAlign}
|
|
172
|
+
options={options}
|
|
173
|
+
onChange={(textBlockAlign) => {
|
|
174
|
+
setInputValue((oldInputValue) => ({
|
|
175
|
+
...oldInputValue,
|
|
176
|
+
textBlockAlign,
|
|
177
|
+
}))
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
<Label>內文</Label>
|
|
181
|
+
<BasicEditor
|
|
182
|
+
editorState={inputValue.editorStateOfBasicEditor}
|
|
183
|
+
onChange={(editorStateOfBasicEditor) => {
|
|
184
|
+
setInputValue((oldInputValue) => ({
|
|
185
|
+
...oldInputValue,
|
|
186
|
+
editorStateOfBasicEditor,
|
|
187
|
+
}))
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
</Drawer>
|
|
191
|
+
</DrawerController>
|
|
192
|
+
</>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
type BGVideoButtonProps = {
|
|
197
|
+
className: string
|
|
198
|
+
editorState: EditorState
|
|
199
|
+
onChange: ({ editorState }: { editorState: EditorState }) => void
|
|
200
|
+
VideoSelector: typeof DefaultVideoSelector
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function BGVideoButton(props: BGVideoButtonProps) {
|
|
204
|
+
const [toShowInput, setToShowInput] = useState(false)
|
|
205
|
+
const {
|
|
206
|
+
className,
|
|
207
|
+
editorState,
|
|
208
|
+
onChange: onEditorStateChange,
|
|
209
|
+
VideoSelector,
|
|
210
|
+
} = props
|
|
211
|
+
|
|
212
|
+
const onChange: BGVideoInputOnChange = ({
|
|
213
|
+
textBlockAlign,
|
|
214
|
+
video,
|
|
215
|
+
rawContentState,
|
|
216
|
+
}) => {
|
|
217
|
+
const contentState = editorState.getCurrentContent()
|
|
218
|
+
|
|
219
|
+
// create an BGVideo entity
|
|
220
|
+
const contentStateWithEntity = contentState.createEntity(
|
|
221
|
+
'BACKGROUNDVIDEO',
|
|
222
|
+
'IMMUTABLE',
|
|
223
|
+
{
|
|
224
|
+
textBlockAlign,
|
|
225
|
+
video,
|
|
226
|
+
rawContentState,
|
|
227
|
+
body: draftConverter.convertToHtml(rawContentState),
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
|
|
231
|
+
const newEditorState = EditorState.set(editorState, {
|
|
232
|
+
currentContent: contentStateWithEntity,
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
//The third parameter here is a space string, not an empty string
|
|
236
|
+
//If you set an empty string, you will get an error: Unknown DraftEntity key: null
|
|
237
|
+
onEditorStateChange(
|
|
238
|
+
AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, ' ')
|
|
239
|
+
)
|
|
240
|
+
setToShowInput(false)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<React.Fragment>
|
|
245
|
+
<BGVideoInput
|
|
246
|
+
onChange={onChange}
|
|
247
|
+
onCancel={() => {
|
|
248
|
+
setToShowInput(false)
|
|
249
|
+
}}
|
|
250
|
+
isOpen={toShowInput}
|
|
251
|
+
VideoSelector={VideoSelector}
|
|
252
|
+
/>
|
|
253
|
+
<div
|
|
254
|
+
className={className}
|
|
255
|
+
onClick={() => {
|
|
256
|
+
setToShowInput(true)
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<svg
|
|
260
|
+
width="16"
|
|
261
|
+
height="10"
|
|
262
|
+
viewBox="0 0 16 10"
|
|
263
|
+
fill="none"
|
|
264
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
265
|
+
>
|
|
266
|
+
<path
|
|
267
|
+
d="M10.6667 1.25V8.75C10.6667 9.44036 10.0697 10 9.33333 10H1.33333C0.596944 10 0 9.44036 0 8.75V1.25C0 0.559635 0.596944 0 1.33333 0H9.33333C10.0694 0 10.6667 0.559635 10.6667 1.25ZM16 1.65365V8.34375C16 9.00781 15.1897 9.39557 14.6003 9.01536L11.5556 7.04948V2.95052L14.6 0.982812C15.1917 0.602344 16 0.992188 16 1.65365Z"
|
|
268
|
+
fill="#6b7280"
|
|
269
|
+
/>
|
|
270
|
+
</svg>
|
|
271
|
+
<span>Background Video</span>
|
|
272
|
+
</div>
|
|
273
|
+
</React.Fragment>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import decorators from '../editor/entity-decorator'
|
|
3
|
+
import {
|
|
4
|
+
AtomicBlockUtils,
|
|
5
|
+
EditorState,
|
|
6
|
+
RawDraftContentState,
|
|
7
|
+
convertToRaw,
|
|
8
|
+
convertFromRaw,
|
|
9
|
+
} from 'draft-js'
|
|
10
|
+
import { BasicEditor } from '../editor/basic-editor'
|
|
11
|
+
import { Drawer, DrawerController } from '@keystone-ui/modals'
|
|
12
|
+
import { TextInput } from '@keystone-ui/fields'
|
|
13
|
+
import draftConverter from '../editor/draft-converter'
|
|
14
|
+
import styled from 'styled-components'
|
|
15
|
+
|
|
16
|
+
const Label = styled.label`
|
|
17
|
+
display: block;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
margin: 10px 0;
|
|
20
|
+
`
|
|
21
|
+
|
|
22
|
+
const ColorHexInput = styled(TextInput)`
|
|
23
|
+
margin-bottom: 10px;
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
type ColorBoxInputType = {
|
|
27
|
+
color?: string
|
|
28
|
+
rawContentStateForColorBoxEditor?: RawDraftContentState
|
|
29
|
+
isOpen: boolean
|
|
30
|
+
onChange: ({
|
|
31
|
+
color,
|
|
32
|
+
rawContentState,
|
|
33
|
+
}: {
|
|
34
|
+
color: string
|
|
35
|
+
rawContentState: RawDraftContentState
|
|
36
|
+
}) => void
|
|
37
|
+
onCancel: () => void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ColorBoxInput(props: ColorBoxInputType) {
|
|
41
|
+
const {
|
|
42
|
+
isOpen,
|
|
43
|
+
onChange,
|
|
44
|
+
onCancel,
|
|
45
|
+
color,
|
|
46
|
+
rawContentStateForColorBoxEditor,
|
|
47
|
+
} = props
|
|
48
|
+
const rawContentState = rawContentStateForColorBoxEditor || {
|
|
49
|
+
blocks: [],
|
|
50
|
+
entityMap: {},
|
|
51
|
+
}
|
|
52
|
+
const initialInputValue = {
|
|
53
|
+
color: color || '',
|
|
54
|
+
// create an `editorState` from raw content state object
|
|
55
|
+
editorStateOfBasicEditor: EditorState.createWithContent(
|
|
56
|
+
convertFromRaw(rawContentState),
|
|
57
|
+
decorators
|
|
58
|
+
),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const [inputValue, setInputValue] = useState(initialInputValue)
|
|
62
|
+
|
|
63
|
+
const clearInputValue = () => {
|
|
64
|
+
setInputValue({
|
|
65
|
+
color: '',
|
|
66
|
+
editorStateOfBasicEditor: EditorState.createWithContent(
|
|
67
|
+
convertFromRaw({
|
|
68
|
+
blocks: [],
|
|
69
|
+
entityMap: {},
|
|
70
|
+
}),
|
|
71
|
+
decorators
|
|
72
|
+
),
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (isOpen) {
|
|
78
|
+
setInputValue(initialInputValue)
|
|
79
|
+
}
|
|
80
|
+
}, [isOpen])
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<DrawerController isOpen={isOpen}>
|
|
84
|
+
<Drawer
|
|
85
|
+
title={`Insert Color Box`}
|
|
86
|
+
actions={{
|
|
87
|
+
cancel: {
|
|
88
|
+
label: 'Cancel',
|
|
89
|
+
action: () => {
|
|
90
|
+
clearInputValue()
|
|
91
|
+
onCancel()
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
confirm: {
|
|
95
|
+
label: 'Confirm',
|
|
96
|
+
action: () => {
|
|
97
|
+
onChange({
|
|
98
|
+
color: inputValue.color,
|
|
99
|
+
// convert `contentState` of the `editorState` into raw content state object
|
|
100
|
+
rawContentState: convertToRaw(
|
|
101
|
+
inputValue.editorStateOfBasicEditor.getCurrentContent()
|
|
102
|
+
),
|
|
103
|
+
})
|
|
104
|
+
clearInputValue()
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<Label>Hex Color Code (#ffffff)</Label>
|
|
110
|
+
<ColorHexInput
|
|
111
|
+
onChange={(e) =>
|
|
112
|
+
setInputValue({
|
|
113
|
+
color: e.target.value,
|
|
114
|
+
editorStateOfBasicEditor: inputValue.editorStateOfBasicEditor,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
type="text"
|
|
118
|
+
placeholder="Color"
|
|
119
|
+
value={inputValue.color}
|
|
120
|
+
/>
|
|
121
|
+
<Label>內文</Label>
|
|
122
|
+
<BasicEditor
|
|
123
|
+
editorState={inputValue.editorStateOfBasicEditor}
|
|
124
|
+
onChange={(editorStateOfBasicEditor) => {
|
|
125
|
+
setInputValue({
|
|
126
|
+
color: inputValue.color,
|
|
127
|
+
editorStateOfBasicEditor,
|
|
128
|
+
})
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
</Drawer>
|
|
132
|
+
</DrawerController>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
type ColorBoxButtonProps = {
|
|
137
|
+
className: string
|
|
138
|
+
editorState: EditorState
|
|
139
|
+
onChange: ({ editorState }: { editorState: EditorState }) => void
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function ColorBoxButton(props: ColorBoxButtonProps) {
|
|
143
|
+
const [toShowInput, setToShowInput] = useState(false)
|
|
144
|
+
const { className, editorState, onChange: onEditorStateChange } = props
|
|
145
|
+
|
|
146
|
+
const onChange = ({ color, rawContentState }) => {
|
|
147
|
+
const contentState = editorState.getCurrentContent()
|
|
148
|
+
|
|
149
|
+
// create an ColorBox entity
|
|
150
|
+
const contentStateWithEntity = contentState.createEntity(
|
|
151
|
+
'COLORBOX',
|
|
152
|
+
'IMMUTABLE',
|
|
153
|
+
{
|
|
154
|
+
color,
|
|
155
|
+
rawContentState,
|
|
156
|
+
body: draftConverter.convertToHtml(rawContentState),
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
|
|
160
|
+
const newEditorState = EditorState.set(editorState, {
|
|
161
|
+
currentContent: contentStateWithEntity,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
//The third parameter here is a space string, not an empty string
|
|
165
|
+
//If you set an empty string, you will get an error: Unknown DraftEntity key: null
|
|
166
|
+
onEditorStateChange(
|
|
167
|
+
AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, ' ')
|
|
168
|
+
)
|
|
169
|
+
setToShowInput(false)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<React.Fragment>
|
|
174
|
+
<ColorBoxInput
|
|
175
|
+
onChange={onChange}
|
|
176
|
+
onCancel={() => {
|
|
177
|
+
setToShowInput(false)
|
|
178
|
+
}}
|
|
179
|
+
isOpen={toShowInput}
|
|
180
|
+
/>
|
|
181
|
+
<div
|
|
182
|
+
className={className}
|
|
183
|
+
onClick={() => {
|
|
184
|
+
setToShowInput(true)
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
<svg
|
|
188
|
+
width="16"
|
|
189
|
+
height="16"
|
|
190
|
+
viewBox="0 0 16 16"
|
|
191
|
+
fill="none"
|
|
192
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
193
|
+
>
|
|
194
|
+
<path
|
|
195
|
+
d="M14 0H2C0.895431 0 0 0.895431 0 2V14C0 15.1046 0.895431 16 2 16H14C15.1046 16 16 15.1046 16 14V2C16 0.895431 15.1046 0 14 0Z"
|
|
196
|
+
fill="#6b7280"
|
|
197
|
+
/>
|
|
198
|
+
<path
|
|
199
|
+
d="M12.3867 2.66667H3.61332C3.36225 2.66667 3.12146 2.76641 2.94393 2.94394C2.76639 3.12148 2.66666 3.36227 2.66666 3.61334V5.51112C2.68312 5.75156 2.79025 5.97678 2.96639 6.14127C3.14253 6.30576 3.37454 6.39725 3.61555 6.39725C3.85655 6.39725 4.08856 6.30576 4.2647 6.14127C4.44084 5.97678 4.54797 5.75156 4.56443 5.51112V4.56445H6.94221V11.4356H5.99555C5.86547 11.4267 5.73496 11.4446 5.61211 11.4882C5.48926 11.5319 5.3767 11.6003 5.28141 11.6893C5.18612 11.7783 5.11015 11.8859 5.0582 12.0055C5.00626 12.1251 4.97946 12.2541 4.97946 12.3844C4.97946 12.5148 5.00626 12.6438 5.0582 12.7634C5.11015 12.883 5.18612 12.9906 5.28141 13.0796C5.3767 13.1686 5.48926 13.237 5.61211 13.2807C5.73496 13.3243 5.86547 13.3422 5.99555 13.3333H9.78666C10.0271 13.3169 10.2523 13.2097 10.4168 13.0336C10.5813 12.8575 10.6728 12.6255 10.6728 12.3844C10.6728 12.1434 10.5813 11.9114 10.4168 11.7353C10.2523 11.5592 10.0271 11.452 9.78666 11.4356H8.83999V4.56445H11.4355V5.51112C11.452 5.75156 11.5591 5.97678 11.7353 6.14127C11.9114 6.30576 12.1434 6.39725 12.3844 6.39725C12.6254 6.39725 12.8575 6.30576 13.0336 6.14127C13.2097 5.97678 13.3169 5.75156 13.3333 5.51112V3.61334C13.3333 3.36227 13.2336 3.12148 13.0561 2.94394C12.8785 2.76641 12.6377 2.66667 12.3867 2.66667Z"
|
|
200
|
+
fill="white"
|
|
201
|
+
/>
|
|
202
|
+
</svg>
|
|
203
|
+
<span> ColorBox</span>
|
|
204
|
+
</div>
|
|
205
|
+
</React.Fragment>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { AtomicBlockUtils, EditorState } from 'draft-js'
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
|
|
5
|
+
const IconWrapper = styled.span`
|
|
6
|
+
display: inline-block;
|
|
7
|
+
position: relative;
|
|
8
|
+
top: 2px;
|
|
9
|
+
`
|
|
10
|
+
|
|
11
|
+
export function DividerButton(props) {
|
|
12
|
+
const { editorState, onChange, className } = props
|
|
13
|
+
|
|
14
|
+
const onClick = () => {
|
|
15
|
+
const contentState = editorState.getCurrentContent()
|
|
16
|
+
const contentStateWithEntity = contentState.createEntity(
|
|
17
|
+
'DIVIDER',
|
|
18
|
+
'IMMUTABLE',
|
|
19
|
+
{}
|
|
20
|
+
)
|
|
21
|
+
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
|
|
22
|
+
const newEditorState = EditorState.set(editorState, {
|
|
23
|
+
currentContent: contentStateWithEntity,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// The third parameter here is a space string, not an empty string
|
|
27
|
+
// If you set an empty string, you will get an error: Unknown DraftEntity key: null
|
|
28
|
+
onChange(AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, ' '))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<React.Fragment>
|
|
33
|
+
<div onClick={onClick} className={className}>
|
|
34
|
+
<IconWrapper>
|
|
35
|
+
<svg
|
|
36
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
+
height="16"
|
|
38
|
+
viewBox="0 0 16 16"
|
|
39
|
+
width="16"
|
|
40
|
+
>
|
|
41
|
+
<g fill="none" fillRule="evenodd">
|
|
42
|
+
<path d="M0 0h16v16H0z" />
|
|
43
|
+
<path
|
|
44
|
+
d="M15 10.501a0.5 0.5 0 0 1 0.496 0.442l0.004 0.059v2.031a2.468 2.468 0 0 1 -2.361 2.466l-0.107 0.003H2.967a2.467 2.467 0 0 1 -2.465 -2.36L0.5 13.032v-2.03a0.5 0.5 0 0 1 0.997 -0.058l0.004 0.059v2.03a1.468 1.468 0 0 0 1.381 1.464l0.086 0.003h10.065a1.468 1.468 0 0 0 1.466 -1.382l0.003 -0.086v-2.031a0.5 0.5 0 0 1 0.5 -0.5zM15.5 7.5a0.5 0.5 0 0 1 0 1H0.5a0.5 0.5 0 0 1 0 -1zM13.029 0.5a2.471 2.471 0 0 1 2.469 2.364l0.003 0.107v2.031a0.5 0.5 0 0 1 -0.997 0.058L14.5 5.003V2.971a1.471 1.471 0 0 0 -1.385 -1.468L13.029 1.5H2.97a1.47 1.47 0 0 0 -1.467 1.383L1.5 2.97V5a0.5 0.5 0 0 1 -0.997 0.058L0.5 5V2.97a2.47 2.47 0 0 1 2.362 -2.467L2.97 0.5z"
|
|
45
|
+
fill="#6b7280"
|
|
46
|
+
fillRule="nonzero"
|
|
47
|
+
stroke="#6b7280"
|
|
48
|
+
strokeWidth="0.5"
|
|
49
|
+
/>
|
|
50
|
+
</g>
|
|
51
|
+
</svg>
|
|
52
|
+
</IconWrapper>
|
|
53
|
+
</div>
|
|
54
|
+
</React.Fragment>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import { AtomicBlockUtils, EditorState } from 'draft-js'
|
|
4
|
+
import { Drawer, DrawerController } from '@keystone-ui/modals'
|
|
5
|
+
import { TextInput, TextArea } from '@keystone-ui/fields'
|
|
6
|
+
|
|
7
|
+
export const Block = styled.div`
|
|
8
|
+
position: relative;
|
|
9
|
+
/* styles for image link */
|
|
10
|
+
img.img-responsive {
|
|
11
|
+
margin: 0 auto;
|
|
12
|
+
max-width: 100%;
|
|
13
|
+
height: auto;
|
|
14
|
+
display: block;
|
|
15
|
+
}
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
export const Caption = styled.div`
|
|
19
|
+
line-height: 1.43;
|
|
20
|
+
letter-spacing: 0.4px;
|
|
21
|
+
font-size: 14px;
|
|
22
|
+
color: #808080;
|
|
23
|
+
padding: 15px 15px 0 15px;
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
export function EmbeddedCodeButton(props) {
|
|
27
|
+
const { editorState, onChange, className } = props
|
|
28
|
+
|
|
29
|
+
const [toShowInput, setToShowInput] = useState(false)
|
|
30
|
+
const [inputValue, setInputValue] = useState({
|
|
31
|
+
caption: '',
|
|
32
|
+
embeddedCode: '',
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const promptForInput = () => {
|
|
36
|
+
setToShowInput(true)
|
|
37
|
+
setInputValue({
|
|
38
|
+
caption: '',
|
|
39
|
+
embeddedCode: '',
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const confirmInput = () => {
|
|
44
|
+
const contentState = editorState.getCurrentContent()
|
|
45
|
+
const contentStateWithEntity = contentState.createEntity(
|
|
46
|
+
'EMBEDDEDCODE',
|
|
47
|
+
'IMMUTABLE',
|
|
48
|
+
inputValue
|
|
49
|
+
)
|
|
50
|
+
const entityKey = contentStateWithEntity.getLastCreatedEntityKey()
|
|
51
|
+
const newEditorState = EditorState.set(editorState, {
|
|
52
|
+
currentContent: contentStateWithEntity,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// The third parameter here is a space string, not an empty string
|
|
56
|
+
// If you set an empty string, you will get an error: Unknown DraftEntity key: null
|
|
57
|
+
onChange(AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, ' '))
|
|
58
|
+
|
|
59
|
+
setToShowInput(false)
|
|
60
|
+
setInputValue({
|
|
61
|
+
caption: '',
|
|
62
|
+
embeddedCode: '',
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const input = (
|
|
67
|
+
<DrawerController isOpen={toShowInput}>
|
|
68
|
+
<Drawer
|
|
69
|
+
title={`Insert Embedded Code`}
|
|
70
|
+
//isOpen={toShowInput}
|
|
71
|
+
actions={{
|
|
72
|
+
cancel: {
|
|
73
|
+
label: 'Cancel',
|
|
74
|
+
action: () => {
|
|
75
|
+
setToShowInput(false)
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
confirm: {
|
|
79
|
+
label: 'Confirm',
|
|
80
|
+
action: confirmInput,
|
|
81
|
+
},
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
<TextInput
|
|
85
|
+
onChange={(e) =>
|
|
86
|
+
setInputValue({
|
|
87
|
+
caption: e.target.value,
|
|
88
|
+
embeddedCode: inputValue.embeddedCode,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
type="text"
|
|
92
|
+
placeholder="Caption"
|
|
93
|
+
value={inputValue.caption}
|
|
94
|
+
style={{ marginBottom: '10px', marginTop: '30px' }}
|
|
95
|
+
/>
|
|
96
|
+
<TextArea
|
|
97
|
+
onChange={(e) =>
|
|
98
|
+
setInputValue({
|
|
99
|
+
caption: inputValue.caption,
|
|
100
|
+
embeddedCode: e.target.value,
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
placeholder="Embedded Code"
|
|
104
|
+
type="text"
|
|
105
|
+
value={inputValue.embeddedCode}
|
|
106
|
+
style={{ marginBottom: '30px' }}
|
|
107
|
+
/>
|
|
108
|
+
</Drawer>
|
|
109
|
+
</DrawerController>
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<React.Fragment>
|
|
114
|
+
{input}
|
|
115
|
+
<div
|
|
116
|
+
onClick={() => {
|
|
117
|
+
promptForInput()
|
|
118
|
+
}}
|
|
119
|
+
className={className}
|
|
120
|
+
>
|
|
121
|
+
<i className="far"></i>
|
|
122
|
+
<span>Embed</span>
|
|
123
|
+
</div>
|
|
124
|
+
</React.Fragment>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
export function EnlargeButton(props) {
|
|
4
|
+
const { onToggle, isEnlarged, className } = props
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<div className={className} onClick={onToggle}>
|
|
8
|
+
<i className={isEnlarged ? 'fas fa-compress' : 'fas fa-expand'}></i>
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
}
|