@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.
- package/dist/toastui-editor-plugin-details.js +54 -47
- package/package.json +1 -1
- package/src/index.ts +61 -47
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* TOAST UI Editor : Text Align Plugin
|
|
3
|
-
* @version 1.0.
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
545
|
-
var
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
//
|
|
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
|
|
552
|
-
// Check if we
|
|
553
|
-
if (parent.type.name === 'paragraph' && parent.content.size === 0) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
|
560
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (
|
|
566
|
-
tr.setSelection(
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
package/src/index.ts
CHANGED
|
@@ -83,90 +83,104 @@ export default function detailsPlugin(
|
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
//
|
|
86
|
+
// Simplified keydown handler with better logic
|
|
87
87
|
dom.addEventListener('keydown', (e) => {
|
|
88
88
|
const { state } = view;
|
|
89
|
-
const { selection
|
|
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
|
-
//
|
|
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
|
-
|
|
105
|
-
const
|
|
103
|
+
// Calculate end position
|
|
104
|
+
const endPos = pos + currentNode.nodeSize;
|
|
106
105
|
|
|
107
|
-
//
|
|
108
|
-
|
|
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 {
|
|
111
|
+
const { $from } = selection;
|
|
114
112
|
|
|
115
|
-
// Check if we
|
|
116
|
-
if (parent.type.name === 'paragraph' && parent.content.size === 0) {
|
|
117
|
-
|
|
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
|
|
120
|
-
|
|
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
|
|
124
|
+
const { tr, schema } = state;
|
|
125
125
|
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
const newPos = endOfDetails - (parentEnd - parentPos);
|
|
133
|
+
const insertPos = endPos - (afterPara - beforePara);
|
|
130
134
|
|
|
131
|
-
tr.insert(
|
|
135
|
+
tr.insert(insertPos, p);
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
}
|
|
140
|
-
const parentEnd = $from.after();
|
|
145
|
+
}
|
|
141
146
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
147
|
+
// Handle ArrowDown key
|
|
148
|
+
if (e.key === 'ArrowDown') {
|
|
149
|
+
const { $from } = selection;
|
|
145
150
|
|
|
146
|
-
//
|
|
147
|
-
const
|
|
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 (!
|
|
154
|
+
if (!atEndOfParent) {
|
|
150
155
|
return;
|
|
151
156
|
}
|
|
152
157
|
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
// Check if parent is the last child in details
|
|
159
|
+
const afterPara = $from.after();
|
|
155
160
|
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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,
|
|
216
|
+
itemIndex: 4,
|
|
203
217
|
item: toolbarItem,
|
|
204
218
|
},
|
|
205
219
|
],
|