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