@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,65 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import styled from 'styled-components'
3
+ import { Select } from '@keystone-ui/fields'
4
+
5
+ const Label = styled.label`
6
+ display: block;
7
+ margin: 10px 0;
8
+ font-weight: 600;
9
+ `
10
+
11
+ const AlignSelect = styled(Select)`
12
+ ${({ menuHeight }) => {
13
+ return `margin-bottom: ${menuHeight}px;`
14
+ }}
15
+ `
16
+
17
+ type Option = { label: string; value: string; isDisabled?: boolean }
18
+ type AlignSelectorOnChangeFn = (param: string) => void
19
+ type Options = Option[]
20
+
21
+ export function AlignSelector(props: {
22
+ align: string
23
+ options: Options
24
+ onChange: AlignSelectorOnChangeFn
25
+ onOpen?: () => void
26
+ }) {
27
+ const [isOpen, setIsOpen] = useState(false)
28
+ const [menuHeight, setMenuHeight] = useState(0)
29
+ const { align, options, onChange, onOpen } = props
30
+
31
+ useEffect(() => {
32
+ const selectMenu = document.querySelector(
33
+ '.css-nabggt-menu'
34
+ ) as HTMLElement | null
35
+
36
+ if (selectMenu) {
37
+ const styles = window.getComputedStyle(selectMenu)
38
+ const margin =
39
+ parseFloat(styles['marginTop']) + parseFloat(styles['marginBottom'])
40
+ setMenuHeight(selectMenu.offsetHeight + margin)
41
+ } else {
42
+ setMenuHeight(0)
43
+ }
44
+ if (isOpen && onOpen) {
45
+ onOpen()
46
+ }
47
+ }, [isOpen])
48
+ return (
49
+ <React.Fragment>
50
+ <Label htmlFor="alignment">對齊</Label>
51
+ <AlignSelect
52
+ id="alignment"
53
+ // default align === undefined
54
+ value={options.find((option) => option.value === align)}
55
+ options={options}
56
+ onChange={(option) => {
57
+ onChange(option.value)
58
+ }}
59
+ onMenuOpen={() => setIsOpen(true)}
60
+ onMenuClose={() => setIsOpen(false)}
61
+ menuHeight={menuHeight}
62
+ />
63
+ </React.Fragment>
64
+ )
65
+ }
@@ -0,0 +1,485 @@
1
+ import React, { useState, useEffect, useRef } from 'react'
2
+ import debounce from 'lodash/debounce'
3
+ import styled from 'styled-components'
4
+ import { TextInput } from '@keystone-ui/fields'
5
+ import { Drawer, DrawerController } from '@keystone-ui/modals'
6
+ import { gql, useLazyQuery } from '@keystone-6/core/admin-ui/apollo'
7
+ import { AlignSelector } from './align-selector'
8
+ import { SearchBox, SearchBoxOnChangeFn } from './search-box'
9
+ import { Pagination } from './pagination'
10
+
11
+ const imagesQuery = gql`
12
+ query Photos($searchText: String!, $take: Int, $skip: Int) {
13
+ photosCount(where: { name: { contains: $searchText } })
14
+ photos(
15
+ where: { name: { contains: $searchText } }
16
+ take: $take
17
+ skip: $skip
18
+ ) {
19
+ id
20
+ name
21
+ imageFile {
22
+ url
23
+ }
24
+ resized {
25
+ original
26
+ w480
27
+ w800
28
+ w1200
29
+ w1600
30
+ w2400
31
+ }
32
+ }
33
+ }
34
+ `
35
+
36
+ const _ = {
37
+ debounce,
38
+ }
39
+
40
+ const ImageSearchBox = styled(SearchBox)`
41
+ margin-top: 10px;
42
+ `
43
+
44
+ const ImageSelectionWrapper = styled.div`
45
+ overflow: auto;
46
+ margin-top: 10px;
47
+ `
48
+ const ImageBlockMetaWrapper = styled.div``
49
+
50
+ const ImageGridsWrapper = styled.div`
51
+ display: flex;
52
+ flex-wrap: wrap;
53
+ overflow: auto;
54
+ margin-top: 5px;
55
+ `
56
+
57
+ const ImageGridWrapper = styled.div`
58
+ flex: 0 0 33.3333%;
59
+ cursor: pointer;
60
+ padding: 0 10px 10px;
61
+ `
62
+
63
+ const ImageMetaGridsWrapper = styled.div`
64
+ display: flex;
65
+ flex-wrap: wrap;
66
+ overflow: auto;
67
+ `
68
+
69
+ const ImageMetaGridWrapper = styled.div`
70
+ flex: 0 0 33.3333%;
71
+ cursor: pointer;
72
+ padding: 0 10px 10px;
73
+ `
74
+
75
+ const Image = styled.img`
76
+ display: block;
77
+ width: 100%;
78
+ aspect-ratio: 2;
79
+ object-fit: cover;
80
+ `
81
+
82
+ const Label = styled.label`
83
+ display: block;
84
+ margin: 10px 0;
85
+ font-weight: 600;
86
+ `
87
+
88
+ const SeparationLine = styled.div`
89
+ border: #e1e5e9 1px solid;
90
+ margin-top: 10px;
91
+ margin-bottom: 10px;
92
+ `
93
+
94
+ const ImageSelected = styled.div`
95
+ height: 1.4rem;
96
+ `
97
+
98
+ const ErrorWrapper = styled.div`
99
+ & * {
100
+ margin: 0;
101
+ }
102
+ `
103
+
104
+ type ID = string
105
+
106
+ export type ImageEntity = {
107
+ id: ID
108
+ name?: string
109
+ imageFile: {
110
+ url: string
111
+ }
112
+ resized: {
113
+ original: string
114
+ w480: string
115
+ w800: string
116
+ w1200: string
117
+ w1600: string
118
+ w2400: string
119
+ }
120
+ }
121
+
122
+ export type ImageEntityWithMeta = {
123
+ image: ImageEntity
124
+ desc?: string
125
+ url?: string
126
+ }
127
+
128
+ type ImageEntityOnSelectFn = (param: ImageEntity) => void
129
+
130
+ function ImageGrids(props: {
131
+ images: ImageEntity[]
132
+ selected: ImageEntity[]
133
+ onSelect: ImageEntityOnSelectFn
134
+ }): React.ReactElement {
135
+ const { images, selected, onSelect } = props
136
+
137
+ return (
138
+ <ImageGridsWrapper>
139
+ {images.map((image) => {
140
+ return (
141
+ <ImageGrid
142
+ key={image.id}
143
+ isSelected={selected?.includes(image)}
144
+ onSelect={() => onSelect(image)}
145
+ image={image}
146
+ />
147
+ )
148
+ })}
149
+ </ImageGridsWrapper>
150
+ )
151
+ }
152
+
153
+ function ImageGrid(props: {
154
+ image: ImageEntity
155
+ isSelected: boolean
156
+ onSelect: ImageEntityOnSelectFn
157
+ }) {
158
+ const { image, onSelect, isSelected } = props
159
+ return (
160
+ <ImageGridWrapper key={image?.id} onClick={() => onSelect(image)}>
161
+ <ImageSelected>
162
+ {isSelected ? <i className="fas fa-check-circle"></i> : null}
163
+ </ImageSelected>
164
+ <Image
165
+ src={image?.resized?.w800}
166
+ onError={(e) => (e.currentTarget.src = image?.imageFile?.url)}
167
+ />
168
+ </ImageGridWrapper>
169
+ )
170
+ }
171
+
172
+ type ImageMetaOnChangeFn = (params: ImageEntityWithMeta) => void
173
+
174
+ function ImageMetaGrids(props: {
175
+ imageMetas: ImageEntityWithMeta[]
176
+ onChange: ImageMetaOnChangeFn
177
+ enableCaption: boolean
178
+ enableUrl: boolean
179
+ }) {
180
+ const { imageMetas, onChange, enableCaption, enableUrl } = props
181
+ return (
182
+ <ImageMetaGridsWrapper>
183
+ {imageMetas.map((imageMeta) => (
184
+ <ImageMetaGrid
185
+ key={imageMeta?.image?.id}
186
+ imageMeta={imageMeta}
187
+ enableCaption={enableCaption}
188
+ enableUrl={enableUrl}
189
+ onChange={onChange}
190
+ />
191
+ ))}
192
+ </ImageMetaGridsWrapper>
193
+ )
194
+ }
195
+
196
+ function ImageMetaGrid(props: {
197
+ imageMeta: ImageEntityWithMeta
198
+ onChange: ImageMetaOnChangeFn
199
+ enableCaption: boolean
200
+ enableUrl: boolean
201
+ }): React.ReactElement {
202
+ const { imageMeta, enableCaption, enableUrl, onChange } = props
203
+ const { image, desc, url } = imageMeta
204
+
205
+ return (
206
+ <ImageMetaGridWrapper>
207
+ <Image
208
+ src={image?.resized?.w800}
209
+ onError={(e) => (e.currentTarget.src = image?.imageFile?.url)}
210
+ />
211
+ {enableCaption && (
212
+ <React.Fragment>
213
+ <Label htmlFor="caption">Image Caption:</Label>
214
+ <TextInput
215
+ id="caption"
216
+ type="text"
217
+ placeholder={image?.name}
218
+ defaultValue={desc}
219
+ onChange={_.debounce((e) => {
220
+ onChange({
221
+ image,
222
+ desc: e.target.value,
223
+ url,
224
+ })
225
+ })}
226
+ />
227
+ </React.Fragment>
228
+ )}
229
+ {enableUrl && (
230
+ <React.Fragment>
231
+ <Label htmlFor="url">Url:</Label>
232
+ <TextInput
233
+ id="url"
234
+ type="text"
235
+ placeholder="(Optional)"
236
+ defaultValue={url}
237
+ onChange={_.debounce((e) => {
238
+ onChange({
239
+ image,
240
+ desc,
241
+ url: e.target.value,
242
+ })
243
+ })}
244
+ />
245
+ </React.Fragment>
246
+ )}
247
+ </ImageMetaGridWrapper>
248
+ )
249
+ }
250
+
251
+ type DelayInputOnChangeFn = (param: string) => void
252
+
253
+ function DelayInput(props: {
254
+ delay: string
255
+ onChange: DelayInputOnChangeFn
256
+ }): React.ReactElement {
257
+ const { delay, onChange } = props
258
+
259
+ return (
260
+ <React.Fragment>
261
+ <Label>Slideshow delay:</Label>
262
+ <TextInput
263
+ type="number"
264
+ placeholder="請輸入自動切換秒數"
265
+ step="0.5"
266
+ min="1"
267
+ value={delay}
268
+ onChange={(e) => {
269
+ onChange(e.target.value)
270
+ }}
271
+ />
272
+ </React.Fragment>
273
+ )
274
+ }
275
+
276
+ type ImageSelectorOnChangeFn = (
277
+ params: ImageEntityWithMeta[],
278
+ align?: string,
279
+ delay?: number
280
+ ) => void
281
+
282
+ export function ImageSelector(props: {
283
+ enableMultiSelect?: boolean
284
+ enableCaption?: boolean
285
+ enableUrl?: boolean
286
+ enableAlignment?: boolean
287
+ enableDelay?: boolean
288
+ onChange: ImageSelectorOnChangeFn
289
+ }) {
290
+ const [
291
+ queryImages,
292
+ {
293
+ loading,
294
+ error,
295
+ data: { photos: images = [], photosCount: imagesCount = 0 } = {},
296
+ },
297
+ ] = useLazyQuery(imagesQuery, { fetchPolicy: 'no-cache' })
298
+ const [currentPage, setCurrentPage] = useState(0) // page starts with 1, 0 is used to detect initialization
299
+ const [searchText, setSearchText] = useState('')
300
+ const [selected, setSelected] = useState<ImageEntityWithMeta[]>([])
301
+ const [delay, setDelay] = useState('5')
302
+ const [align, setAlign] = useState(undefined)
303
+ const contentWrapperRef = useRef<HTMLDivElement>()
304
+
305
+ const pageSize = 6
306
+
307
+ const options = [
308
+ { value: undefined, label: 'default', isDisabled: false },
309
+ { value: 'left', label: 'left', isDisabled: false },
310
+ { value: 'right', label: 'right', isDisabled: false },
311
+ ]
312
+
313
+ const {
314
+ enableMultiSelect = false,
315
+ enableCaption = false,
316
+ enableUrl = false,
317
+ enableAlignment = false,
318
+ enableDelay = false,
319
+ onChange,
320
+ } = props
321
+
322
+ const onSave = () => {
323
+ let adjustedDelay = +delay
324
+ adjustedDelay = adjustedDelay < 1 ? 1 : adjustedDelay
325
+ onChange(selected, align, adjustedDelay)
326
+ }
327
+
328
+ const onCancel = () => {
329
+ onChange([])
330
+ }
331
+
332
+ const onSearchBoxChange: SearchBoxOnChangeFn = async (searchInput) => {
333
+ setSearchText(searchInput)
334
+ setCurrentPage(1)
335
+ }
336
+
337
+ const onDealyChange = (delay: string) => {
338
+ setDelay(delay)
339
+ }
340
+
341
+ const onAlignSelectChange = (align: string) => {
342
+ setAlign(align)
343
+ }
344
+
345
+ const onAlignSelectOpen = () => {
346
+ const scrollWrapper = contentWrapperRef.current?.parentElement
347
+ scrollWrapper.scrollTop = scrollWrapper.scrollHeight
348
+ }
349
+
350
+ const onImageMetaChange: ImageMetaOnChangeFn = (imageEntityWithMeta) => {
351
+ if (enableMultiSelect) {
352
+ const foundIndex = selected.findIndex(
353
+ (ele) => ele?.image?.id === imageEntityWithMeta?.image?.id
354
+ )
355
+ if (foundIndex !== -1) {
356
+ selected[foundIndex] = imageEntityWithMeta
357
+ setSelected(selected)
358
+ }
359
+ return
360
+ }
361
+ setSelected([imageEntityWithMeta])
362
+ }
363
+
364
+ const onImagesGridSelect: ImageEntityOnSelectFn = (imageEntity) => {
365
+ setSelected((selected) => {
366
+ const filterdSelected = selected.filter(
367
+ (ele) => ele.image?.id !== imageEntity.id
368
+ )
369
+
370
+ // deselect the image
371
+ if (filterdSelected.length !== selected.length) {
372
+ return filterdSelected
373
+ }
374
+
375
+ // add new selected one
376
+ if (enableMultiSelect) {
377
+ return selected.concat([{ image: imageEntity, desc: '' }])
378
+ }
379
+
380
+ // single select
381
+ return [{ image: imageEntity, desc: '' }]
382
+ })
383
+ }
384
+
385
+ const selectedImages = selected.map((ele: ImageEntityWithMeta) => {
386
+ return ele.image
387
+ })
388
+
389
+ useEffect(() => {
390
+ if (currentPage !== 0) {
391
+ queryImages({
392
+ variables: {
393
+ searchText: searchText,
394
+ skip: (currentPage - 1) * pageSize,
395
+ take: pageSize,
396
+ },
397
+ })
398
+ }
399
+ }, [currentPage, searchText])
400
+
401
+ let searchResult = (
402
+ <React.Fragment>
403
+ <ImageGrids
404
+ images={images}
405
+ selected={selectedImages}
406
+ onSelect={onImagesGridSelect}
407
+ />
408
+ <Pagination
409
+ currentPage={currentPage}
410
+ total={imagesCount}
411
+ pageSize={pageSize}
412
+ onChange={(pageIndex) => {
413
+ setCurrentPage(pageIndex)
414
+ }}
415
+ />
416
+ </React.Fragment>
417
+ )
418
+ if (loading) {
419
+ searchResult = <p>searching...</p>
420
+ }
421
+ if (error) {
422
+ searchResult = (
423
+ <ErrorWrapper>
424
+ <h3>Errors occurs in the `images` query</h3>
425
+ <div>
426
+ <br />
427
+ <b>Message:</b>
428
+ <div>{error.message}</div>
429
+ <br />
430
+ <b>Stack:</b>
431
+ <div>{error.stack}</div>
432
+ <br />
433
+ <b>Query:</b>
434
+ <pre>{imagesQuery.loc.source.body}</pre>
435
+ </div>
436
+ </ErrorWrapper>
437
+ )
438
+ }
439
+
440
+ return (
441
+ <DrawerController isOpen={true}>
442
+ <Drawer
443
+ title="Select image"
444
+ actions={{
445
+ cancel: {
446
+ label: 'Cancel',
447
+ action: onCancel,
448
+ },
449
+ confirm: {
450
+ label: 'Confirm',
451
+ action: onSave,
452
+ },
453
+ }}
454
+ >
455
+ <div ref={contentWrapperRef}>
456
+ <ImageSearchBox onChange={onSearchBoxChange} />
457
+ <ImageSelectionWrapper>
458
+ <div>{searchResult}</div>
459
+ {!!selected.length && <SeparationLine />}
460
+ <ImageMetaGrids
461
+ imageMetas={selected}
462
+ onChange={onImageMetaChange}
463
+ enableCaption={enableCaption}
464
+ enableUrl={enableUrl}
465
+ />
466
+ </ImageSelectionWrapper>
467
+ <ImageBlockMetaWrapper>
468
+ {(enableDelay || enableAlignment) && <SeparationLine />}
469
+ {enableDelay && (
470
+ <DelayInput delay={delay} onChange={onDealyChange} />
471
+ )}
472
+ {enableAlignment && (
473
+ <AlignSelector
474
+ align={align}
475
+ options={options}
476
+ onChange={onAlignSelectChange}
477
+ onOpen={onAlignSelectOpen}
478
+ />
479
+ )}
480
+ </ImageBlockMetaWrapper>
481
+ </div>
482
+ </Drawer>
483
+ </DrawerController>
484
+ )
485
+ }
@@ -0,0 +1,83 @@
1
+ import React from 'react'
2
+ import styled from 'styled-components'
3
+
4
+ const PaginationWrapper = styled.div`
5
+ display: flex;
6
+ justify-content: end;
7
+ `
8
+
9
+ const Arrows = styled.div`
10
+ display: flex;
11
+ `
12
+
13
+ const ArrowButtonWrapper = styled.a`
14
+ color: #415269;
15
+ cursor: pointer;
16
+ ${({ disable }) => {
17
+ if (disable) {
18
+ return `
19
+ pointer-events: none;
20
+ opacity: 0.65;
21
+ cursor: unset;
22
+ `
23
+ }
24
+ }}
25
+ `
26
+
27
+ export function Pagination({ currentPage, total, pageSize, onChange }) {
28
+ const minPage = 1
29
+ const limit = Math.ceil(total / pageSize)
30
+ const nextPage = currentPage + 1
31
+ const prevPage = currentPage - 1
32
+
33
+ // Don't render the pagiantion component if the pageSize is greater than the total number of items in the list.
34
+ if (total <= pageSize) return null
35
+
36
+ return (
37
+ <PaginationWrapper>
38
+ <div>
39
+ {currentPage} of {limit} pages
40
+ </div>
41
+ <Arrows>
42
+ <ArrowButtonWrapper
43
+ onClick={() => {
44
+ onChange(prevPage)
45
+ }}
46
+ disable={prevPage < minPage}
47
+ >
48
+ <svg
49
+ aria-hidden="true"
50
+ focusable="false"
51
+ height="24px"
52
+ width="24px"
53
+ role="img"
54
+ viewBox="0 0 24 24"
55
+ xmlns="http://www.w3.org/2000/svg"
56
+ className="css-bztyua"
57
+ >
58
+ <polyline points="15 18 9 12 15 6"></polyline>{' '}
59
+ </svg>
60
+ </ArrowButtonWrapper>
61
+ <ArrowButtonWrapper
62
+ onClick={() => {
63
+ onChange(nextPage)
64
+ }}
65
+ disable={nextPage > limit}
66
+ >
67
+ <svg
68
+ aria-hidden="true"
69
+ focusable="false"
70
+ height="24px"
71
+ width="24px"
72
+ role="img"
73
+ viewBox="0 0 24 24"
74
+ xmlns="http://www.w3.org/2000/svg"
75
+ className="css-bztyua"
76
+ >
77
+ <polyline points="9 18 15 12 9 6"></polyline>
78
+ </svg>
79
+ </ArrowButtonWrapper>
80
+ </Arrows>
81
+ </PaginationWrapper>
82
+ )
83
+ }