@pie-lib/editable-html 7.22.5 → 8.1.0
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/CHANGELOG.md +37 -0
- package/lib/editor.js +214 -102
- package/lib/editor.js.map +1 -1
- package/lib/plugins/characters/custom-popover.js +73 -0
- package/lib/plugins/characters/custom-popover.js.map +1 -0
- package/lib/plugins/characters/index.js +271 -0
- package/lib/plugins/characters/index.js.map +1 -0
- package/lib/plugins/characters/utils.js +362 -0
- package/lib/plugins/characters/utils.js.map +1 -0
- package/lib/plugins/image/component.js +190 -29
- package/lib/plugins/image/component.js.map +1 -1
- package/lib/plugins/image/image-toolbar.js +4 -58
- package/lib/plugins/image/image-toolbar.js.map +1 -1
- package/lib/plugins/image/index.js +3 -1
- package/lib/plugins/image/index.js.map +1 -1
- package/lib/plugins/index.js +19 -4
- package/lib/plugins/index.js.map +1 -1
- package/lib/serialization.js +18 -1
- package/lib/serialization.js.map +1 -1
- package/package.json +5 -5
- package/src/editor.jsx +93 -17
- package/src/plugins/characters/custom-popover.js +45 -0
- package/src/plugins/characters/index.jsx +237 -0
- package/src/plugins/characters/utils.js +444 -0
- package/src/plugins/image/component.jsx +171 -19
- package/src/plugins/image/image-toolbar.jsx +2 -32
- package/src/plugins/image/index.jsx +3 -1
- package/src/plugins/index.jsx +3 -0
- package/src/serialization.jsx +9 -0
package/src/editor.jsx
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { Editor as SlateEditor, findNode } from 'slate-react';
|
|
1
|
+
import { Editor as SlateEditor, findNode, getEventRange, getEventTransfer } from 'slate-react';
|
|
2
2
|
import SlateTypes from 'slate-prop-types';
|
|
3
3
|
|
|
4
4
|
import isEqual from 'lodash/isEqual';
|
|
5
5
|
import * as serialization from './serialization';
|
|
6
6
|
import PropTypes from 'prop-types';
|
|
7
7
|
import React from 'react';
|
|
8
|
-
import { Value, Block } from 'slate';
|
|
8
|
+
import { Value, Block, Inline } from 'slate';
|
|
9
9
|
import { buildPlugins, ALL_PLUGINS, DEFAULT_PLUGINS } from './plugins';
|
|
10
10
|
import debug from 'debug';
|
|
11
11
|
import { withStyles } from '@material-ui/core/styles';
|
|
12
12
|
import classNames from 'classnames';
|
|
13
13
|
import { color } from '@pie-lib/render-ui';
|
|
14
|
+
import { getBase64 } from './serialization';
|
|
14
15
|
|
|
15
16
|
export { ALL_PLUGINS, DEFAULT_PLUGINS, serialization };
|
|
16
17
|
|
|
@@ -30,6 +31,8 @@ const defaultResponseAreaProps = {
|
|
|
30
31
|
onHandleAreaChange: () => {}
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
const defaultLanguageCharactersProps = [];
|
|
35
|
+
|
|
33
36
|
const createToolbarOpts = toolbarOpts => {
|
|
34
37
|
return {
|
|
35
38
|
...defaultToolbarOpts,
|
|
@@ -49,6 +52,7 @@ export class Editor extends React.Component {
|
|
|
49
52
|
focus: PropTypes.func.isRequired,
|
|
50
53
|
value: SlateTypes.value.isRequired,
|
|
51
54
|
imageSupport: PropTypes.object,
|
|
55
|
+
charactersLimit: PropTypes.number,
|
|
52
56
|
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
53
57
|
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
54
58
|
minHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
@@ -72,6 +76,13 @@ export class Editor extends React.Component {
|
|
|
72
76
|
respAreaToolbar: PropTypes.func,
|
|
73
77
|
onHandleAreaChange: PropTypes.func
|
|
74
78
|
}),
|
|
79
|
+
languageCharactersProps: PropTypes.arrayOf(
|
|
80
|
+
PropTypes.shape({
|
|
81
|
+
language: PropTypes.string,
|
|
82
|
+
characterIcon: PropTypes.string,
|
|
83
|
+
characters: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string))
|
|
84
|
+
})
|
|
85
|
+
),
|
|
75
86
|
toolbarOpts: PropTypes.shape({
|
|
76
87
|
position: PropTypes.oneOf(['bottom', 'top']),
|
|
77
88
|
alignment: PropTypes.oneOf(['left', 'right']),
|
|
@@ -87,7 +98,9 @@ export class Editor extends React.Component {
|
|
|
87
98
|
new Error(`Invalid values: ${values}, values must be one of [${ALL_PLUGINS.join(',')}]`)
|
|
88
99
|
);
|
|
89
100
|
}),
|
|
90
|
-
className: PropTypes.string
|
|
101
|
+
className: PropTypes.string,
|
|
102
|
+
maxImageWidth: PropTypes.number,
|
|
103
|
+
maxImageHeight: PropTypes.number
|
|
91
104
|
};
|
|
92
105
|
|
|
93
106
|
static defaultProps = {
|
|
@@ -96,7 +109,8 @@ export class Editor extends React.Component {
|
|
|
96
109
|
onBlur: () => {},
|
|
97
110
|
onKeyDown: () => {},
|
|
98
111
|
toolbarOpts: defaultToolbarOpts,
|
|
99
|
-
responseAreaProps: defaultResponseAreaProps
|
|
112
|
+
responseAreaProps: defaultResponseAreaProps,
|
|
113
|
+
languageCharactersProps: defaultLanguageCharactersProps
|
|
100
114
|
};
|
|
101
115
|
|
|
102
116
|
constructor(props) {
|
|
@@ -106,15 +120,19 @@ export class Editor extends React.Component {
|
|
|
106
120
|
toolbarOpts: createToolbarOpts(props.toolbarOpts)
|
|
107
121
|
};
|
|
108
122
|
|
|
123
|
+
this.onResize = () => {
|
|
124
|
+
props.onChange(this.state.value, true);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
this.handlePlugins(this.props);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
handlePlugins = props => {
|
|
109
131
|
const normalizedResponseAreaProps = {
|
|
110
132
|
...defaultResponseAreaProps,
|
|
111
133
|
...props.responseAreaProps
|
|
112
134
|
};
|
|
113
135
|
|
|
114
|
-
this.onResize = () => {
|
|
115
|
-
props.onChange(this.state.value, true);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
136
|
this.plugins = buildPlugins(props.activePlugins, {
|
|
119
137
|
math: {
|
|
120
138
|
onClick: this.onMathClick,
|
|
@@ -123,25 +141,27 @@ export class Editor extends React.Component {
|
|
|
123
141
|
},
|
|
124
142
|
image: {
|
|
125
143
|
onDelete:
|
|
126
|
-
|
|
127
|
-
|
|
144
|
+
props.imageSupport &&
|
|
145
|
+
props.imageSupport.delete &&
|
|
128
146
|
((src, done) => {
|
|
129
|
-
|
|
147
|
+
props.imageSupport.delete(src, e => {
|
|
130
148
|
done(e, this.state.value);
|
|
131
149
|
});
|
|
132
150
|
}),
|
|
133
151
|
insertImageRequested:
|
|
134
|
-
|
|
152
|
+
props.imageSupport &&
|
|
135
153
|
(getHandler => {
|
|
136
154
|
/**
|
|
137
155
|
* The handler is the object through which the outer context
|
|
138
156
|
* communicates file upload events like: fileChosen, cancel, progress
|
|
139
157
|
*/
|
|
140
158
|
const handler = getHandler(() => this.state.value);
|
|
141
|
-
|
|
159
|
+
props.imageSupport.add(handler);
|
|
142
160
|
}),
|
|
143
161
|
onFocus: this.onPluginFocus,
|
|
144
|
-
onBlur: this.onPluginBlur
|
|
162
|
+
onBlur: this.onPluginBlur,
|
|
163
|
+
maxImageWidth: this.props.maxImageWidth,
|
|
164
|
+
maxImageHeight: this.props.maxImageHeight
|
|
145
165
|
},
|
|
146
166
|
toolbar: {
|
|
147
167
|
/**
|
|
@@ -151,7 +171,7 @@ export class Editor extends React.Component {
|
|
|
151
171
|
disableUnderline: props.disableUnderline,
|
|
152
172
|
autoWidth: props.autoWidthToolbar,
|
|
153
173
|
onDone: () => {
|
|
154
|
-
const { nonEmpty } =
|
|
174
|
+
const { nonEmpty } = props;
|
|
155
175
|
|
|
156
176
|
log('[onDone]');
|
|
157
177
|
this.setState({ toolbarInFocus: false, focusedNode: null });
|
|
@@ -192,13 +212,14 @@ export class Editor extends React.Component {
|
|
|
192
212
|
this.onPluginBlur();
|
|
193
213
|
}
|
|
194
214
|
},
|
|
215
|
+
languageCharacters: props.languageCharactersProps,
|
|
195
216
|
media: {
|
|
196
217
|
focus: this.focus,
|
|
197
218
|
createChange: () => this.state.value.change(),
|
|
198
219
|
onChange: this.onChange
|
|
199
220
|
}
|
|
200
221
|
});
|
|
201
|
-
}
|
|
222
|
+
};
|
|
202
223
|
|
|
203
224
|
componentDidMount() {
|
|
204
225
|
// onRef is needed to get the ref of the component because we export it using withStyles
|
|
@@ -232,6 +253,10 @@ export class Editor extends React.Component {
|
|
|
232
253
|
toolbarOpts: newToolbarOpts
|
|
233
254
|
});
|
|
234
255
|
}
|
|
256
|
+
|
|
257
|
+
if (!isEqual(nextProps.languageCharactersProps, this.props.languageCharactersProps)) {
|
|
258
|
+
this.handlePlugins(nextProps);
|
|
259
|
+
}
|
|
235
260
|
}
|
|
236
261
|
|
|
237
262
|
componentDidUpdate() {
|
|
@@ -440,7 +465,20 @@ export class Editor extends React.Component {
|
|
|
440
465
|
|
|
441
466
|
onChange = (change, done) => {
|
|
442
467
|
log('[onChange]');
|
|
443
|
-
|
|
468
|
+
|
|
469
|
+
const { value } = change;
|
|
470
|
+
const { charactersLimit } = this.props;
|
|
471
|
+
|
|
472
|
+
if (
|
|
473
|
+
value &&
|
|
474
|
+
value.document &&
|
|
475
|
+
value.document.text &&
|
|
476
|
+
value.document.text.length > charactersLimit
|
|
477
|
+
) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
this.setState({ value }, () => {
|
|
444
482
|
log('[onChange], call done()');
|
|
445
483
|
|
|
446
484
|
if (done) {
|
|
@@ -533,6 +571,42 @@ export class Editor extends React.Component {
|
|
|
533
571
|
this.props.focus(position, node);
|
|
534
572
|
};
|
|
535
573
|
|
|
574
|
+
onDropPaste = async (event, change, dropContext) => {
|
|
575
|
+
if (!this.props.imageSupport) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const editor = change.editor;
|
|
579
|
+
const transfer = getEventTransfer(event);
|
|
580
|
+
const file = transfer.files[0];
|
|
581
|
+
|
|
582
|
+
if (file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png') {
|
|
583
|
+
try {
|
|
584
|
+
log('[onDropPaste]');
|
|
585
|
+
const src = await getBase64(file);
|
|
586
|
+
const inline = Inline.create({
|
|
587
|
+
type: 'image',
|
|
588
|
+
isVoid: true,
|
|
589
|
+
data: {
|
|
590
|
+
loading: false,
|
|
591
|
+
src
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
if (dropContext) {
|
|
595
|
+
this.focus();
|
|
596
|
+
} else {
|
|
597
|
+
const range = getEventRange(event, editor);
|
|
598
|
+
if (range) {
|
|
599
|
+
change.select(range);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const ch = change.insertInline(inline);
|
|
603
|
+
this.onChange(ch);
|
|
604
|
+
} catch (err) {
|
|
605
|
+
log('[onDropPaste] error: ', err);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
536
610
|
render() {
|
|
537
611
|
const {
|
|
538
612
|
disabled,
|
|
@@ -581,6 +655,8 @@ export class Editor extends React.Component {
|
|
|
581
655
|
onKeyDown={onKeyDown}
|
|
582
656
|
onChange={this.onChange}
|
|
583
657
|
onBlur={this.onBlur}
|
|
658
|
+
onDrop={(event, editor) => this.onDropPaste(event, editor, true)}
|
|
659
|
+
onPaste={(event, editor) => this.onDropPaste(event, editor)}
|
|
584
660
|
onFocus={this.onFocus}
|
|
585
661
|
onEditingDone={this.onEditingDone}
|
|
586
662
|
focusedNode={focusedNode}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { withStyles } from '@material-ui/core/styles';
|
|
3
|
+
import Popover from '@material-ui/core/Popover';
|
|
4
|
+
import Typography from '@material-ui/core/Typography';
|
|
5
|
+
|
|
6
|
+
const styles = () => ({
|
|
7
|
+
popover: {
|
|
8
|
+
pointerEvents: 'none',
|
|
9
|
+
zIndex: 99999
|
|
10
|
+
},
|
|
11
|
+
paper: {
|
|
12
|
+
padding: 20,
|
|
13
|
+
height: 'auto',
|
|
14
|
+
width: 'auto'
|
|
15
|
+
},
|
|
16
|
+
typography: {
|
|
17
|
+
fontSize: 50,
|
|
18
|
+
textAlign: 'center'
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const CustomPopOver = withStyles(styles)(({ classes, children, ...props }) => (
|
|
23
|
+
<Popover
|
|
24
|
+
id="mouse-over-popover"
|
|
25
|
+
open
|
|
26
|
+
className={classes.popover}
|
|
27
|
+
classes={{
|
|
28
|
+
paper: classes.paper
|
|
29
|
+
}}
|
|
30
|
+
anchorOrigin={{
|
|
31
|
+
vertical: 'bottom',
|
|
32
|
+
horizontal: 'left'
|
|
33
|
+
}}
|
|
34
|
+
transformOrigin={{
|
|
35
|
+
vertical: 'top',
|
|
36
|
+
horizontal: 'left'
|
|
37
|
+
}}
|
|
38
|
+
disableRestoreFocus
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
<Typography classes={{ root: classes.typography }}>{children}</Typography>
|
|
42
|
+
</Popover>
|
|
43
|
+
));
|
|
44
|
+
|
|
45
|
+
export default CustomPopOver;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
3
|
+
import debug from 'debug';
|
|
4
|
+
import get from 'lodash/get';
|
|
5
|
+
|
|
6
|
+
import { PureToolbar } from '@pie-lib/math-toolbar';
|
|
7
|
+
|
|
8
|
+
import CustomPopOver from './custom-popover';
|
|
9
|
+
import { insertSnackBar } from '../respArea/utils';
|
|
10
|
+
import { characterIcons, spanishConfig, specialConfig } from './utils';
|
|
11
|
+
const log = debug('@pie-lib:editable-html:plugins:characters');
|
|
12
|
+
|
|
13
|
+
const removeDialogs = () => {
|
|
14
|
+
const prevDialogs = document.querySelectorAll('.insert-character-dialog');
|
|
15
|
+
|
|
16
|
+
log('[characters:removeDialogs]');
|
|
17
|
+
prevDialogs.forEach(s => s.remove());
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const removePopOvers = () => {
|
|
21
|
+
const prevPopOvers = document.querySelectorAll('#mouse-over-popover');
|
|
22
|
+
|
|
23
|
+
log('[characters:removePopOvers]');
|
|
24
|
+
prevPopOvers.forEach(s => s.remove());
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const insertDialog = ({ value, callback, opts }) => {
|
|
28
|
+
const newEl = document.createElement('div');
|
|
29
|
+
const initialBodyOverflow = document.body.style.overflow;
|
|
30
|
+
|
|
31
|
+
log('[characters:insertDialog]');
|
|
32
|
+
|
|
33
|
+
removeDialogs();
|
|
34
|
+
|
|
35
|
+
newEl.className = 'insert-character-dialog';
|
|
36
|
+
document.body.style.overflow = 'hidden';
|
|
37
|
+
|
|
38
|
+
let configToUse;
|
|
39
|
+
|
|
40
|
+
switch (true) {
|
|
41
|
+
case opts.language === 'spanish':
|
|
42
|
+
configToUse = spanishConfig;
|
|
43
|
+
break;
|
|
44
|
+
case opts.language === 'special':
|
|
45
|
+
configToUse = specialConfig;
|
|
46
|
+
break;
|
|
47
|
+
default:
|
|
48
|
+
configToUse = opts;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!configToUse.characters) {
|
|
52
|
+
insertSnackBar('No characters provided or language not recognized');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const layoutForCharacters = configToUse.characters.reduce(
|
|
57
|
+
(obj, arr) => {
|
|
58
|
+
if (arr.length >= obj.columns) {
|
|
59
|
+
obj.columns = arr.length;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return obj;
|
|
63
|
+
},
|
|
64
|
+
{ rows: configToUse.characters.length, columns: 0 }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
let popoverEl;
|
|
68
|
+
|
|
69
|
+
const closePopOver = () => {
|
|
70
|
+
if (popoverEl) {
|
|
71
|
+
popoverEl.remove();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
removePopOvers();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const renderPopOver = (event, el) => {
|
|
78
|
+
if (!event) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const infoStyle = { fontSize: '20px', lineHeight: '20px' };
|
|
83
|
+
|
|
84
|
+
closePopOver();
|
|
85
|
+
|
|
86
|
+
popoverEl = document.createElement('div');
|
|
87
|
+
ReactDOM.render(
|
|
88
|
+
<CustomPopOver onClose={closePopOver} anchorEl={event.currentTarget}>
|
|
89
|
+
<div>{el.label}</div>
|
|
90
|
+
|
|
91
|
+
<div style={infoStyle}>{el.description}</div>
|
|
92
|
+
|
|
93
|
+
<div style={infoStyle}>{el.unicode}</div>
|
|
94
|
+
</CustomPopOver>,
|
|
95
|
+
popoverEl
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
document.body.appendChild(newEl);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleClose = () => {
|
|
102
|
+
newEl.remove();
|
|
103
|
+
closePopOver();
|
|
104
|
+
document.body.style.overflow = initialBodyOverflow;
|
|
105
|
+
callback(undefined, true);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleChange = val => {
|
|
109
|
+
if (typeof val === 'string') {
|
|
110
|
+
callback(val);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (configToUse.autoClose) {
|
|
114
|
+
handleClose();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const el = (
|
|
119
|
+
<PureToolbar
|
|
120
|
+
autoFocus
|
|
121
|
+
noDecimal
|
|
122
|
+
hideInput
|
|
123
|
+
noLatexHandling
|
|
124
|
+
layoutForKeyPad={layoutForCharacters}
|
|
125
|
+
additionalKeys={configToUse.characters.reduce((arr, n) => {
|
|
126
|
+
arr = [
|
|
127
|
+
...arr,
|
|
128
|
+
...n.map(k => ({
|
|
129
|
+
name: get(k, 'name') || k,
|
|
130
|
+
write: get(k, 'write') || k,
|
|
131
|
+
label: get(k, 'label') || k,
|
|
132
|
+
category: 'character',
|
|
133
|
+
extraClass: 'character',
|
|
134
|
+
...(configToUse.hasPreview
|
|
135
|
+
? {
|
|
136
|
+
actions: { onMouseEnter: ev => renderPopOver(ev, k), onMouseLeave: closePopOver }
|
|
137
|
+
}
|
|
138
|
+
: {})
|
|
139
|
+
}))
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
return arr;
|
|
143
|
+
}, [])}
|
|
144
|
+
keypadMode="language"
|
|
145
|
+
onChange={handleChange}
|
|
146
|
+
onDone={handleClose}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
ReactDOM.render(el, newEl, () => {
|
|
151
|
+
const cursorItem = document.querySelector(`[data-key="${value.anchorKey}"]`);
|
|
152
|
+
|
|
153
|
+
if (cursorItem) {
|
|
154
|
+
const boundRect = cursorItem.getBoundingClientRect();
|
|
155
|
+
|
|
156
|
+
document.body.appendChild(newEl);
|
|
157
|
+
newEl.style.position = 'fixed';
|
|
158
|
+
newEl.style.top = `${boundRect.top - newEl.offsetHeight - 10}px`;
|
|
159
|
+
newEl.style.left = `${boundRect.left + cursorItem.offsetWidth + 10}px`;
|
|
160
|
+
newEl.style.zIndex = 99999;
|
|
161
|
+
|
|
162
|
+
let firstCallMade = false;
|
|
163
|
+
|
|
164
|
+
const listener = () => {
|
|
165
|
+
// this will be triggered right after setting it because
|
|
166
|
+
// this toolbar is added on the mousedown event
|
|
167
|
+
// so right after mouseup, the click will be triggered
|
|
168
|
+
if (firstCallMade) {
|
|
169
|
+
document.body.removeEventListener('click', listener);
|
|
170
|
+
handleClose();
|
|
171
|
+
} else {
|
|
172
|
+
firstCallMade = true;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if (configToUse.autoClose) {
|
|
177
|
+
document.body.addEventListener('click', listener);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const CharacterIcon = ({ letter }) => (
|
|
184
|
+
<div
|
|
185
|
+
style={{
|
|
186
|
+
fontSize: '25px',
|
|
187
|
+
lineHeight: '15px'
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
{letter}
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
export default function CharactersPlugin(opts) {
|
|
195
|
+
removeDialogs();
|
|
196
|
+
return {
|
|
197
|
+
name: 'math',
|
|
198
|
+
toolbar: {
|
|
199
|
+
icon: <CharacterIcon letter={opts.characterIcon || characterIcons[opts.language] || 'ñ'} />,
|
|
200
|
+
onClick: (value, onChange) => {
|
|
201
|
+
let valueToUse = value;
|
|
202
|
+
const callback = (char, focus) => {
|
|
203
|
+
if (char) {
|
|
204
|
+
const change = valueToUse
|
|
205
|
+
.change()
|
|
206
|
+
.insertTextByKey(valueToUse.anchorKey, valueToUse.anchorOffset, char);
|
|
207
|
+
|
|
208
|
+
valueToUse = change.value;
|
|
209
|
+
log('[characters:insert]: ', value);
|
|
210
|
+
onChange(change);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
log('[characters:click]');
|
|
214
|
+
|
|
215
|
+
if (focus) {
|
|
216
|
+
const editorDOM = document.querySelector(`[data-key="${valueToUse.document.key}"]`);
|
|
217
|
+
|
|
218
|
+
if (editorDOM) {
|
|
219
|
+
editorDOM.focus();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
insertDialog({ value: valueToUse, callback, opts });
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
pluginStyles: (node, parentNode, p) => {
|
|
229
|
+
if (p) {
|
|
230
|
+
return {
|
|
231
|
+
position: 'absolute',
|
|
232
|
+
top: 'initial'
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|