@os-design/editor 1.0.204 → 1.0.206

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 (33) hide show
  1. package/package.json +15 -8
  2. package/src/@types/emotion.d.ts +7 -0
  3. package/src/Editor/BlockToolbar.tsx +49 -0
  4. package/src/Editor/StyleToolbar.tsx +80 -0
  5. package/src/Editor/Toolbar.tsx +18 -0
  6. package/src/Editor/ToolbarButton.tsx +65 -0
  7. package/src/Editor/blocks/Figure.tsx +10 -0
  8. package/src/Editor/blocks/FigureCaption.tsx +19 -0
  9. package/src/Editor/blocks/imageBlock.tsx +162 -0
  10. package/src/Editor/blocks/types.ts +21 -0
  11. package/src/Editor/blocks/videoBlock.tsx +83 -0
  12. package/src/Editor/decorators/linkDecorator.tsx +23 -0
  13. package/src/Editor/hooks/useBlockToolbarProps.ts +72 -0
  14. package/src/Editor/hooks/usePastedTextHandler.ts +47 -0
  15. package/src/Editor/hooks/useReturnHandler.ts +37 -0
  16. package/src/Editor/hooks/useStyleToolbarProps.ts +53 -0
  17. package/src/Editor/index.tsx +203 -0
  18. package/src/Editor/styles/defaultDraftJsStyles.ts +179 -0
  19. package/src/Editor/styles/overrideDraftJsStyles.ts +20 -0
  20. package/src/Editor/utils/addNewBlockAt.ts +59 -0
  21. package/src/Editor/utils/changeBlock.ts +20 -0
  22. package/src/Editor/utils/createContentEditorState.ts +7 -0
  23. package/src/Editor/utils/createDecorator.ts +7 -0
  24. package/src/Editor/utils/createEmptyEditorState.ts +7 -0
  25. package/src/Editor/utils/defaultStyleToolbarItems.tsx +30 -0
  26. package/src/Editor/utils/getCurrentBlock.ts +9 -0
  27. package/src/Editor/utils/getSelectedBlockElement.ts +17 -0
  28. package/src/Editor/utils/getSelectionRange.ts +7 -0
  29. package/src/Editor/utils/setLink.ts +22 -0
  30. package/src/Editor/utils/transformers.ts +20 -0
  31. package/src/Editor/utils/unsetLink.ts +11 -0
  32. package/src/EditorSkeleton/index.tsx +21 -0
  33. package/src/index.ts +16 -0
