@pie-lib/editable-html-tip-tap 1.2.0-next.2 → 1.2.0-next.21

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 (126) hide show
  1. package/CHANGELOG.md +115 -21
  2. package/LICENSE.md +5 -0
  3. package/lib/components/CharacterPicker.js +2 -1
  4. package/lib/components/CharacterPicker.js.map +1 -1
  5. package/lib/components/EditableHtml.js +34 -17
  6. package/lib/components/EditableHtml.js.map +1 -1
  7. package/lib/components/MenuBar.js +77 -45
  8. package/lib/components/MenuBar.js.map +1 -1
  9. package/lib/components/TiptapContainer.js +15 -9
  10. package/lib/components/TiptapContainer.js.map +1 -1
  11. package/lib/components/characters/characterUtils.js +1 -1
  12. package/lib/components/characters/custom-popper.js +1 -1
  13. package/lib/components/common/done-button.js +1 -1
  14. package/lib/components/common/toolbar-buttons.js +1 -1
  15. package/lib/components/icons/CssIcon.js +1 -1
  16. package/lib/components/icons/RespArea.js +1 -1
  17. package/lib/components/icons/TableIcons.js +1 -1
  18. package/lib/components/icons/TextAlign.js +3 -3
  19. package/lib/components/icons/TextAlign.js.map +1 -1
  20. package/lib/components/image/AltDialog.js +1 -1
  21. package/lib/components/image/ImageToolbar.js +1 -1
  22. package/lib/components/image/InsertImageHandler.js +1 -1
  23. package/lib/components/media/MediaDialog.js +1 -1
  24. package/lib/components/media/MediaToolbar.js +1 -1
  25. package/lib/components/media/MediaWrapper.js +1 -1
  26. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +7 -2
  27. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +1 -1
  28. package/lib/components/respArea/DragInTheBlank/choice.js +16 -8
  29. package/lib/components/respArea/DragInTheBlank/choice.js.map +1 -1
  30. package/lib/components/respArea/ExplicitConstructedResponse.js +2 -2
  31. package/lib/components/respArea/ExplicitConstructedResponse.js.map +1 -1
  32. package/lib/components/respArea/InlineDropdown.js +11 -4
  33. package/lib/components/respArea/InlineDropdown.js.map +1 -1
  34. package/lib/components/respArea/MathTemplated.js +130 -0
  35. package/lib/components/respArea/MathTemplated.js.map +1 -0
  36. package/lib/components/respArea/ToolbarIcon.js +1 -1
  37. package/lib/constants.js +1 -1
  38. package/lib/extensions/css.js +1 -1
  39. package/lib/extensions/custom-toolbar-wrapper.js +1 -1
  40. package/lib/extensions/div-node.js +39 -0
  41. package/lib/extensions/div-node.js.map +1 -0
  42. package/lib/extensions/extended-table.js +1 -1
  43. package/lib/extensions/image-component.js +1 -1
  44. package/lib/extensions/image-component.js.map +1 -1
  45. package/lib/extensions/image.js +1 -1
  46. package/lib/extensions/index.js +4 -2
  47. package/lib/extensions/index.js.map +1 -1
  48. package/lib/extensions/math.js +7 -4
  49. package/lib/extensions/math.js.map +1 -1
  50. package/lib/extensions/media.js +15 -12
  51. package/lib/extensions/media.js.map +1 -1
  52. package/lib/extensions/responseArea.js +13 -10
  53. package/lib/extensions/responseArea.js.map +1 -1
  54. package/lib/index.js +1 -1
  55. package/lib/styles/editorContainerStyles.js +6 -5
  56. package/lib/styles/editorContainerStyles.js.map +1 -1
  57. package/lib/theme.js +1 -1
  58. package/lib/utils/helper.js +17 -0
  59. package/lib/utils/helper.js.map +1 -0
  60. package/lib/utils/size.js +1 -1
  61. package/package.json +11 -11
  62. package/src/__tests__/EditableHtml.test.jsx +41 -1
  63. package/src/__tests__/index.test.jsx +4 -1
  64. package/src/components/CharacterPicker.jsx +1 -0
  65. package/src/components/EditableHtml.jsx +40 -16
  66. package/src/components/MenuBar.jsx +66 -25
  67. package/src/components/TiptapContainer.jsx +10 -7
  68. package/src/components/__tests__/CharacterPicker.test.jsx +22 -0
  69. package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +1 -1
  70. package/src/components/__tests__/InlineDropdown.test.jsx +150 -1
  71. package/src/components/__tests__/MenuBar.test.jsx +32 -0
  72. package/src/components/icons/TextAlign.jsx +1 -1
  73. package/src/components/respArea/DragInTheBlank/DragInTheBlank.jsx +6 -1
  74. package/src/components/respArea/DragInTheBlank/choice.jsx +32 -4
  75. package/src/components/respArea/ExplicitConstructedResponse.jsx +1 -1
  76. package/src/components/respArea/InlineDropdown.jsx +12 -2
  77. package/src/components/respArea/MathTemplated.jsx +124 -0
  78. package/src/components/respArea/__tests__/MathTemplated.test.jsx +210 -0
  79. package/src/extensions/__tests__/divNode.test.js +87 -0
  80. package/src/extensions/__tests__/math.test.js +327 -0
  81. package/src/extensions/__tests__/media-node-view.test.jsx +296 -0
  82. package/src/extensions/__tests__/media.test.js +1 -0
  83. package/src/extensions/__tests__/responseArea.test.js +157 -0
  84. package/src/extensions/div-node.js +36 -0
  85. package/src/extensions/index.js +2 -0
  86. package/src/extensions/math.js +6 -3
  87. package/src/extensions/media.js +17 -14
  88. package/src/extensions/responseArea.js +4 -8
  89. package/src/styles/editorContainerStyles.js +5 -4
  90. package/src/utils/helper.js +17 -0
  91. package/lib/__tests__/EditableHtml.test.js +0 -377
  92. package/lib/__tests__/constants.test.js +0 -21
  93. package/lib/__tests__/extensions.test.js +0 -209
  94. package/lib/__tests__/index.test.js +0 -235
  95. package/lib/__tests__/size-utils.test.js +0 -57
  96. package/lib/__tests__/theme.test.js +0 -17
  97. package/lib/components/__tests__/AltDialog.test.js +0 -201
  98. package/lib/components/__tests__/CharacterPicker.test.js +0 -305
  99. package/lib/components/__tests__/CssIcon.test.js +0 -58
  100. package/lib/components/__tests__/DragInTheBlank.test.js +0 -295
  101. package/lib/components/__tests__/ExplicitConstructedResponse.test.js +0 -253
  102. package/lib/components/__tests__/ImageToolbar.test.js +0 -185
  103. package/lib/components/__tests__/InlineDropdown.test.js +0 -287
  104. package/lib/components/__tests__/InsertImageHandler.test.js +0 -162
  105. package/lib/components/__tests__/MediaDialog.test.js +0 -433
  106. package/lib/components/__tests__/MediaToolbar.test.js +0 -126
  107. package/lib/components/__tests__/MediaWrapper.test.js +0 -96
  108. package/lib/components/__tests__/MenuBar.test.js +0 -459
  109. package/lib/components/__tests__/RespArea.test.js +0 -171
  110. package/lib/components/__tests__/TableIcons.test.js +0 -153
  111. package/lib/components/__tests__/TextAlign.test.js +0 -209
  112. package/lib/components/__tests__/TiptapContainer.test.js +0 -196
  113. package/lib/components/__tests__/characterUtils.test.js +0 -178
  114. package/lib/components/__tests__/choice.test.js +0 -213
  115. package/lib/components/__tests__/custom-popper.test.js +0 -108
  116. package/lib/components/__tests__/done-button.test.js +0 -72
  117. package/lib/components/__tests__/toolbar-buttons.test.js +0 -277
  118. package/lib/extensions/__tests__/component.test.js +0 -314
  119. package/lib/extensions/__tests__/css.test.js +0 -214
  120. package/lib/extensions/__tests__/custom-toolbar-wrapper.test.js +0 -175
  121. package/lib/extensions/__tests__/extended-table.test.js +0 -92
  122. package/lib/extensions/__tests__/image-component.test.js +0 -305
  123. package/lib/extensions/__tests__/image.test.js +0 -164
  124. package/lib/extensions/__tests__/media.test.js +0 -292
  125. package/lib/extensions/__tests__/responseArea.test.js +0 -330
  126. 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
