@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.
|
|
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-
|
|
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
|
+
// }
|