@alitons/ckeditor5 0.0.3 → 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.
- package/README.md +33 -0
- package/build/ckeditor.d.ts +7 -13
- package/build/ckeditor.js +2 -2
- package/build/ckeditor.js.map +1 -1
- package/build/components/list/listitemview.d.ts +15 -0
- package/build/components/list/listseparatorview.d.ts +9 -0
- package/build/components/list/listview.d.ts +25 -0
- package/build/plugins/NumberedDivListSplit.d.ts +6 -0
- package/build/plugins/listaNumerada-bkp.d.ts +5 -0
- package/build/plugins/listaNumerada.d.ts +5 -0
- package/package.json +3 -2
- package/sample/index.html +12 -2
- package/sample/script.js +44 -17
- package/src/ckeditor.ts +7 -2
- package/src/components/list/listitemview.d.ts +35 -0
- package/src/components/list/listitemview.js +42 -0
- package/src/components/list/listview.d.ts +65 -0
- package/src/components/list/listview.js +92 -0
- package/src/css/custom.css +87 -0
- package/src/plugins/NumberedDivListSplit.ts +174 -0
- package/src/plugins/listaNumerada.ts +743 -0
- package/src/plugins/modelo.ts +102 -102
|
@@ -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
|
+
}
|