@pie-lib/editable-html-tip-tap 1.1.0-next.6059 → 1.1.1-next.1
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 +44 -0
- package/lib/__tests__/EditableHtml.test.js +374 -0
- package/lib/__tests__/constants.test.js +28 -0
- package/lib/__tests__/extensions.test.js +214 -0
- package/lib/__tests__/index.test.js +246 -0
- package/lib/__tests__/size-utils.test.js +57 -0
- package/lib/__tests__/theme.test.js +17 -0
- package/lib/components/CharacterPicker.js +18 -0
- package/lib/components/CharacterPicker.js.map +1 -1
- package/lib/components/EditableHtml.js +22 -5
- package/lib/components/EditableHtml.js.map +1 -1
- package/lib/components/MenuBar.js +17 -0
- package/lib/components/MenuBar.js.map +1 -1
- package/lib/components/TiptapContainer.js +16 -0
- package/lib/components/TiptapContainer.js.map +1 -1
- package/lib/components/__tests__/AltDialog.test.js +201 -0
- package/lib/components/__tests__/CharacterPicker.test.js +313 -0
- package/lib/components/__tests__/CssIcon.test.js +58 -0
- package/lib/components/__tests__/DragInTheBlank.test.js +309 -0
- package/lib/components/__tests__/ExplicitConstructedResponse.test.js +263 -0
- package/lib/components/__tests__/ImageToolbar.test.js +195 -0
- package/lib/components/__tests__/InlineDropdown.test.js +297 -0
- package/lib/components/__tests__/InsertImageHandler.test.js +162 -0
- package/lib/components/__tests__/MediaDialog.test.js +435 -0
- package/lib/components/__tests__/MediaToolbar.test.js +126 -0
- package/lib/components/__tests__/MediaWrapper.test.js +96 -0
- package/lib/components/__tests__/MenuBar.test.js +459 -0
- package/lib/components/__tests__/RespArea.test.js +171 -0
- package/lib/components/__tests__/TableIcons.test.js +153 -0
- package/lib/components/__tests__/TextAlign.test.js +216 -0
- package/lib/components/__tests__/TiptapContainer.test.js +196 -0
- package/lib/components/__tests__/characterUtils.test.js +189 -0
- package/lib/components/__tests__/choice.test.js +213 -0
- package/lib/components/__tests__/custom-popper.test.js +108 -0
- package/lib/components/__tests__/done-button.test.js +72 -0
- package/lib/components/__tests__/toolbar-buttons.test.js +277 -0
- package/lib/components/characters/characterUtils.js +2 -0
- package/lib/components/characters/characterUtils.js.map +1 -1
- package/lib/components/characters/custom-popper.js +1 -0
- package/lib/components/characters/custom-popper.js.map +1 -1
- package/lib/components/common/done-button.js +1 -0
- package/lib/components/common/done-button.js.map +1 -1
- package/lib/components/common/toolbar-buttons.js +12 -0
- package/lib/components/common/toolbar-buttons.js.map +1 -1
- package/lib/components/icons/CssIcon.js +1 -0
- package/lib/components/icons/CssIcon.js.map +1 -1
- package/lib/components/icons/RespArea.js +10 -0
- package/lib/components/icons/RespArea.js.map +1 -1
- package/lib/components/icons/TableIcons.js +1 -0
- package/lib/components/icons/TableIcons.js.map +1 -1
- package/lib/components/icons/TextAlign.js +7 -0
- package/lib/components/icons/TextAlign.js.map +1 -1
- package/lib/components/image/AltDialog.js +5 -0
- package/lib/components/image/AltDialog.js.map +1 -1
- package/lib/components/image/ImageToolbar.js +13 -0
- package/lib/components/image/ImageToolbar.js.map +1 -1
- package/lib/components/image/InsertImageHandler.js +10 -0
- package/lib/components/image/InsertImageHandler.js.map +1 -1
- package/lib/components/media/MediaDialog.js +18 -0
- package/lib/components/media/MediaDialog.js.map +1 -1
- package/lib/components/media/MediaToolbar.js +2 -0
- package/lib/components/media/MediaToolbar.js.map +1 -1
- package/lib/components/media/MediaWrapper.js +11 -0
- package/lib/components/media/MediaWrapper.js.map +1 -1
- package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +10 -0
- package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +1 -1
- package/lib/components/respArea/DragInTheBlank/choice.js +8 -0
- package/lib/components/respArea/DragInTheBlank/choice.js.map +1 -1
- package/lib/components/respArea/ExplicitConstructedResponse.js +8 -0
- package/lib/components/respArea/ExplicitConstructedResponse.js.map +1 -1
- package/lib/components/respArea/InlineDropdown.js +7 -0
- package/lib/components/respArea/InlineDropdown.js.map +1 -1
- package/lib/components/respArea/ToolbarIcon.js +10 -0
- package/lib/components/respArea/ToolbarIcon.js.map +1 -1
- package/lib/constants.js +1 -0
- package/lib/constants.js.map +1 -1
- package/lib/extensions/__tests__/component.test.js +314 -0
- package/lib/extensions/__tests__/css.test.js +218 -0
- package/lib/extensions/__tests__/custom-toolbar-wrapper.test.js +185 -0
- package/lib/extensions/__tests__/extended-table.test.js +114 -0
- package/lib/extensions/__tests__/image.test.js +178 -0
- package/lib/extensions/__tests__/media.test.js +296 -0
- package/lib/extensions/__tests__/responseArea.test.js +332 -0
- package/lib/extensions/component.js +22 -2
- package/lib/extensions/css.js +11 -0
- package/lib/extensions/css.js.map +1 -1
- package/lib/extensions/custom-toolbar-wrapper.js +15 -0
- package/lib/extensions/custom-toolbar-wrapper.js.map +1 -1
- package/lib/extensions/extended-table.js +4 -0
- package/lib/extensions/extended-table.js.map +1 -1
- package/lib/extensions/image-component.js +314 -0
- package/lib/extensions/image-component.js.map +1 -0
- package/lib/extensions/image.js +13 -2
- package/lib/extensions/image.js.map +1 -1
- package/lib/extensions/index.js +12 -2
- package/lib/extensions/index.js.map +1 -1
- package/lib/extensions/math.js +16 -0
- package/lib/extensions/math.js.map +1 -1
- package/lib/extensions/media.js +15 -0
- package/lib/extensions/media.js.map +1 -1
- package/lib/extensions/responseArea.js +22 -0
- package/lib/extensions/responseArea.js.map +1 -1
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -1
- package/lib/styles/editorContainerStyles.js +1 -0
- package/lib/styles/editorContainerStyles.js.map +1 -1
- package/lib/theme.js +1 -0
- package/lib/theme.js.map +1 -1
- package/lib/utils/size.js +6 -0
- package/lib/utils/size.js.map +1 -1
- package/package.json +8 -8
- package/src/__tests__/EditableHtml.test.jsx +266 -0
- package/src/__tests__/constants.test.js +20 -0
- package/src/__tests__/extensions.test.js +208 -0
- package/src/__tests__/index.test.jsx +146 -0
- package/src/__tests__/size-utils.test.js +64 -0
- package/src/__tests__/theme.test.js +17 -0
- package/src/components/EditableHtml.jsx +8 -6
- package/src/components/__tests__/AltDialog.test.jsx +147 -0
- package/src/components/__tests__/CharacterPicker.test.jsx +195 -0
- package/src/components/__tests__/CssIcon.test.jsx +46 -0
- package/src/components/__tests__/DragInTheBlank.test.jsx +255 -0
- package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +161 -0
- package/src/components/__tests__/ImageToolbar.test.jsx +128 -0
- package/src/components/__tests__/InlineDropdown.test.jsx +187 -0
- package/src/components/__tests__/InsertImageHandler.test.js +154 -0
- package/src/components/__tests__/MediaDialog.test.jsx +293 -0
- package/src/components/__tests__/MediaToolbar.test.jsx +74 -0
- package/src/components/__tests__/MediaWrapper.test.jsx +81 -0
- package/src/components/__tests__/MenuBar.test.jsx +217 -0
- package/src/components/__tests__/RespArea.test.jsx +122 -0
- package/src/components/__tests__/TableIcons.test.jsx +149 -0
- package/src/components/__tests__/TextAlign.test.jsx +167 -0
- package/src/components/__tests__/TiptapContainer.test.jsx +138 -0
- package/src/components/__tests__/characterUtils.test.js +166 -0
- package/src/components/__tests__/choice.test.jsx +171 -0
- package/src/components/__tests__/custom-popper.test.jsx +82 -0
- package/src/components/__tests__/done-button.test.jsx +54 -0
- package/src/components/__tests__/toolbar-buttons.test.jsx +234 -0
- package/src/extensions/__tests__/css.test.js +196 -0
- package/src/extensions/__tests__/custom-toolbar-wrapper.test.jsx +180 -0
- package/src/extensions/__tests__/extended-table.test.js +107 -0
- package/src/extensions/__tests__/image-component.test.jsx +249 -0
- package/src/extensions/__tests__/image.test.js +136 -0
- package/src/extensions/__tests__/media.test.js +270 -0
- package/src/extensions/__tests__/responseArea.test.js +310 -0
- package/src/extensions/{component.jsx → image-component.jsx} +11 -1
- package/src/extensions/image.js +1 -1
- package/src/extensions/index.js +5 -1
- package/LICENSE.md +0 -5
- package/NEXT.CHANGELOG.json +0 -1
- package/lib/extensions/component.js.map +0 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { Media, insertDialog } from '../media';
|
|
2
|
+
|
|
3
|
+
jest.mock('@tiptap/core', () => ({
|
|
4
|
+
Node: { create: jest.fn((config) => config) },
|
|
5
|
+
mergeAttributes: jest.fn((...args) => Object.assign({}, ...args)),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
jest.mock('@tiptap/react', () => ({
|
|
9
|
+
NodeViewWrapper: ({ children }) => <div data-testid="node-view-wrapper">{children}</div>,
|
|
10
|
+
ReactNodeViewRenderer: jest.fn((component) => component),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock('react-dom', () => ({
|
|
14
|
+
render: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.mock('../../components/media/MediaDialog', () => ({
|
|
18
|
+
__esModule: true,
|
|
19
|
+
default: jest.fn(() => <div data-testid="media-dialog" />),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock('../../components/media/MediaToolbar', () => ({
|
|
23
|
+
__esModule: true,
|
|
24
|
+
default: jest.fn(() => <div data-testid="media-toolbar" />),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
describe('Media', () => {
|
|
28
|
+
describe('configuration', () => {
|
|
29
|
+
it('has correct name', () => {
|
|
30
|
+
expect(Media.name).toBe('media');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('is inline', () => {
|
|
34
|
+
expect(Media.inline).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('is in inline group', () => {
|
|
38
|
+
expect(Media.group).toBe('inline');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('is atomic', () => {
|
|
42
|
+
expect(Media.atom).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('addAttributes', () => {
|
|
47
|
+
it('returns all media attributes with defaults', () => {
|
|
48
|
+
const attributes = Media.addAttributes();
|
|
49
|
+
|
|
50
|
+
expect(attributes).toHaveProperty('type');
|
|
51
|
+
expect(attributes).toHaveProperty('src');
|
|
52
|
+
expect(attributes).toHaveProperty('width');
|
|
53
|
+
expect(attributes).toHaveProperty('height');
|
|
54
|
+
expect(attributes).toHaveProperty('title');
|
|
55
|
+
expect(attributes).toHaveProperty('starts');
|
|
56
|
+
expect(attributes).toHaveProperty('ends');
|
|
57
|
+
expect(attributes).toHaveProperty('editing');
|
|
58
|
+
expect(attributes).toHaveProperty('tag');
|
|
59
|
+
expect(attributes).toHaveProperty('url');
|
|
60
|
+
|
|
61
|
+
expect(attributes.type.default).toBe('video');
|
|
62
|
+
expect(attributes.src.default).toBeNull();
|
|
63
|
+
expect(attributes.tag.default).toBe('iframe');
|
|
64
|
+
expect(attributes.editing.default).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('parseHTML', () => {
|
|
69
|
+
it('returns parsing rules for iframe and audio', () => {
|
|
70
|
+
const rules = Media.parseHTML();
|
|
71
|
+
|
|
72
|
+
expect(Array.isArray(rules)).toBe(true);
|
|
73
|
+
expect(rules).toHaveLength(2);
|
|
74
|
+
expect(rules[0].tag).toBe('iframe[data-type="video"]');
|
|
75
|
+
expect(rules[1].tag).toBe('audio');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('parses video iframe attributes', () => {
|
|
79
|
+
const rules = Media.parseHTML();
|
|
80
|
+
const mockEl = {
|
|
81
|
+
getAttribute: jest.fn((attr) => {
|
|
82
|
+
const attrs = {
|
|
83
|
+
src: 'https://video.com/embed',
|
|
84
|
+
width: '640',
|
|
85
|
+
height: '480',
|
|
86
|
+
};
|
|
87
|
+
return attrs[attr] || null;
|
|
88
|
+
}),
|
|
89
|
+
dataset: {
|
|
90
|
+
title: 'Test Video',
|
|
91
|
+
starts: '10',
|
|
92
|
+
ends: '60',
|
|
93
|
+
url: 'https://video.com/watch',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const result = rules[0].getAttrs(mockEl);
|
|
98
|
+
|
|
99
|
+
expect(result.type).toBe('video');
|
|
100
|
+
expect(result.tag).toBe('iframe');
|
|
101
|
+
expect(result.src).toBe('https://video.com/embed');
|
|
102
|
+
expect(result.width).toBe('640');
|
|
103
|
+
expect(result.height).toBe('480');
|
|
104
|
+
expect(result.title).toBe('Test Video');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('parses audio element attributes', () => {
|
|
108
|
+
const rules = Media.parseHTML();
|
|
109
|
+
const mockEl = {
|
|
110
|
+
querySelector: jest.fn(() => ({
|
|
111
|
+
getAttribute: jest.fn(() => 'https://audio.com/file.mp3'),
|
|
112
|
+
})),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const result = rules[1].getAttrs(mockEl);
|
|
116
|
+
|
|
117
|
+
expect(result.type).toBe('audio');
|
|
118
|
+
expect(result.tag).toBe('audio');
|
|
119
|
+
expect(result.src).toBe('https://audio.com/file.mp3');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('renderHTML', () => {
|
|
124
|
+
it('renders audio element for audio type', () => {
|
|
125
|
+
const HTMLAttributes = {
|
|
126
|
+
tag: 'audio',
|
|
127
|
+
src: 'test.mp3',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const result = Media.renderHTML({ HTMLAttributes });
|
|
131
|
+
|
|
132
|
+
expect(result[0]).toBe('audio');
|
|
133
|
+
expect(result[1]).toEqual({ controls: 'controls', controlsList: 'nodownload' });
|
|
134
|
+
expect(result[2][0]).toBe('source');
|
|
135
|
+
expect(result[2][1]).toEqual({ src: 'test.mp3', type: 'audio/mp3' });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('renders iframe for video type', () => {
|
|
139
|
+
const HTMLAttributes = {
|
|
140
|
+
tag: 'iframe',
|
|
141
|
+
src: 'https://video.com/embed',
|
|
142
|
+
width: '640',
|
|
143
|
+
height: '480',
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const result = Media.renderHTML({ HTMLAttributes });
|
|
147
|
+
|
|
148
|
+
expect(result[0]).toBe('iframe');
|
|
149
|
+
expect(result[1]).toHaveProperty('data-type', 'video');
|
|
150
|
+
expect(result[1]).toHaveProperty('frameborder', '0');
|
|
151
|
+
expect(result[1]).toHaveProperty('src', 'https://video.com/embed');
|
|
152
|
+
expect(result[1]).toHaveProperty('width', '640');
|
|
153
|
+
expect(result[1]).toHaveProperty('height', '480');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('renders iframe without dimensions if not provided', () => {
|
|
157
|
+
const HTMLAttributes = {
|
|
158
|
+
tag: 'iframe',
|
|
159
|
+
src: 'https://video.com/embed',
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const result = Media.renderHTML({ HTMLAttributes });
|
|
163
|
+
|
|
164
|
+
expect(result[1]).not.toHaveProperty('width');
|
|
165
|
+
expect(result[1]).not.toHaveProperty('height');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('addCommands', () => {
|
|
170
|
+
it('returns insertMedia and updateMedia commands', () => {
|
|
171
|
+
const commands = Media.addCommands();
|
|
172
|
+
|
|
173
|
+
expect(commands).toHaveProperty('insertMedia');
|
|
174
|
+
expect(commands).toHaveProperty('updateMedia');
|
|
175
|
+
expect(typeof commands.insertMedia).toBe('function');
|
|
176
|
+
expect(typeof commands.updateMedia).toBe('function');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('insertMedia inserts content with attributes', () => {
|
|
180
|
+
const context = { name: 'media' };
|
|
181
|
+
const commands = Media.addCommands.call(context);
|
|
182
|
+
const mockCommands = {
|
|
183
|
+
insertContent: jest.fn(() => true),
|
|
184
|
+
};
|
|
185
|
+
const attrs = { type: 'video', src: 'test.mp4' };
|
|
186
|
+
|
|
187
|
+
const result = commands.insertMedia(attrs)({ commands: mockCommands });
|
|
188
|
+
|
|
189
|
+
expect(mockCommands.insertContent).toHaveBeenCalledWith({
|
|
190
|
+
type: 'media',
|
|
191
|
+
attrs,
|
|
192
|
+
});
|
|
193
|
+
expect(result).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('updateMedia updates attributes', () => {
|
|
197
|
+
const context = { name: 'media' };
|
|
198
|
+
const commands = Media.addCommands.call(context);
|
|
199
|
+
const mockCommands = {
|
|
200
|
+
updateAttributes: jest.fn(() => true),
|
|
201
|
+
};
|
|
202
|
+
const attrs = { width: '800' };
|
|
203
|
+
|
|
204
|
+
const result = commands.updateMedia(attrs)({ commands: mockCommands });
|
|
205
|
+
|
|
206
|
+
expect(mockCommands.updateAttributes).toHaveBeenCalledWith('media', attrs);
|
|
207
|
+
expect(result).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('addNodeView', () => {
|
|
212
|
+
it('returns ReactNodeViewRenderer result', () => {
|
|
213
|
+
const result = Media.addNodeView();
|
|
214
|
+
|
|
215
|
+
expect(result).toBeDefined();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('insertDialog', () => {
|
|
221
|
+
beforeEach(() => {
|
|
222
|
+
document.body.innerHTML = '';
|
|
223
|
+
document.body.style.overflow = 'auto';
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('creates dialog element', () => {
|
|
227
|
+
const mockCallback = jest.fn();
|
|
228
|
+
const props = {
|
|
229
|
+
type: 'video',
|
|
230
|
+
callback: mockCallback,
|
|
231
|
+
options: {},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
insertDialog(props);
|
|
235
|
+
|
|
236
|
+
const dialog = document.querySelector('.insert-media-dialog');
|
|
237
|
+
expect(dialog).toBeTruthy();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('sets body overflow to hidden', () => {
|
|
241
|
+
const mockCallback = jest.fn();
|
|
242
|
+
const props = {
|
|
243
|
+
type: 'video',
|
|
244
|
+
callback: mockCallback,
|
|
245
|
+
options: {},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
insertDialog(props);
|
|
249
|
+
|
|
250
|
+
expect(document.body.style.overflow).toBe('hidden');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('removes previous dialogs', () => {
|
|
254
|
+
const existingDialog = document.createElement('div');
|
|
255
|
+
existingDialog.className = 'insert-media-dialog';
|
|
256
|
+
document.body.appendChild(existingDialog);
|
|
257
|
+
|
|
258
|
+
const mockCallback = jest.fn();
|
|
259
|
+
const props = {
|
|
260
|
+
type: 'video',
|
|
261
|
+
callback: mockCallback,
|
|
262
|
+
options: {},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
insertDialog(props);
|
|
266
|
+
|
|
267
|
+
const dialogs = document.querySelectorAll('.insert-media-dialog');
|
|
268
|
+
expect(dialogs).toHaveLength(1);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ResponseAreaExtension,
|
|
3
|
+
ExplicitConstructedResponseNode,
|
|
4
|
+
DragInTheBlankNode,
|
|
5
|
+
InlineDropdownNode,
|
|
6
|
+
} from '../responseArea';
|
|
7
|
+
|
|
8
|
+
jest.mock('@tiptap/core', () => ({
|
|
9
|
+
Extension: { create: jest.fn((config) => config) },
|
|
10
|
+
Node: { create: jest.fn((config) => config) },
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock('@tiptap/react', () => ({
|
|
14
|
+
Node: { create: jest.fn((config) => config) },
|
|
15
|
+
ReactNodeViewRenderer: jest.fn((component) => component),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock('prosemirror-state', () => ({
|
|
19
|
+
Plugin: jest.fn(function (config) {
|
|
20
|
+
return config;
|
|
21
|
+
}),
|
|
22
|
+
PluginKey: jest.fn(function (key) {
|
|
23
|
+
this.key = key;
|
|
24
|
+
}),
|
|
25
|
+
TextSelection: {
|
|
26
|
+
near: jest.fn((pos, dir) => ({ type: 'text', pos, dir })),
|
|
27
|
+
create: jest.fn((doc, pos) => ({ type: 'text', pos })),
|
|
28
|
+
},
|
|
29
|
+
NodeSelection: {
|
|
30
|
+
create: jest.fn((doc, pos) => ({ type: 'node', pos })),
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
jest.mock('../../components/respArea/ExplicitConstructedResponse', () => ({
|
|
35
|
+
__esModule: true,
|
|
36
|
+
default: jest.fn(() => <div data-testid="explicit-constructed-response" />),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
jest.mock('../../components/respArea/DragInTheBlank/DragInTheBlank', () => ({
|
|
40
|
+
__esModule: true,
|
|
41
|
+
default: jest.fn(() => <div data-testid="drag-in-the-blank" />),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
jest.mock('../../components/respArea/InlineDropdown', () => ({
|
|
45
|
+
__esModule: true,
|
|
46
|
+
default: jest.fn(() => <div data-testid="inline-dropdown" />),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
describe('ResponseAreaExtension', () => {
|
|
50
|
+
describe('configuration', () => {
|
|
51
|
+
it('has correct name', () => {
|
|
52
|
+
expect(ResponseAreaExtension.name).toBe('responseArea');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('addOptions', () => {
|
|
57
|
+
it('returns default options', () => {
|
|
58
|
+
const options = ResponseAreaExtension.addOptions();
|
|
59
|
+
|
|
60
|
+
expect(options).toHaveProperty('maxResponseAreas', null);
|
|
61
|
+
expect(options).toHaveProperty('error', null);
|
|
62
|
+
expect(options).toHaveProperty('options', null);
|
|
63
|
+
expect(options).toHaveProperty('respAreaToolbar', null);
|
|
64
|
+
expect(options).toHaveProperty('onHandleAreaChange', null);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('addProseMirrorPlugins', () => {
|
|
69
|
+
it('returns empty array when no type specified', () => {
|
|
70
|
+
const context = {
|
|
71
|
+
options: {},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const plugins = ResponseAreaExtension.addProseMirrorPlugins.call(context);
|
|
75
|
+
|
|
76
|
+
expect(Array.isArray(plugins)).toBe(true);
|
|
77
|
+
expect(plugins).toHaveLength(0);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('returns plugin array when type is specified', () => {
|
|
81
|
+
const context = {
|
|
82
|
+
options: {
|
|
83
|
+
type: 'explicit-constructed-response',
|
|
84
|
+
maxResponseAreas: 5,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const plugins = ResponseAreaExtension.addProseMirrorPlugins.call(context);
|
|
89
|
+
|
|
90
|
+
expect(Array.isArray(plugins)).toBe(true);
|
|
91
|
+
expect(plugins).toHaveLength(1);
|
|
92
|
+
expect(plugins[0]).toHaveProperty('key');
|
|
93
|
+
expect(plugins[0]).toHaveProperty('view');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('addCommands', () => {
|
|
98
|
+
it('returns insertResponseArea command', () => {
|
|
99
|
+
const commands = ResponseAreaExtension.addCommands();
|
|
100
|
+
|
|
101
|
+
expect(commands).toHaveProperty('insertResponseArea');
|
|
102
|
+
expect(typeof commands.insertResponseArea).toBe('function');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('ExplicitConstructedResponseNode', () => {
|
|
108
|
+
describe('configuration', () => {
|
|
109
|
+
it('has correct name', () => {
|
|
110
|
+
expect(ExplicitConstructedResponseNode.name).toBe('explicit_constructed_response');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('is inline', () => {
|
|
114
|
+
expect(ExplicitConstructedResponseNode.inline).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('is in inline group', () => {
|
|
118
|
+
expect(ExplicitConstructedResponseNode.group).toBe('inline');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('is atomic', () => {
|
|
122
|
+
expect(ExplicitConstructedResponseNode.atom).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('addAttributes', () => {
|
|
127
|
+
it('returns required attributes', () => {
|
|
128
|
+
const attributes = ExplicitConstructedResponseNode.addAttributes();
|
|
129
|
+
|
|
130
|
+
expect(attributes).toHaveProperty('index');
|
|
131
|
+
expect(attributes).toHaveProperty('value');
|
|
132
|
+
expect(attributes).toHaveProperty('updated');
|
|
133
|
+
|
|
134
|
+
expect(attributes.index).toEqual({ default: null });
|
|
135
|
+
expect(attributes.value).toEqual({ default: '' });
|
|
136
|
+
expect(attributes.updated).toEqual({ default: '' });
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('parseHTML', () => {
|
|
141
|
+
it('returns parsing rules', () => {
|
|
142
|
+
const rules = ExplicitConstructedResponseNode.parseHTML();
|
|
143
|
+
|
|
144
|
+
expect(Array.isArray(rules)).toBe(true);
|
|
145
|
+
expect(rules).toHaveLength(1);
|
|
146
|
+
expect(rules[0]).toHaveProperty('tag');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('renderHTML', () => {
|
|
151
|
+
it('renders span with attributes', () => {
|
|
152
|
+
const result = ExplicitConstructedResponseNode.renderHTML({
|
|
153
|
+
HTMLAttributes: {
|
|
154
|
+
index: '1',
|
|
155
|
+
id: '1',
|
|
156
|
+
value: 'test',
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(result[0]).toBe('span');
|
|
161
|
+
expect(result[1]).toHaveProperty('data-type', 'explicit_constructed_response');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('addNodeView', () => {
|
|
166
|
+
it('returns ReactNodeViewRenderer result', () => {
|
|
167
|
+
const result = ExplicitConstructedResponseNode.addNodeView();
|
|
168
|
+
|
|
169
|
+
expect(result).toBeDefined();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('DragInTheBlankNode', () => {
|
|
175
|
+
describe('configuration', () => {
|
|
176
|
+
it('has correct name', () => {
|
|
177
|
+
expect(DragInTheBlankNode.name).toBe('drag_in_the_blank');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('is inline', () => {
|
|
181
|
+
expect(DragInTheBlankNode.inline).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('is in inline group', () => {
|
|
185
|
+
expect(DragInTheBlankNode.group).toBe('inline');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('is atomic', () => {
|
|
189
|
+
expect(DragInTheBlankNode.atom).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('addAttributes', () => {
|
|
194
|
+
it('returns required attributes', () => {
|
|
195
|
+
const attributes = DragInTheBlankNode.addAttributes();
|
|
196
|
+
|
|
197
|
+
expect(attributes).toHaveProperty('index');
|
|
198
|
+
expect(attributes).toHaveProperty('id');
|
|
199
|
+
expect(attributes).toHaveProperty('value');
|
|
200
|
+
expect(attributes).toHaveProperty('inTable');
|
|
201
|
+
expect(attributes).toHaveProperty('updated');
|
|
202
|
+
|
|
203
|
+
expect(attributes.index).toEqual({ default: null });
|
|
204
|
+
expect(attributes.id).toEqual({ default: null });
|
|
205
|
+
expect(attributes.value).toEqual({ default: '' });
|
|
206
|
+
expect(attributes.inTable).toEqual({ default: null });
|
|
207
|
+
expect(attributes.updated).toEqual({ default: '' });
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('parseHTML', () => {
|
|
212
|
+
it('returns parsing rules', () => {
|
|
213
|
+
const rules = DragInTheBlankNode.parseHTML();
|
|
214
|
+
|
|
215
|
+
expect(Array.isArray(rules)).toBe(true);
|
|
216
|
+
expect(rules).toHaveLength(1);
|
|
217
|
+
expect(rules[0]).toHaveProperty('tag');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('renderHTML', () => {
|
|
222
|
+
it('renders span with attributes', () => {
|
|
223
|
+
const result = DragInTheBlankNode.renderHTML({
|
|
224
|
+
HTMLAttributes: {
|
|
225
|
+
index: '1',
|
|
226
|
+
id: '1',
|
|
227
|
+
value: 'test',
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(result[0]).toBe('span');
|
|
232
|
+
expect(result[1]).toHaveProperty('data-type', 'drag_in_the_blank');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('addNodeView', () => {
|
|
237
|
+
it('returns ReactNodeViewRenderer result', () => {
|
|
238
|
+
const result = DragInTheBlankNode.addNodeView();
|
|
239
|
+
|
|
240
|
+
expect(result).toBeDefined();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('InlineDropdownNode', () => {
|
|
246
|
+
describe('configuration', () => {
|
|
247
|
+
it('has correct name', () => {
|
|
248
|
+
expect(InlineDropdownNode.name).toBe('inline_dropdown');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('is inline', () => {
|
|
252
|
+
expect(InlineDropdownNode.inline).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('is in inline group', () => {
|
|
256
|
+
expect(InlineDropdownNode.group).toBe('inline');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('is atomic', () => {
|
|
260
|
+
expect(InlineDropdownNode.atom).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('addAttributes', () => {
|
|
265
|
+
it('returns required attributes', () => {
|
|
266
|
+
const attributes = InlineDropdownNode.addAttributes();
|
|
267
|
+
|
|
268
|
+
expect(attributes).toHaveProperty('index');
|
|
269
|
+
expect(attributes).toHaveProperty('value');
|
|
270
|
+
expect(attributes).toHaveProperty('updated');
|
|
271
|
+
|
|
272
|
+
expect(attributes.index).toEqual({ default: null });
|
|
273
|
+
expect(attributes.value).toEqual({ default: '' });
|
|
274
|
+
expect(attributes.updated).toEqual({ default: '' });
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('parseHTML', () => {
|
|
279
|
+
it('returns parsing rules', () => {
|
|
280
|
+
const rules = InlineDropdownNode.parseHTML();
|
|
281
|
+
|
|
282
|
+
expect(Array.isArray(rules)).toBe(true);
|
|
283
|
+
expect(rules).toHaveLength(1);
|
|
284
|
+
expect(rules[0]).toHaveProperty('tag');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('renderHTML', () => {
|
|
289
|
+
it('renders span with attributes', () => {
|
|
290
|
+
const result = InlineDropdownNode.renderHTML({
|
|
291
|
+
HTMLAttributes: {
|
|
292
|
+
index: '1',
|
|
293
|
+
id: '1',
|
|
294
|
+
value: 'test',
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(result[0]).toBe('span');
|
|
299
|
+
expect(result[1]).toHaveProperty('data-type', 'inline_dropdown');
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('addNodeView', () => {
|
|
304
|
+
it('returns ReactNodeViewRenderer result', () => {
|
|
305
|
+
const result = InlineDropdownNode.addNodeView();
|
|
306
|
+
|
|
307
|
+
expect(result).toBeDefined();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|
|
@@ -80,6 +80,7 @@ function ImageComponent(props) {
|
|
|
80
80
|
|
|
81
81
|
const [showToolbar, setShowToolbar] = useState(false);
|
|
82
82
|
|
|
83
|
+
const latestNodeRef = useRef(node);
|
|
83
84
|
const imgRef = useRef(null);
|
|
84
85
|
const resizeRef = useRef(null);
|
|
85
86
|
const toolbarRef = useRef(null);
|
|
@@ -102,6 +103,11 @@ function ImageComponent(props) {
|
|
|
102
103
|
}
|
|
103
104
|
}, [editor, node.attrs, getPercentFromWidth]);
|
|
104
105
|
|
|
106
|
+
// keep ref in sync with latest node
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
latestNodeRef.current = node;
|
|
109
|
+
}, [node]);
|
|
110
|
+
|
|
105
111
|
useEffect(() => {
|
|
106
112
|
const { selection } = editor.state;
|
|
107
113
|
const onlyThisNodeSelected = selection.from + node.nodeSize === selection.to;
|
|
@@ -124,7 +130,11 @@ function ImageComponent(props) {
|
|
|
124
130
|
resizeHandle.addEventListener('mousedown', initResize, false);
|
|
125
131
|
}
|
|
126
132
|
return () => {
|
|
127
|
-
if (resizeHandle)
|
|
133
|
+
if (resizeHandle) {
|
|
134
|
+
resizeHandle.removeEventListener('mousedown', initResize, false);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
options.imageHandling.onDelete(latestNodeRef.current);
|
|
128
138
|
};
|
|
129
139
|
}, []);
|
|
130
140
|
|
package/src/extensions/image.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Node, mergeAttributes } from '@tiptap/core';
|
|
2
2
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import ImageComponent from './component';
|
|
4
|
+
import ImageComponent from './image-component';
|
|
5
5
|
|
|
6
6
|
export const ImageUploadNode = Node.create({
|
|
7
7
|
name: 'imageUploadNode',
|
package/src/extensions/index.js
CHANGED
|
@@ -30,6 +30,10 @@ export const ALL_PLUGINS = [
|
|
|
30
30
|
'subscript',
|
|
31
31
|
];
|
|
32
32
|
|
|
33
|
+
export const PLUGINS_MAP = {
|
|
34
|
+
'text-align': 'textAlign',
|
|
35
|
+
};
|
|
36
|
+
|
|
33
37
|
export const DEFAULT_PLUGINS = ALL_PLUGINS.filter((plug) => !['responseArea', 'h3', 'blockquote'].includes(plug));
|
|
34
38
|
|
|
35
39
|
export const buildExtensions = (activeExtensions, customExtensions, opts) => {
|
|
@@ -39,7 +43,7 @@ export const buildExtensions = (activeExtensions, customExtensions, opts) => {
|
|
|
39
43
|
|
|
40
44
|
const addIf = (key, shouldAdd = true) => activeExtensions.includes(key) && shouldAdd && key;
|
|
41
45
|
|
|
42
|
-
const imagePlugin = opts.image && opts.image.
|
|
46
|
+
const imagePlugin = opts.image && opts.image.delete;
|
|
43
47
|
const mathPlugin = opts.math;
|
|
44
48
|
const respAreaPlugin = opts.responseArea && opts.responseArea.type;
|
|
45
49
|
const cssPlugin = !isEmpty(opts.extraCSSRules);
|
package/LICENSE.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
Copyright 2019 CoreSpring Inc
|
|
2
|
-
|
|
3
|
-
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
4
|
-
|
|
5
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/NEXT.CHANGELOG.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[]
|