@pie-lib/editable-html-tip-tap 1.2.0-next.2 → 1.2.0-next.20
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 +109 -21
- package/LICENSE.md +5 -0
- package/lib/components/CharacterPicker.js +2 -1
- package/lib/components/CharacterPicker.js.map +1 -1
- package/lib/components/EditableHtml.js +34 -17
- package/lib/components/EditableHtml.js.map +1 -1
- package/lib/components/MenuBar.js +77 -45
- package/lib/components/MenuBar.js.map +1 -1
- package/lib/components/TiptapContainer.js +15 -9
- package/lib/components/TiptapContainer.js.map +1 -1
- package/lib/components/characters/characterUtils.js +1 -1
- package/lib/components/characters/custom-popper.js +1 -1
- package/lib/components/common/done-button.js +1 -1
- package/lib/components/common/toolbar-buttons.js +1 -1
- package/lib/components/icons/CssIcon.js +1 -1
- package/lib/components/icons/RespArea.js +1 -1
- package/lib/components/icons/TableIcons.js +1 -1
- package/lib/components/icons/TextAlign.js +3 -3
- package/lib/components/icons/TextAlign.js.map +1 -1
- package/lib/components/image/AltDialog.js +1 -1
- package/lib/components/image/ImageToolbar.js +1 -1
- package/lib/components/image/InsertImageHandler.js +1 -1
- package/lib/components/media/MediaDialog.js +1 -1
- package/lib/components/media/MediaToolbar.js +1 -1
- package/lib/components/media/MediaWrapper.js +1 -1
- package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +7 -2
- package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +1 -1
- package/lib/components/respArea/DragInTheBlank/choice.js +16 -8
- package/lib/components/respArea/DragInTheBlank/choice.js.map +1 -1
- package/lib/components/respArea/ExplicitConstructedResponse.js +2 -2
- package/lib/components/respArea/ExplicitConstructedResponse.js.map +1 -1
- package/lib/components/respArea/InlineDropdown.js +11 -4
- package/lib/components/respArea/InlineDropdown.js.map +1 -1
- package/lib/components/respArea/MathTemplated.js +130 -0
- package/lib/components/respArea/MathTemplated.js.map +1 -0
- package/lib/components/respArea/ToolbarIcon.js +1 -1
- package/lib/constants.js +1 -1
- package/lib/extensions/css.js +1 -1
- package/lib/extensions/custom-toolbar-wrapper.js +1 -1
- package/lib/extensions/div-node.js +39 -0
- package/lib/extensions/div-node.js.map +1 -0
- package/lib/extensions/extended-table.js +1 -1
- package/lib/extensions/image-component.js +1 -1
- package/lib/extensions/image-component.js.map +1 -1
- package/lib/extensions/image.js +1 -1
- package/lib/extensions/index.js +4 -2
- package/lib/extensions/index.js.map +1 -1
- package/lib/extensions/math.js +2 -1
- package/lib/extensions/math.js.map +1 -1
- package/lib/extensions/media.js +15 -12
- package/lib/extensions/media.js.map +1 -1
- package/lib/extensions/responseArea.js +13 -10
- package/lib/extensions/responseArea.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/styles/editorContainerStyles.js +6 -5
- package/lib/styles/editorContainerStyles.js.map +1 -1
- package/lib/theme.js +1 -1
- package/lib/utils/helper.js +17 -0
- package/lib/utils/helper.js.map +1 -0
- package/lib/utils/size.js +1 -1
- package/package.json +11 -11
- package/src/__tests__/EditableHtml.test.jsx +41 -1
- package/src/__tests__/index.test.jsx +4 -1
- package/src/components/CharacterPicker.jsx +1 -0
- package/src/components/EditableHtml.jsx +40 -16
- package/src/components/MenuBar.jsx +66 -25
- package/src/components/TiptapContainer.jsx +10 -7
- package/src/components/__tests__/CharacterPicker.test.jsx +22 -0
- package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +1 -1
- package/src/components/__tests__/InlineDropdown.test.jsx +150 -1
- package/src/components/__tests__/MenuBar.test.jsx +32 -0
- package/src/components/icons/TextAlign.jsx +1 -1
- package/src/components/respArea/DragInTheBlank/DragInTheBlank.jsx +6 -1
- package/src/components/respArea/DragInTheBlank/choice.jsx +32 -4
- package/src/components/respArea/ExplicitConstructedResponse.jsx +1 -1
- package/src/components/respArea/InlineDropdown.jsx +12 -2
- package/src/components/respArea/MathTemplated.jsx +124 -0
- package/src/components/respArea/__tests__/MathTemplated.test.jsx +210 -0
- package/src/extensions/__tests__/divNode.test.js +87 -0
- package/src/extensions/__tests__/math.test.js +327 -0
- package/src/extensions/__tests__/media-node-view.test.jsx +296 -0
- package/src/extensions/__tests__/media.test.js +1 -0
- package/src/extensions/__tests__/responseArea.test.js +157 -0
- package/src/extensions/div-node.js +36 -0
- package/src/extensions/index.js +2 -0
- package/src/extensions/math.js +1 -0
- package/src/extensions/media.js +17 -14
- package/src/extensions/responseArea.js +4 -8
- package/src/styles/editorContainerStyles.js +5 -4
- package/src/utils/helper.js +17 -0
- package/lib/__tests__/EditableHtml.test.js +0 -377
- package/lib/__tests__/constants.test.js +0 -21
- package/lib/__tests__/extensions.test.js +0 -209
- package/lib/__tests__/index.test.js +0 -235
- package/lib/__tests__/size-utils.test.js +0 -57
- package/lib/__tests__/theme.test.js +0 -17
- package/lib/components/__tests__/AltDialog.test.js +0 -201
- package/lib/components/__tests__/CharacterPicker.test.js +0 -305
- package/lib/components/__tests__/CssIcon.test.js +0 -58
- package/lib/components/__tests__/DragInTheBlank.test.js +0 -295
- package/lib/components/__tests__/ExplicitConstructedResponse.test.js +0 -253
- package/lib/components/__tests__/ImageToolbar.test.js +0 -185
- package/lib/components/__tests__/InlineDropdown.test.js +0 -287
- package/lib/components/__tests__/InsertImageHandler.test.js +0 -162
- package/lib/components/__tests__/MediaDialog.test.js +0 -433
- package/lib/components/__tests__/MediaToolbar.test.js +0 -126
- package/lib/components/__tests__/MediaWrapper.test.js +0 -96
- package/lib/components/__tests__/MenuBar.test.js +0 -459
- package/lib/components/__tests__/RespArea.test.js +0 -171
- package/lib/components/__tests__/TableIcons.test.js +0 -153
- package/lib/components/__tests__/TextAlign.test.js +0 -209
- package/lib/components/__tests__/TiptapContainer.test.js +0 -196
- package/lib/components/__tests__/characterUtils.test.js +0 -178
- package/lib/components/__tests__/choice.test.js +0 -213
- package/lib/components/__tests__/custom-popper.test.js +0 -108
- package/lib/components/__tests__/done-button.test.js +0 -72
- package/lib/components/__tests__/toolbar-buttons.test.js +0 -277
- package/lib/extensions/__tests__/component.test.js +0 -314
- package/lib/extensions/__tests__/css.test.js +0 -214
- package/lib/extensions/__tests__/custom-toolbar-wrapper.test.js +0 -175
- package/lib/extensions/__tests__/extended-table.test.js +0 -92
- package/lib/extensions/__tests__/image-component.test.js +0 -305
- package/lib/extensions/__tests__/image.test.js +0 -164
- package/lib/extensions/__tests__/media.test.js +0 -292
- package/lib/extensions/__tests__/responseArea.test.js +0 -330
- package/lib/extensions/component.js +0 -305
|
@@ -101,6 +101,163 @@ describe('ResponseAreaExtension', () => {
|
|
|
101
101
|
expect(commands).toHaveProperty('insertResponseArea');
|
|
102
102
|
expect(typeof commands.insertResponseArea).toBe('function');
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
it('returns refreshResponseArea command', () => {
|
|
106
|
+
const commands = ResponseAreaExtension.addCommands();
|
|
107
|
+
|
|
108
|
+
expect(commands).toHaveProperty('refreshResponseArea');
|
|
109
|
+
expect(typeof commands.refreshResponseArea).toBe('function');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('refreshResponseArea handles node with attrs safely', () => {
|
|
113
|
+
const context = {
|
|
114
|
+
options: {
|
|
115
|
+
type: 'explicit-constructed-response',
|
|
116
|
+
maxResponseAreas: 5,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const commands = ResponseAreaExtension.addCommands.call(context);
|
|
121
|
+
const refreshCommand = commands.refreshResponseArea();
|
|
122
|
+
|
|
123
|
+
// Mock transaction and state
|
|
124
|
+
const mockNode = {
|
|
125
|
+
attrs: {
|
|
126
|
+
index: '0',
|
|
127
|
+
value: 'test',
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const mockTr = {
|
|
132
|
+
setNodeMarkup: jest.fn(),
|
|
133
|
+
setSelection: jest.fn(),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const mockState = {
|
|
137
|
+
selection: {
|
|
138
|
+
from: 0,
|
|
139
|
+
$from: {
|
|
140
|
+
nodeAfter: mockNode,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
tr: mockTr,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const mockCommands = {
|
|
147
|
+
focus: jest.fn(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const mockDispatch = jest.fn();
|
|
151
|
+
|
|
152
|
+
refreshCommand({
|
|
153
|
+
tr: mockTr,
|
|
154
|
+
state: mockState,
|
|
155
|
+
commands: mockCommands,
|
|
156
|
+
dispatch: mockDispatch,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(mockTr.setNodeMarkup).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('refreshResponseArea handles node without attrs safely (optional chaining)', () => {
|
|
163
|
+
const context = {
|
|
164
|
+
options: {
|
|
165
|
+
type: 'explicit-constructed-response',
|
|
166
|
+
maxResponseAreas: 5,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const commands = ResponseAreaExtension.addCommands.call(context);
|
|
171
|
+
const refreshCommand = commands.refreshResponseArea();
|
|
172
|
+
|
|
173
|
+
// Mock transaction and state with node that has no attrs
|
|
174
|
+
const mockNode = null;
|
|
175
|
+
|
|
176
|
+
const mockTr = {
|
|
177
|
+
setNodeMarkup: jest.fn(),
|
|
178
|
+
setSelection: jest.fn(),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const mockState = {
|
|
182
|
+
selection: {
|
|
183
|
+
from: 0,
|
|
184
|
+
$from: {
|
|
185
|
+
nodeAfter: mockNode,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
tr: mockTr,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const mockCommands = {
|
|
192
|
+
focus: jest.fn(),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const mockDispatch = jest.fn();
|
|
196
|
+
|
|
197
|
+
// This should not throw an error due to optional chaining on node?.attrs
|
|
198
|
+
expect(() => {
|
|
199
|
+
refreshCommand({
|
|
200
|
+
tr: mockTr,
|
|
201
|
+
state: mockState,
|
|
202
|
+
commands: mockCommands,
|
|
203
|
+
dispatch: mockDispatch,
|
|
204
|
+
});
|
|
205
|
+
}).not.toThrow();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('refreshResponseArea updates timestamp in node attributes', () => {
|
|
209
|
+
const context = {
|
|
210
|
+
options: {
|
|
211
|
+
type: 'explicit-constructed-response',
|
|
212
|
+
maxResponseAreas: 5,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const commands = ResponseAreaExtension.addCommands.call(context);
|
|
217
|
+
const refreshCommand = commands.refreshResponseArea();
|
|
218
|
+
|
|
219
|
+
const mockNode = {
|
|
220
|
+
attrs: {
|
|
221
|
+
index: '0',
|
|
222
|
+
value: 'test',
|
|
223
|
+
updated: '1234567890',
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const mockTr = {
|
|
228
|
+
setNodeMarkup: jest.fn((pos, type, attrs) => {
|
|
229
|
+
// Verify that updated timestamp is being set
|
|
230
|
+
expect(attrs.updated).toBeDefined();
|
|
231
|
+
expect(attrs.updated).not.toBe('1234567890');
|
|
232
|
+
}),
|
|
233
|
+
setSelection: jest.fn(),
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const mockState = {
|
|
237
|
+
selection: {
|
|
238
|
+
from: 0,
|
|
239
|
+
$from: {
|
|
240
|
+
nodeAfter: mockNode,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
tr: mockTr,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const mockCommands = {
|
|
247
|
+
focus: jest.fn(),
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const mockDispatch = jest.fn();
|
|
251
|
+
|
|
252
|
+
refreshCommand({
|
|
253
|
+
tr: mockTr,
|
|
254
|
+
state: mockState,
|
|
255
|
+
commands: mockCommands,
|
|
256
|
+
dispatch: mockDispatch,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(mockTr.setNodeMarkup).toHaveBeenCalled();
|
|
260
|
+
});
|
|
104
261
|
});
|
|
105
262
|
});
|
|
106
263
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// DivNode.ts
|
|
2
|
+
import { Node } from '@tiptap/core';
|
|
3
|
+
|
|
4
|
+
export const DivNode = Node.create({
|
|
5
|
+
name: 'div',
|
|
6
|
+
group: 'block',
|
|
7
|
+
content: 'inline*',
|
|
8
|
+
|
|
9
|
+
parseHTML() {
|
|
10
|
+
return [{ tag: 'div' }];
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
renderHTML({ HTMLAttributes }) {
|
|
14
|
+
return ['div', HTMLAttributes, 0];
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
addKeyboardShortcuts() {
|
|
18
|
+
return {
|
|
19
|
+
Enter: () => {
|
|
20
|
+
const { state } = this.editor;
|
|
21
|
+
const { $from } = state.selection;
|
|
22
|
+
|
|
23
|
+
if ($from.parent.type.name !== 'div') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return this.editor
|
|
28
|
+
.chain()
|
|
29
|
+
.focus()
|
|
30
|
+
.setNode('paragraph') // current div becomes <p>
|
|
31
|
+
.splitBlock() // create another <p>
|
|
32
|
+
.run();
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
});
|
package/src/extensions/index.js
CHANGED
|
@@ -31,6 +31,8 @@ export const ALL_PLUGINS = [
|
|
|
31
31
|
|
|
32
32
|
export const PLUGINS_MAP = {
|
|
33
33
|
'text-align': 'textAlign',
|
|
34
|
+
'bulleted-list': 'ul_list',
|
|
35
|
+
'numbered-list': 'ol_list',
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
export const DEFAULT_PLUGINS = ALL_PLUGINS.filter((plug) => !['responseArea', 'h3', 'blockquote'].includes(plug));
|
package/src/extensions/math.js
CHANGED
package/src/extensions/media.js
CHANGED
|
@@ -153,20 +153,23 @@ export default function MediaNodeView({ editor, node, updateAttributes, deleteNo
|
|
|
153
153
|
};
|
|
154
154
|
|
|
155
155
|
useEffect(() => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
156
|
+
// Only open dialog for newly inserted media without a src
|
|
157
|
+
if (!src) {
|
|
158
|
+
insertDialog({
|
|
159
|
+
...node.attrs,
|
|
160
|
+
options: options,
|
|
161
|
+
edit: true,
|
|
162
|
+
callback: (val, data) => {
|
|
163
|
+
if (val) {
|
|
164
|
+
updateAttributes(data);
|
|
165
|
+
} else {
|
|
166
|
+
deleteNode();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
editor.chain().focus().run();
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
170
173
|
}, []);
|
|
171
174
|
|
|
172
175
|
return (
|
|
@@ -5,6 +5,7 @@ import { Node, ReactNodeViewRenderer } from '@tiptap/react';
|
|
|
5
5
|
import ExplicitConstructedResponse from '../components/respArea/ExplicitConstructedResponse';
|
|
6
6
|
import DragInTheBlank from '../components/respArea/DragInTheBlank/DragInTheBlank';
|
|
7
7
|
import InlineDropdown from '../components/respArea/InlineDropdown';
|
|
8
|
+
import MathTemplated from '../components/respArea/MathTemplated';
|
|
8
9
|
|
|
9
10
|
const lastIndexMap = {};
|
|
10
11
|
|
|
@@ -187,14 +188,9 @@ export const ResponseAreaExtension = Extension.create({
|
|
|
187
188
|
// tr.setSelection(NodeSelection.create(tr.doc, usedPos))
|
|
188
189
|
|
|
189
190
|
// --- Cursor move behavior for certain types (Slate: moveFocusTo next text) ---
|
|
190
|
-
if (
|
|
191
|
-
['math_templated', 'inline_dropdown', 'drag_in_the_blank', 'explicit_constructed_response'].includes(
|
|
192
|
-
typeName,
|
|
193
|
-
)
|
|
194
|
-
) {
|
|
191
|
+
if (['math_templated', 'inline_dropdown', 'explicit_constructed_response'].includes(typeName)) {
|
|
195
192
|
tr.setSelection(NodeSelection.create(tr.doc, usedPos));
|
|
196
193
|
} else {
|
|
197
|
-
// Default: put cursor after inserted node
|
|
198
194
|
const after = usedPos + newInline.nodeSize;
|
|
199
195
|
tr.setSelection(selectionAfterPos(tr.doc, after));
|
|
200
196
|
}
|
|
@@ -213,7 +209,7 @@ export const ResponseAreaExtension = Extension.create({
|
|
|
213
209
|
const node = selection.$from.nodeAfter;
|
|
214
210
|
const nodePos = selection.from;
|
|
215
211
|
|
|
216
|
-
tr.setNodeMarkup(nodePos, undefined, { ...node
|
|
212
|
+
tr.setNodeMarkup(nodePos, undefined, { ...node?.attrs, updated: `${Date.now()}` });
|
|
217
213
|
tr.setSelection(NodeSelection.create(tr.doc, nodePos));
|
|
218
214
|
|
|
219
215
|
if (dispatch) {
|
|
@@ -305,7 +301,7 @@ export const MathTemplatedNode = Node.create({
|
|
|
305
301
|
];
|
|
306
302
|
},
|
|
307
303
|
addNodeView() {
|
|
308
|
-
return ReactNodeViewRenderer(() => <
|
|
304
|
+
return ReactNodeViewRenderer((props) => <MathTemplated {...{ ...props, options: this.options }} />);
|
|
309
305
|
},
|
|
310
306
|
});
|
|
311
307
|
|
|
@@ -68,7 +68,7 @@ const styles = (theme) => ({
|
|
|
68
68
|
background: 'var(--black)',
|
|
69
69
|
borderRadius: '0.5rem',
|
|
70
70
|
color: 'var(--white)',
|
|
71
|
-
fontFamily:
|
|
71
|
+
fontFamily: '\'JetBrainsMono\', monospace',
|
|
72
72
|
margin: '1.5rem 0',
|
|
73
73
|
padding: '0.75rem 1rem',
|
|
74
74
|
|
|
@@ -81,9 +81,10 @@ const styles = (theme) => ({
|
|
|
81
81
|
},
|
|
82
82
|
|
|
83
83
|
'& blockquote': {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
background: '#f9f9f9',
|
|
85
|
+
borderLeft: '5px solid #ccc',
|
|
86
|
+
margin: '1.5em 10px',
|
|
87
|
+
padding: '.5em 10px',
|
|
87
88
|
},
|
|
88
89
|
|
|
89
90
|
'& hr': {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const escapeHtml = (str) =>
|
|
2
|
+
String(str)
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"')
|
|
7
|
+
.replace(/'/g, ''');
|
|
8
|
+
|
|
9
|
+
export const normalizeInitialMarkup = (markup) => {
|
|
10
|
+
const trimmed = String(markup ?? '').trim();
|
|
11
|
+
if (!trimmed) return '<div></div>';
|
|
12
|
+
|
|
13
|
+
const looksLikeHtml = /<[^>]+>/.test(trimmed);
|
|
14
|
+
if (looksLikeHtml) return trimmed;
|
|
15
|
+
|
|
16
|
+
return `<div>${escapeHtml(trimmed)}</div>`;
|
|
17
|
+
};
|