@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 _ = {
12
+ debounce,
13
+ }
14
+
15
+ const ImageSearchBox = styled(SearchBox)`
16
+ margin-top: 10px;
17
+ `
18
+
19
+ const ImageSelectionWrapper = styled.div`
20
+ overflow: auto;
21
+ margin-top: 10px;
22
+ `
23
+ const ImageBlockMetaWrapper = styled.div``
24
+
25
+ const ImageGridsWrapper = styled.div`
26
+ display: flex;
27
+ flex-wrap: wrap;
28
+ overflow: auto;
29
+ margin-top: 5px;
30
+ `
31
+
32
+ const ImageGridWrapper = styled.div`
33
+ flex: 0 0 33.3333%;
34
+ cursor: pointer;
35
+ padding: 0 10px 10px;
36
+ `
37
+
38
+ const ImageMetaGridsWrapper = styled.div`
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ overflow: auto;
42
+ `
43
+
44
+ const ImageMetaGridWrapper = styled.div`
45
+ flex: 0 0 33.3333%;
46
+ cursor: pointer;
47
+ padding: 0 10px 10px;
48
+ `
49
+
50
+ const Image = styled.img`
51
+ display: block;
52
+ width: 100%;
53
+ aspect-ratio: 2;
54
+ object-fit: cover;
55
+ `
56
+
57
+ const Label = styled.label`
58
+ display: block;
59
+ margin: 10px 0;
60
+ font-weight: 600;
61
+ `
62
+
63
+ const SeparationLine = styled.div`
64
+ border: #e1e5e9 1px solid;
65
+ margin-top: 10px;
66
+ margin-bottom: 10px;
67
+ `
68
+
69
+ const ImageSelected = styled.div`
70
+ height: 1.4rem;
71
+ `
72
+
73
+ const ErrorWrapper = styled.div`
74
+ & * {
75
+ margin: 0;
76
+ }
77
+ `
78
+
79
+ type ID = string
80
+
81
+ export type ImageEntity = {
82
+ id: ID
83
+ name?: string
84
+ imageFile: {
85
+ url: string
86
+ }
87
+ resized: {
88
+ original: string
89
+ w480: string
90
+ w800: string
91
+ w1200: string
92
+ w1600: string
93
+ w2400: string
94
+ }
95
+ }
96
+
97
+ export type ImageEntityWithMeta = {
98
+ image: ImageEntity
99
+ desc?: string
100
+ url?: string
101
+ }
102
+
103
+ type ImageEntityOnSelectFn = (param: ImageEntity) => void
104
+
105
+ function ImageGrids(props: {
106
+ images: ImageEntity[]
107
+ selected: ImageEntity[]
108
+ onSelect: ImageEntityOnSelectFn
109
+ }): React.ReactElement {
110
+ const { images, selected, onSelect } = props
111
+
112
+ return (
113
+ <ImageGridsWrapper>
114
+ {images.map((image) => {
115
+ return (
116
+ <ImageGrid
117
+ key={image.id}
118
+ isSelected={selected?.includes(image)}
119
+ onSelect={() => onSelect(image)}
120
+ image={image}
121
+ />
122
+ )
123
+ })}
124
+ </ImageGridsWrapper>
125
+ )
126
+ }
127
+
128
+ function ImageGrid(props: {
129
+ image: ImageEntity
130
+ isSelected: boolean
131
+ onSelect: ImageEntityOnSelectFn
132
+ }) {
133
+ const { image, onSelect, isSelected } = props
134
+ return (
135
+ <ImageGridWrapper key={image?.id} onClick={() => onSelect(image)}>
136
+ <ImageSelected>
137
+ {isSelected ? <i className="fas fa-check-circle"></i> : null}
138
+ </ImageSelected>
139
+ <Image
140
+ src={image?.resized?.w800}
141
+ onError={(e) => (e.currentTarget.src = image?.imageFile?.url)}
142
+ />
143
+ </ImageGridWrapper>
144
+ )
145
+ }
146
+
147
+ type ImageMetaOnChangeFn = (params: ImageEntityWithMeta) => void
148
+
149
+ function ImageMetaGrids(props: {
150
+ imageMetas: ImageEntityWithMeta[]
151
+ onChange: ImageMetaOnChangeFn
152
+ enableCaption: boolean
153
+ enableUrl: boolean
154
+ }) {
155
+ const { imageMetas, onChange, enableCaption, enableUrl } = props
156
+ return (
157
+ <ImageMetaGridsWrapper>
158
+ {imageMetas.map((imageMeta) => (
159
+ <ImageMetaGrid
160
+ key={imageMeta?.image?.id}
161
+ imageMeta={imageMeta}
162
+ enableCaption={enableCaption}
163
+ enableUrl={enableUrl}
164
+ onChange={onChange}
165
+ />
166
+ ))}
167
+ </ImageMetaGridsWrapper>
168
+ )
169
+ }
170
+
171
+ function ImageMetaGrid(props: {
172
+ imageMeta: ImageEntityWithMeta
173
+ onChange: ImageMetaOnChangeFn
174
+ enableCaption: boolean
175
+ enableUrl: boolean
176
+ }): React.ReactElement {
177
+ const { imageMeta, enableCaption, enableUrl, onChange } = props
178
+ const { image, desc, url } = imageMeta
179
+
180
+ return (
181
+ <ImageMetaGridWrapper>
182
+ <Image
183
+ src={image?.resized?.w800}
184
+ onError={(e) => (e.currentTarget.src = image?.imageFile?.url)}
185
+ />
186
+ {enableCaption && (
187
+ <React.Fragment>
188
+ <Label htmlFor="caption">Image Caption:</Label>
189
+ <TextInput
190
+ id="caption"
191
+ type="text"
192
+ placeholder={image?.name}
193
+ defaultValue={desc}
194
+ onChange={_.debounce((e) => {
195
+ onChange({
196
+ image,
197
+ desc: e.target.value,
198
+ url,
199
+ })
200
+ })}
201
+ />
202
+ </React.Fragment>
203
+ )}
204
+ {enableUrl && (
205
+ <React.Fragment>
206
+ <Label htmlFor="url">Url:</Label>
207
+ <TextInput
208
+ id="url"
209
+ type="text"
210
+ placeholder="(Optional)"
211
+ defaultValue={url}
212
+ onChange={_.debounce((e) => {
213
+ onChange({
214
+ image,
215
+ desc,
216
+ url: e.target.value,
217
+ })
218
+ })}
219
+ />
220
+ </React.Fragment>
221
+ )}
222
+ </ImageMetaGridWrapper>
223
+ )
224
+ }
225
+
226
+ type DelayInputOnChangeFn = (param: string) => void
227
+
228
+ function DelayInput(props: {
229
+ delay: string
230
+ onChange: DelayInputOnChangeFn
231
+ }): React.ReactElement {
232
+ const { delay, onChange } = props
233
+
234
+ return (
235
+ <React.Fragment>
236
+ <Label>Slideshow delay:</Label>
237
+ <TextInput
238
+ type="number"
239
+ placeholder="請輸入自動切換秒數"
240
+ step="0.5"
241
+ min="1"
242
+ value={delay}
243
+ onChange={(e) => {
244
+ onChange(e.target.value)
245
+ }}
246
+ />
247
+ </React.Fragment>
248
+ )
249
+ }
250
+
251
+ const imagesQuery = gql`
252
+ query Photos($searchText: String!, $take: Int, $skip: Int) {
253
+ photosCount(where: { name: { contains: $searchText } })
254
+ photos(
255
+ where: { name: { contains: $searchText } }
256
+ take: $take
257
+ skip: $skip
258
+ ) {
259
+ id
260
+ name
261
+ imageFile {
262
+ url
263
+ }
264
+ resized {
265
+ original
266
+ w480
267
+ w800
268
+ w1200
269
+ w1600
270
+ w2400
271
+ }
272
+ }
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
+ }