@licium/editor-plugin-details 1.0.1 → 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 -77
- package/package.json +1 -1
- package/src/css/plugin.css +23 -0
- package/src/index.ts +132 -95
|
@@ -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,92 +623,20 @@ function detailsPlugin(context, options) {
|
|
|
527
623
|
}
|
|
528
624
|
}
|
|
529
625
|
});
|
|
530
|
-
// Add Keydown listener for exit behavior
|
|
531
|
-
dom.addEventListener('keydown', function (e) {
|
|
532
|
-
var state = view.state;
|
|
533
|
-
var selection = state.selection, tr = state.tr, schema = state.schema;
|
|
534
|
-
var $from = selection.$from;
|
|
535
|
-
var pos = getPos();
|
|
536
|
-
if (typeof pos !== 'number') {
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
// Fetch current node to ensure nodeSize is fresh!
|
|
540
|
-
var currentNode = state.doc.nodeAt(pos);
|
|
541
|
-
if (!currentNode || currentNode.type.name !== 'details') {
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
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;
|
|
550
|
-
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
|
|
557
|
-
e.preventDefault();
|
|
558
|
-
e.stopPropagation();
|
|
559
|
-
var parentPos = $from.before();
|
|
560
|
-
tr.delete(parentPos, parentEnd);
|
|
561
|
-
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);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
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) {
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
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);
|
|
589
|
-
}
|
|
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
|
-
view.dispatch(tr);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
626
|
return { dom: dom, contentDOM: dom };
|
|
603
627
|
},
|
|
604
628
|
},
|
|
605
629
|
wysiwygCommands: {
|
|
606
630
|
details: function (payload, _a, dispatch) {
|
|
607
|
-
var tr = _a.tr,
|
|
631
|
+
var tr = _a.tr, schema = _a.schema;
|
|
608
632
|
var summaryText = i18n.get('summary');
|
|
609
633
|
var contentText = i18n.get('content');
|
|
610
634
|
var _b = schema.nodes, details = _b.details, summary = _b.summary, paragraph = _b.paragraph;
|
|
611
635
|
if (details && summary && paragraph) {
|
|
612
636
|
var summaryNode = summary.create({}, schema.text(summaryText));
|
|
613
637
|
var contentNode = paragraph.create({}, schema.text(contentText));
|
|
614
|
-
var
|
|
615
|
-
tr.replaceSelectionWith(
|
|
638
|
+
var detailsNode = details.create({}, [summaryNode, contentNode]);
|
|
639
|
+
tr.replaceSelectionWith(detailsNode);
|
|
616
640
|
dispatch(tr);
|
|
617
641
|
return true;
|
|
618
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,101 +210,11 @@ export default function detailsPlugin(
|
|
|
83
210
|
}
|
|
84
211
|
});
|
|
85
212
|
|
|
86
|
-
// Add Keydown listener for exit behavior
|
|
87
|
-
dom.addEventListener('keydown', (e) => {
|
|
88
|
-
const { state } = view;
|
|
89
|
-
const { selection, tr, schema } = state;
|
|
90
|
-
const { $from } = selection;
|
|
91
|
-
const pos = getPos();
|
|
92
|
-
|
|
93
|
-
if (typeof pos !== 'number') {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Fetch current node to ensure nodeSize is fresh!
|
|
98
|
-
const currentNode = state.doc.nodeAt(pos);
|
|
99
|
-
|
|
100
|
-
if (!currentNode || currentNode.type.name !== 'details') {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const { nodeSize } = currentNode;
|
|
105
|
-
const endOfDetails = pos + nodeSize;
|
|
106
|
-
|
|
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;
|
|
111
|
-
|
|
112
|
-
if (e.key === 'Enter') {
|
|
113
|
-
const { parent } = $from;
|
|
114
|
-
|
|
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();
|
|
118
|
-
|
|
119
|
-
if (parentEnd === endOfDetails - 1) {
|
|
120
|
-
// Empty last paragraph -> Exit logic
|
|
121
|
-
e.preventDefault();
|
|
122
|
-
e.stopPropagation();
|
|
123
|
-
|
|
124
|
-
const parentPos = $from.before();
|
|
125
|
-
|
|
126
|
-
tr.delete(parentPos, parentEnd);
|
|
127
|
-
const p = schema.nodes.paragraph.createAndFill()!;
|
|
128
|
-
// After deletion, the position shifts back
|
|
129
|
-
const newPos = endOfDetails - (parentEnd - parentPos);
|
|
130
|
-
|
|
131
|
-
tr.insert(newPos, p);
|
|
132
|
-
|
|
133
|
-
if (SelectionHeader && SelectionHeader.near) {
|
|
134
|
-
tr.setSelection(SelectionHeader.near(tr.doc.resolve(newPos + 1)));
|
|
135
|
-
view.dispatch(tr);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
} else if (e.key === 'ArrowDown') {
|
|
140
|
-
const parentEnd = $from.after();
|
|
141
|
-
|
|
142
|
-
if (parentEnd !== endOfDetails - 1) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// We are in the last child
|
|
147
|
-
const atEnd = $from.parentOffset === $from.parent.content.size;
|
|
148
|
-
|
|
149
|
-
if (!atEnd) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
e.preventDefault();
|
|
154
|
-
e.stopPropagation();
|
|
155
|
-
|
|
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()!;
|
|
165
|
-
|
|
166
|
-
tr.insert(endOfDetails, p);
|
|
167
|
-
|
|
168
|
-
if (SelectionHeader && SelectionHeader.near) {
|
|
169
|
-
tr.setSelection(SelectionHeader.near(tr.doc.resolve(endOfDetails + 1)));
|
|
170
|
-
view.dispatch(tr);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
|
|
176
213
|
return { dom, contentDOM: dom };
|
|
177
214
|
},
|
|
178
215
|
},
|
|
179
216
|
wysiwygCommands: {
|
|
180
|
-
details: (payload, { tr,
|
|
217
|
+
details: (payload, { tr, schema }, dispatch) => {
|
|
181
218
|
const summaryText = i18n.get('summary');
|
|
182
219
|
const contentText = i18n.get('content');
|
|
183
220
|
|
|
@@ -186,9 +223,9 @@ export default function detailsPlugin(
|
|
|
186
223
|
if (details && summary && paragraph) {
|
|
187
224
|
const summaryNode = summary.create({}, schema.text(summaryText));
|
|
188
225
|
const contentNode = paragraph.create({}, schema.text(contentText));
|
|
189
|
-
const
|
|
226
|
+
const detailsNode = details.create({}, [summaryNode, contentNode]);
|
|
190
227
|
|
|
191
|
-
tr.replaceSelectionWith(
|
|
228
|
+
tr.replaceSelectionWith(detailsNode);
|
|
192
229
|
dispatch!(tr);
|
|
193
230
|
return true;
|
|
194
231
|
}
|
|
@@ -199,7 +236,7 @@ export default function detailsPlugin(
|
|
|
199
236
|
toolbarItems: [
|
|
200
237
|
{
|
|
201
238
|
groupIndex: 0,
|
|
202
|
-
itemIndex: 4,
|
|
239
|
+
itemIndex: 4,
|
|
203
240
|
item: toolbarItem,
|
|
204
241
|
},
|
|
205
242
|
],
|