@alitons/ckeditor5 0.0.29 → 0.0.31

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,389 @@
1
+ // @ts-nocheck
2
+ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
3
+ import Command from '@ckeditor/ckeditor5-core/src/command';
4
+
5
+ import Widget from '@ckeditor/ckeditor5-widget/src/widget';
6
+ import { toWidget, viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget/src/utils';
7
+
8
+ import Collection from '@ckeditor/ckeditor5-utils/src/collection';
9
+ import Model from '@ckeditor/ckeditor5-ui/src/model';
10
+ import { createDropdown, addListToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
11
+
12
+ export default class DocumentosPlugin extends Plugin {
13
+ static get pluginName() {
14
+ return 'DocumentosPlugin';
15
+ }
16
+
17
+ static get requires() {
18
+ return [ Widget ];
19
+ }
20
+
21
+ async init() {
22
+ const editor = this.editor;
23
+
24
+ this._lastHtmlBySourceId = new Map();
25
+ this._pendingUpdates = new Set();
26
+ this._itemsBySourceId = new Map();
27
+ this._getHtmlBySourceId = new Map();
28
+
29
+ const cfg = editor.config.get('inserirDocumento') || {};
30
+ const dropdownLabel = cfg.dropdownLabel || 'Inserir Documento';
31
+ const loadingLabel = cfg.loadingLabel || 'Carregando...';
32
+ const getItems = typeof cfg.getItems === 'function' ? cfg.getItems : null;
33
+
34
+ editor.model.schema.register('inserirDocumento', {
35
+ isObject: true,
36
+ isBlock: true,
37
+ allowWhere: '$block',
38
+ allowAttributes: [ 'sourceId', 'htmlB64' ]
39
+ });
40
+
41
+ editor.editing.mapper.on(
42
+ 'viewToModelPosition',
43
+ viewToModelPositionOutsideModelElement(
44
+ editor.model,
45
+ viewElement => viewElement.hasClass('ck-conteudo-dinamico')
46
+ )
47
+ );
48
+
49
+ editor.conversion.for('upcast').elementToElement({
50
+ view: {
51
+ name: 'div',
52
+ attributes: { 'data-conteudo-dinamico': '1' }
53
+ },
54
+ model: (viewElement, { writer }) => {
55
+ const sourceId = viewElement.getAttribute('data-source-id') || '';
56
+ const htmlB64 = viewElement.getAttribute('data-html') || '';
57
+
58
+ return writer.createElement('inserirDocumento', { sourceId, htmlB64 });
59
+ }
60
+ });
61
+
62
+ editor.conversion.for('dataDowncast').elementToElement({
63
+ model: 'inserirDocumento',
64
+ view: (modelItem, { writer }) => {
65
+ const sourceId = modelItem.getAttribute('sourceId') || '';
66
+ const htmlB64 = modelItem.getAttribute('htmlB64') || '';
67
+ const html = decodeHtml(htmlB64) || defaultHtml();
68
+
69
+ return writer.createRawElement(
70
+ 'div',
71
+ {
72
+ class: 'ck-conteudo-dinamico',
73
+ 'data-conteudo-dinamico': '1',
74
+ 'data-source-id': sourceId,
75
+ 'data-html': htmlB64
76
+ },
77
+ domElement => {
78
+ domElement.innerHTML = html;
79
+ }
80
+ );
81
+ }
82
+ });
83
+
84
+ editor.conversion.for('editingDowncast').elementToElement({
85
+ model: 'inserirDocumento',
86
+ view: (modelItem, { writer }) => {
87
+ const sourceId = modelItem.getAttribute('sourceId') || '';
88
+ const htmlB64 = modelItem.getAttribute('htmlB64') || '';
89
+ const html = decodeHtml(htmlB64) || defaultHtml();
90
+
91
+ const meta = this._itemsBySourceId.get(sourceId);
92
+ const widgetLabel = meta?.label || sourceId || 'Conteúdo dinâmico';
93
+
94
+ const container = writer.createContainerElement('div', {
95
+ class: 'ck-conteudo-dinamico',
96
+ 'data-conteudo-dinamico': '1',
97
+ 'data-source-id': sourceId
98
+ });
99
+
100
+ if (meta?.showLabel !== false) {
101
+ const header = writer.createContainerElement('div', {
102
+ class: 'ck-conteudo-dinamico__header'
103
+ });
104
+
105
+ if (meta?.iconSvg) {
106
+ const icon = writer.createRawElement(
107
+ 'span',
108
+ { class: 'ck-conteudo-dinamico__icon' },
109
+ domEl => {
110
+ domEl.innerHTML = meta.iconSvg;
111
+ }
112
+ );
113
+ writer.insert(writer.createPositionAt(header, 'end'), icon);
114
+ }
115
+
116
+ if (meta?.label) {
117
+ const title = writer.createContainerElement('span', {
118
+ class: 'ck-conteudo-dinamico__title'
119
+ });
120
+ writer.insert(writer.createPositionAt(title, 0), writer.createText(meta.label));
121
+ writer.insert(writer.createPositionAt(header, 'end'), title);
122
+ }
123
+
124
+ writer.insert(writer.createPositionAt(container, 'end'), header);
125
+ }
126
+
127
+ const raw = writer.createRawElement(
128
+ 'div',
129
+ { class: 'ck-conteudo-dinamico__inner' },
130
+ domElement => {
131
+ domElement.innerHTML = html;
132
+ }
133
+ );
134
+
135
+ writer.insert(writer.createPositionAt(container, 'end'), raw);
136
+
137
+ return toWidget(container, writer, { label: widgetLabel });
138
+ }
139
+ });
140
+
141
+ editor.commands.add('insertInserirDocumento', new InsertInserirDocumentoCommand(editor));
142
+
143
+ editor.model.document.on('change:data', () => {
144
+ if (!this._lastHtmlBySourceId.size) return;
145
+
146
+ for (const [ sourceId ] of this._lastHtmlBySourceId.entries()) {
147
+ this._pendingUpdates.add(sourceId);
148
+ }
149
+
150
+ flushPendingUpdates(editor, this);
151
+ });
152
+
153
+ let initialItems = [];
154
+
155
+ try {
156
+ if (getItems) {
157
+ const result = await getItems();
158
+ initialItems = Array.isArray(result) ? result : [];
159
+ }
160
+ } catch (error) {
161
+ console.error('Erro ao carregar lista de documentos na inicialização', error);
162
+ initialItems = [];
163
+ }
164
+
165
+ for (const item of initialItems) {
166
+ if (!item?.id) continue;
167
+
168
+ const sourceId = String(item.id);
169
+
170
+ this._itemsBySourceId.set(sourceId, {
171
+ sourceId,
172
+ label: item.label || sourceId,
173
+ iconSvg: item.iconSvg,
174
+ showLabel: item.showLabel
175
+ });
176
+
177
+ if (typeof item.getHtml === 'function') {
178
+ this._getHtmlBySourceId.set(sourceId, item.getHtml);
179
+ } else if (typeof item.html === 'string') {
180
+ this._getHtmlBySourceId.set(sourceId, async () => item.html);
181
+ } else if (typeof item.text === 'string') {
182
+ this._getHtmlBySourceId.set(sourceId, async () => `<p>${escapeHtml(item.text)}</p>`);
183
+ }
184
+ }
185
+
186
+ editor.ui.componentFactory.add('insertDocumento', locale => {
187
+ const dropdown = createDropdown(locale);
188
+
189
+ dropdown.buttonView.set({
190
+ label: dropdownLabel,
191
+ tooltip: true,
192
+ withText: true,
193
+ isVisible: initialItems.length > 0
194
+ });
195
+
196
+ const listItems = new Collection();
197
+ addListToDropdown(dropdown, listItems);
198
+
199
+ for (const item of initialItems) {
200
+ if (!item?.id) continue;
201
+
202
+ const sourceId = String(item.id);
203
+
204
+ listItems.add({
205
+ type: 'button',
206
+ model: new Model({
207
+ label: item.label || sourceId,
208
+ withText: true,
209
+ documentItemId: sourceId
210
+ })
211
+ });
212
+ }
213
+
214
+ dropdown.on('execute', async evt => {
215
+ const baseSourceId = evt.source.documentItemId;
216
+
217
+ if (!baseSourceId) return;
218
+
219
+ const getHtmlFn = this._getHtmlBySourceId.get(baseSourceId);
220
+ if (!getHtmlFn) return;
221
+
222
+ const sourceId = createInstanceSourceId(baseSourceId);
223
+ const meta = this._itemsBySourceId.get(baseSourceId);
224
+
225
+ if (meta) {
226
+ this._itemsBySourceId.set(sourceId, {
227
+ ...meta,
228
+ sourceId
229
+ });
230
+ }
231
+
232
+ try {
233
+ editor.execute('insertInserirDocumento', {
234
+ sourceId,
235
+ html: loadingDotsHtml('Aguarde...')
236
+ });
237
+ editor.editing.view.focus();
238
+
239
+ const html = await getHtmlFn();
240
+
241
+ this.updateBySourceId(sourceId, html);
242
+ } catch (error) {
243
+ console.error('Erro ao carregar conteúdo do documento', error);
244
+
245
+ this.updateBySourceId(
246
+ sourceId,
247
+ `
248
+ <div class="ck-conteudo-dinamico__error" contenteditable="false">
249
+ Não foi possível carregar o conteúdo.
250
+ </div>
251
+ `
252
+ );
253
+ }
254
+ });
255
+
256
+ return dropdown;
257
+ });
258
+
259
+ this.updateBySourceId = (sourceId, html) => {
260
+ this._lastHtmlBySourceId.set(sourceId, html);
261
+ this._pendingUpdates.add(sourceId);
262
+ flushPendingUpdates(editor, this);
263
+ };
264
+
265
+ function flushPendingUpdates(editor, plugin) {
266
+ if (!plugin._pendingUpdates.size) return;
267
+
268
+ const ids = Array.from(plugin._pendingUpdates);
269
+ plugin._pendingUpdates.clear();
270
+
271
+ for (const sourceId of ids) {
272
+ const html = plugin._lastHtmlBySourceId.get(sourceId);
273
+ if (html == null) continue;
274
+ updateConteudo(editor, sourceId, html);
275
+ }
276
+ }
277
+ }
278
+ }
279
+
280
+ class InsertInserirDocumentoCommand extends Command {
281
+ execute({ sourceId, html }) {
282
+ const editor = this.editor;
283
+ const htmlB64 = encodeHtml(html);
284
+
285
+ editor.model.change(writer => {
286
+ const element = writer.createElement('inserirDocumento', {
287
+ sourceId,
288
+ htmlB64
289
+ });
290
+
291
+ editor.model.insertContent(element);
292
+ writer.setSelection(element, 'on');
293
+ });
294
+ }
295
+
296
+ refresh() {
297
+ const model = this.editor.model;
298
+ const selection = model.document.selection;
299
+ const allowedIn = model.schema.findAllowedParent(
300
+ selection.getFirstPosition(),
301
+ 'inserirDocumento'
302
+ );
303
+
304
+ this.isEnabled = !!allowedIn;
305
+ }
306
+ }
307
+
308
+ function updateConteudo(editor, sourceId, html) {
309
+ const newHtmlB64 = encodeHtml(html);
310
+
311
+ editor.model.change(writer => {
312
+ const root = editor.model.document.getRoot();
313
+
314
+ const targets = [];
315
+ for (const item of editor.model.createRangeIn(root).getItems()) {
316
+ if (
317
+ item.is('element', 'inserirDocumento') &&
318
+ item.getAttribute('sourceId') === sourceId
319
+ ) {
320
+ targets.push(item);
321
+ }
322
+ }
323
+
324
+ for (const item of targets) {
325
+ const currentB64 = item.getAttribute('htmlB64') || '';
326
+ if (currentB64 === newHtmlB64) continue;
327
+
328
+ const wasSelected = editor.model.document.selection.getSelectedElement() === item;
329
+ const insertPos = writer.createPositionBefore(item);
330
+
331
+ const attrs = Object.fromEntries(item.getAttributes());
332
+ attrs.htmlB64 = newHtmlB64;
333
+
334
+ writer.remove(item);
335
+
336
+ const newElement = writer.createElement('inserirDocumento', attrs);
337
+ writer.insert(newElement, insertPos);
338
+
339
+ if (wasSelected) {
340
+ writer.setSelection(newElement, 'on');
341
+ }
342
+ }
343
+ });
344
+ }
345
+
346
+ function defaultHtml() {
347
+ return '';
348
+ }
349
+
350
+ function encodeHtml(str) {
351
+ try {
352
+ return btoa(unescape(encodeURIComponent(str)));
353
+ } catch {
354
+ return '';
355
+ }
356
+ }
357
+
358
+ function decodeHtml(b64) {
359
+ if (!b64) return '';
360
+ try {
361
+ return decodeURIComponent(escape(atob(b64)));
362
+ } catch {
363
+ return '';
364
+ }
365
+ }
366
+
367
+ function escapeHtml(str = '') {
368
+ return String(str)
369
+ .replaceAll('&', '&amp;')
370
+ .replaceAll('<', '&lt;')
371
+ .replaceAll('>', '&gt;')
372
+ .replaceAll('"', '&quot;')
373
+ .replaceAll("'", '&#39;');
374
+ }
375
+
376
+ function loadingDotsHtml(label = '') {
377
+ return `
378
+ <div class="ck-conteudo-dinamico__loading" contenteditable="false">
379
+ <span class="ck-conteudo-dinamico__dots" aria-hidden="true">
380
+ <span></span><span></span><span></span>
381
+ </span>
382
+ <span class="ck-conteudo-dinamico__loading-text">${escapeHtml(label)}</span>
383
+ </div>
384
+ `;
385
+ }
386
+
387
+ function createInstanceSourceId(baseId) {
388
+ return `${baseId}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
389
+ }