@@ -0,0 +1,47 @@
1
+ import {
2
+ EditorProps as DraftEditorProps,
3
+ DraftHandleValue,
4
+ EditorState,
5
+ Modifier,
6
+ RichUtils,
7
+ } from 'draft-js';
8
+ import { useCallback } from 'react';
9
+
10
+ type UsePastedTextHandlerRes = Exclude<
11
+ DraftEditorProps['handlePastedText'],
12
+ undefined
13
+ >;
14
+
15
+ /**
16
+ * Pastes only text if the current block is atomic.
17
+ */
18
+ const usePastedTextHandler = (
19
+ onChange: (value: EditorState) => void,
20
+ handler: UsePastedTextHandlerRes
21
+ ): UsePastedTextHandlerRes =>
22
+ useCallback<UsePastedTextHandlerRes>(
23
+ (text, html, editorState): DraftHandleValue => {
24
+ const currentBlockType = RichUtils.getCurrentBlockType(editorState);
25
+
26
+ if (currentBlockType.startsWith('atomic')) {
27
+ const contentState = editorState.getCurrentContent();
28
+ const nextContentState = Modifier.insertText(
29
+ contentState,
30
+ editorState.getSelection(),
31
+ text
32
+ );
33
+ const nextEditorState = EditorState.push(
34
+ editorState,
35
+ nextContentState,
36
+ 'insert-characters'
37
+ );
38
+ onChange(nextEditorState);
39
+ return 'handled';
40
+ }
41
+
42
+ return handler(text, html, editorState);
43
+ },
44
+ [onChange, handler]
45
+ );
46
+
47
+ export default usePastedTextHandler;
@@ -0,0 +1,37 @@
1
+ import { EditorProps as DraftEditorProps, EditorState } from 'draft-js';
2
+ import { useCallback } from 'react';
3
+ import addNewBlockAt from '../utils/addNewBlockAt';
4
+ import getCurrentBlock from '../utils/getCurrentBlock';
5
+
6
+ type UseReturnHandlerRes = NonNullable<DraftEditorProps['handleReturn']>;
7
+
8
+ /**
9
+ * Adds a new unstyled block if the user presses the return key.
10
+ */
11
+ const useReturnHandler = (
12
+ onChange: (value: EditorState) => void,
13
+ handler: UseReturnHandlerRes
14
+ ): UseReturnHandlerRes =>
15
+ useCallback<UseReturnHandlerRes>(
16
+ (e, editorState) => {
17
+ const currentBlock = getCurrentBlock(editorState);
18
+ const currentBlockType = currentBlock.getType();
19
+
20
+ if (
21
+ currentBlockType.startsWith('atomic') ||
22
+ currentBlockType.startsWith('header')
23
+ ) {
24
+ const nextEditorState = addNewBlockAt(
25
+ editorState,
26
+ currentBlock.getKey()
27
+ );
28
+ onChange(nextEditorState);
29
+ return 'handled';
30
+ }
31
+
32
+ return handler(e, editorState);
33
+ },
34
+ [onChange, handler]
35
+ );
36
+
37
+ export default useReturnHandler;
@@ -0,0 +1,53 @@
1
+ import { EditorState, RichUtils } from 'draft-js';
2
+ import { useEffect, useState } from 'react';
3
+ import getSelectionRange from '../utils/getSelectionRange';
4
+
5
+ interface Rect {
6
+ top: number;
7
+ left: number;
8
+ width: number;
9
+ height: number;
10
+ }
11
+
12
+ interface UseStyleToolbarPropsRes {
13
+ trigger: Rect;
14
+ visible: boolean;
15
+ }
16
+
17
+ /**
18
+ * Updates the visibility of the style toolbar
19
+ */
20
+ const useStyleToolbarProps = (value: EditorState): UseStyleToolbarPropsRes => {
21
+ const [trigger, setTrigger] = useState({
22
+ top: 0,
23
+ left: 0,
24
+ width: 0,
25
+ height: 0,
26
+ });
27
+ const [visible, setVisible] = useState(false);
28
+
29
+ useEffect(() => {
30
+ if (!value) return;
31
+ const selectionRange = getSelectionRange();
32
+
33
+ // Do not show the panel if either there is no selected text,
34
+ // or the selection range is collapsed,
35
+ // or the selected text is in an atomic block.
36
+ if (
37
+ !selectionRange ||
38
+ value.getSelection().isCollapsed() ||
39
+ RichUtils.getCurrentBlockType(value).startsWith('atomic')
40
+ ) {
41
+ setVisible(false);
42
+ return;
43
+ }
44
+
45
+ // Otherwise, set the rect of the selection range
46
+ setTrigger(selectionRange.getBoundingClientRect());
47
+ setVisible(true);
48
+ }, [value]);
49
+
50
+ return { trigger, visible };
51
+ };
52
+
53
+ export default useStyleToolbarProps;
@@ -0,0 +1,203 @@
1
+ import { css } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { InputContainer } from '@os-design/core';
4
+ import { WithSize } from '@os-design/styles';
5
+ import { useForwardedRef, useForwardedState } from '@os-design/utils';
6
+
7
+ import {
8
+ Editor as DraftEditor,
9
+ EditorProps as DraftEditorProps,
10
+ EditorState,
11
+ } from 'draft-js';
12
+ import React, { forwardRef, useCallback, useState } from 'react';
13
+ import BlockToolbar from './BlockToolbar';
14
+ import StyleToolbar from './StyleToolbar';
15
+ import { BlockToolbarItem } from './blocks/types';
16
+ import useBlockToolbarProps from './hooks/useBlockToolbarProps';
17
+ import usePastedTextHandler from './hooks/usePastedTextHandler';
18
+ import useReturnHandler from './hooks/useReturnHandler';
19
+ import useStyleToolbarProps from './hooks/useStyleToolbarProps';
20
+
21
+ import defaultDraftJsStyles from './styles/defaultDraftJsStyles';
22
+ import overrideDraftJsStyles from './styles/overrideDraftJsStyles';
23
+ import createEmptyEditorState from './utils/createEmptyEditorState';
24
+
25
+ import defaultStyleToolbarItems, {
26
+ StyleToolbarItem,
27
+ } from './utils/defaultStyleToolbarItems';
28
+
29
+ export interface EditorProps
30
+ extends Omit<DraftEditorProps, 'editorState' | 'onChange'>,
31
+ WithSize {
32
+ /**
33
+ * Whether the editor is disabled.
34
+ * @default false
35
+ */
36
+ disabled?: boolean;
37
+ /**
38
+ * Available styles in the toolbar.
39
+ * @default undefined
40
+ */
41
+ styleToolbarItems?: StyleToolbarItem[];
42
+ /**
43
+ * Available blocks in the toolbar.
44
+ * @default undefined
45
+ */
46
+ blockToolbarItems?: BlockToolbarItem[];
47
+ /**
48
+ * The editor state.
49
+ * @default undefined
50
+ */
51
+ value?: EditorState;
52
+ /**
53
+ * The default value.
54
+ * @default undefined
55
+ */
56
+ defaultValue?: EditorState;
57
+ /**
58
+ * The change event handler.
59
+ * @default undefined
60
+ */
61
+ onChange?: (value: EditorState) => void;
62
+ }
63
+
64
+ const disabledStyles = (p) =>
65
+ p.disabled &&
66
+ css`
67
+ cursor: not-allowed;
68
+ `;
69
+
70
+ const Container = styled(InputContainer)`
71
+ height: unset;
72
+ cursor: text;
73
+
74
+ padding: ${(p) => p.theme.editorPaddingVertical}em
75
+ ${(p) => p.theme.inputPaddingHorizontal}em;
76
+ min-height: ${(p) => p.theme.editorMinHeight}em;
77
+
78
+ ${defaultDraftJsStyles};
79
+ ${overrideDraftJsStyles};
80
+ ${disabledStyles};
81
+ `;
82
+
83
+ /**
84
+ * Rich text editor based on the Draft.js.
85
+ */
86
+ const Editor = forwardRef<DraftEditor, EditorProps>(
87
+ (
88
+ {
89
+ disabled = false,
90
+ styleToolbarItems = defaultStyleToolbarItems,
91
+ blockToolbarItems = [],
92
+ value,
93
+ defaultValue = createEmptyEditorState(),
94
+ onChange,
95
+ size,
96
+ readOnly,
97
+ handleReturn = () => 'not-handled',
98
+ handlePastedText = () => 'not-handled',
99
+ ...rest
100
+ },
101
+ ref
102
+ ) => {
103
+ const [editorRef, mergedEditorRef] = useForwardedRef(ref);
104
+ const [forwardedValue, setForwardedValue] = useForwardedState({
105
+ value,
106
+ defaultValue,
107
+ onChange,
108
+ });
109
+
110
+ // Used by ImageBlock to make the editor read-only
111
+ // while an image is being uploaded
112
+ const [innerReadOnly, setInnerReadOnly] = useState(false);
113
+
114
+ // Get the trigger and visible props for the toolbars
115
+ const styleToolbarProps = useStyleToolbarProps(
116
+ forwardedValue || defaultValue
117
+ );
118
+ const blockToolbarProps = useBlockToolbarProps(
119
+ forwardedValue || defaultValue,
120
+ (blockToolbarItems || []).length > 0
121
+ );
122
+
123
+ // Move the caret to the end of the content when the focus event was fired
124
+ const setEditorState = useCallback(
125
+ (editorState: EditorState) => {
126
+ if (!editorState.getSelection().getHasFocus()) {
127
+ setForwardedValue(EditorState.moveSelectionToEnd(editorState));
128
+ return;
129
+ }
130
+ setForwardedValue(editorState);
131
+ },
132
+ [setForwardedValue]
133
+ );
134
+
135
+ const blockRenderer = useCallback(
136
+ (block) => {
137
+ const blockConfig = blockToolbarItems.find(
138
+ (item) => item.type === block.getType()
139
+ );
140
+ if (!blockConfig) return null;
141
+ return { component: blockConfig.component };
142
+ },
143
+ [blockToolbarItems]
144
+ );
145
+
146
+ // Custom handlers
147
+ const returnHandler = useReturnHandler(setForwardedValue, handleReturn);
148
+ const pastedTextHandler = usePastedTextHandler(
149
+ setForwardedValue,
150
+ handlePastedText
151
+ );
152
+
153
+ return (
154
+ <>
155
+ <Container
156
+ disabled={disabled}
157
+ size={size}
158
+ tabIndex={!disabled ? 0 : -1}
159
+ role={!disabled ? 'textbox' : undefined}
160
+ onFocus={() => {
161
+ if (disabled || !editorRef.current) return;
162
+ editorRef.current.focus();
163
+ }}
164
+ >
165
+ <DraftEditor
166
+ onChange={setEditorState}
167
+ blockRendererFn={blockRenderer}
168
+ handleReturn={returnHandler}
169
+ handlePastedText={pastedTextHandler}
170
+ readOnly={readOnly || innerReadOnly || disabled}
171
+ stripPastedStyles
172
+ {...rest}
173
+ editorState={forwardedValue || defaultValue}
174
+ ref={mergedEditorRef}
175
+ />
176
+ </Container>
177
+
178
+ <StyleToolbar
179
+ {...styleToolbarProps}
180
+ size={size}
181
+ items={styleToolbarItems}
182
+ value={forwardedValue || defaultValue}
183
+ onChange={setForwardedValue}
184
+ />
185
+
186
+ <BlockToolbar
187
+ {...blockToolbarProps}
188
+ placement='right'
189
+ gap={2.5}
190
+ size={size}
191
+ items={blockToolbarItems}
192
+ value={forwardedValue || defaultValue}
193
+ onChange={setForwardedValue}
194
+ setReadOnly={setInnerReadOnly}
195
+ />
196
+ </>
197
+ );
198
+ }
199
+ );
200
+
201
+ Editor.displayName = 'Editor';
202
+
203
+ export default Editor;
@@ -0,0 +1,179 @@
1
+ import { css } from '@emotion/react';
2
+
3
+ /**
4
+ * Default Draft.js styles.
5
+ * The original styles located in `node_modules/draft-js/dist/Draft.css`.
6
+ */
7
+ const defaultDraftJsStyles = css`
8
+ .DraftEditor-editorContainer,
9
+ .DraftEditor-root,
10
+ .public-DraftEditor-content {
11
+ height: inherit;
12
+ text-align: initial;
13
+ }
14
+ .public-DraftEditor-content[contenteditable='true'] {
15
+ -webkit-user-modify: read-write-plaintext-only;
16
+ }
17
+ .DraftEditor-root {
18
+ position: relative;
19
+ }
20
+ .DraftEditor-editorContainer {
21
+ background-color: rgba(255, 255, 255, 0);
22
+ border-left: 0.1px solid transparent;
23
+ position: relative;
24
+ z-index: 1;
25
+ }
26
+ .public-DraftEditor-block {
27
+ position: relative;
28
+ }
29
+ .DraftEditor-alignLeft .public-DraftStyleDefault-block {
30
+ text-align: left;
31
+ }
32
+ .DraftEditor-alignLeft .public-DraftEditorPlaceholder-root {
33
+ left: 0;
34
+ text-align: left;
35
+ }
36
+ .DraftEditor-alignCenter .public-DraftStyleDefault-block {
37
+ text-align: center;
38
+ }
39
+ .DraftEditor-alignCenter .public-DraftEditorPlaceholder-root {
40
+ margin: 0 auto;
41
+ text-align: center;
42
+ width: 100%;
43
+ }
44
+ .DraftEditor-alignRight .public-DraftStyleDefault-block {
45
+ text-align: right;
46
+ }
47
+ .DraftEditor-alignRight .public-DraftEditorPlaceholder-root {
48
+ right: 0;
49
+ text-align: right;
50
+ }
51
+ .public-DraftEditorPlaceholder-root {
52
+ color: #9197a3;
53
+ position: absolute;
54
+ width: 100%;
55
+ z-index: 1;
56
+ }
57
+ .public-DraftEditorPlaceholder-hasFocus {
58
+ color: #bdc1c9;
59
+ }
60
+ .DraftEditorPlaceholder-hidden {
61
+ display: none;
62
+ }
63
+ .public-DraftStyleDefault-block {
64
+ position: relative;
65
+ white-space: pre-wrap;
66
+ }
67
+ .public-DraftStyleDefault-ltr {
68
+ direction: ltr;
69
+ text-align: left;
70
+ }
71
+ .public-DraftStyleDefault-rtl {
72
+ direction: rtl;
73
+ text-align: right;
74
+ }
75
+ .public-DraftStyleDefault-listLTR {
76
+ direction: ltr;
77
+ }
78
+ .public-DraftStyleDefault-listRTL {
79
+ direction: rtl;
80
+ }
81
+ .public-DraftStyleDefault-ol,
82
+ .public-DraftStyleDefault-ul {
83
+ margin: 16px 0;
84
+ padding: 0;
85
+ }
86
+ .public-DraftStyleDefault-depth0.public-DraftStyleDefault-listLTR {
87
+ margin-left: 1.5em;
88
+ }
89
+ .public-DraftStyleDefault-depth0.public-DraftStyleDefault-listRTL {
90
+ margin-right: 1.5em;
91
+ }
92
+ .public-DraftStyleDefault-depth1.public-DraftStyleDefault-listLTR {
93
+ margin-left: 3em;
94
+ }
95
+ .public-DraftStyleDefault-depth1.public-DraftStyleDefault-listRTL {
96
+ margin-right: 3em;
97
+ }
98
+ .public-DraftStyleDefault-depth2.public-DraftStyleDefault-listLTR {
99
+ margin-left: 4.5em;
100
+ }
101
+ .public-DraftStyleDefault-depth2.public-DraftStyleDefault-listRTL {
102
+ margin-right: 4.5em;
103
+ }
104
+ .public-DraftStyleDefault-depth3.public-DraftStyleDefault-listLTR {
105
+ margin-left: 6em;
106
+ }
107
+ .public-DraftStyleDefault-depth3.public-DraftStyleDefault-listRTL {
108
+ margin-right: 6em;
109
+ }
110
+ .public-DraftStyleDefault-depth4.public-DraftStyleDefault-listLTR {
111
+ margin-left: 7.5em;
112
+ }
113
+ .public-DraftStyleDefault-depth4.public-DraftStyleDefault-listRTL {
114
+ margin-right: 7.5em;
115
+ }
116
+ .public-DraftStyleDefault-unorderedListItem {
117
+ list-style-type: square;
118
+ position: relative;
119
+ }
120
+ .public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth0 {
121
+ list-style-type: disc;
122
+ }
123
+ .public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth1 {
124
+ list-style-type: circle;
125
+ }
126
+ .public-DraftStyleDefault-orderedListItem {
127
+ list-style-type: none;
128
+ position: relative;
129
+ }
130
+ .public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listLTR:before {
131
+ left: -36px;
132
+ position: absolute;
133
+ text-align: right;
134
+ width: 30px;
135
+ }
136
+ .public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listRTL:before {
137
+ position: absolute;
138
+ right: -36px;
139
+ text-align: left;
140
+ width: 30px;
141
+ }
142
+ .public-DraftStyleDefault-orderedListItem:before {
143
+ content: counter(ol0) '. ';
144
+ counter-increment: ol0;
145
+ }
146
+ .public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth1:before {
147
+ content: counter(ol1, lower-alpha) '. ';
148
+ counter-increment: ol1;
149
+ }
150
+ .public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth2:before {
151
+ content: counter(ol2, lower-roman) '. ';
152
+ counter-increment: ol2;
153
+ }
154
+ .public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth3:before {
155
+ content: counter(ol3) '. ';
156
+ counter-increment: ol3;
157
+ }
158
+ .public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth4:before {
159
+ content: counter(ol4, lower-alpha) '. ';
160
+ counter-increment: ol4;
161
+ }
162
+ .public-DraftStyleDefault-depth0.public-DraftStyleDefault-reset {
163
+ counter-reset: ol0;
164
+ }
165
+ .public-DraftStyleDefault-depth1.public-DraftStyleDefault-reset {
166
+ counter-reset: ol1;
167
+ }
168
+ .public-DraftStyleDefault-depth2.public-DraftStyleDefault-reset {
169
+ counter-reset: ol2;
170
+ }
171
+ .public-DraftStyleDefault-depth3.public-DraftStyleDefault-reset {
172
+ counter-reset: ol3;
173
+ }
174
+ .public-DraftStyleDefault-depth4.public-DraftStyleDefault-reset {
175
+ counter-reset: ol4;
176
+ }
177
+ `;
178
+
179
+ export default defaultDraftJsStyles;
@@ -0,0 +1,20 @@
1
+ import { css } from '@emotion/react';
2
+ import { clr } from '@os-design/theming';
3
+
4
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
5
+ const overrideDraftJsStyles = (p) => css`
6
+ .DraftEditor-root {
7
+ width: 100%;
8
+ overflow: hidden;
9
+ line-height: ${p.theme.lineHeight};
10
+ color: ${clr(p.theme.colorText)};
11
+ }
12
+ .public-DraftEditorPlaceholder-root {
13
+ color: ${clr(p.theme.inputColorPlaceholder)};
14
+ }
15
+ .public-DraftStyleDefault-block {
16
+ margin: 0 0 ${p.theme.paragraphMarginBottom}em;
17
+ }
18
+ `;
19
+
20
+ export default overrideDraftJsStyles;
@@ -0,0 +1,59 @@
1
+ import { ContentBlock, ContentState, EditorState, genKey } from 'draft-js';
2
+ import Immutable from 'immutable';
3
+
4
+ const addNewBlockAt = (
5
+ editorState: EditorState,
6
+ pivotBlockKey: string,
7
+ type = 'unstyled',
8
+ data: Record<string, any> = {} // eslint-disable-line @typescript-eslint/no-explicit-any
9
+ ): EditorState => {
10
+ const contentState = editorState.getCurrentContent();
11
+ const blockMap = contentState.getBlockMap();
12
+ const pivotBlock = blockMap.get(pivotBlockKey);
13
+ if (!pivotBlock)
14
+ throw new Error(
15
+ `The pivot key - ${pivotBlockKey} is not present in blockMap`
16
+ );
17
+ const blocksBefore = blockMap.toSeq().takeUntil((v) => v === pivotBlock);
18
+ const blocksAfter = blockMap
19
+ .toSeq()
20
+ .skipUntil((v) => v === pivotBlock)
21
+ .rest();
22
+ const key = genKey();
23
+
24
+ const block = new ContentBlock({
25
+ key,
26
+ type,
27
+ text: '',
28
+ characterList: Immutable.List(),
29
+ depth: 0,
30
+ data: Immutable.Map(data),
31
+ });
32
+
33
+ const selectionState = editorState.getSelection();
34
+ const nextBlockMap = blocksBefore
35
+ .concat(
36
+ [
37
+ [pivotBlockKey, pivotBlock],
38
+ [key, block],
39
+ ],
40
+ blocksAfter
41
+ )
42
+ .toOrderedMap();
43
+
44
+ const nextContentState = contentState.merge({
45
+ blockMap: nextBlockMap,
46
+ selectionBefore: selectionState,
47
+ selectionAfter: selectionState.merge({
48
+ anchorKey: key,
49
+ anchorOffset: 0,
50
+ focusKey: key,
51
+ focusOffset: 0,
52
+ isBackward: false,
53
+ }),
54
+ }) as ContentState;
55
+
56
+ return EditorState.push(editorState, nextContentState, 'split-block');
57
+ };
58
+
59
+ export default addNewBlockAt;
@@ -0,0 +1,20 @@
1
+ import { ContentBlock, ContentState, EditorState } from 'draft-js';
2
+
3
+ const changeBlock = (
4
+ editorState: EditorState,
5
+ block: ContentBlock,
6
+ type: string,
7
+ data: Record<string, any> = {} // eslint-disable-line @typescript-eslint/no-explicit-any
8
+ ): EditorState => {
9
+ const contentState = editorState.getCurrentContent();
10
+
11
+ const nextCurrentBlock = block.merge({ type, data }) as ContentBlock;
12
+ const nextContentState = contentState.merge({
13
+ blockMap: contentState.getBlockMap().set(block.getKey(), nextCurrentBlock),
14
+ selectionAfter: editorState.getSelection(),
15
+ }) as ContentState;
16
+
17
+ return EditorState.push(editorState, nextContentState, 'change-block-type');
18
+ };
19
+
20
+ export default changeBlock;
@@ -0,0 +1,7 @@
1
+ import { ContentState, EditorState } from 'draft-js';
2
+ import createDecorator from './createDecorator';
3
+
4
+ const createContentEditorState = (contentState: ContentState): EditorState =>
5
+ EditorState.createWithContent(contentState, createDecorator());
6
+
7
+ export default createContentEditorState;
@@ -0,0 +1,7 @@
1
+ import { CompositeDecorator } from 'draft-js';
2
+ import linkDecorator from '../decorators/linkDecorator';
3
+
4
+ const createDecorator = (): CompositeDecorator =>
5
+ new CompositeDecorator([linkDecorator]);
6
+
7
+ export default createDecorator;
@@ -0,0 +1,7 @@
1
+ import { EditorState } from 'draft-js';
2
+ import createDecorator from './createDecorator';
3
+
4
+ const createEmptyEditorState = (): EditorState =>
5
+ EditorState.createEmpty(createDecorator());
6
+
7
+ export default createEmptyEditorState;
@@ -0,0 +1,30 @@
1
+ import styled from '@emotion/styled';
2
+ import {
3
+ Bold,
4
+ FontSize,
5
+ Link as LinkIcon,
6
+ OrderedList,
7
+ UnorderedList,
8
+ } from '@os-design/icons';
9
+ import React from 'react';
10
+
11
+ export interface StyleToolbarItem {
12
+ name: string;
13
+ type: 'block' | 'inline';
14
+ icon: React.ReactNode;
15
+ }
16
+
17
+ const FontSizeSmall = styled(FontSize)`
18
+ transform: scale(0.7);
19
+ `;
20
+
21
+ const defaultStyleToolbarItems: StyleToolbarItem[] = [
22
+ { name: 'header-two', type: 'block', icon: <FontSize /> },
23
+ { name: 'header-three', type: 'block', icon: <FontSizeSmall /> },
24
+ { name: 'BOLD', type: 'inline', icon: <Bold /> },
25
+ { name: 'unordered-list-item', type: 'block', icon: <UnorderedList /> },
26
+ { name: 'ordered-list-item', type: 'block', icon: <OrderedList /> },
27
+ { name: 'LINK', type: 'inline', icon: <LinkIcon /> },
28
+ ];
29
+
30
+ export default defaultStyleToolbarItems;
@@ -0,0 +1,9 @@
1
+ import { ContentBlock, EditorState } from 'draft-js';
2
+
3
+ const getCurrentBlock = (editorState: EditorState): ContentBlock => {
4
+ const selectionState = editorState.getSelection();
5
+ const contentState = editorState.getCurrentContent();
6
+ return contentState.getBlockForKey(selectionState.getStartKey());
7
+ };
8
+
9
+ export default getCurrentBlock;