1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | import ListCommand from './listcommand';
|
9 | import IndentCommand from './indentcommand';
|
10 | import ListUtils from './listutils';
|
11 | import { Plugin } from 'ckeditor5/src/core';
|
12 | import { Enter } from 'ckeditor5/src/enter';
|
13 | import { Delete } from 'ckeditor5/src/typing';
|
14 | import { cleanList, cleanListItem, modelViewInsertion, modelViewChangeType, modelViewMergeAfterChangeType, modelViewMergeAfter, modelViewRemove, modelViewSplitOnInsert, modelViewChangeIndent, modelChangePostFixer, modelIndentPasteFixer, viewModelConverter, modelToViewPosition, viewToModelPosition } from './converters';
|
15 | import '../../theme/list.css';
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | export default class ListEditing extends Plugin {
|
22 | |
23 |
|
24 |
|
25 | static get pluginName() {
|
26 | return 'ListEditing';
|
27 | }
|
28 | |
29 |
|
30 |
|
31 | static get requires() {
|
32 | return [Enter, Delete, ListUtils];
|
33 | }
|
34 | |
35 |
|
36 |
|
37 | init() {
|
38 | const editor = this.editor;
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | editor.model.schema.register('listItem', {
|
44 | inheritAllFrom: '$block',
|
45 | allowAttributes: ['listType', 'listIndent']
|
46 | });
|
47 |
|
48 | const data = editor.data;
|
49 | const editing = editor.editing;
|
50 | editor.model.document.registerPostFixer(writer => modelChangePostFixer(editor.model, writer));
|
51 | editing.mapper.registerViewToModelLength('li', getViewListItemLength);
|
52 | data.mapper.registerViewToModelLength('li', getViewListItemLength);
|
53 | editing.mapper.on('modelToViewPosition', modelToViewPosition(editing.view));
|
54 | editing.mapper.on('viewToModelPosition', viewToModelPosition(editor.model));
|
55 | data.mapper.on('modelToViewPosition', modelToViewPosition(editing.view));
|
56 | editor.conversion.for('editingDowncast')
|
57 | .add(dispatcher => {
|
58 | dispatcher.on('insert', modelViewSplitOnInsert, { priority: 'high' });
|
59 | dispatcher.on('insert:listItem', modelViewInsertion(editor.model));
|
60 | dispatcher.on('attribute:listType:listItem', modelViewChangeType, { priority: 'high' });
|
61 | dispatcher.on('attribute:listType:listItem', modelViewMergeAfterChangeType, { priority: 'low' });
|
62 | dispatcher.on('attribute:listIndent:listItem', modelViewChangeIndent(editor.model));
|
63 | dispatcher.on('remove:listItem', modelViewRemove(editor.model));
|
64 | dispatcher.on('remove', modelViewMergeAfter, { priority: 'low' });
|
65 | });
|
66 | editor.conversion.for('dataDowncast')
|
67 | .add(dispatcher => {
|
68 | dispatcher.on('insert', modelViewSplitOnInsert, { priority: 'high' });
|
69 | dispatcher.on('insert:listItem', modelViewInsertion(editor.model));
|
70 | });
|
71 | editor.conversion.for('upcast')
|
72 | .add(dispatcher => {
|
73 | dispatcher.on('element:ul', cleanList, { priority: 'high' });
|
74 | dispatcher.on('element:ol', cleanList, { priority: 'high' });
|
75 | dispatcher.on('element:li', cleanListItem, { priority: 'high' });
|
76 | dispatcher.on('element:li', viewModelConverter);
|
77 | });
|
78 |
|
79 | editor.model.on('insertContent', modelIndentPasteFixer, { priority: 'high' });
|
80 |
|
81 | editor.commands.add('numberedList', new ListCommand(editor, 'numbered'));
|
82 | editor.commands.add('bulletedList', new ListCommand(editor, 'bulleted'));
|
83 |
|
84 | editor.commands.add('indentList', new IndentCommand(editor, 'forward'));
|
85 | editor.commands.add('outdentList', new IndentCommand(editor, 'backward'));
|
86 | const viewDocument = editing.view.document;
|
87 |
|
88 |
|
89 | this.listenTo(viewDocument, 'enter', (evt, data) => {
|
90 | const doc = this.editor.model.document;
|
91 | const positionParent = doc.selection.getLastPosition().parent;
|
92 | if (doc.selection.isCollapsed && positionParent.name == 'listItem' && positionParent.isEmpty) {
|
93 | this.editor.execute('outdentList');
|
94 | data.preventDefault();
|
95 | evt.stop();
|
96 | }
|
97 | }, { context: 'li' });
|
98 |
|
99 |
|
100 | this.listenTo(viewDocument, 'delete', (evt, data) => {
|
101 |
|
102 | if (data.direction !== 'backward') {
|
103 | return;
|
104 | }
|
105 | const selection = this.editor.model.document.selection;
|
106 | if (!selection.isCollapsed) {
|
107 | return;
|
108 | }
|
109 | const firstPosition = selection.getFirstPosition();
|
110 | if (!firstPosition.isAtStart) {
|
111 | return;
|
112 | }
|
113 | const positionParent = firstPosition.parent;
|
114 | if (positionParent.name !== 'listItem') {
|
115 | return;
|
116 | }
|
117 | const previousIsAListItem = positionParent.previousSibling && positionParent.previousSibling.name === 'listItem';
|
118 | if (previousIsAListItem) {
|
119 | return;
|
120 | }
|
121 | this.editor.execute('outdentList');
|
122 | data.preventDefault();
|
123 | evt.stop();
|
124 | }, { context: 'li' });
|
125 | this.listenTo(editor.editing.view.document, 'tab', (evt, data) => {
|
126 | const commandName = data.shiftKey ? 'outdentList' : 'indentList';
|
127 | const command = this.editor.commands.get(commandName);
|
128 | if (command.isEnabled) {
|
129 | editor.execute(commandName);
|
130 | data.stopPropagation();
|
131 | data.preventDefault();
|
132 | evt.stop();
|
133 | }
|
134 | }, { context: 'li' });
|
135 | }
|
136 | |
137 |
|
138 |
|
139 | afterInit() {
|
140 | const commands = this.editor.commands;
|
141 | const indent = commands.get('indent');
|
142 | const outdent = commands.get('outdent');
|
143 | if (indent) {
|
144 | indent.registerChildCommand(commands.get('indentList'));
|
145 | }
|
146 | if (outdent) {
|
147 | outdent.registerChildCommand(commands.get('outdentList'));
|
148 | }
|
149 | }
|
150 | }
|
151 | function getViewListItemLength(element) {
|
152 | let length = 1;
|
153 | for (const child of element.getChildren()) {
|
154 | if (child.name == 'ul' || child.name == 'ol') {
|
155 | for (const item of child.getChildren()) {
|
156 | length += getViewListItemLength(item);
|
157 | }
|
158 | }
|
159 | }
|
160 | return length;
|
161 | }
|