@pie-lib/editable-html-tip-tap 1.0.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.
Files changed (167) hide show
  1. package/CHANGELOG.json +32 -0
  2. package/CHANGELOG.md +2280 -0
  3. package/lib/__tests__/editor.test.js +470 -0
  4. package/lib/__tests__/serialization.test.js +246 -0
  5. package/lib/__tests__/utils.js +106 -0
  6. package/lib/block-tags.js +25 -0
  7. package/lib/constants.js +16 -0
  8. package/lib/editor.js +1356 -0
  9. package/lib/extensions/MediaView.js +112 -0
  10. package/lib/extensions/characters.js +65 -0
  11. package/lib/extensions/component.js +325 -0
  12. package/lib/extensions/css.js +252 -0
  13. package/lib/extensions/custom-toolbar-wrapper.js +124 -0
  14. package/lib/extensions/image.js +106 -0
  15. package/lib/extensions/math.js +330 -0
  16. package/lib/extensions/media.js +276 -0
  17. package/lib/extensions/responseArea.js +278 -0
  18. package/lib/index.js +1213 -0
  19. package/lib/old-index.js +269 -0
  20. package/lib/parse-html.js +16 -0
  21. package/lib/plugins/characters/custom-popper.js +73 -0
  22. package/lib/plugins/characters/index.js +305 -0
  23. package/lib/plugins/characters/utils.js +381 -0
  24. package/lib/plugins/css/icons/index.js +37 -0
  25. package/lib/plugins/css/index.js +390 -0
  26. package/lib/plugins/customPlugin/index.js +114 -0
  27. package/lib/plugins/html/icons/index.js +38 -0
  28. package/lib/plugins/html/index.js +81 -0
  29. package/lib/plugins/image/__tests__/component.test.js +51 -0
  30. package/lib/plugins/image/__tests__/image-toolbar-logic.test.js +56 -0
  31. package/lib/plugins/image/__tests__/image-toolbar.test.js +26 -0
  32. package/lib/plugins/image/__tests__/index.test.js +98 -0
  33. package/lib/plugins/image/__tests__/insert-image-handler.test.js +125 -0
  34. package/lib/plugins/image/__tests__/mock-change.js +25 -0
  35. package/lib/plugins/image/alt-dialog.js +129 -0
  36. package/lib/plugins/image/component.js +419 -0
  37. package/lib/plugins/image/image-toolbar.js +177 -0
  38. package/lib/plugins/image/index.js +263 -0
  39. package/lib/plugins/image/insert-image-handler.js +117 -0
  40. package/lib/plugins/index.js +413 -0
  41. package/lib/plugins/list/__tests__/index.test.js +79 -0
  42. package/lib/plugins/list/index.js +334 -0
  43. package/lib/plugins/math/__tests__/index.test.js +300 -0
  44. package/lib/plugins/math/index.js +454 -0
  45. package/lib/plugins/media/__tests__/index.test.js +71 -0
  46. package/lib/plugins/media/index.js +387 -0
  47. package/lib/plugins/media/media-dialog.js +709 -0
  48. package/lib/plugins/media/media-toolbar.js +101 -0
  49. package/lib/plugins/media/media-wrapper.js +93 -0
  50. package/lib/plugins/rendering/index.js +46 -0
  51. package/lib/plugins/respArea/drag-in-the-blank/choice.js +289 -0
  52. package/lib/plugins/respArea/drag-in-the-blank/index.js +94 -0
  53. package/lib/plugins/respArea/explicit-constructed-response/index.js +120 -0
  54. package/lib/plugins/respArea/icons/index.js +95 -0
  55. package/lib/plugins/respArea/index.js +341 -0
  56. package/lib/plugins/respArea/inline-dropdown/index.js +126 -0
  57. package/lib/plugins/respArea/math-templated/index.js +130 -0
  58. package/lib/plugins/respArea/utils.js +125 -0
  59. package/lib/plugins/table/CustomTablePlugin.js +133 -0
  60. package/lib/plugins/table/__tests__/index.test.js +442 -0
  61. package/lib/plugins/table/__tests__/table-toolbar.test.js +54 -0
  62. package/lib/plugins/table/icons/index.js +69 -0
  63. package/lib/plugins/table/index.js +483 -0
  64. package/lib/plugins/table/table-toolbar.js +187 -0
  65. package/lib/plugins/textAlign/icons/index.js +194 -0
  66. package/lib/plugins/textAlign/index.js +34 -0
  67. package/lib/plugins/toolbar/__tests__/default-toolbar.test.js +128 -0
  68. package/lib/plugins/toolbar/__tests__/editor-and-toolbar.test.js +51 -0
  69. package/lib/plugins/toolbar/__tests__/toolbar-buttons.test.js +54 -0
  70. package/lib/plugins/toolbar/__tests__/toolbar.test.js +120 -0
  71. package/lib/plugins/toolbar/default-toolbar.js +229 -0
  72. package/lib/plugins/toolbar/done-button.js +53 -0
  73. package/lib/plugins/toolbar/editor-and-toolbar.js +286 -0
  74. package/lib/plugins/toolbar/index.js +34 -0
  75. package/lib/plugins/toolbar/toolbar-buttons.js +194 -0
  76. package/lib/plugins/toolbar/toolbar.js +376 -0
  77. package/lib/plugins/utils.js +62 -0
  78. package/lib/serialization.js +677 -0
  79. package/lib/shared/alert-dialog.js +75 -0
  80. package/lib/theme.js +9 -0
  81. package/package.json +69 -0
  82. package/src/__tests__/editor.test.jsx +363 -0
  83. package/src/__tests__/serialization.test.js +291 -0
  84. package/src/__tests__/utils.js +36 -0
  85. package/src/block-tags.js +17 -0
  86. package/src/constants.js +7 -0
  87. package/src/editor.jsx +1197 -0
  88. package/src/extensions/characters.js +46 -0
  89. package/src/extensions/component.jsx +294 -0
  90. package/src/extensions/css.js +217 -0
  91. package/src/extensions/custom-toolbar-wrapper.jsx +100 -0
  92. package/src/extensions/image.js +55 -0
  93. package/src/extensions/math.js +259 -0
  94. package/src/extensions/media.js +182 -0
  95. package/src/extensions/responseArea.js +205 -0
  96. package/src/index.jsx +1462 -0
  97. package/src/old-index.jsx +162 -0
  98. package/src/parse-html.js +8 -0
  99. package/src/plugins/README.md +27 -0
  100. package/src/plugins/characters/custom-popper.js +48 -0
  101. package/src/plugins/characters/index.jsx +284 -0
  102. package/src/plugins/characters/utils.js +447 -0
  103. package/src/plugins/css/icons/index.jsx +17 -0
  104. package/src/plugins/css/index.jsx +340 -0
  105. package/src/plugins/customPlugin/index.jsx +85 -0
  106. package/src/plugins/html/icons/index.jsx +19 -0
  107. package/src/plugins/html/index.jsx +72 -0
  108. package/src/plugins/image/__tests__/__snapshots__/component.test.jsx.snap +51 -0
  109. package/src/plugins/image/__tests__/__snapshots__/image-toolbar-logic.test.jsx.snap +27 -0
  110. package/src/plugins/image/__tests__/__snapshots__/image-toolbar.test.jsx.snap +44 -0
  111. package/src/plugins/image/__tests__/component.test.jsx +41 -0
  112. package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +42 -0
  113. package/src/plugins/image/__tests__/image-toolbar.test.jsx +11 -0
  114. package/src/plugins/image/__tests__/index.test.js +95 -0
  115. package/src/plugins/image/__tests__/insert-image-handler.test.js +113 -0
  116. package/src/plugins/image/__tests__/mock-change.js +15 -0
  117. package/src/plugins/image/alt-dialog.jsx +82 -0
  118. package/src/plugins/image/component.jsx +343 -0
  119. package/src/plugins/image/image-toolbar.jsx +100 -0
  120. package/src/plugins/image/index.jsx +227 -0
  121. package/src/plugins/image/insert-image-handler.js +79 -0
  122. package/src/plugins/index.jsx +377 -0
  123. package/src/plugins/list/__tests__/index.test.js +54 -0
  124. package/src/plugins/list/index.jsx +305 -0
  125. package/src/plugins/math/__tests__/__snapshots__/index.test.jsx.snap +48 -0
  126. package/src/plugins/math/__tests__/index.test.jsx +245 -0
  127. package/src/plugins/math/index.jsx +379 -0
  128. package/src/plugins/media/__tests__/index.test.js +75 -0
  129. package/src/plugins/media/index.jsx +325 -0
  130. package/src/plugins/media/media-dialog.js +624 -0
  131. package/src/plugins/media/media-toolbar.jsx +56 -0
  132. package/src/plugins/media/media-wrapper.jsx +43 -0
  133. package/src/plugins/rendering/index.js +31 -0
  134. package/src/plugins/respArea/drag-in-the-blank/choice.jsx +215 -0
  135. package/src/plugins/respArea/drag-in-the-blank/index.jsx +70 -0
  136. package/src/plugins/respArea/explicit-constructed-response/index.jsx +92 -0
  137. package/src/plugins/respArea/icons/index.jsx +71 -0
  138. package/src/plugins/respArea/index.jsx +299 -0
  139. package/src/plugins/respArea/inline-dropdown/index.jsx +108 -0
  140. package/src/plugins/respArea/math-templated/index.jsx +104 -0
  141. package/src/plugins/respArea/utils.jsx +90 -0
  142. package/src/plugins/table/CustomTablePlugin.js +113 -0
  143. package/src/plugins/table/__tests__/__snapshots__/table-toolbar.test.jsx.snap +44 -0
  144. package/src/plugins/table/__tests__/index.test.jsx +401 -0
  145. package/src/plugins/table/__tests__/table-toolbar.test.jsx +42 -0
  146. package/src/plugins/table/icons/index.jsx +53 -0
  147. package/src/plugins/table/index.jsx +427 -0
  148. package/src/plugins/table/table-toolbar.jsx +136 -0
  149. package/src/plugins/textAlign/icons/index.jsx +114 -0
  150. package/src/plugins/textAlign/index.jsx +23 -0
  151. package/src/plugins/toolbar/__tests__/__snapshots__/default-toolbar.test.jsx.snap +923 -0
  152. package/src/plugins/toolbar/__tests__/__snapshots__/editor-and-toolbar.test.jsx.snap +20 -0
  153. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar-buttons.test.jsx.snap +36 -0
  154. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar.test.jsx.snap +46 -0
  155. package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +94 -0
  156. package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +37 -0
  157. package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +51 -0
  158. package/src/plugins/toolbar/__tests__/toolbar.test.jsx +106 -0
  159. package/src/plugins/toolbar/default-toolbar.jsx +206 -0
  160. package/src/plugins/toolbar/done-button.jsx +38 -0
  161. package/src/plugins/toolbar/editor-and-toolbar.jsx +257 -0
  162. package/src/plugins/toolbar/index.jsx +23 -0
  163. package/src/plugins/toolbar/toolbar-buttons.jsx +138 -0
  164. package/src/plugins/toolbar/toolbar.jsx +338 -0
  165. package/src/plugins/utils.js +31 -0
  166. package/src/serialization.jsx +621 -0
  167. package/src/theme.js +1 -0
