@alitons/ckeditor5 0.0.29 → 0.0.30

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,7 @@
1
+ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
2
+ import Widget from '@ckeditor/ckeditor5-widget/src/widget';
3
+ export default class ConteudoDinamicoPlugin extends Plugin {
4
+ static get pluginName(): string;
5
+ static get requires(): (typeof Widget)[];
6
+ init(): void;
7
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@alitons/ckeditor5",
3
3
  "author": "Aliton Silva",
4
4
  "description": "Ckeditor 5 Personalizado adicionados campos para atender as demandas da SEAD/AC",
5
- "version": "0.0.29",
5
+ "version": "0.0.30",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
7
7
  "private": false,
8
8
  "main": "./build/ckeditor.js",
package/sample/index.html CHANGED
@@ -22,9 +22,12 @@
22
22
  <div class="editor" ready="initEditorPage"></div>
23
23
  </div>
24
24
  </div>
25
- <div class="col-12">
25
+ <div class="col-3">
26
26
  <button id="get-html">Obter conteúdo HTML</button>
27
27
  </div>
28
+ <div class="col-3">
29
+ <button id="update-content">UPDATE</button>
30
+ </div>
28
31
  </div>
29
32
 
30
33
  <p class="item1">conteudo 1</p><p class="item2">subconteudo</p>
package/sample/script.js CHANGED
@@ -6,12 +6,20 @@ watchdog.setCreator( ( element, config ) => {
6
6
  .then( editor => {
7
7
  document.querySelector( '.document-editor__toolbar' ).appendChild( editor.ui.view.toolbar.element );
8
8
  document.querySelector( '.ck-toolbar' ).classList.add( 'ck-reset_all' );
9
-
10
9
  return editor;
11
10
  });
12
11
  });
13
12
 
14
13
  watchdog.create( document.querySelector( '.editor' ), {
14
+ conteudoDinamico: {
15
+ buttons: [
16
+ {
17
+ name: 'produtos',
18
+ label: 'Produtos',
19
+ sourceId: 'tabela:produtos',
20
+ }
21
+ ]
22
+ },
15
23
  toolbar: {
16
24
  autoTextoOptions: {
17
25
  ajax: {
@@ -95,7 +103,7 @@ watchdog.create( document.querySelector( '.editor' ), {
95
103
  'link',
96
104
  'bulletedList',
97
105
  'numberedList',
98
- 'numberedDivListSplit',
106
+ // 'numberedDivListSplit',
99
107
  '|',
100
108
  'undo',
101
109
  'redo',
@@ -105,7 +113,9 @@ watchdog.create( document.querySelector( '.editor' ), {
105
113
  '|',
106
114
  'autotexto',
107
115
  'importarsei',
108
- 'documentoModelo'
116
+ 'documentoModelo',
117
+ '|',
118
+ 'conteudoDinamico:produtos'
109
119
  ]
110
120
  },
111
121
  heading: {
@@ -151,4 +161,16 @@ document.getElementById('get-html').addEventListener('click', () => {
151
161
  const htmlContent = editor.getData();
152
162
  const htmlTextarea = document.querySelector('.html textarea');
153
163
  htmlTextarea.value = htmlContent;
164
+ });
165
+
166
+ document.getElementById('update-content').addEventListener('click', () => {
167
+ const editor = watchdog.editor;
168
+ const conteudoDinamico = editor.plugins.get('ConteudoDinamicoPlugin');
169
+ conteudoDinamico.updateBySourceId('tabela:produtos', `<table border="1" cellpadding="5" cellspacing="0">
170
+ <tr>
171
+ <th>Produto</th>
172
+ <th>Preço</th>
173
+ <th>Quantidade</th>
174
+ </tr>
175
+ </table>`);
154
176
  });
package/src/ckeditor.ts CHANGED
@@ -90,6 +90,7 @@ import SalvarComo from './plugins/salvarcomo';
90
90
  import HeadingUiClassPlugin from './plugins/HeadingUiClassPlugin';
91
91
  // import NumberedDivList from './plugins/listaNumerada';
92
92
  // import NumberedDivListSplit from './plugins/NumberedDivListSplit';
93
+ import ConteudoDinamicoPlugin from './plugins/ConteudoDinamicoPlugin';
93
94
 
94
95
  import './css/custom.css';
95
96
  import './css/sei.css';
@@ -175,6 +176,7 @@ class Editor extends DecoupledEditor {
175
176
  DocumentoModelo,
176
177
  SalvarComo,
177
178
  HeadingUiClassPlugin,
179
+ ConteudoDinamicoPlugin,
178
180
  // NumberedDivList,
179
181
  // NumberedDivListSplit
180
182
  ];
@@ -256,6 +258,7 @@ class Editor extends DecoupledEditor {
256
258
  'documentoModelo',
257
259
  'salvarcomo',
258
260
  // 'numberedDivListSplit'
261
+ 'conteudoDinamico'
259
262
  ],
260
263
  shouldNotGroupWhenFull: true
261
264
  },
@@ -0,0 +1,332 @@
1
+ // @ts-nocheck
2
+ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
3
+ import Command from '@ckeditor/ckeditor5-core/src/command';
4
+
5
+ import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
6
+
7
+ import Widget from '@ckeditor/ckeditor5-widget/src/widget';
8
+ import { toWidget, viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget/src/utils';
9
+
10
+ export default class ConteudoDinamicoPlugin extends Plugin {
11
+ static get pluginName() {
12
+ return 'ConteudoDinamicoPlugin';
13
+ }
14
+
15
+ static get requires() {
16
+ return [ Widget ];
17
+ }
18
+
19
+ init() {
20
+ const editor = this.editor;
21
+
22
+ this._lastHtmlBySourceId = new Map();
23
+ this._pendingUpdates = new Set();
24
+
25
+ /* ================= CONFIG ================= */
26
+ const cfg = editor.config.get('conteudoDinamico') || {};
27
+ const buttons = Array.isArray(cfg.buttons) ? cfg.buttons : [];
28
+
29
+ /* ================= SCHEMA ================= */
30
+ editor.model.schema.register('conteudoDinamico', {
31
+ isObject: true,
32
+ isBlock: true,
33
+ allowWhere: '$block',
34
+ allowAttributes: ['sourceId', 'htmlB64']
35
+ });
36
+
37
+ editor.editing.mapper.on(
38
+ 'viewToModelPosition',
39
+ viewToModelPositionOutsideModelElement(editor.model, viewElement =>
40
+ viewElement.hasClass('ck-conteudo-dinamico')
41
+ )
42
+ );
43
+
44
+ /* ================= UPCAST ================= */
45
+ editor.conversion.for('upcast').elementToElement({
46
+ view: {
47
+ name: 'div',
48
+ attributes: { 'data-conteudo-dinamico': '1' }
49
+ },
50
+ model: (viewElement, { writer }) => {
51
+ const sourceId = viewElement.getAttribute('data-source-id') || '';
52
+ const htmlB64 = viewElement.getAttribute('data-html') || '';
53
+
54
+ return writer.createElement('conteudoDinamico', { sourceId, htmlB64 });
55
+ }
56
+ });
57
+
58
+ /* ================= DATA DOWNCAST ================= */
59
+ editor.conversion.for('dataDowncast').elementToElement({
60
+ model: 'conteudoDinamico',
61
+ view: (modelItem, { writer }) => {
62
+ const sourceId = modelItem.getAttribute('sourceId') || '';
63
+ const htmlB64 = modelItem.getAttribute('htmlB64') || '';
64
+ const html = decodeHtml(htmlB64) || defaultHtml();
65
+
66
+ return writer.createRawElement(
67
+ 'div',
68
+ {
69
+ class: 'ck-conteudo-dinamico',
70
+ 'data-conteudo-dinamico': '1',
71
+ 'data-source-id': sourceId,
72
+ 'data-html': htmlB64
73
+ },
74
+ domElement => {
75
+ domElement.innerHTML = html;
76
+ }
77
+ );
78
+ }
79
+ });
80
+
81
+ /* ================= EDITING DOWNCAST ================= */
82
+ editor.conversion.for('editingDowncast').elementToElement({
83
+ model: 'conteudoDinamico',
84
+ view: (modelItem, { writer }) => {
85
+ const sourceId = modelItem.getAttribute('sourceId') || '';
86
+ const htmlB64 = modelItem.getAttribute('htmlB64') || '';
87
+ const html = decodeHtml(htmlB64) || defaultHtml();
88
+
89
+ // Label dinâmico por sourceId (usa config.buttons)
90
+ const meta = getButtonMetaBySourceId(buttons, sourceId);
91
+ const widgetLabel = meta?.label || sourceId || 'Conteúdo dinâmico';
92
+
93
+ // ContainerElement (widget)
94
+ const container = writer.createContainerElement('div', {
95
+ class: 'ck-conteudo-dinamico',
96
+ 'data-conteudo-dinamico': '1',
97
+ 'data-source-id': sourceId
98
+ });
99
+
100
+ // Header visual opcional (ícone + texto) — fica fora do raw, então não quebra toWidget
101
+ if ( meta?.showLabel ) {
102
+ const header = writer.createContainerElement('div', {
103
+ class: 'ck-conteudo-dinamico__header'
104
+ });
105
+
106
+ if (meta?.iconSvg) {
107
+ const icon = writer.createRawElement(
108
+ 'span',
109
+ { class: 'ck-conteudo-dinamico__icon' },
110
+ domEl => { domEl.innerHTML = meta.iconSvg; }
111
+ );
112
+ writer.insert(writer.createPositionAt(header, 'end'), icon);
113
+ }
114
+
115
+ if (meta?.label) {
116
+ const title = writer.createContainerElement('span', {
117
+ class: 'ck-conteudo-dinamico__title'
118
+ });
119
+ writer.insert(writer.createPositionAt(title, 0), writer.createText(meta.label));
120
+ writer.insert(writer.createPositionAt(header, 'end'), title);
121
+ }
122
+
123
+ writer.insert(writer.createPositionAt(container, 'end'), header);
124
+ }
125
+
126
+ // Conteúdo travado
127
+ const raw = writer.createRawElement(
128
+ 'div',
129
+ { class: 'ck-conteudo-dinamico__inner' },
130
+ domElement => { domElement.innerHTML = html; }
131
+ );
132
+
133
+ writer.insert(writer.createPositionAt(container, 'end'), raw);
134
+
135
+ return toWidget(container, writer, { label: widgetLabel });
136
+ }
137
+ });
138
+
139
+ /* ================= COMMAND ================= */
140
+ editor.commands.add('insertConteudoDinamico', new InsertConteudoDinamicoCommand(editor));
141
+
142
+ editor.model.document.on('change:data', () => {
143
+ // Sempre que o doc muda, tenta reaplicar a versão mais recente
144
+ // para qualquer sourceId que esteja no cache.
145
+ if (!this._lastHtmlBySourceId.size) return;
146
+
147
+ for (const [sourceId] of this._lastHtmlBySourceId.entries()) {
148
+ this._pendingUpdates.add(sourceId);
149
+ }
150
+
151
+ flushPendingUpdates(editor, this);
152
+ });
153
+
154
+ /* ================= TOOLBAR BUTTONS (N) ================= */
155
+ // Cria 1 botão por item configurado: conteudoDinamico:<name>
156
+ for (const b of buttons) {
157
+ if (!b?.name) continue;
158
+
159
+ const componentName = `conteudoDinamico:${b.name}`;
160
+
161
+ editor.ui.componentFactory.add(componentName, locale => {
162
+ const view = new ButtonView(locale);
163
+
164
+ // ✅ Texto ou ícone
165
+ const label = b.label || '+';
166
+
167
+ view.set({
168
+ label,
169
+ tooltip: true,
170
+ withText: !b.iconSvg // se tem svg, não precisa mostrar texto
171
+ });
172
+
173
+ if (b.iconSvg) {
174
+ // ButtonView espera "icon" como string SVG
175
+ view.icon = b.iconSvg;
176
+ }
177
+
178
+ view.on('execute', () => {
179
+ const sourceId = String(b.sourceId || `api:${b.name}`);
180
+
181
+ // ✅ se já existe update da API, insere já atualizado
182
+ const cached = this._lastHtmlBySourceId.get(sourceId);
183
+ const html = cached ?? defaultHtml();
184
+
185
+ editor.execute('insertConteudoDinamico', { sourceId, html });
186
+ editor.editing.view.focus();
187
+ });
188
+
189
+ return view;
190
+ });
191
+ }
192
+
193
+ /* ================= API PÚBLICA ================= */
194
+ this.updateBySourceId = (sourceId, html) => {
195
+ // 1) sempre guarda a última versão
196
+ this._lastHtmlBySourceId.set(sourceId, html);
197
+
198
+ // 2) marca como pendente e tenta aplicar
199
+ this._pendingUpdates.add(sourceId);
200
+ flushPendingUpdates(editor, this);
201
+ };
202
+
203
+ function flushPendingUpdates(editor, plugin) {
204
+ // Só aplica quando o editor já tiver model pronto (no mínimo 1 change já aconteceu).
205
+ // Mesmo assim, pode chamar quantas vezes quiser que é idempotente.
206
+ if (!plugin._pendingUpdates.size) return;
207
+
208
+ const ids = Array.from(plugin._pendingUpdates);
209
+ plugin._pendingUpdates.clear();
210
+
211
+ for (const sourceId of ids) {
212
+ const html = plugin._lastHtmlBySourceId.get(sourceId);
213
+ if (html == null) continue;
214
+ updateConteudo(editor, sourceId, html);
215
+ }
216
+ }
217
+
218
+ }
219
+ }
220
+
221
+ /* ================= COMMAND ================= */
222
+
223
+ class InsertConteudoDinamicoCommand extends Command {
224
+ execute({ sourceId, html }) {
225
+ const editor = this.editor;
226
+
227
+ editor.model.change(writer => {
228
+ const htmlB64 = encodeHtml(html || '');
229
+
230
+ const element = writer.createElement('conteudoDinamico', {
231
+ sourceId: sourceId || '',
232
+ htmlB64
233
+ });
234
+
235
+ editor.model.insertContent(element);
236
+ });
237
+ }
238
+
239
+ refresh() {
240
+ this.isEnabled = true;
241
+ }
242
+ }
243
+
244
+ /* ================= UPDATE ================= */
245
+
246
+ function updateConteudo(editor, sourceId, html) {
247
+ const newHtmlB64 = encodeHtml(html);
248
+
249
+ editor.model.change(writer => {
250
+ const root = editor.model.document.getRoot();
251
+
252
+ // Coleta primeiro para não bagunçar o iterator removendo/inserindo durante o loop.
253
+ const targets = [];
254
+ for (const item of editor.model.createRangeIn(root).getItems()) {
255
+ if (item.is('element', 'conteudoDinamico') && item.getAttribute('sourceId') === sourceId) {
256
+ targets.push(item);
257
+ }
258
+ }
259
+
260
+ for (const item of targets) {
261
+ const currentB64 = item.getAttribute('htmlB64') || '';
262
+ if (currentB64 === newHtmlB64) continue;
263
+
264
+ const wasSelected = editor.model.document.selection.getSelectedElement() === item;
265
+ const insertPos = writer.createPositionBefore(item);
266
+
267
+ const attrs = Object.fromEntries(item.getAttributes());
268
+ attrs.htmlB64 = newHtmlB64;
269
+
270
+ writer.remove(item);
271
+
272
+ const newElement = writer.createElement('conteudoDinamico', attrs);
273
+ writer.insert(newElement, insertPos);
274
+
275
+ if (wasSelected) {
276
+ writer.setSelection(newElement, 'on');
277
+ }
278
+ }
279
+ });
280
+ }
281
+
282
+
283
+
284
+ /* ================= HELPERS ================= */
285
+
286
+ function getButtonMetaBySourceId(buttons, sourceId) {
287
+ return (buttons || []).find(b => String(b.sourceId || '') === String(sourceId || ''));
288
+ }
289
+
290
+ function defaultHtml() {
291
+ return `
292
+ <table border="1" style="width:100%; border-collapse:collapse;">
293
+ <tr><th>Exemplo</th><th>Conteúdo</th></tr>
294
+ <tr><td>A</td><td>B</td></tr>
295
+ </table>
296
+ `;
297
+ }
298
+
299
+ function encodeHtml(str) {
300
+ try {
301
+ return btoa(unescape(encodeURIComponent(str)));
302
+ } catch {
303
+ return '';
304
+ }
305
+ }
306
+
307
+ function decodeHtml(b64) {
308
+ if (!b64) return '';
309
+ try {
310
+ return decodeURIComponent(escape(atob(b64)));
311
+ } catch {
312
+ return '';
313
+ }
314
+ }
315
+
316
+ /* ===== Opcional: impedir duplicar o mesmo sourceId ===== */
317
+
318
+ // function findConteudoBySourceId(editor, sourceId) {
319
+ // const root = editor.model.document.getRoot();
320
+ // for (const item of editor.model.createRangeIn(root).getItems()) {
321
+ // if (item.is('element', 'conteudoDinamico') && item.getAttribute('sourceId') === sourceId) {
322
+ // return item;
323
+ // }
324
+ // }
325
+ // return null;
326
+ // }
327
+
328
+ // function selectModelElement(editor, element) {
329
+ // editor.model.change(writer => {
330
+ // writer.setSelection(element, 'on');
331
+ // });
332
+ // }