@licium/editor-plugin-details 1.0.1 → 1.0.2

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.
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * TOAST UI Editor : Text Align Plugin
3
- * @version 1.0.0 | Sat Jan 03 2026
3
+ * @version 1.0.1 | Sat Jan 03 2026
4
4
  * @author NHN Cloud FE Development Lab <dl_javascript@nhn.com>
5
5
  * @license MIT
6
6
  */
@@ -527,73 +527,80 @@ function detailsPlugin(context, options) {
527
527
  }
528
528
  }
529
529
  });
530
- // Add Keydown listener for exit behavior
530
+ // Simplified keydown handler with better logic
531
531
  dom.addEventListener('keydown', function (e) {
532
532
  var state = view.state;
533
- var selection = state.selection, tr = state.tr, schema = state.schema;
534
- var $from = selection.$from;
533
+ var selection = state.selection;
535
534
  var pos = getPos();
536
535
  if (typeof pos !== 'number') {
537
536
  return;
538
537
  }
539
- // Fetch current node to ensure nodeSize is fresh!
538
+ // Get fresh node
540
539
  var currentNode = state.doc.nodeAt(pos);
541
540
  if (!currentNode || currentNode.type.name !== 'details') {
542
541
  return;
543
542
  }
544
- var nodeSize = currentNode.nodeSize;
545
- var endOfDetails = pos + nodeSize;
546
- // Workaround for Dual Package Hazard:
547
- // Get the TextSelection constructor from the current selection instance
548
- // assuming the current selection is a TextSelection (which it is while typing).
549
- var SelectionHeader = state.selection.constructor;
543
+ // Calculate end position
544
+ var endPos = pos + currentNode.nodeSize;
545
+ // Get TextSelection constructor
546
+ var SelectionCtor = state.selection.constructor;
547
+ // Handle Enter key
550
548
  if (e.key === 'Enter') {
551
- var parent = $from.parent;
552
- // Check if we are in an empty paragraph that is the last child of details
553
- if (parent.type.name === 'paragraph' && parent.content.size === 0) {
554
- var parentEnd = $from.after();
555
- if (parentEnd === endOfDetails - 1) {
556
- // Empty last paragraph -> Exit logic
549
+ var $from = selection.$from;
550
+ // Check if we're in an empty paragraph
551
+ if ($from.parent.type.name === 'paragraph' && $from.parent.content.size === 0) {
552
+ // Get position after this paragraph
553
+ var afterPara = $from.after();
554
+ // Check if this empty paragraph is the last thing in the details block
555
+ // endPos - 1 is the position just before the closing tag
556
+ if (afterPara >= endPos - 1) {
557
557
  e.preventDefault();
558
558
  e.stopPropagation();
559
- var parentPos = $from.before();
560
- tr.delete(parentPos, parentEnd);
559
+ var tr = state.tr, schema = state.schema;
560
+ // Delete the empty paragraph
561
+ var beforePara = $from.before();
562
+ tr.delete(beforePara, afterPara);
563
+ // Insert new paragraph after the details block
561
564
  var p = schema.nodes.paragraph.createAndFill();
562
- // After deletion, the position shifts back
563
- var newPos = endOfDetails - (parentEnd - parentPos);
564
- tr.insert(newPos, p);
565
- if (SelectionHeader && SelectionHeader.near) {
566
- tr.setSelection(SelectionHeader.near(tr.doc.resolve(newPos + 1)));
567
- view.dispatch(tr);
565
+ var insertPos = endPos - (afterPara - beforePara);
566
+ tr.insert(insertPos, p);
567
+ // Move cursor into new paragraph
568
+ if (SelectionCtor && SelectionCtor.near) {
569
+ tr.setSelection(SelectionCtor.near(tr.doc.resolve(insertPos + 1)));
568
570
  }
571
+ view.dispatch(tr);
569
572
  }
570
573
  }
571
574
  }
572
- else if (e.key === 'ArrowDown') {
573
- var parentEnd = $from.after();
574
- if (parentEnd !== endOfDetails - 1) {
575
- return;
576
- }
577
- // We are in the last child
578
- var atEnd = $from.parentOffset === $from.parent.content.size;
579
- if (!atEnd) {
575
+ // Handle ArrowDown key
576
+ if (e.key === 'ArrowDown') {
577
+ var $from = selection.$from;
578
+ // Check if cursor is at the end of its parent node
579
+ var atEndOfParent = $from.parentOffset === $from.parent.content.size;
580
+ if (!atEndOfParent) {
580
581
  return;
581
582
  }
582
- e.preventDefault();
583
- e.stopPropagation();
584
- if (endOfDetails < state.doc.content.size) {
585
- // Move cursor out to existing content
586
- if (SelectionHeader && SelectionHeader.near) {
587
- tr.setSelection(SelectionHeader.near(tr.doc.resolve(endOfDetails)));
588
- view.dispatch(tr);
583
+ // Check if parent is the last child in details
584
+ var afterPara = $from.after();
585
+ if (afterPara >= endPos - 1) {
586
+ e.preventDefault();
587
+ e.stopPropagation();
588
+ var tr = state.tr, schema = state.schema;
589
+ // Try to move to position after details block
590
+ if (endPos < state.doc.content.size) {
591
+ // There's content after, just move there
592
+ if (SelectionCtor && SelectionCtor.near) {
593
+ tr.setSelection(SelectionCtor.near(tr.doc.resolve(endPos)));
594
+ view.dispatch(tr);
595
+ }
589
596
  }
590
- }
591
- else {
592
- // Create new paragraph below
593
- var p = schema.nodes.paragraph.createAndFill();
594
- tr.insert(endOfDetails, p);
595
- if (SelectionHeader && SelectionHeader.near) {
596
- tr.setSelection(SelectionHeader.near(tr.doc.resolve(endOfDetails + 1)));
597
+ else {
598
+ // No content after, create a new paragraph
599
+ var p = schema.nodes.paragraph.createAndFill();
600
+ tr.insert(endPos, p);
601
+ if (SelectionCtor && SelectionCtor.near) {
602
+ tr.setSelection(SelectionCtor.near(tr.doc.resolve(endPos + 1)));
603
+ }
597
604
  view.dispatch(tr);
598
605
  }
599
606
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@licium/editor-plugin-details",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Details/Summary plugin for Toast UI Editor",
5
5
  "keywords": [
6
6
  "nhn",
package/src/index.ts CHANGED
@@ -83,90 +83,104 @@ export default function detailsPlugin(
83
83
  }
84
84
  });
85
85
 
86
- // Add Keydown listener for exit behavior
86
+ // Simplified keydown handler with better logic
87
87
  dom.addEventListener('keydown', (e) => {
88
88
  const { state } = view;
89
- const { selection, tr, schema } = state;
90
- const { $from } = selection;
89
+ const { selection } = state;
91
90
  const pos = getPos();
92
91
 
93
92
  if (typeof pos !== 'number') {
94
93
  return;
95
94
  }
96
95
 
97
- // Fetch current node to ensure nodeSize is fresh!
96
+ // Get fresh node
98
97
  const currentNode = state.doc.nodeAt(pos);
99
98
 
100
99
  if (!currentNode || currentNode.type.name !== 'details') {
101
100
  return;
102
101
  }
103
102
 
104
- const { nodeSize } = currentNode;
105
- const endOfDetails = pos + nodeSize;
103
+ // Calculate end position
104
+ const endPos = pos + currentNode.nodeSize;
106
105
 
107
- // Workaround for Dual Package Hazard:
108
- // Get the TextSelection constructor from the current selection instance
109
- // assuming the current selection is a TextSelection (which it is while typing).
110
- const { constructor: SelectionHeader } = state.selection as any;
106
+ // Get TextSelection constructor
107
+ const { constructor: SelectionCtor } = state.selection as any;
111
108
 
109
+ // Handle Enter key
112
110
  if (e.key === 'Enter') {
113
- const { parent } = $from;
111
+ const { $from } = selection;
114
112
 
115
- // Check if we are in an empty paragraph that is the last child of details
116
- if (parent.type.name === 'paragraph' && parent.content.size === 0) {
117
- const parentEnd = $from.after();
113
+ // Check if we're in an empty paragraph
114
+ if ($from.parent.type.name === 'paragraph' && $from.parent.content.size === 0) {
115
+ // Get position after this paragraph
116
+ const afterPara = $from.after();
118
117
 
119
- if (parentEnd === endOfDetails - 1) {
120
- // Empty last paragraph -> Exit logic
118
+ // Check if this empty paragraph is the last thing in the details block
119
+ // endPos - 1 is the position just before the closing tag
120
+ if (afterPara >= endPos - 1) {
121
121
  e.preventDefault();
122
122
  e.stopPropagation();
123
123
 
124
- const parentPos = $from.before();
124
+ const { tr, schema } = state;
125
125
 
126
- tr.delete(parentPos, parentEnd);
126
+ // Delete the empty paragraph
127
+ const beforePara = $from.before();
128
+
129
+ tr.delete(beforePara, afterPara);
130
+
131
+ // Insert new paragraph after the details block
127
132
  const p = schema.nodes.paragraph.createAndFill()!;
128
- // After deletion, the position shifts back
129
- const newPos = endOfDetails - (parentEnd - parentPos);
133
+ const insertPos = endPos - (afterPara - beforePara);
130
134
 
131
- tr.insert(newPos, p);
135
+ tr.insert(insertPos, p);
132
136
 
133
- if (SelectionHeader && SelectionHeader.near) {
134
- tr.setSelection(SelectionHeader.near(tr.doc.resolve(newPos + 1)));
135
- view.dispatch(tr);
137
+ // Move cursor into new paragraph
138
+ if (SelectionCtor && SelectionCtor.near) {
139
+ tr.setSelection(SelectionCtor.near(tr.doc.resolve(insertPos + 1)));
136
140
  }
141
+
142
+ view.dispatch(tr);
137
143
  }
138
144
  }
139
- } else if (e.key === 'ArrowDown') {
140
- const parentEnd = $from.after();
145
+ }
141
146
 
142
- if (parentEnd !== endOfDetails - 1) {
143
- return;
144
- }
147
+ // Handle ArrowDown key
148
+ if (e.key === 'ArrowDown') {
149
+ const { $from } = selection;
145
150
 
146
- // We are in the last child
147
- const atEnd = $from.parentOffset === $from.parent.content.size;
151
+ // Check if cursor is at the end of its parent node
152
+ const atEndOfParent = $from.parentOffset === $from.parent.content.size;
148
153
 
149
- if (!atEnd) {
154
+ if (!atEndOfParent) {
150
155
  return;
151
156
  }
152
157
 
153
- e.preventDefault();
154
- e.stopPropagation();
158
+ // Check if parent is the last child in details
159
+ const afterPara = $from.after();
155
160
 
156
- if (endOfDetails < state.doc.content.size) {
157
- // Move cursor out to existing content
158
- if (SelectionHeader && SelectionHeader.near) {
159
- tr.setSelection(SelectionHeader.near(tr.doc.resolve(endOfDetails)));
160
- view.dispatch(tr);
161
- }
162
- } else {
163
- // Create new paragraph below
164
- const p = schema.nodes.paragraph.createAndFill()!;
161
+ if (afterPara >= endPos - 1) {
162
+ e.preventDefault();
163
+ e.stopPropagation();
164
+
165
+ const { tr, schema } = state;
165
166
 
166
- tr.insert(endOfDetails, p);
167
+ // Try to move to position after details block
168
+ if (endPos < state.doc.content.size) {
169
+ // There's content after, just move there
170
+ if (SelectionCtor && SelectionCtor.near) {
171
+ tr.setSelection(SelectionCtor.near(tr.doc.resolve(endPos)));
172
+ view.dispatch(tr);
173
+ }
174
+ } else {
175
+ // No content after, create a new paragraph
176
+ const p = schema.nodes.paragraph.createAndFill()!;
177
+
178
+ tr.insert(endPos, p);
179
+
180
+ if (SelectionCtor && SelectionCtor.near) {
181
+ tr.setSelection(SelectionCtor.near(tr.doc.resolve(endPos + 1)));
182
+ }
167
183
 
168
- if (SelectionHeader && SelectionHeader.near) {
169
- tr.setSelection(SelectionHeader.near(tr.doc.resolve(endOfDetails + 1)));
170
184
  view.dispatch(tr);
171
185
  }
172
186
  }
@@ -199,7 +213,7 @@ export default function detailsPlugin(
199
213
  toolbarItems: [
200
214
  {
201
215
  groupIndex: 0,
202
- itemIndex: 4, // Append after CodeBlock
216
+ itemIndex: 4,
203
217
  item: toolbarItem,
204
218
  },
205
219
  ],