@licium/editor-plugin-details 1.0.2 → 1.0.3
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 +101 -84
- package/package.json +1 -1
- package/src/css/plugin.css +23 -0
- package/src/index.ts +131 -108
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* TOAST UI Editor : Text Align Plugin
|
|
3
|
-
* @version 1.0.
|
|
3
|
+
* @version 1.0.2 | Sun Jan 04 2026
|
|
4
4
|
* @author NHN Cloud FE Development Lab <dl_javascript@nhn.com>
|
|
5
5
|
* @license MIT
|
|
6
6
|
*/
|
|
@@ -474,16 +474,112 @@ function createToolbarItemOption(i18n) {
|
|
|
474
474
|
command: 'details',
|
|
475
475
|
};
|
|
476
476
|
}
|
|
477
|
+
function handleDetailsExit(context, event) {
|
|
478
|
+
var instance = context.instance;
|
|
479
|
+
if (!instance) {
|
|
480
|
+
console.log('[Details Plugin] No instance found');
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
var editor = instance.getCurrentModeEditor();
|
|
484
|
+
if (!editor || !editor.view) {
|
|
485
|
+
console.log('[Details Plugin] No editor view found');
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
var view = editor.view;
|
|
489
|
+
var state = view.state;
|
|
490
|
+
var selection = state.selection;
|
|
491
|
+
var $from = selection.$from;
|
|
492
|
+
// Find details node in ancestry
|
|
493
|
+
var detailsDepth = -1;
|
|
494
|
+
for (var d = $from.depth; d > 0; d -= 1) {
|
|
495
|
+
if ($from.node(d).type.name === 'details') {
|
|
496
|
+
detailsDepth = d;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (detailsDepth < 0) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
var detailsNode = $from.node(detailsDepth);
|
|
504
|
+
var nodeStart = $from.before(detailsDepth);
|
|
505
|
+
var nodeEnd = nodeStart + detailsNode.nodeSize;
|
|
506
|
+
var afterPara = $from.after();
|
|
507
|
+
var isAtEnd = afterPara >= nodeEnd - 1;
|
|
508
|
+
console.log('[Details Plugin] Inside details:', {
|
|
509
|
+
key: event.key,
|
|
510
|
+
isAtEnd: isAtEnd,
|
|
511
|
+
afterPara: afterPara,
|
|
512
|
+
nodeEnd: nodeEnd,
|
|
513
|
+
parentType: $from.parent.type.name,
|
|
514
|
+
parentEmpty: $from.parent.content.size === 0,
|
|
515
|
+
});
|
|
516
|
+
if (!isAtEnd) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
var tr = state.tr, schema = state.schema;
|
|
520
|
+
var SelectionClass = state.selection.constructor;
|
|
521
|
+
// Enter on empty paragraph
|
|
522
|
+
if (event.key === 'Enter') {
|
|
523
|
+
var isEmptyPara = $from.parent.type.name === 'paragraph' && $from.parent.content.size === 0;
|
|
524
|
+
if (!isEmptyPara) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
console.log('[Details Plugin] EXITING via Enter!');
|
|
528
|
+
event.preventDefault();
|
|
529
|
+
var beforePara = $from.before();
|
|
530
|
+
tr.delete(beforePara, afterPara);
|
|
531
|
+
var p = schema.nodes.paragraph.createAndFill();
|
|
532
|
+
var insertPos = nodeEnd - (afterPara - beforePara);
|
|
533
|
+
tr.insert(insertPos, p);
|
|
534
|
+
if (SelectionClass.near) {
|
|
535
|
+
tr.setSelection(SelectionClass.near(tr.doc.resolve(insertPos + 1)));
|
|
536
|
+
}
|
|
537
|
+
view.dispatch(tr);
|
|
538
|
+
}
|
|
539
|
+
// ArrowDown at end of content
|
|
540
|
+
if (event.key === 'ArrowDown') {
|
|
541
|
+
var atEndOfParent = $from.parentOffset === $from.parent.content.size;
|
|
542
|
+
if (!atEndOfParent) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
console.log('[Details Plugin] EXITING via ArrowDown!');
|
|
546
|
+
event.preventDefault();
|
|
547
|
+
if (nodeEnd < state.doc.content.size) {
|
|
548
|
+
if (SelectionClass.near) {
|
|
549
|
+
tr.setSelection(SelectionClass.near(tr.doc.resolve(nodeEnd)));
|
|
550
|
+
view.dispatch(tr);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
var p = schema.nodes.paragraph.createAndFill();
|
|
555
|
+
tr.insert(nodeEnd, p);
|
|
556
|
+
if (SelectionClass.near) {
|
|
557
|
+
tr.setSelection(SelectionClass.near(tr.doc.resolve(nodeEnd + 1)));
|
|
558
|
+
}
|
|
559
|
+
view.dispatch(tr);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
477
563
|
function detailsPlugin(context, options) {
|
|
478
564
|
if (options === void 0) { options = {}; }
|
|
479
565
|
var i18n = context.i18n, eventEmitter = context.eventEmitter;
|
|
480
566
|
addLangs(i18n);
|
|
481
567
|
var toolbarItem = createToolbarItemOption(i18n);
|
|
568
|
+
// Hook into the global keydown event for exit behavior
|
|
569
|
+
eventEmitter.listen('keydown', function (editorType, event) {
|
|
570
|
+
console.log('[Details Plugin] keydown via eventEmitter!', { editorType: editorType, key: event.key });
|
|
571
|
+
if (editorType !== 'wysiwyg') {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (event.key !== 'Enter' && event.key !== 'ArrowDown') {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
handleDetailsExit(context, event);
|
|
578
|
+
});
|
|
482
579
|
return {
|
|
483
580
|
markdownCommands: {
|
|
484
581
|
details: function (payload, _a, dispatch) {
|
|
485
582
|
var tr = _a.tr, selection = _a.selection, schema = _a.schema;
|
|
486
|
-
var from = selection.from, to = selection.to;
|
|
487
583
|
var slice = selection.content();
|
|
488
584
|
var textContent = slice.content.textBetween(0, slice.content.size, '\n') || i18n.get('content');
|
|
489
585
|
var summaryText = i18n.get('summary');
|
|
@@ -527,99 +623,20 @@ function detailsPlugin(context, options) {
|
|
|
527
623
|
}
|
|
528
624
|
}
|
|
529
625
|
});
|
|
530
|
-
// Simplified keydown handler with better logic
|
|
531
|
-
dom.addEventListener('keydown', function (e) {
|
|
532
|
-
var state = view.state;
|
|
533
|
-
var selection = state.selection;
|
|
534
|
-
var pos = getPos();
|
|
535
|
-
if (typeof pos !== 'number') {
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
// Get fresh node
|
|
539
|
-
var currentNode = state.doc.nodeAt(pos);
|
|
540
|
-
if (!currentNode || currentNode.type.name !== 'details') {
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
// Calculate end position
|
|
544
|
-
var endPos = pos + currentNode.nodeSize;
|
|
545
|
-
// Get TextSelection constructor
|
|
546
|
-
var SelectionCtor = state.selection.constructor;
|
|
547
|
-
// Handle Enter key
|
|
548
|
-
if (e.key === 'Enter') {
|
|
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
|
-
e.preventDefault();
|
|
558
|
-
e.stopPropagation();
|
|
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
|
|
564
|
-
var p = schema.nodes.paragraph.createAndFill();
|
|
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)));
|
|
570
|
-
}
|
|
571
|
-
view.dispatch(tr);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
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) {
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
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
|
-
}
|
|
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
|
-
}
|
|
604
|
-
view.dispatch(tr);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
626
|
return { dom: dom, contentDOM: dom };
|
|
610
627
|
},
|
|
611
628
|
},
|
|
612
629
|
wysiwygCommands: {
|
|
613
630
|
details: function (payload, _a, dispatch) {
|
|
614
|
-
var tr = _a.tr,
|
|
631
|
+
var tr = _a.tr, schema = _a.schema;
|
|
615
632
|
var summaryText = i18n.get('summary');
|
|
616
633
|
var contentText = i18n.get('content');
|
|
617
634
|
var _b = schema.nodes, details = _b.details, summary = _b.summary, paragraph = _b.paragraph;
|
|
618
635
|
if (details && summary && paragraph) {
|
|
619
636
|
var summaryNode = summary.create({}, schema.text(summaryText));
|
|
620
637
|
var contentNode = paragraph.create({}, schema.text(contentText));
|
|
621
|
-
var
|
|
622
|
-
tr.replaceSelectionWith(
|
|
638
|
+
var detailsNode = details.create({}, [summaryNode, contentNode]);
|
|
639
|
+
tr.replaceSelectionWith(detailsNode);
|
|
623
640
|
dispatch(tr);
|
|
624
641
|
return true;
|
|
625
642
|
}
|
package/package.json
CHANGED
package/src/css/plugin.css
CHANGED
|
@@ -7,7 +7,30 @@
|
|
|
7
7
|
text-indent: -9999px !important;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
details {
|
|
11
|
+
display: block;
|
|
12
|
+
background-color: #f7f7f7;
|
|
13
|
+
border: 1px solid #e1e1e1;
|
|
14
|
+
border-radius: 4px;
|
|
15
|
+
padding: 10px;
|
|
16
|
+
margin: 10px 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
summary {
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
font-weight: bold;
|
|
22
|
+
margin-bottom: 5px;
|
|
23
|
+
outline: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
/* Dark Mode */
|
|
11
27
|
.toastui-editor-dark .toastui-editor-toolbar-icons.details {
|
|
12
28
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23eee' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z'/%3E%3Cpath d='M9 10L12 13 15 10' stroke='%23eee'/%3E%3C/svg%3E") !important;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.toastui-editor-dark details {
|
|
32
|
+
background-color: #282a36;
|
|
33
|
+
/* Darker background for contrast against editor bg */
|
|
34
|
+
border-color: #44475a;
|
|
35
|
+
color: #f8f8f2;
|
|
13
36
|
}
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,119 @@ function createToolbarItemOption(i18n: I18n) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function handleDetailsExit(context: PluginContext, event: KeyboardEvent): void {
|
|
17
|
+
const { instance } = context as any;
|
|
18
|
+
|
|
19
|
+
if (!instance) {
|
|
20
|
+
console.log('[Details Plugin] No instance found');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const editor = instance.getCurrentModeEditor();
|
|
25
|
+
|
|
26
|
+
if (!editor || !editor.view) {
|
|
27
|
+
console.log('[Details Plugin] No editor view found');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { view } = editor;
|
|
32
|
+
const { state } = view;
|
|
33
|
+
const { selection } = state;
|
|
34
|
+
const { $from } = selection;
|
|
35
|
+
|
|
36
|
+
// Find details node in ancestry
|
|
37
|
+
let detailsDepth = -1;
|
|
38
|
+
|
|
39
|
+
for (let d = $from.depth; d > 0; d -= 1) {
|
|
40
|
+
if ($from.node(d).type.name === 'details') {
|
|
41
|
+
detailsDepth = d;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (detailsDepth < 0) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const detailsNode = $from.node(detailsDepth);
|
|
51
|
+
const nodeStart = $from.before(detailsDepth);
|
|
52
|
+
const nodeEnd = nodeStart + detailsNode.nodeSize;
|
|
53
|
+
const afterPara = $from.after();
|
|
54
|
+
const isAtEnd = afterPara >= nodeEnd - 1;
|
|
55
|
+
|
|
56
|
+
console.log('[Details Plugin] Inside details:', {
|
|
57
|
+
key: event.key,
|
|
58
|
+
isAtEnd,
|
|
59
|
+
afterPara,
|
|
60
|
+
nodeEnd,
|
|
61
|
+
parentType: $from.parent.type.name,
|
|
62
|
+
parentEmpty: $from.parent.content.size === 0,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!isAtEnd) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { tr, schema } = state;
|
|
70
|
+
const SelectionClass = state.selection.constructor as any;
|
|
71
|
+
|
|
72
|
+
// Enter on empty paragraph
|
|
73
|
+
if (event.key === 'Enter') {
|
|
74
|
+
const isEmptyPara = $from.parent.type.name === 'paragraph' && $from.parent.content.size === 0;
|
|
75
|
+
|
|
76
|
+
if (!isEmptyPara) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('[Details Plugin] EXITING via Enter!');
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
|
|
83
|
+
const beforePara = $from.before();
|
|
84
|
+
|
|
85
|
+
tr.delete(beforePara, afterPara);
|
|
86
|
+
|
|
87
|
+
const p = schema.nodes.paragraph.createAndFill()!;
|
|
88
|
+
const insertPos = nodeEnd - (afterPara - beforePara);
|
|
89
|
+
|
|
90
|
+
tr.insert(insertPos, p);
|
|
91
|
+
|
|
92
|
+
if (SelectionClass.near) {
|
|
93
|
+
tr.setSelection(SelectionClass.near(tr.doc.resolve(insertPos + 1)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
view.dispatch(tr);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ArrowDown at end of content
|
|
100
|
+
if (event.key === 'ArrowDown') {
|
|
101
|
+
const atEndOfParent = $from.parentOffset === $from.parent.content.size;
|
|
102
|
+
|
|
103
|
+
if (!atEndOfParent) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log('[Details Plugin] EXITING via ArrowDown!');
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
|
|
110
|
+
if (nodeEnd < state.doc.content.size) {
|
|
111
|
+
if (SelectionClass.near) {
|
|
112
|
+
tr.setSelection(SelectionClass.near(tr.doc.resolve(nodeEnd)));
|
|
113
|
+
view.dispatch(tr);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
const p = schema.nodes.paragraph.createAndFill()!;
|
|
117
|
+
|
|
118
|
+
tr.insert(nodeEnd, p);
|
|
119
|
+
|
|
120
|
+
if (SelectionClass.near) {
|
|
121
|
+
tr.setSelection(SelectionClass.near(tr.doc.resolve(nodeEnd + 1)));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
view.dispatch(tr);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
16
129
|
export default function detailsPlugin(
|
|
17
130
|
context: PluginContext,
|
|
18
131
|
options: PluginOptions = {}
|
|
@@ -23,10 +136,24 @@ export default function detailsPlugin(
|
|
|
23
136
|
|
|
24
137
|
const toolbarItem = createToolbarItemOption(i18n);
|
|
25
138
|
|
|
139
|
+
// Hook into the global keydown event for exit behavior
|
|
140
|
+
eventEmitter.listen('keydown', (editorType: string, event: KeyboardEvent) => {
|
|
141
|
+
console.log('[Details Plugin] keydown via eventEmitter!', { editorType, key: event.key });
|
|
142
|
+
|
|
143
|
+
if (editorType !== 'wysiwyg') {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (event.key !== 'Enter' && event.key !== 'ArrowDown') {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
handleDetailsExit(context, event);
|
|
152
|
+
});
|
|
153
|
+
|
|
26
154
|
return {
|
|
27
155
|
markdownCommands: {
|
|
28
156
|
details: (payload, { tr, selection, schema }, dispatch) => {
|
|
29
|
-
const { from, to } = selection;
|
|
30
157
|
const slice = selection.content();
|
|
31
158
|
const textContent =
|
|
32
159
|
slice.content.textBetween(0, slice.content.size, '\n') || i18n.get('content');
|
|
@@ -83,115 +210,11 @@ export default function detailsPlugin(
|
|
|
83
210
|
}
|
|
84
211
|
});
|
|
85
212
|
|
|
86
|
-
// Simplified keydown handler with better logic
|
|
87
|
-
dom.addEventListener('keydown', (e) => {
|
|
88
|
-
const { state } = view;
|
|
89
|
-
const { selection } = state;
|
|
90
|
-
const pos = getPos();
|
|
91
|
-
|
|
92
|
-
if (typeof pos !== 'number') {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Get fresh node
|
|
97
|
-
const currentNode = state.doc.nodeAt(pos);
|
|
98
|
-
|
|
99
|
-
if (!currentNode || currentNode.type.name !== 'details') {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Calculate end position
|
|
104
|
-
const endPos = pos + currentNode.nodeSize;
|
|
105
|
-
|
|
106
|
-
// Get TextSelection constructor
|
|
107
|
-
const { constructor: SelectionCtor } = state.selection as any;
|
|
108
|
-
|
|
109
|
-
// Handle Enter key
|
|
110
|
-
if (e.key === 'Enter') {
|
|
111
|
-
const { $from } = selection;
|
|
112
|
-
|
|
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();
|
|
117
|
-
|
|
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
|
-
e.preventDefault();
|
|
122
|
-
e.stopPropagation();
|
|
123
|
-
|
|
124
|
-
const { tr, schema } = state;
|
|
125
|
-
|
|
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
|
|
132
|
-
const p = schema.nodes.paragraph.createAndFill()!;
|
|
133
|
-
const insertPos = endPos - (afterPara - beforePara);
|
|
134
|
-
|
|
135
|
-
tr.insert(insertPos, p);
|
|
136
|
-
|
|
137
|
-
// Move cursor into new paragraph
|
|
138
|
-
if (SelectionCtor && SelectionCtor.near) {
|
|
139
|
-
tr.setSelection(SelectionCtor.near(tr.doc.resolve(insertPos + 1)));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
view.dispatch(tr);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Handle ArrowDown key
|
|
148
|
-
if (e.key === 'ArrowDown') {
|
|
149
|
-
const { $from } = selection;
|
|
150
|
-
|
|
151
|
-
// Check if cursor is at the end of its parent node
|
|
152
|
-
const atEndOfParent = $from.parentOffset === $from.parent.content.size;
|
|
153
|
-
|
|
154
|
-
if (!atEndOfParent) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Check if parent is the last child in details
|
|
159
|
-
const afterPara = $from.after();
|
|
160
|
-
|
|
161
|
-
if (afterPara >= endPos - 1) {
|
|
162
|
-
e.preventDefault();
|
|
163
|
-
e.stopPropagation();
|
|
164
|
-
|
|
165
|
-
const { tr, schema } = state;
|
|
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
|
-
}
|
|
183
|
-
|
|
184
|
-
view.dispatch(tr);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
213
|
return { dom, contentDOM: dom };
|
|
191
214
|
},
|
|
192
215
|
},
|
|
193
216
|
wysiwygCommands: {
|
|
194
|
-
details: (payload, { tr,
|
|
217
|
+
details: (payload, { tr, schema }, dispatch) => {
|
|
195
218
|
const summaryText = i18n.get('summary');
|
|
196
219
|
const contentText = i18n.get('content');
|
|
197
220
|
|
|
@@ -200,9 +223,9 @@ export default function detailsPlugin(
|
|
|
200
223
|
if (details && summary && paragraph) {
|
|
201
224
|
const summaryNode = summary.create({}, schema.text(summaryText));
|
|
202
225
|
const contentNode = paragraph.create({}, schema.text(contentText));
|
|
203
|
-
const
|
|
226
|
+
const detailsNode = details.create({}, [summaryNode, contentNode]);
|
|
204
227
|
|
|
205
|
-
tr.replaceSelectionWith(
|
|
228
|
+
tr.replaceSelectionWith(detailsNode);
|
|
206
229
|
dispatch!(tr);
|
|
207
230
|
return true;
|
|
208
231
|
}
|