@@ -0,0 +1,162 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Editor, { DEFAULT_PLUGINS, ALL_PLUGINS } from './editor';
4
+ import { extraCSSRulesOpts, htmlToValue, valueToHtml, reduceMultipleBrs } from './serialization';
5
+ import { parseDegrees } from './parse-html';
6
+ import constants from './constants';
7
+ import debug from 'debug';
8
+ import { Range } from 'slate';
9
+
10
+ const log = debug('@pie-lib:editable-html');
11
+
12
+ /**
13
+ * Wrapper around the editor that exposes a `markup` and `onChange(markup:string)` api.
14
+ * Because of the mismatch between the markup and the `Value` we need to convert the incoming markup to a value and
15
+ * compare it. TODO: This is an interim fix, we'll need to strip back `Editor` and look how best to maintain the
16
+ * `markup` api whilst avoiding the serialization mismatch. We should be making better use of schemas w/ normalize.
17
+ */
18
+ export default class EditableHtml extends React.Component {
19
+ static propTypes = {
20
+ error: PropTypes.any,
21
+ onChange: PropTypes.func.isRequired,
22
+ onDone: PropTypes.func,
23
+ markup: PropTypes.string.isRequired,
24
+ allowValidation: PropTypes.bool,
25
+ toolbarOpts: PropTypes.object,
26
+ extraCSSRules: PropTypes.shape({
27
+ names: PropTypes.arrayOf(PropTypes.string),
28
+ rules: PropTypes.string,
29
+ }),
30
+ };
31
+
32
+ static defaultProps = {
33
+ onDone: () => {},
34
+ allowValidation: false,
35
+ };
36
+
37
+ constructor(props) {
38
+ super(props);
39
+
40
+ if (props.extraCSSRules) {
41
+ Object.assign(extraCSSRulesOpts, this.props.extraCSSRules);
42
+ }
43
+
44
+ const v = htmlToValue(props.markup);
45
+ this.state = {
46
+ value: v,
47
+ };
48
+ }
49
+
50
+ // eslint-disable-next-line react/no-deprecated
51
+ componentWillReceiveProps(props) {
52
+ if (!props.allowValidation && props.markup === this.props.markup) {
53
+ return;
54
+ }
55
+
56
+ const v = htmlToValue(props.markup);
57
+ const current = htmlToValue(this.props.markup);
58
+
59
+ if (v.equals && !v.equals(current)) {
60
+ this.setState({ value: v });
61
+ }
62
+ }
63
+
64
+ runSerializationOnMarkup = () => {
65
+ if (!this.props.markup) {
66
+ return;
67
+ }
68
+
69
+ const v = htmlToValue(reduceMultipleBrs(this.props.markup));
70
+
71
+ this.setState({ value: v });
72
+ };
73
+
74
+ onChange = (value, done) => {
75
+ const html = valueToHtml(value);
76
+ const htmlParsed = parseDegrees(html);
77
+
78
+ if (htmlParsed !== this.props.markup && this.props.onChange) {
79
+ this.props.onChange(htmlParsed);
80
+ }
81
+
82
+ if (done && this.props.onDone) {
83
+ this.props.onDone(htmlParsed);
84
+ }
85
+ };
86
+
87
+ focus = (position, node, select = false) => {
88
+ if (this.editorRef) {
89
+ this.editorRef.change((c) => {
90
+ const lastText = node ? c.value.document.getNextText(node.key) : c.value.document.getLastText();
91
+ const editorDOM = document.querySelector(`[data-key="${this.editorRef.value.document.key}"]`);
92
+
93
+ if (editorDOM !== document.activeElement) {
94
+ document.activeElement.blur();
95
+ }
96
+
97
+ c.focus();
98
+
99
+ if (position === 'end' && lastText) {
100
+ c.moveFocusTo(lastText.key, lastText.text?.length).moveAnchorTo(lastText.key, lastText.text?.length);
101
+ if (select) {
102
+ const range = Range.fromJSON({
103
+ anchorKey: lastText.key,
104
+ anchorOffset: 0,
105
+ focusKey: lastText.key,
106
+ focusOffset: lastText.text?.length,
107
+ isFocused: true,
108
+ isBackward: false,
109
+ });
110
+ c.select(range);
111
+ }
112
+ }
113
+
114
+ if (position === 'beginning' && lastText) {
115
+ c.moveFocusTo(lastText.key, 0).moveAnchorTo(lastText.key, 0);
116
+ }
117
+ editorDOM.focus();
118
+ });
119
+ }
120
+ };
121
+
122
+ finishEditing = () => {
123
+ if (this.editorRef) {
124
+ this.editorRef.props.onEditingDone();
125
+ }
126
+ };
127
+
128
+ render() {
129
+ const { value } = this.state;
130
+ const { toolbarOpts, error } = this.props;
131
+
132
+ if (toolbarOpts) {
133
+ toolbarOpts.error = error;
134
+ }
135
+
136
+ const props = {
137
+ ...this.props,
138
+ markup: null,
139
+ value,
140
+ onChange: this.onChange,
141
+ focus: this.focus,
142
+ runSerializationOnMarkup: this.runSerializationOnMarkup,
143
+ };
144
+
145
+ return (
146
+ <Editor
147
+ onRef={(ref) => {
148
+ if (ref) {
149
+ this.rootRef = ref;
150
+ }
151
+ }}
152
+ editorRef={(ref) => ref && (this.editorRef = ref)}
153
+ {...props}
154
+ />
155
+ );
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Export lower level Editor and serialization functions.
161
+ */
162
+ export { htmlToValue, valueToHtml, Editor, DEFAULT_PLUGINS, ALL_PLUGINS, constants };
@@ -0,0 +1,8 @@
1
+ export const parseDegrees = (html) =>
2
+ html
3
+ // removes \( use case: 50°
4
+ .replace(/\\[(]/g, '')
5
+ // removes \) use case: 50°+m<1
6
+ .replace(/\\[)]/g, '')
7
+ // removes \degree use case: 50°
8
+ .replace(/\\degree/g, '&deg;');
@@ -0,0 +1,27 @@
1
+ # plugins
2
+
3
+ ## Custom toolbar
4
+
5
+ To create a custom toolbar you need to add the following methods to the toolbar object:
6
+
7
+ ```typescript
8
+ type ChangeFn = (key:string, update : object) : void;
9
+
10
+ type Toolbar = {
11
+ /**
12
+ * return true if this plugin supports this node type
13
+ */
14
+ supports : (node: Slate.Node) : Boolean;
15
+ /**
16
+ * return a React component to edit the data within the node,
17
+ * call toolbarDone to finish editing, call toolbarChange to update
18
+ * the main editor without closing editing.
19
+ */
20
+ customToolbar: (node: Slate.Node, toolbarDone : ChangeFn, toolbarChange: ChangeFn),
21
+ /**
22
+ * Takes the output of the `customToolbar#toolbarDone` call
23
+ * and passes in a value so that you can create a Slate.Change.
24
+ */
25
+ applyChange: (key: string, data: object, value: Slate.Value) : Slate.Change | undefined
26
+ }
27
+ ```
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { withStyles } from '@material-ui/core/styles';
3
+ import Popper from '@material-ui/core/Popper';
4
+ import Typography from '@material-ui/core/Typography';
5
+
6
+ const styles = () => ({
7
+ popover: {
8
+ background: '#fff',
9
+ padding: '10px',
10
+ pointerEvents: 'none',
11
+ zIndex: 99999,
12
+ },
13
+ paper: {
14
+ padding: 20,
15
+ height: 'auto',
16
+ width: 'auto',
17
+ },
18
+ typography: {
19
+ fontSize: 50,
20
+ textAlign: 'center',
21
+ },
22
+ });
23
+
24
+ const CustomPopper = withStyles(styles)(({ classes, children, ...props }) => (
25
+ <Popper
26
+ id="mouse-over-popover"
27
+ open
28
+ className={classes.popover}
29
+ classes={{
30
+ paper: classes.paper,
31
+ }}
32
+ anchorOrigin={{
33
+ vertical: 'bottom',
34
+ horizontal: 'left',
35
+ }}
36
+ transformOrigin={{
37
+ vertical: 'top',
38
+ horizontal: 'left',
39
+ }}
40
+ disableRestoreFocus
41
+ disableAutoFocus
42
+ {...props}
43
+ >
44
+ <Typography classes={{ root: classes.typography }}>{children}</Typography>
45
+ </Popper>
46
+ ));
47
+
48
+ export default CustomPopper;
@@ -0,0 +1,284 @@
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 CustomPopper from './custom-popper';
9
+ import { insertSnackBar } from '../respArea/utils';
10
+ import { characterIcons, spanishConfig, specialConfig } from './utils';
11
+ import PropTypes from 'prop-types';
12
+
13
+ const log = debug('@pie-lib:editable-html:plugins:characters');
14
+
15
+ const removePopOvers = () => {
16
+ const prevPopOvers = document.querySelectorAll('#mouse-over-popover');
17
+
18
+ log('[characters:removePopOvers]');
19
+ prevPopOvers.forEach((s) => s.remove());
20
+ };
21
+
22
+ export const removeDialogs = () => {
23
+ const prevDialogs = document.querySelectorAll('.insert-character-dialog');
24
+
25
+ log('[characters:removeDialogs]');
26
+ prevDialogs.forEach((s) => s.remove());
27
+ removePopOvers();
28
+ };
29
+
30
+ const insertDialog = ({ editorDOM, value, callback, opts }) => {
31
+ const newEl = document.createElement('div');
32
+
33
+ log('[characters:insertDialog]');
34
+
35
+ removeDialogs();
36
+
37
+ newEl.className = 'insert-character-dialog';
38
+
39
+ let configToUse;
40
+
41
+ switch (true) {
42
+ case opts.language === 'spanish':
43
+ configToUse = spanishConfig;
44
+ break;
45
+ case opts.language === 'special':
46
+ configToUse = specialConfig;
47
+ break;
48
+ default:
49
+ configToUse = opts;
50
+ }
51
+
52
+ if (!configToUse.characters) {
53
+ insertSnackBar('No characters provided or language not recognized');
54
+ return;
55
+ }
56
+
57
+ const layoutForCharacters = configToUse.characters.reduce(
58
+ (obj, arr) => {
59
+ if (arr.length >= obj.columns) {
60
+ obj.columns = arr.length;
61
+ }
62
+
63
+ return obj;
64
+ },
65
+ { rows: configToUse.characters.length, columns: 0 },
66
+ );
67
+
68
+ let popoverEl;
69
+
70
+ const closePopOver = () => {
71
+ if (popoverEl) {
72
+ popoverEl.remove();
73
+ }
74
+
75
+ removePopOvers();
76
+ };
77
+
78
+ const renderPopOver = (event, el) => {
79
+ if (!event) {
80
+ return;
81
+ }
82
+
83
+ const infoStyle = { fontSize: '20px', lineHeight: '20px' };
84
+
85
+ closePopOver();
86
+
87
+ popoverEl = document.createElement('div');
88
+ ReactDOM.render(
89
+ <CustomPopper onClose={closePopOver} anchorEl={event.currentTarget}>
90
+ <div>{el.label}</div>
91
+
92
+ <div style={infoStyle}>{el.description}</div>
93
+
94
+ <div style={infoStyle}>{el.unicode}</div>
95
+ </CustomPopper>,
96
+ popoverEl,
97
+ );
98
+
99
+ document.body.appendChild(newEl);
100
+ };
101
+
102
+ let firstCallMade = false;
103
+
104
+ const listener = (e) => {
105
+ // this will be triggered right after setting it because
106
+ // this toolbar is added on the mousedown event
107
+ // so right after mouseup, the click will be triggered
108
+ if (firstCallMade) {
109
+ const focusIsInModals = newEl.contains(e.target) || (popoverEl && popoverEl.contains(e.target));
110
+ const focusIsInEditor = editorDOM.contains(e.target);
111
+
112
+ if (!(focusIsInModals || focusIsInEditor)) {
113
+ handleClose();
114
+ }
115
+ } else {
116
+ firstCallMade = true;
117
+ }
118
+ };
119
+
120
+ const handleClose = () => {
121
+ callback(undefined, true);
122
+ newEl.remove();
123
+ closePopOver();
124
+ document.body.removeEventListener('click', listener);
125
+ };
126
+
127
+ const handleChange = (val) => {
128
+ if (typeof val === 'string') {
129
+ callback(val, true);
130
+ }
131
+ };
132
+
133
+ const el = (
134
+ <PureToolbar
135
+ keyPadCharacterRef={opts.keyPadCharacterRef}
136
+ setKeypadInteraction={opts.setKeypadInteraction}
137
+ autoFocus
138
+ noDecimal
139
+ hideInput
140
+ noLatexHandling
141
+ hideDoneButtonBackground
142
+ layoutForKeyPad={layoutForCharacters}
143
+ additionalKeys={configToUse.characters.reduce((arr, n) => {
144
+ arr = [
145
+ ...arr,
146
+ ...n.map((k) => ({
147
+ name: get(k, 'name') || k,
148
+ write: get(k, 'write') || k,
149
+ label: get(k, 'label') || k,
150
+ category: 'character',
151
+ extraClass: 'character',
152
+ extraProps: {
153
+ ...(k.extraProps || {}),
154
+ style: {
155
+ ...(k.extraProps || {}).style,
156
+ border: '1px solid #000',
157
+ },
158
+ },
159
+ ...(configToUse.hasPreview
160
+ ? {
161
+ actions: { onMouseEnter: (ev) => renderPopOver(ev, k), onMouseLeave: closePopOver },
162
+ }
163
+ : {}),
164
+ })),
165
+ ];
166
+
167
+ return arr;
168
+ }, [])}
169
+ keypadMode="language"
170
+ onChange={handleChange}
171
+ onDone={handleClose}
172
+ />
173
+ );
174
+
175
+ ReactDOM.render(el, newEl, () => {
176
+ const cursorItem = document.querySelector(`[data-key="${value.anchorKey}"]`);
177
+
178
+ if (cursorItem) {
179
+ const bodyRect = document.body.getBoundingClientRect();
180
+ const boundRect = cursorItem.getBoundingClientRect();
181
+
182
+ document.body.appendChild(newEl);
183
+
184
+ // when height of toolbar exceeds screen - can happen in scrollable contexts
185
+ let additionalTopOffset = 0;
186
+ if (boundRect.y < newEl.offsetHeight) {
187
+ additionalTopOffset = newEl.offsetHeight - boundRect.y + 10;
188
+ }
189
+
190
+ newEl.style.maxWidth = '500px';
191
+ newEl.style.position = 'absolute';
192
+ newEl.style.top = `${boundRect.top + Math.abs(bodyRect.top) - newEl.offsetHeight - 10 + additionalTopOffset}px`;
193
+ newEl.style.zIndex = 99999;
194
+
195
+ const leftValue = `${boundRect.left + Math.abs(bodyRect.left) + cursorItem.offsetWidth + 10}px`;
196
+
197
+ const rightValue = `${boundRect.x}px`;
198
+
199
+ newEl.style.left = leftValue;
200
+
201
+ const leftAlignedWidth = newEl.offsetWidth;
202
+
203
+ newEl.style.left = 'unset';
204
+ newEl.style.right = rightValue;
205
+
206
+ const rightAlignedWidth = newEl.offsetWidth;
207
+
208
+ newEl.style.left = 'unset';
209
+ newEl.style.right = 'unset';
210
+
211
+ if (leftAlignedWidth >= rightAlignedWidth) {
212
+ newEl.style.left = leftValue;
213
+ } else {
214
+ newEl.style.right = rightValue;
215
+ }
216
+
217
+ document.body.addEventListener('click', listener);
218
+ }
219
+ });
220
+ };
221
+
222
+ const CharacterIcon = ({ letter }) => (
223
+ <div
224
+ style={{
225
+ fontSize: '24px',
226
+ lineHeight: '24px',
227
+ }}
228
+ >
229
+ {letter}
230
+ </div>
231
+ );
232
+
233
+ CharacterIcon.propTypes = {
234
+ letter: PropTypes.string,
235
+ };
236
+
237
+ export default function CharactersPlugin(opts) {
238
+ removeDialogs();
239
+
240
+ return {
241
+ name: 'characters',
242
+ toolbar: {
243
+ icon: <CharacterIcon letter={opts.characterIcon || characterIcons[opts.language] || 'ñ'} />,
244
+ ariaLabel: `${opts.language} characters Toolbar`,
245
+ onClick: (value, onChange, getFocusedValue) => {
246
+ const editorDOM = document.querySelector(`[data-key="${value.document.key}"]`);
247
+ let valueToUse = value;
248
+
249
+ const callback = (char, focus) => {
250
+ if (getFocusedValue) {
251
+ valueToUse = getFocusedValue() || valueToUse;
252
+ }
253
+
254
+ if (char) {
255
+ const change = valueToUse.change().insertTextByKey(valueToUse.anchorKey, valueToUse.anchorOffset, char);
256
+
257
+ valueToUse = change.value;
258
+ log('[characters:insert]: ', value);
259
+ onChange(change);
260
+ }
261
+
262
+ log('[characters:click]');
263
+
264
+ if (focus) {
265
+ if (editorDOM) {
266
+ editorDOM.focus();
267
+ }
268
+ }
269
+ };
270
+
271
+ insertDialog({ editorDOM, value: valueToUse, callback, opts });
272
+ },
273
+ },
274
+
275
+ pluginStyles: (node, parentNode, p) => {
276
+ if (p) {
277
+ return {
278
+ position: 'absolute',
279
+ top: 'initial',
280
+ };
281
+ }
282
+ },
283
+ };
284
+ }