+ });
@@ -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));
@@ -209,12 +209,14 @@ export const MathNodeView = (props) => {
209
209
  };
210
210
 
211
211
  if (showToolbar) {
212
- document.addEventListener('mousedown', handleClickOutside);
212
+ // Use `click` (not `mousedown`) so interacting with browser UI like the scrollbar
213
+ // doesn't automatically dismiss the math toolbar.
214
+ document.addEventListener('click', handleClickOutside);
213
215
  } else {
214
- document.removeEventListener('mousedown', handleClickOutside);
216
+ document.removeEventListener('click', handleClickOutside);
215
217
  }
216
218
 
217
- return () => document.removeEventListener('mousedown', handleClickOutside);
219
+ return () => document.removeEventListener('click', handleClickOutside);
218
220
  }, [editor, showToolbar]);
219
221
 
220
222
  const handleChange = (newLatex) => {
@@ -253,6 +255,7 @@ export const MathNodeView = (props) => {
253
255
  ReactDOM.createPortal(
254
256
  <div
255
257
  ref={toolbarRef}
258
+ data-toolbar-for={editor.instanceId}
256
259
  style={{
257
260
  position: 'absolute',
258
261
  top: `${position.top}px`,
@@ -153,20 +153,23 @@ export default function MediaNodeView({ editor, node, updateAttributes, deleteNo
153
153
  };
154
154
 
155
155
  useEffect(() => {
156
- insertDialog({
157
- ...node.attrs,
158
- options: options,
159
- edit: true,
160
- callback: (val, data) => {
161
- if (val) {
162
- updateAttributes(data);
163
- } else {
164
- deleteNode();
165
- }
166
-
167
- editor.chain().focus().run();
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.attrs, updated: `${Date.now()}` });
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(() => <div></div>);
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: "'JetBrainsMono', monospace",
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
- borderLeft: '3px solid var(--gray-3)',
85
- margin: '1.5rem 0',
86
- paddingLeft: '1rem',
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, '&amp;')
4
+ .replace(/</g, '&lt;')
5
+ .replace(/>/g, '&gt;')
6
+ .replace(/"/g, '&quot;')
7
+ .replace(/'/g, '&#39;');
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
+ };