@alitons/ckeditor5 0.0.4 → 0.0.5

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.
@@ -0,0 +1,743 @@
1
+ import { Plugin, Command } from '@ckeditor/ckeditor5-core';
2
+ import { findOptimalInsertionRange } from '@ckeditor/ckeditor5-widget/src/utils';
3
+ import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
4
+
5
+ export default class NumberedDivList extends Plugin {
6
+ static get pluginName() {
7
+ return 'NumberedDivList';
8
+ }
9
+
10
+ init() {
11
+ const editor = this.editor;
12
+ const toolbar = editor.config.get('toolbar') as any;
13
+ const config = toolbar!.listaNumeradaOptions || {};
14
+
15
+ editor.model.schema.register('numList', {
16
+ allowWhere: '$block',
17
+ allowContentOf: '$root',
18
+ allowAttributesOf: '$block',
19
+ isBlock: true,
20
+ isLimit: true,
21
+ });
22
+
23
+ editor.model.schema.register('numItem', {
24
+ allowIn: 'numList',
25
+ allowContentOf: '$root',
26
+ allowAttributesOf: '$block',
27
+ isLimit: true,
28
+ });
29
+
30
+ const downcastAttr = (conv: any, key: 'dataStyle'|'dataBlock', viewKey: 'data-style'|'data-block') => {
31
+ conv.attributeToAttribute({
32
+ model: { name: 'numList', key },
33
+ view: (val: unknown) => {
34
+ if (val == null || val === '') return { key: viewKey, value: null };
35
+ return { key: viewKey, value: String(val) };
36
+ }
37
+ });
38
+ };
39
+ downcastAttr(editor.conversion.for('editingDowncast'), 'dataStyle', 'data-style');
40
+ downcastAttr(editor.conversion.for('dataDowncast'), 'dataStyle', 'data-style');
41
+ downcastAttr(editor.conversion.for('editingDowncast'), 'dataBlock', 'data-block');
42
+ downcastAttr(editor.conversion.for('dataDowncast'), 'dataBlock', 'data-block');
43
+
44
+ editor.conversion.for('dataDowncast').elementToElement({
45
+ model: 'numList',
46
+ view: (modelElement, { writer }) => {
47
+ const attrs: Record<string, string> = { class: 'num-list' };
48
+
49
+ const ds = modelElement.getAttribute('data-style');
50
+ if (ds != null) attrs['data-style'] = String(ds);
51
+
52
+ const db = modelElement.getAttribute('data-block');
53
+ if (db != null) attrs['data-block'] = String(db);
54
+
55
+ const sq = modelElement.getAttribute('start');
56
+ if (sq != null) attrs['start'] = String(sq);
57
+
58
+ return writer.createContainerElement('div', attrs)
59
+ }
60
+ });
61
+
62
+ editor.conversion.for('editingDowncast').elementToElement({
63
+ model: 'numList',
64
+ view: (modelElement, { writer }) => {
65
+ const attrs: Record<string, string> = { class: 'num-list' };
66
+
67
+ const ds = modelElement.getAttribute('data-style');
68
+ if (ds != null) attrs['data-style'] = String(ds);
69
+
70
+ const db = modelElement.getAttribute('data-block');
71
+ if (db != null) attrs['data-block'] = String(db);
72
+
73
+ const sq = modelElement.getAttribute('start');
74
+ if (sq != null) attrs['start'] = String(sq);
75
+
76
+ return writer.createContainerElement('div', attrs);
77
+ }
78
+ });
79
+
80
+ editor.conversion.for('dataDowncast').elementToElement({
81
+ model: 'numItem',
82
+ view: (modelElement, { writer }) =>
83
+ writer.createContainerElement('div', { class: 'num-li' })
84
+ });
85
+
86
+ editor.conversion.for('editingDowncast').elementToElement({
87
+ model: 'numItem',
88
+ view: (modelElement, { writer }) =>
89
+ writer.createContainerElement('div', { class: 'num-li' })
90
+ });
91
+
92
+ editor.conversion.for('upcast').elementToElement({
93
+ view: {
94
+ name: 'div',
95
+ classes: 'num-list',
96
+ },
97
+ model: (viewElement, { writer }) => {
98
+ console.log('upcast num-list', viewElement);
99
+ const attrs: any = {};
100
+
101
+ const ds = viewElement.getAttribute('data-style');
102
+ if (ds != null) attrs.dataStyle = ds;
103
+
104
+ const db = viewElement.getAttribute('data-block');
105
+ if (db != null) attrs.dataBlock = db;
106
+
107
+ const sq = viewElement.getAttribute('start');
108
+ if (sq != null) attrs.start = sq;
109
+
110
+ return writer.createElement('numList', attrs);
111
+ }
112
+ });
113
+
114
+ editor.conversion.for('upcast').elementToElement({
115
+ view: {
116
+ name: 'div',
117
+ classes: 'num-li'
118
+ },
119
+ model: (viewElement, { writer }) => writer.createElement('numItem')
120
+ });
121
+
122
+ editor.conversion.for('upcast').elementToElement({
123
+ view: { name: 'ol' },
124
+ model: (viewElement, { writer }) => writer.createElement('numList')
125
+ });
126
+
127
+ editor.conversion.for('upcast').elementToElement({
128
+ view: { name: 'li' },
129
+ model: (viewElement, { writer }) => writer.createElement('numItem')
130
+ });
131
+
132
+ const viewDoc = editor.editing.view.document;
133
+
134
+ const selectionSpansBlocks = (model: any) => {
135
+ const sel = model.document.selection;
136
+ if (sel.isCollapsed) return false;
137
+ const blocks = Array.from(sel.getSelectedBlocks());
138
+ return blocks.length > 1;
139
+ };
140
+
141
+ const selectionTouchesElements = (model: any) => {
142
+ const sel = model.document.selection;
143
+ if (sel.isCollapsed) return false;
144
+ const range = sel.getFirstRange();
145
+ for (const item of range.getItems()) {
146
+ if (item.is?.('element')) return true;
147
+ }
148
+ return false;
149
+ };
150
+
151
+ function getNearestSealedListFromPos(pos: any): any | null {
152
+ // ancestors: [root, ..., parent]
153
+ const ancestors = pos.getAncestors({ includeSelf: false }); // CKEditor retorna de root -> parent
154
+ // percorre de baixo pra cima (mais próximo primeiro)
155
+ for (let i = ancestors.length - 1; i >= 0; i--) {
156
+ const node = ancestors[i];
157
+ if (node.is?.('element', 'numList') && !!node.getAttribute?.('dataBlock')) {
158
+ return node;
159
+ }
160
+ }
161
+ return null;
162
+ }
163
+
164
+ // Verifica se `node` está DENTRO do `list` (qualquer profundidade)
165
+ function isInside(node: any, ancestor: any): boolean {
166
+ let p = node;
167
+ while (p) {
168
+ if (p === ancestor) return true;
169
+ p = p.parent;
170
+ }
171
+ return false;
172
+ }
173
+
174
+ const firstItem = (list: any) => list.getChild(0);
175
+ const lastItem = (list: any) => list.getChild(list.childCount - 1);
176
+
177
+ function ensureTypablePosInItem(writer: any, item: any, atEnd = false) {
178
+ let p = null;
179
+ for (const c of item.getChildren()) {
180
+ if (c.is?.('element', 'paragraph')) { p = c; break; }
181
+ }
182
+ if (!p) {
183
+ p = writer.createElement('paragraph');
184
+ writer.insert(p, writer.createPositionAt(item, 0));
185
+ }
186
+ return atEnd
187
+ ? writer.createPositionAt(p, 'end')
188
+ : writer.createPositionAt(p, 0);
189
+ }
190
+
191
+ editor.plugins.get('ClipboardPipeline').on('inputTransformation', (evt: any, data: any) => {
192
+ const { model } = editor;
193
+ const sel = model.document.selection;
194
+ const pos = sel.getFirstPosition();
195
+ if (!pos) return;
196
+
197
+ const sealed = getNearestSealedListFromPos(pos);
198
+ if (!sealed) return;
199
+
200
+ const item = pos.findAncestor('numItem');
201
+ if (item && isInside(item, sealed)) return; // já dentro: ok
202
+
203
+ // redireciona para o primeiro item do sealed
204
+ evt.stop();
205
+ model.change((writer: any) => {
206
+ const target = firstItem(sealed) || writer.createElement('numItem');
207
+ if (!firstItem(sealed)) {
208
+ writer.insert(target, writer.createPositionAt(sealed, 0));
209
+ }
210
+ const at = ensureTypablePosInItem(writer, target, false);
211
+ writer.setSelection(at);
212
+ // reaplica como texto simples (ajuste conforme sua pipeline)
213
+ const text = data.dataTransfer.getData('text/plain');
214
+ if (text) editor.execute('input', { text });
215
+ });
216
+ });
217
+
218
+
219
+ // não permitir que saia da lista caso exista o atributo data-block
220
+ viewDoc.on('keydown', (evt, data) => {
221
+ if (data.keyCode !== keyCodes.enter || data.shiftKey) return;
222
+ if (!editor.model.document.selection.isCollapsed) return;
223
+
224
+ if(config?.disableEnter === true) {
225
+ return;
226
+ }
227
+
228
+ const { model } = editor;
229
+ const pos = model.document.selection.getFirstPosition();
230
+ if (!pos) return;
231
+
232
+ const sealed = getNearestSealedListFromPos(pos);
233
+ if (!sealed) return; // só intercepta se estiver dentro de um sealed
234
+
235
+ data.preventDefault(); evt.stop();
236
+
237
+ model.change(writer => {
238
+ // deixa o enter nativo dividir o bloco
239
+ editor.execute('enter');
240
+
241
+ const posAfter = model.document.selection.getFirstPosition();
242
+ if (!posAfter) return;
243
+ const newBlock = posAfter.parent as any;
244
+ if (!newBlock) return;
245
+
246
+ // queremos transformar o novo bloco em um novo numItem,
247
+ // mantendo-o dentro de ALGUM numList que esteja dentro do `sealed` (o mais próximo)
248
+ // pega o numItem atual (mais próximo)
249
+ let currentItem = posAfter.findAncestor('numItem');
250
+
251
+ // se o bloco recém criado ficou fora de um numItem,
252
+ // cria um numItem irmão do atual (se existir) dentro do mesmo numList
253
+ if (!currentItem) {
254
+ const listForNew = getNearestSealedListFromPos(posAfter) || sealed;
255
+ const newItem = writer.createElement('numItem');
256
+ writer.insert(newItem, writer.createPositionAt(listForNew, 'end'));
257
+ writer.move(writer.createRangeOn(newBlock), writer.createPositionAt(newItem, 0));
258
+ writer.setSelection(ensureTypablePosInItem(writer, newItem, false));
259
+ return;
260
+ }
261
+
262
+ // garantir que este numItem pertence a um numList que está dentro do sealed mais próximo
263
+ let itsList = currentItem.parent; // deve ser um numList
264
+ if (!isInside(itsList, sealed)) {
265
+ // se por alguma razão o split empurrou pra fora, anexa de volta ao sealed
266
+ const fallback = firstItem(sealed) || null;
267
+ if (fallback) {
268
+ const newItem = writer.createElement('numItem');
269
+ writer.insert(newItem, writer.createPositionAfter(fallback));
270
+ writer.move(writer.createRangeOn(newBlock), writer.createPositionAt(newItem, 0));
271
+ writer.setSelection(ensureTypablePosInItem(writer, newItem, false));
272
+ } else {
273
+ // sealed vazio (raro): crie o primeiro item
274
+ const newItem = writer.createElement('numItem');
275
+ writer.insert(newItem, writer.createPositionAt(sealed, 0));
276
+ writer.move(writer.createRangeOn(newBlock), writer.createPositionAt(newItem, 0));
277
+ writer.setSelection(ensureTypablePosInItem(writer, newItem, false));
278
+ }
279
+ return;
280
+ }
281
+
282
+ // caso normal: cria irmão dentro do mesmo numList
283
+ const newItem = writer.createElement('numItem');
284
+ writer.insert(newItem, writer.createPositionAfter(currentItem));
285
+ writer.move(writer.createRangeOn(newBlock), writer.createPositionAt(newItem, 0));
286
+ writer.setSelection(ensureTypablePosInItem(writer, newItem, false));
287
+ });
288
+ }, { priority: 'high' });
289
+
290
+ // não permitir que backspace/delete saia da lista caso exista o atributo data-block
291
+ viewDoc.on(
292
+ 'keydown',
293
+ (evt, data) => {
294
+ const isBackspace = data.keyCode === keyCodes.backspace;
295
+ const isDelete = data.keyCode === keyCodes.delete;
296
+ if (!isBackspace && !isDelete) return;
297
+ if (!editor.model.document.selection.isCollapsed) return;
298
+
299
+ const model = editor.model;
300
+ const sel = model.document.selection;
301
+
302
+ if (!sel.isCollapsed && (selectionSpansBlocks(model) || selectionTouchesElements(model))) {
303
+ data.preventDefault();
304
+ evt.stop();
305
+
306
+ model.change( writer => {
307
+ const anchor = sel.anchor!;
308
+ writer.setSelection( anchor );
309
+ // apaga 1 caractere para trás/à frente (se existir)
310
+ if (isBackspace) {
311
+ // @ts-ignore
312
+ editor.execute('delete', { unit: 'character', direction: 'backward' });
313
+ } else {
314
+ // @ts-ignore
315
+ editor.execute('delete', { unit: 'character', direction: 'forward' });
316
+ }
317
+ });
318
+ return;
319
+ }
320
+
321
+ model.change(writer => {
322
+ const pos = model.document.selection.getFirstPosition();
323
+ if (!pos) return;
324
+
325
+ const sealed = getNearestSealedListFromPos(pos);
326
+ if (!sealed) return;
327
+
328
+ const numItem = pos.findAncestor('numItem');
329
+ const numList = pos.findAncestor('numList') as any;
330
+ const block = pos.parent as any;
331
+
332
+ if (!numItem || !block?.is?.('element') || !numList || numList.getAttribute('data-block')) return;
333
+
334
+
335
+ // é o primeiro bloco dentro do item?
336
+ const isFirstItem = sealed.getChild(0) === block;
337
+ const isLastItem = sealed.getChild(sealed.childCount - 1) === block;
338
+ const atStartOfBlock = pos.isAtStart && numItem.getChild(0) === block;
339
+ const atEndOfBlock = pos.isAtEnd && numItem.getChild(numItem.childCount - 1) === block;
340
+
341
+ if (isBackspace && isFirstItem && atStartOfBlock) {
342
+ data.preventDefault(); evt.stop();
343
+ writer.setSelection(ensureTypablePosInItem(writer, numItem, false)); // não deixa "sair"
344
+ return;
345
+ }
346
+
347
+ if (isDelete && isLastItem && atEndOfBlock) {
348
+ data.preventDefault(); evt.stop();
349
+ writer.setSelection(ensureTypablePosInItem(writer, numItem, true)); // idem
350
+ return;
351
+ }
352
+
353
+ if (isFirstItem) {
354
+ // Evita que o Delete padrão remova o container todo
355
+ data.preventDefault();
356
+ evt.stop();
357
+
358
+ // Estratégia: cria (se necessário) um parágrafo antes do numList e move o caret pra lá.
359
+ // Se já existir algo antes do numList e for bloco digitável, só posiciona o caret.
360
+ const before = writer.createPositionBefore(numItem);
361
+ const parent = before.parent;
362
+
363
+ // Se o pai aceita parágrafos diretamente e não há bloco imediatamente anterior,
364
+ // crie um parágrafo novo; do contrário, apenas posicione o caret.
365
+ let setToPos = before;
366
+
367
+ const prevSibling = numItem.previousSibling;
368
+ if (!prevSibling) {
369
+ const canParagraph =
370
+ // @ts-ignore
371
+ editor.model.schema.checkChild(parent, 'paragraph');
372
+
373
+ const hasText = Array.from(parent.getChildren()).some(child => {
374
+ // @ts-ignore
375
+ return child.is('element', 'paragraph') && child.getChild(0)?.is('text');
376
+ });
377
+
378
+ if (canParagraph) {
379
+ if (hasText) {
380
+ const paragraph = writer.createElement('paragraph');
381
+ writer.insert(paragraph, before);
382
+ setToPos = writer.createPositionAt(paragraph, 0);
383
+ } else {
384
+ writer.remove(numItem);
385
+ setToPos = before;
386
+ }
387
+ }
388
+ } else {
389
+ writer.remove(numItem);
390
+ setToPos = writer.createPositionAfter(prevSibling);
391
+ }
392
+
393
+ writer.setSelection(setToPos);
394
+
395
+ if (numList.childCount === 0 && !numList.getAttribute('data-block')) {
396
+ const aboveList = numList.findAncestor('numList');
397
+ const lastChild = aboveList ? Array.from(aboveList.getChildren()).pop() : null as any;
398
+ if (aboveList && lastChild) {
399
+ writer.setSelection(writer.createPositionAfter(lastChild));
400
+ }
401
+
402
+ writer.remove(numList);
403
+ }
404
+ }
405
+ });
406
+ },
407
+ { priority: 'high' }
408
+ );
409
+
410
+ /* Ação ao pressionar a tecla TAB */
411
+ viewDoc.on(
412
+ 'keydown',
413
+ (evt, data) => {
414
+ if (!(data.keyCode == keyCodes.tab && !data.shiftKey)) return;
415
+ if (!editor.model.document.selection.isCollapsed) return;
416
+
417
+ const model = editor.model;
418
+
419
+ const posBefore = model.document.selection.getFirstPosition();
420
+ const inItem = posBefore?.findAncestor('numItem');
421
+ const inList = posBefore?.findAncestor('numList');
422
+ if (!inItem || !inList) return; // fora da nossa lista -> deixa o Tab padrão agir
423
+
424
+ data.preventDefault();
425
+ evt.stop();
426
+
427
+ editor.execute('toggleNumberedDivList');
428
+ },
429
+ { priority: 'high' }
430
+ );
431
+
432
+ /* Ação ao pressionar as teclas Shift + Tab */
433
+ viewDoc.on(
434
+ 'keydown',
435
+ (evt, data) => {
436
+ if (data.keyCode !== keyCodes.tab || !data.shiftKey) return;
437
+ if (!editor.model.document.selection.isCollapsed) return;
438
+
439
+ const posBefore = editor.model.document.selection.getFirstPosition();
440
+ const inItem = posBefore?.findAncestor('numItem');
441
+ const inList = posBefore?.findAncestor('numList');
442
+ if (!inItem || !inList) return;
443
+
444
+ data.preventDefault();
445
+ evt.stop();
446
+
447
+ shiftTab(editor);
448
+
449
+ },
450
+ { priority: 'high' }
451
+ );
452
+
453
+
454
+ editor.model.document.on('change:data', () => {
455
+ executeForceList(editor, config);
456
+ });
457
+
458
+
459
+ editor.model.schema.extend('$text', {
460
+ allowAttributes: ['dataBlock', 'start']
461
+ });
462
+
463
+ // quando o editor for inicializado, dispara o change:data uma vez
464
+ editor.on('ready', () => {
465
+ executeForceList(editor, config);
466
+ });
467
+
468
+
469
+ // @ts-ignore
470
+ editor.commands.add('toggleNumberedDivList', new ToggleNumberedDivListCommand(editor));
471
+ }
472
+ }
473
+
474
+ class ToggleNumberedDivListCommand {
475
+ declare editor: any;
476
+ declare value: boolean;
477
+ declare isEnabled: boolean;
478
+
479
+ constructor(editor: any) {
480
+ this.editor = editor;
481
+ this.value = false;
482
+ this.isEnabled = true;
483
+ }
484
+
485
+ refresh() {
486
+ this.isEnabled = true;
487
+ }
488
+
489
+ execute(options?: { value?: string; start?: number } ): void {
490
+ const { value, start } = options || {};
491
+ const editor = this?.editor || null;
492
+ const model = editor.model;
493
+
494
+ const selection = model.document.selection;
495
+ const firstPos = selection.getFirstPosition();
496
+ const existingItem = firstPos?.findAncestor('numItem');
497
+ let existingList = firstPos?.findAncestor('numList');
498
+
499
+ if(value === 'recuar') {
500
+ if (!existingItem || !existingList) return;
501
+ if(value === 'recuar') return shiftTab(editor);
502
+ }
503
+
504
+ if(value !== undefined) {
505
+ const block = firstPos.parent as any;
506
+
507
+ if(existingItem?.getChild(0) === block) {
508
+ return editor.execute('toggleNumberedDivList', { value: undefined, start: start });
509
+ }
510
+ }
511
+
512
+ model.change((writer: any) => {
513
+ const paragraphAbove = firstPos?.findAncestor('paragraph');
514
+ const firstParagraphItem = existingItem ? existingItem?.getChild(0) : null;
515
+
516
+ console.log(firstParagraphItem, paragraphAbove);
517
+
518
+ if (value == undefined && firstParagraphItem == paragraphAbove) {
519
+ const prevItem = existingItem?.previousSibling ?? null;
520
+ if (prevItem) {
521
+ writer.setSelection(writer.createPositionAt(prevItem, 'end'));
522
+ const items: any[] = [];
523
+ for (const child of existingItem.getChildren()) {
524
+ items.push(child);
525
+ }
526
+ for (const item of items) {
527
+ while (item.childCount > 0) {
528
+ const child = item?.getChild(0);
529
+ const rangeOnChild = writer.createRangeOn(child);
530
+ const paragraph = writer.createElement('paragraph');
531
+ writer.insert(paragraph, writer.createPositionAt(prevItem, 'end'));
532
+ writer.move(rangeOnChild, writer.createPositionAt(paragraph, 'end'));
533
+ }
534
+ }
535
+
536
+ writer.remove(existingItem);
537
+
538
+ const lastParagraph = prevItem?.getChild(prevItem.childCount - 1);
539
+
540
+ // coloca o cursor no final do novo item
541
+ // @ts-ignore
542
+ writer.setSelection(writer.createPositionAt(lastParagraph, 'end'));
543
+
544
+ return editor.execute('toggleNumberedDivList', { value: value ?? 'decimal', start: start });
545
+ }
546
+ return;
547
+ }
548
+
549
+ // VERIFICA SE EXISTE UMA NUMLIST ACIMA DESSA SELEÇÃO
550
+ let parent = firstPos.parent;
551
+
552
+ while (parent) {
553
+ if (parent.is('element', 'numList')) {
554
+ existingList = parent;
555
+ break;
556
+ }
557
+ parent = parent.parent;
558
+ }
559
+
560
+ const blocks = Array.from(selection.getSelectedBlocks());
561
+
562
+ if (!blocks.length) {
563
+ const insertRange = findOptimalInsertionRange(selection, model);
564
+ const numList = writer.createElement('numList');
565
+ const numItem = writer.createElement('numItem');
566
+ writer.insert(numList, insertRange.start);
567
+ writer.insert(numItem, writer.createPositionAt(numList, 0));
568
+ const paragraph = writer.createElement('paragraph');
569
+ writer.insert(paragraph, writer.createPositionAt(numItem, 0));
570
+ writer.setSelection(paragraph, 'in');
571
+ return;
572
+ }
573
+
574
+ if (existingList && value === undefined) {
575
+ const insertPos = writer.createPositionBefore(paragraphAbove);
576
+ for (const block of blocks) {
577
+ const numItem = writer.createElement('numItem');
578
+ writer.insert(numItem, insertPos);
579
+ writer.move(writer.createRangeOn(block), writer.createPositionAt(numItem, 0));
580
+ }
581
+ return;
582
+ }
583
+
584
+ // verifica se existe algum numList com data-first dentro de todo o conteúdo selecionado
585
+ let first = true;
586
+ for (const block of blocks) {
587
+ // @ts-ignore
588
+ const foundList = block.findAncestor('numList');
589
+ if (foundList) {
590
+ first = false;
591
+ break;
592
+ }
593
+ }
594
+
595
+ const firstBlock = blocks[0];
596
+ const listPos = writer.createPositionBefore(firstBlock);
597
+ const numList = writer.createElement('numList', {
598
+ ...(first && !value ? { 'data-block': 'true' } : {}),
599
+ ...(first && !start ? { 'start': start } : {}),
600
+ ...((value && value !== 'recuar' && value !== 'decimal') ? { 'data-style': value } : {})
601
+ });
602
+
603
+ writer.insert(numList, listPos);
604
+
605
+ for (const block of blocks) {
606
+ const numItem = writer.createElement('numItem');
607
+ writer.insert(numItem, writer.createPositionAt(numList, 'end'));
608
+ writer.move(writer.createRangeOn(block), writer.createPositionAt(numItem, 0));
609
+ }
610
+ });
611
+ }
612
+ }
613
+
614
+ function shiftTab(editor: any) {
615
+ const model = editor.model;
616
+
617
+ const posBefore = model.document.selection.getFirstPosition();
618
+ const inItem = posBefore?.findAncestor('numItem');
619
+ const inList = posBefore?.findAncestor('numList');
620
+ const parentItem = inList.findAncestor('numItem') ?? null;
621
+ const db = inList.getAttribute('dataBlock');
622
+
623
+ model.change((writer: any) => {
624
+
625
+ if (parentItem) {
626
+ const insertPos = writer.createPositionAfter(parentItem);
627
+ const newItem = writer.createElement('numItem');
628
+ writer.insert(newItem, insertPos);
629
+ const items: any[] = [];
630
+ for (const child of inItem.getChildren()) {
631
+ items.push(child);
632
+ }
633
+ for (const item of items) {
634
+ while (item.childCount > 0) {
635
+ const child = item.getChild(0);
636
+ const rangeOnChild = writer.createRangeOn(child);
637
+ const paragraph = writer.createElement('paragraph');
638
+ writer.insert(paragraph, writer.createPositionAt(newItem, 'end'));
639
+ writer.move(rangeOnChild, writer.createPositionAt(paragraph, 'end'));
640
+ }
641
+ }
642
+
643
+ const lastChild = newItem.getChild(newItem.childCount - 1);
644
+ if (lastChild) {
645
+ // @ts-ignore
646
+ writer.setSelection(writer.createPositionAt(lastChild, 'end'));
647
+ } else {
648
+ writer.setSelection(writer.createPositionAt(newItem, 'end'));
649
+ }
650
+ } else {
651
+ const prevList = db ? inList : inItem.previousSibling;
652
+
653
+ if ((prevList && prevList.is('element', 'numItem')) || db) {
654
+ const items: any[] = [];
655
+ for (const child of inItem.getChildren()) {
656
+ items.push(child);
657
+ }
658
+ for (const item of items) {
659
+ while (item.childCount > 0) {
660
+ const child = item.getChild(0);
661
+ const rangeOnChild = writer.createRangeOn(child);
662
+ const paragraph = writer.createElement('paragraph');
663
+ writer.insert(paragraph, writer.createPositionAt(prevList, 'end'));
664
+ writer.move(rangeOnChild, writer.createPositionAt(paragraph, 'end'));
665
+ }
666
+ }
667
+
668
+ const lastChild = prevList.getChild(prevList.childCount - 1);
669
+ if (lastChild) {
670
+ // @ts-ignore
671
+ writer.setSelection(writer.createPositionAt(lastChild, 'end'));
672
+ } else {
673
+ writer.setSelection(writer.createPositionAt(prevList, 'end'));
674
+ }
675
+
676
+ } else {
677
+ const insertPos = db ? writer.createPositionAt(inList, 'end') : writer.createPositionAfter(inList);
678
+
679
+ const items: any[] = [];
680
+ for (const child of inItem.getChildren()) {
681
+ items.push(child);
682
+ }
683
+ for (const item of items) {
684
+ while (item.childCount > 0) {
685
+ const child = item.getChild(0);
686
+ const rangeOnChild = writer.createRangeOn(child);
687
+ const paragraph = writer.createElement('paragraph');
688
+ writer.insert(paragraph, insertPos);
689
+ writer.move(rangeOnChild, writer.createPositionAt(paragraph, 'end'));
690
+ }
691
+ }
692
+
693
+ }
694
+
695
+ const lastChild = prevList ? prevList.getChild(prevList.childCount - 1) : null;
696
+ if (lastChild) {
697
+ // @ts-ignore
698
+ writer.setSelection(writer.createPositionAt(lastChild, 'end'));
699
+ } else {
700
+ writer.setSelection(writer.createPositionAt(inList, 'end'));
701
+ }
702
+ }
703
+
704
+ writer.remove(inItem);
705
+
706
+
707
+ if (inList.childCount === 0) {
708
+ if (!db) writer.remove(inList);
709
+ }
710
+
711
+ });
712
+ }
713
+
714
+ function executeForceList(editor: any, config: any) {
715
+ const model = editor.model;
716
+ const firstElement = editor.model.document.getRoot().getChild(0) as any;
717
+ if(config?.forceList && firstElement && firstElement.name !== 'numList') {
718
+ const numList = model.change( (writer: any) => {
719
+ const insertPos = writer.createPositionAt(model.document.getRoot(), 0);
720
+ const numList = writer.createElement('numList', {
721
+ 'data-block': 'true',
722
+ 'start': config?.forceList ? config.forceList + 1 : null
723
+ });
724
+ writer.insert(numList, insertPos);
725
+ return numList;
726
+ });
727
+
728
+ model.change( (writer: any) => {
729
+ const root = model.document.getRoot();
730
+ const itemsToMove = [];
731
+ for ( const child of root.getChildren() ) {
732
+ if ( child !== numList ) {
733
+ itemsToMove.push(child);
734
+ }
735
+ }
736
+ for ( const item of itemsToMove ) {
737
+ const numItem = writer.createElement('numList');
738
+ writer.insert( numItem, writer.createPositionAt( numList, 'end' ) );
739
+ writer.move( writer.createRangeOn( item ), writer.createPositionAt( numItem, 0 ) );
740
+ }
741
+ });
742
+ }
743
+ }