@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.
- package/package.json +15 -8
- package/src/@types/emotion.d.ts +7 -0
- package/src/Editor/BlockToolbar.tsx +49 -0
- package/src/Editor/StyleToolbar.tsx +80 -0
- package/src/Editor/Toolbar.tsx +18 -0
- package/src/Editor/ToolbarButton.tsx +65 -0
- package/src/Editor/blocks/Figure.tsx +10 -0
- package/src/Editor/blocks/FigureCaption.tsx +19 -0
- package/src/Editor/blocks/imageBlock.tsx +162 -0
- package/src/Editor/blocks/types.ts +21 -0
- package/src/Editor/blocks/videoBlock.tsx +83 -0
- package/src/Editor/decorators/linkDecorator.tsx +23 -0
- package/src/Editor/hooks/useBlockToolbarProps.ts +72 -0
- package/src/Editor/hooks/usePastedTextHandler.ts +47 -0
- package/src/Editor/hooks/useReturnHandler.ts +37 -0
- package/src/Editor/hooks/useStyleToolbarProps.ts +53 -0
- package/src/Editor/index.tsx +203 -0
- package/src/Editor/styles/defaultDraftJsStyles.ts +179 -0
- package/src/Editor/styles/overrideDraftJsStyles.ts +20 -0
- package/src/Editor/utils/addNewBlockAt.ts +59 -0
- package/src/Editor/utils/changeBlock.ts +20 -0
- package/src/Editor/utils/createContentEditorState.ts +7 -0
- package/src/Editor/utils/createDecorator.ts +7 -0
- package/src/Editor/utils/createEmptyEditorState.ts +7 -0
- package/src/Editor/utils/defaultStyleToolbarItems.tsx +30 -0
- package/src/Editor/utils/getCurrentBlock.ts +9 -0
- package/src/Editor/utils/getSelectedBlockElement.ts +17 -0
- package/src/Editor/utils/getSelectionRange.ts +7 -0
- package/src/Editor/utils/setLink.ts +22 -0
- package/src/Editor/utils/transformers.ts +20 -0
- package/src/Editor/utils/unsetLink.ts +11 -0
- package/src/EditorSkeleton/index.tsx +21 -0
- 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,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;
|