@itfin/components 1.5.0 → 1.5.1
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/package.json +1 -1
- package/src/assets/scss/_css_variables.scss +7 -2
- package/src/assets/scss/_dark-theme.scss +12 -2
- package/src/assets/scss/_variables.scss +9 -34
- package/src/assets/scss/components/_button.scss +10 -29
- package/src/assets/scss/components/_checkbox.scss +0 -9
- package/src/assets/scss/components/_datepicker.scss +3 -3
- package/src/assets/scss/components/_pagination.scss +1 -4
- package/src/assets/scss/components/_popover.scss +0 -22
- package/src/assets/scss/components/_segmeneted-control.scss +8 -19
- package/src/assets/scss/components/_select.scss +8 -6
- package/src/assets/scss/components/_text-field.scss +7 -9
- package/src/assets/scss/components/select/_dropdown-menu.scss +0 -1
- package/src/assets/scss/components/select/_dropdown-toggle.scss +1 -0
- package/src/assets/scss/directives/tooltip.scss +5 -10
- package/src/assets/scss/main.scss +0 -48
- package/src/components/editor/plugins.js +1012 -0
- package/src/components/icon/components/nomi-budget.vue +4 -0
- package/src/components/icon/components/nomi-delta.vue +7 -0
- package/src/components/icon/new-icons/delta.svg +6 -0
- package/src/components/panels/PanelList.vue +3 -2
- package/src/components/panels/helpers.ts +14 -4
- package/src/components/view/View.vue +8 -0
|
@@ -0,0 +1,1012 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* De-minified (beautified + renamed) single-file version of the provided code.
|
|
3
|
+
*
|
|
4
|
+
* Notes:
|
|
5
|
+
* 1) Original snippet references a helper `ct(this, 'name', fn)` (likely from bundler/Babel).
|
|
6
|
+
* Here we implement `ct` as a simple property assignment: obj[name] = fn.
|
|
7
|
+
* 2) This file is designed to keep behavior equivalent to your snippet, but with readable names.
|
|
8
|
+
* 3) This looks like an Editor.js tool (toolbox/render/save/renderSettings + sanitize flags).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// -----------------------------------------------------------------------------
|
|
12
|
+
// Helpers
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
function isNotNull(value) {
|
|
16
|
+
return value != null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create DOM element with classes, attributes, children.
|
|
21
|
+
* @param {string} tag
|
|
22
|
+
* @param {string[] | null} classes
|
|
23
|
+
* @param {Record<string,string> | null} attrs
|
|
24
|
+
* @param {Node[] | null} children
|
|
25
|
+
*/
|
|
26
|
+
function createElement(tag, classes = null, attrs = null, children = null) {
|
|
27
|
+
const el = document.createElement(tag);
|
|
28
|
+
|
|
29
|
+
if (isNotNull(classes)) {
|
|
30
|
+
for (let i = 0; i < classes.length; i++) {
|
|
31
|
+
if (isNotNull(classes[i])) el.classList.add(classes[i]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isNotNull(attrs)) {
|
|
36
|
+
for (const key in attrs) {
|
|
37
|
+
el.setAttribute(key, attrs[key]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isNotNull(children)) {
|
|
42
|
+
for (let i = 0; i < children.length; i++) {
|
|
43
|
+
if (isNotNull(children[i])) el.appendChild(children[i]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return el;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Helper used in original minified build to assign methods.
|
|
52
|
+
* Equivalent to: this[name] = fn;
|
|
53
|
+
*/
|
|
54
|
+
function ct(obj, name, fn) {
|
|
55
|
+
obj[name] = fn;
|
|
56
|
+
return fn;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get absolute page coordinates of an element
|
|
61
|
+
*/
|
|
62
|
+
function getAbsRect(el) {
|
|
63
|
+
const r = el.getBoundingClientRect();
|
|
64
|
+
return {
|
|
65
|
+
y1: Math.floor(r.top + window.pageYOffset),
|
|
66
|
+
x1: Math.floor(r.left + window.pageXOffset),
|
|
67
|
+
x2: Math.floor(r.right + window.pageXOffset),
|
|
68
|
+
y2: Math.floor(r.bottom + window.pageYOffset),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Detect near-edge side based on pointer coordinates
|
|
74
|
+
* (within about 11px tolerance as in original)
|
|
75
|
+
*/
|
|
76
|
+
function detectNearSide(rect, pageX, pageY) {
|
|
77
|
+
let side;
|
|
78
|
+
|
|
79
|
+
if (pageX - rect.x1 >= -1 && pageX - rect.x1 <= 11) side = "left";
|
|
80
|
+
if (rect.x2 - pageX >= -1 && rect.x2 - pageX <= 11) side = "right";
|
|
81
|
+
if (pageY - rect.y1 >= -1 && pageY - rect.y1 <= 11) side = "top";
|
|
82
|
+
if (rect.y2 - pageY >= -1 && rect.y2 - pageY <= 11) side = "bottom";
|
|
83
|
+
|
|
84
|
+
return side;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// -----------------------------------------------------------------------------
|
|
88
|
+
// Resize columns feature
|
|
89
|
+
// -----------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
const RESIZE_CSS = {
|
|
92
|
+
resizedColumnHandle: "tc-table__resize_column",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
class ColumnResizer {
|
|
96
|
+
/**
|
|
97
|
+
* @param {TableUI} table
|
|
98
|
+
*/
|
|
99
|
+
constructor(table) {
|
|
100
|
+
this.table = table;
|
|
101
|
+
|
|
102
|
+
this.active = null;
|
|
103
|
+
this.activeIndex = 0;
|
|
104
|
+
this.startX = 0;
|
|
105
|
+
this.width = 0;
|
|
106
|
+
|
|
107
|
+
this.widthFirst = 0;
|
|
108
|
+
this.widthSecond = 0;
|
|
109
|
+
|
|
110
|
+
ct(this, "getIndex", (evt) => {
|
|
111
|
+
const handles = this.table.body.querySelectorAll(`.${RESIZE_CSS.resizedColumnHandle}`);
|
|
112
|
+
for (let i = 0; i < handles.length; i += 1) {
|
|
113
|
+
if (handles[i] === evt.target) return i;
|
|
114
|
+
}
|
|
115
|
+
return -1;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
ct(this, "onDragStart", (evt) => {
|
|
119
|
+
const index = this.getIndex(evt);
|
|
120
|
+
|
|
121
|
+
this.startX = evt.pageX;
|
|
122
|
+
this.active = evt.target;
|
|
123
|
+
this.activeIndex = index;
|
|
124
|
+
|
|
125
|
+
this.width = this.table.body.offsetWidth;
|
|
126
|
+
|
|
127
|
+
const [w1, w2] = this.getWidthCols();
|
|
128
|
+
this.widthFirst = w1;
|
|
129
|
+
this.widthSecond = w2;
|
|
130
|
+
|
|
131
|
+
document.body.style.cursor = "col-resize";
|
|
132
|
+
|
|
133
|
+
if (evt.preventDefault) evt.preventDefault();
|
|
134
|
+
if (evt.stopPropagation) evt.stopPropagation();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
ct(this, "onDrag", (evt) => {
|
|
138
|
+
if (this.active) this.move(evt.pageX - this.startX);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
ct(this, "onDragEnd", () => {
|
|
142
|
+
this.active = null;
|
|
143
|
+
document.body.style.cursor = "auto";
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
ct(this, "move", (deltaX) => {
|
|
147
|
+
const [col1, col2] = this.getCols();
|
|
148
|
+
|
|
149
|
+
let w1Percent = (this.widthFirst / this.width + deltaX / this.width) * 100;
|
|
150
|
+
let w2Percent = (this.widthSecond / this.width - deltaX / this.width) * 100;
|
|
151
|
+
|
|
152
|
+
// minimum 5% each as in original
|
|
153
|
+
if (w1Percent >= 5 && w2Percent >= 5) {
|
|
154
|
+
col1.style.width = `${w1Percent}%`;
|
|
155
|
+
col2.style.width = `${w2Percent}%`;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
ct(this, "getCols", () => {
|
|
160
|
+
const cols = this.table.colgroup.children;
|
|
161
|
+
const col1 = cols[this.activeIndex];
|
|
162
|
+
const col2 = cols[this.activeIndex + 1];
|
|
163
|
+
return [col1, col2];
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
ct(this, "parseWidth", (col) => (Number.parseFloat(col.style.width) / 100) * this.width);
|
|
167
|
+
|
|
168
|
+
ct(this, "getWidthCols", () => {
|
|
169
|
+
const [c1, c2] = this.getCols();
|
|
170
|
+
return [this.parseWidth(c1), this.parseWidth(c2)];
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
this.init();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
init() {
|
|
177
|
+
document.addEventListener("mousemove", this.onDrag, false);
|
|
178
|
+
document.addEventListener("mouseup", this.onDragEnd, false);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Adds resize handle into a cell element if absent.
|
|
183
|
+
*/
|
|
184
|
+
createElem(cellEl) {
|
|
185
|
+
if (!cellEl.querySelector(`.${RESIZE_CSS.resizedColumnHandle}`)) {
|
|
186
|
+
const handle = createElement("div", [RESIZE_CSS.resizedColumnHandle]);
|
|
187
|
+
handle.addEventListener("mousedown", this.onDragStart, false);
|
|
188
|
+
cellEl.appendChild(handle);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// -----------------------------------------------------------------------------
|
|
194
|
+
// Select line (remove row/column) feature
|
|
195
|
+
// -----------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
const SELECT_LINE_CSS = {
|
|
198
|
+
selectLineCol: "tc-table__select_line_col",
|
|
199
|
+
selectLineRow: "tc-table__select_line_row",
|
|
200
|
+
trRemove: "tc-table__tr_remove",
|
|
201
|
+
tdRemove: "tc-table__td_remove",
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
class SelectLineRemover {
|
|
205
|
+
/**
|
|
206
|
+
* @param {TableUI} table
|
|
207
|
+
*/
|
|
208
|
+
constructor(table) {
|
|
209
|
+
this.table = table;
|
|
210
|
+
|
|
211
|
+
ct(this, "getDirection", (evt) => (evt.target.classList.contains(SELECT_LINE_CSS.selectLineCol) ? 0 : 1));
|
|
212
|
+
|
|
213
|
+
ct(this, "getIndex", (evt) => {
|
|
214
|
+
// original matched by exact className
|
|
215
|
+
const candidates = this.table.body.querySelectorAll(`.${evt.target.className}`);
|
|
216
|
+
for (let i = 0; i < candidates.length; i += 1) {
|
|
217
|
+
if (candidates[i] === evt.target) return i;
|
|
218
|
+
}
|
|
219
|
+
return -1;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
ct(this, "onMouseEnter", (evt) => {
|
|
223
|
+
if (this.getDirection(evt) === 0) {
|
|
224
|
+
// column
|
|
225
|
+
const colIndex = this.getIndex(evt);
|
|
226
|
+
for (let r = 0; r < this.table.body.rows.length; r += 1) {
|
|
227
|
+
const cell = this.table.body.rows[r].children[colIndex];
|
|
228
|
+
if (cell) cell.classList.add(SELECT_LINE_CSS.tdRemove);
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
// row
|
|
232
|
+
evt.target.closest("tr").classList.add(SELECT_LINE_CSS.trRemove);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
ct(this, "onMouseLeave", (evt) => {
|
|
237
|
+
if (!evt.target) return;
|
|
238
|
+
|
|
239
|
+
if (this.getDirection(evt) === 0) {
|
|
240
|
+
this.table.body.querySelectorAll(`.${SELECT_LINE_CSS.tdRemove}`).forEach((n) => n.classList.remove(SELECT_LINE_CSS.tdRemove));
|
|
241
|
+
} else {
|
|
242
|
+
evt.target.closest("tr").classList.remove(SELECT_LINE_CSS.trRemove);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
ct(this, "onClick", (evt) => {
|
|
247
|
+
const idx = this.getIndex(evt);
|
|
248
|
+
if (this.getDirection(evt) === 0) this.table.removeColumn(idx);
|
|
249
|
+
else this.table.removeRow(idx);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @param {HTMLElement} cellEl
|
|
255
|
+
* @param {0|1} direction 0=col, 1=row
|
|
256
|
+
*/
|
|
257
|
+
createElem(cellEl, direction = 0) {
|
|
258
|
+
const cls = direction === 0 ? SELECT_LINE_CSS.selectLineCol : SELECT_LINE_CSS.selectLineRow;
|
|
259
|
+
|
|
260
|
+
if (!cellEl.querySelector(`.${cls}`)) {
|
|
261
|
+
const btn = createElement("div", [cls]);
|
|
262
|
+
btn.addEventListener("click", this.onClick, false);
|
|
263
|
+
btn.addEventListener("mouseenter", this.onMouseEnter, false);
|
|
264
|
+
btn.addEventListener("mouseleave", this.onMouseLeave, false);
|
|
265
|
+
cellEl.appendChild(btn);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// -----------------------------------------------------------------------------
|
|
271
|
+
// Add row/column feature
|
|
272
|
+
// -----------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
const ADD_LINE_CSS = {
|
|
275
|
+
addColumn: "tc-table__add_column",
|
|
276
|
+
addRow: "tc-table__add_row",
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
class AddLineCreator {
|
|
280
|
+
/**
|
|
281
|
+
* @param {TableUI} table
|
|
282
|
+
*/
|
|
283
|
+
constructor(table) {
|
|
284
|
+
this.table = table;
|
|
285
|
+
|
|
286
|
+
ct(this, "createElem", (cellEl, direction = 0) => {
|
|
287
|
+
const cls = [ADD_LINE_CSS.addColumn, ADD_LINE_CSS.addRow][direction];
|
|
288
|
+
|
|
289
|
+
if (!cellEl.querySelector(`.${cls}`)) {
|
|
290
|
+
const clickable = createElement("div");
|
|
291
|
+
clickable.addEventListener("click", this.onClick);
|
|
292
|
+
|
|
293
|
+
// structure like original: <div class="..."><div></div><div></div></div>
|
|
294
|
+
const wrapper = createElement("div", [cls], null, [clickable, createElement("div")]);
|
|
295
|
+
cellEl.appendChild(wrapper);
|
|
296
|
+
return wrapper;
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
ct(this, "getDirection", (el) => (el.classList.contains(ADD_LINE_CSS.addColumn) ? 0 : 1));
|
|
301
|
+
|
|
302
|
+
ct(this, "getIndex", (el) => {
|
|
303
|
+
const nodes = this.table.body.querySelectorAll(`.${el.className}`);
|
|
304
|
+
for (let i = 0; i < nodes.length; i += 1) {
|
|
305
|
+
if (nodes[i] === el) return i;
|
|
306
|
+
}
|
|
307
|
+
return -1;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
ct(this, "onClick", (evt) => {
|
|
311
|
+
const container = evt.target.parentNode; // parent of clickable inner div
|
|
312
|
+
const direction = this.getDirection(container);
|
|
313
|
+
const index = this.getIndex(container);
|
|
314
|
+
|
|
315
|
+
if (direction === 0) this.table.addColumn(index);
|
|
316
|
+
else this.table.addRow(index);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// -----------------------------------------------------------------------------
|
|
322
|
+
// Image upload feature
|
|
323
|
+
// -----------------------------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
const SVG_IMAGE_ADD = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
326
|
+
<path d="M19 5V19H5V5H19ZM19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3ZM14.14 11.86L11.14 15.73L9 13.14L6 17H18L14.14 11.86Z" />
|
|
327
|
+
</svg>`;
|
|
328
|
+
|
|
329
|
+
const SVG_IMAGE_DELETE = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
330
|
+
<path d="M19 5V16.17L21 18.17V5C21 3.9 20.1 3 19 3H5.83L7.83 5H19ZM2.81 2.81L1.39 4.22L3 5.83V19C3 20.1 3.9 21 5 21H18.17L19.78 22.61L21.19 21.2L2.81 2.81ZM5 19V7.83L12.07 14.9L11.25 16L9 13L6 17H14.17L16.17 19H5Z"/>
|
|
331
|
+
</svg>`;
|
|
332
|
+
|
|
333
|
+
const DEFAULT_UPLOAD_ENDPOINT = "/upload_image";
|
|
334
|
+
|
|
335
|
+
const IMAGE_CSS = {
|
|
336
|
+
uploadButton: "tc-table__image_upload_button",
|
|
337
|
+
uploadButtonVisible: "tc-table__image_upload_button_visible",
|
|
338
|
+
image: "tc-table__image",
|
|
339
|
+
wrapper: "tc-table__wrapper_image",
|
|
340
|
+
buttonDelete: "tc-table__wrapper_image_button",
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
class ImageUploader {
|
|
344
|
+
/**
|
|
345
|
+
* @param {TableUI} table
|
|
346
|
+
*/
|
|
347
|
+
constructor(table) {
|
|
348
|
+
this.table = table;
|
|
349
|
+
|
|
350
|
+
this.buttonText = "Добавить изображение"; // original had broken encoding
|
|
351
|
+
this.visible = false;
|
|
352
|
+
|
|
353
|
+
this.cell = null; // selected cell (td)
|
|
354
|
+
this.Button = null; // label element
|
|
355
|
+
this.image = null; // File
|
|
356
|
+
|
|
357
|
+
ct(this, "createElem", (container) => {
|
|
358
|
+
if (!container.querySelector(`.${IMAGE_CSS.uploadButton}`)) {
|
|
359
|
+
const fileInput = createElement("input", [], { type: "file" });
|
|
360
|
+
const icon = createElement("span");
|
|
361
|
+
icon.innerHTML = SVG_IMAGE_ADD;
|
|
362
|
+
|
|
363
|
+
this.Button = createElement(
|
|
364
|
+
"label",
|
|
365
|
+
[IMAGE_CSS.uploadButton],
|
|
366
|
+
{ title: this.buttonText },
|
|
367
|
+
[fileInput, icon]
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
fileInput.addEventListener("change", this.onChange);
|
|
371
|
+
|
|
372
|
+
container.appendChild(this.Button);
|
|
373
|
+
return this.Button;
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
ct(this, "onToggle", (isVisible) => {
|
|
378
|
+
this.visible = isVisible;
|
|
379
|
+
|
|
380
|
+
if (isVisible) {
|
|
381
|
+
this.Button.classList.add(IMAGE_CSS.uploadButtonVisible);
|
|
382
|
+
|
|
383
|
+
if (this.table._selectedCell) {
|
|
384
|
+
this.cell = this.table._selectedCell;
|
|
385
|
+
|
|
386
|
+
// position button under the selected cell
|
|
387
|
+
this.Button.style.top = `${this.cell.offsetTop + this.cell.offsetHeight}px`;
|
|
388
|
+
this.Button.style.left = `${this.cell.offsetLeft}px`;
|
|
389
|
+
}
|
|
390
|
+
} else {
|
|
391
|
+
setTimeout(() => {
|
|
392
|
+
if (!this.table._selectedCell) {
|
|
393
|
+
this.Button.classList.remove(IMAGE_CSS.uploadButtonVisible);
|
|
394
|
+
}
|
|
395
|
+
}, 200);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
ct(this, "createImage", (cell, src) => {
|
|
400
|
+
// original expects: td -> div.tc-table__area -> (contentEditable div)
|
|
401
|
+
const [editable] = cell.children[0].children;
|
|
402
|
+
|
|
403
|
+
const img = createElement("img", [IMAGE_CSS.image], { src });
|
|
404
|
+
|
|
405
|
+
if (this.table.readOnly) {
|
|
406
|
+
editable.replaceWith(img);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const deleteIcon = createElement("div");
|
|
411
|
+
deleteIcon.innerHTML = SVG_IMAGE_DELETE;
|
|
412
|
+
|
|
413
|
+
const deleteBtn = createElement("div", [IMAGE_CSS.buttonDelete], null, [deleteIcon]);
|
|
414
|
+
|
|
415
|
+
const wrapper = createElement("div", [IMAGE_CSS.wrapper], null, [img, deleteBtn]);
|
|
416
|
+
deleteBtn.addEventListener("click", this.removeImage);
|
|
417
|
+
|
|
418
|
+
editable.replaceWith(wrapper);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
ct(this, "removeImage", (evt) => {
|
|
422
|
+
evt.target.closest(`.${IMAGE_CSS.wrapper}`).replaceWith(this.table._createContentEditableArea());
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
ct(this, "onChange", async (evt) => {
|
|
426
|
+
this.image = evt.target.files[0];
|
|
427
|
+
const { url } = await this.onUploadImage();
|
|
428
|
+
if (url) this.createImage(this.cell, url);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
ct(this, "onUploadImage", async () => {
|
|
432
|
+
const data = new FormData();
|
|
433
|
+
data.append("upfile", this.image);
|
|
434
|
+
|
|
435
|
+
const res = await fetch(DEFAULT_UPLOAD_ENDPOINT, { method: "POST", body: data });
|
|
436
|
+
return res.json();
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// -----------------------------------------------------------------------------
|
|
442
|
+
// Table UI (DOM + interactions)
|
|
443
|
+
// -----------------------------------------------------------------------------
|
|
444
|
+
|
|
445
|
+
const TABLE_CSS = {
|
|
446
|
+
table: "tc-table",
|
|
447
|
+
inputField: "tc-table__inp",
|
|
448
|
+
cell: "tc-table__cell",
|
|
449
|
+
container: "tc-table__container",
|
|
450
|
+
containerReadOnly: "tc-table__container_readonly",
|
|
451
|
+
wrapper: "tc-table__wrap",
|
|
452
|
+
area: "tc-table__area",
|
|
453
|
+
|
|
454
|
+
addColumn: "tc-table__add_column",
|
|
455
|
+
addRow: "tc-table__add_row",
|
|
456
|
+
addColumnButton: "tc-table__add_column_button",
|
|
457
|
+
addRowButton: "tc-table__add_row_button",
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
class TableUI {
|
|
461
|
+
/**
|
|
462
|
+
* @param {boolean} readOnly
|
|
463
|
+
*/
|
|
464
|
+
constructor(readOnly) {
|
|
465
|
+
this.readOnly = readOnly;
|
|
466
|
+
|
|
467
|
+
this._numberOfColumns = 0;
|
|
468
|
+
this._numberOfRows = 0;
|
|
469
|
+
|
|
470
|
+
this.resize = new ColumnResizer(this);
|
|
471
|
+
this.selectLine = new SelectLineRemover(this);
|
|
472
|
+
this.createLine = new AddLineCreator(this);
|
|
473
|
+
this.imageUpload = new ImageUploader(this);
|
|
474
|
+
|
|
475
|
+
this._element = this._createTableWrapper();
|
|
476
|
+
this._table = this._element.querySelector("table");
|
|
477
|
+
this.colgroup = this._table.querySelector("colgroup");
|
|
478
|
+
|
|
479
|
+
this._selectedCell = null;
|
|
480
|
+
|
|
481
|
+
if (!this.readOnly) this._hangEvents();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ----- layout helpers -----
|
|
485
|
+
|
|
486
|
+
columnSizeReCalc() {
|
|
487
|
+
const cols = this.colgroup.children;
|
|
488
|
+
for (let i = 0; i < cols.length; i += 1) {
|
|
489
|
+
cols[i].style.width = `${100 / cols.length}%`;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
insertCol(index) {
|
|
494
|
+
this.colgroup.insertBefore(createElement("col", [], { span: 1 }), this.colgroup.children[index]);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
removeCol(index) {
|
|
498
|
+
// original did: this.body.querySelector('colgroup').children[t].remove();
|
|
499
|
+
this.body.querySelector("colgroup").children[index].remove();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ----- public API -----
|
|
503
|
+
|
|
504
|
+
addColumn(index = -1) {
|
|
505
|
+
this._numberOfColumns++;
|
|
506
|
+
|
|
507
|
+
const rows = this._table.rows;
|
|
508
|
+
if (index === 0) this.removeButtons(1);
|
|
509
|
+
|
|
510
|
+
this.insertCol(index);
|
|
511
|
+
|
|
512
|
+
for (let r = 0; r < rows.length; r++) {
|
|
513
|
+
const cell = rows[r].insertCell(index);
|
|
514
|
+
this._fillCell(cell);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (!this.readOnly) {
|
|
518
|
+
this.columnSizeReCalc();
|
|
519
|
+
this.updateButtons();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
removeColumn(index) {
|
|
524
|
+
this._numberOfColumns--;
|
|
525
|
+
|
|
526
|
+
for (let r = 0; r < this._table.rows.length; r += 1) {
|
|
527
|
+
this._table.rows[r].deleteCell(index);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (!this.readOnly) {
|
|
531
|
+
this.removeCol(index);
|
|
532
|
+
this.columnSizeReCalc();
|
|
533
|
+
this.updateButtons();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
addRow(index = -1) {
|
|
538
|
+
this._numberOfRows++;
|
|
539
|
+
|
|
540
|
+
const tr = this._table.insertRow(index);
|
|
541
|
+
if (index === 0) this.removeButtons(0);
|
|
542
|
+
|
|
543
|
+
this._fillRow(tr);
|
|
544
|
+
this.updateButtons();
|
|
545
|
+
|
|
546
|
+
return tr;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
removeRow(index) {
|
|
550
|
+
this._numberOfRows--;
|
|
551
|
+
this._table.rows[index].remove();
|
|
552
|
+
this.updateButtons();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
get htmlElement() {
|
|
556
|
+
return this._element;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
get body() {
|
|
560
|
+
return this._table;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
get selectedCell() {
|
|
564
|
+
return this._selectedCell;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// (kept as in original: getter exists but empty)
|
|
568
|
+
get withBorder() {}
|
|
569
|
+
|
|
570
|
+
// ----- buttons (resizer / add / remove) management -----
|
|
571
|
+
|
|
572
|
+
fillButtons(cellEl, colIndex, rowIndex) {
|
|
573
|
+
// rowIndex === 0 => header row style behavior in original
|
|
574
|
+
if (rowIndex === 0) {
|
|
575
|
+
this.createLine.createElem(cellEl);
|
|
576
|
+
if (colIndex !== 0) this.resize.createElem(cellEl);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// add select line controls on first row/first col zones
|
|
580
|
+
if (colIndex === 0 || rowIndex === 0) {
|
|
581
|
+
this.selectLine.createElem(cellEl, +(colIndex === 0));
|
|
582
|
+
if (colIndex === 0 && rowIndex === 0) this.selectLine.createElem(cellEl);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// row add control on first column cells
|
|
586
|
+
if (colIndex === 0) {
|
|
587
|
+
this.createLine.createElem(cellEl, 1);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
updateButtons() {
|
|
592
|
+
for (let r = 0; r < this._table.rows.length; r += 1) {
|
|
593
|
+
const row = this._table.rows[r];
|
|
594
|
+
for (let c = 0; c < row.children.length; c += 1) {
|
|
595
|
+
const cell = row.children[c];
|
|
596
|
+
this.fillButtons(cell, c, r);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* @param {0|1} axis 0=columns UI, 1=rows UI (as in original)
|
|
603
|
+
*/
|
|
604
|
+
removeButtons(axis = 0) {
|
|
605
|
+
const groups = [
|
|
606
|
+
[TABLE_CSS.addColumn, SELECT_LINE_CSS.selectLineCol],
|
|
607
|
+
[TABLE_CSS.addRow, SELECT_LINE_CSS.selectLineRow],
|
|
608
|
+
][axis];
|
|
609
|
+
|
|
610
|
+
groups.forEach((cls) => {
|
|
611
|
+
const nodes = this._table.querySelectorAll(`.${cls}`);
|
|
612
|
+
for (let i = 0; i < nodes.length; i += 1) nodes[i].remove();
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ----- DOM build -----
|
|
617
|
+
|
|
618
|
+
_createTableWrapper() {
|
|
619
|
+
const containerClass = this.readOnly ? TABLE_CSS.containerReadOnly : TABLE_CSS.container;
|
|
620
|
+
|
|
621
|
+
const container = createElement("div", [containerClass], null, [
|
|
622
|
+
createElement("div", [TABLE_CSS.wrapper], null, [
|
|
623
|
+
createElement("table", [TABLE_CSS.table], null, [
|
|
624
|
+
createElement("colgroup"),
|
|
625
|
+
createElement("tbody"),
|
|
626
|
+
]),
|
|
627
|
+
]),
|
|
628
|
+
]);
|
|
629
|
+
|
|
630
|
+
if (!this.readOnly) {
|
|
631
|
+
const addRowBtn = createElement("div", [TABLE_CSS.addRowButton]);
|
|
632
|
+
const addColBtn = createElement("div", [TABLE_CSS.addColumnButton]);
|
|
633
|
+
|
|
634
|
+
// NOTE: In original these were swapped: addRowBtn click -> addColumn, addColBtn -> addRow
|
|
635
|
+
// Keeping original behavior:
|
|
636
|
+
addRowBtn.addEventListener("click", () => this.addColumn(this._numberOfColumns), true);
|
|
637
|
+
addColBtn.addEventListener("click", () => this.addRow(this._numberOfRows), true);
|
|
638
|
+
|
|
639
|
+
container.appendChild(addRowBtn);
|
|
640
|
+
container.appendChild(addColBtn);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return container;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
_createContentEditableArea() {
|
|
647
|
+
return createElement("div", [TABLE_CSS.inputField], { contenteditable: !this.readOnly });
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
_fillCell(td) {
|
|
651
|
+
td.classList.add(TABLE_CSS.cell);
|
|
652
|
+
const editable = this._createContentEditableArea();
|
|
653
|
+
td.appendChild(createElement("div", [TABLE_CSS.area], null, [editable]));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
_fillRow(tr) {
|
|
657
|
+
for (let c = 0; c < this._numberOfColumns; c++) {
|
|
658
|
+
const td = tr.insertCell();
|
|
659
|
+
this._fillCell(td);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// ----- events -----
|
|
664
|
+
|
|
665
|
+
_hangEvents() {
|
|
666
|
+
this._table.addEventListener(
|
|
667
|
+
"focus",
|
|
668
|
+
(evt) => this._focusEditField(evt),
|
|
669
|
+
true
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
this._table.addEventListener(
|
|
673
|
+
"blur",
|
|
674
|
+
(evt) => this._blurEditField(evt),
|
|
675
|
+
true
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
this._table.addEventListener("keydown", (evt) => this._pressedEnterInEditField(evt));
|
|
679
|
+
this._table.addEventListener("click", (evt) => this._clickedOnCell(evt));
|
|
680
|
+
|
|
681
|
+
this._table.addEventListener(
|
|
682
|
+
"mouseover",
|
|
683
|
+
(evt) => {
|
|
684
|
+
this._mouseEnterInDetectArea(evt);
|
|
685
|
+
evt.stopPropagation();
|
|
686
|
+
},
|
|
687
|
+
true
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
_focusEditField(evt) {
|
|
692
|
+
if (evt.target.classList.contains(TABLE_CSS.inputField)) {
|
|
693
|
+
this._selectedCell = evt.target.closest("." + TABLE_CSS.cell);
|
|
694
|
+
this.imageUpload.onToggle(true);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
_blurEditField(evt) {
|
|
699
|
+
if (evt.target.classList.contains(TABLE_CSS.inputField)) {
|
|
700
|
+
this._selectedCell = null;
|
|
701
|
+
this.imageUpload.onToggle(false);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
_pressedEnterInEditField(evt) {
|
|
706
|
+
if (evt.target.classList.contains(TABLE_CSS.inputField) && evt.keyCode === 13 && !evt.shiftKey) {
|
|
707
|
+
evt.preventDefault();
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
_clickedOnCell(evt) {
|
|
712
|
+
// ignore clicks on delete button wrapper
|
|
713
|
+
if (evt.target.classList.contains(IMAGE_CSS.buttonDelete) === false) {
|
|
714
|
+
if (!evt.target.classList.contains(TABLE_CSS.cell)) return;
|
|
715
|
+
evt.target.querySelector("." + TABLE_CSS.inputField).focus();
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
_mouseEnterInDetectArea(evt) {
|
|
720
|
+
if (!evt.target.classList.contains(TABLE_CSS.area)) return;
|
|
721
|
+
|
|
722
|
+
const rect = getAbsRect(evt.target.closest("TD"));
|
|
723
|
+
const side = detectNearSide(rect, evt.pageX, evt.pageY);
|
|
724
|
+
|
|
725
|
+
evt.target.dispatchEvent(
|
|
726
|
+
new CustomEvent("mouseInActivatingArea", {
|
|
727
|
+
detail: { side },
|
|
728
|
+
bubbles: true,
|
|
729
|
+
})
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// -----------------------------------------------------------------------------
|
|
735
|
+
// Table constructor used by the Editor.js tool wrapper
|
|
736
|
+
// -----------------------------------------------------------------------------
|
|
737
|
+
|
|
738
|
+
class TableConstructor {
|
|
739
|
+
/**
|
|
740
|
+
* @param {{content?: any[][], settings?: any}} data
|
|
741
|
+
* @param {{rows: string|number, cols: string|number}} config
|
|
742
|
+
* @param {any} api
|
|
743
|
+
* @param {boolean} readOnly
|
|
744
|
+
*/
|
|
745
|
+
constructor(data, config, api, readOnly) {
|
|
746
|
+
this.readOnly = readOnly;
|
|
747
|
+
|
|
748
|
+
this._CSS = {
|
|
749
|
+
editor: "tc-editor",
|
|
750
|
+
inputField: "tc-table__inp",
|
|
751
|
+
withBorder: "tc-table__with_border",
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
this._table = new TableUI(readOnly);
|
|
756
|
+
const { rows, cols } = this._resizeTable(data, config);
|
|
757
|
+
this._fillTable(data, { rows, cols });
|
|
758
|
+
} catch (e) {
|
|
759
|
+
console.log(e);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// container: <div class="tc-editor {api.styles.block}"> {tableUI} </div>
|
|
763
|
+
this._container = createElement("div", [this._CSS.editor, api.styles.block], null, [
|
|
764
|
+
this._table.htmlElement,
|
|
765
|
+
]);
|
|
766
|
+
|
|
767
|
+
// add image upload floating button
|
|
768
|
+
this._table.imageUpload.createElem(this._container);
|
|
769
|
+
|
|
770
|
+
this._hoveredCell = null;
|
|
771
|
+
this._hoveredCellSide = null;
|
|
772
|
+
|
|
773
|
+
if (!this.readOnly) this._hangEvents();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
get htmlElement() {
|
|
777
|
+
return this._container;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
_fillTable(data, tableSize) {
|
|
781
|
+
if (data.content !== void 0) {
|
|
782
|
+
for (let r = 0; r < tableSize.rows && r < data.content.length; r++) {
|
|
783
|
+
for (let c = 0; c < tableSize.cols && c < data.content[r].length; c++) {
|
|
784
|
+
const cellData = data.content[r][c];
|
|
785
|
+
const td = this._table.body.rows[r].cells[c];
|
|
786
|
+
|
|
787
|
+
if (typeof cellData === "string") {
|
|
788
|
+
const editable = td.querySelector("." + this._CSS.inputField);
|
|
789
|
+
editable.innerHTML = cellData;
|
|
790
|
+
} else {
|
|
791
|
+
if ((cellData?.type) === "image") {
|
|
792
|
+
this._table.imageUpload.createImage(td, cellData.src);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
_resizeTable(data, config) {
|
|
801
|
+
const hasContentArray = Array.isArray(data.content);
|
|
802
|
+
const contentRows = hasContentArray ? data.content.length : false;
|
|
803
|
+
const contentRowsCount = hasContentArray ? data.content.length : void 0;
|
|
804
|
+
const contentColsCount = contentRows ? data.content[0].length : void 0;
|
|
805
|
+
|
|
806
|
+
const rowsCfg = Number.parseInt(config.rows);
|
|
807
|
+
const colsCfg = Number.parseInt(config.cols);
|
|
808
|
+
|
|
809
|
+
const rowsFromCfg = !isNaN(rowsCfg) && rowsCfg > 0 ? rowsCfg : void 0;
|
|
810
|
+
const colsFromCfg = !isNaN(colsCfg) && colsCfg > 0 ? colsCfg : void 0;
|
|
811
|
+
|
|
812
|
+
const settings = data.settings;
|
|
813
|
+
|
|
814
|
+
// defaults from original
|
|
815
|
+
const defaultRows = 3;
|
|
816
|
+
const defaultCols = 2;
|
|
817
|
+
|
|
818
|
+
const rows = contentRowsCount || rowsFromCfg || defaultRows;
|
|
819
|
+
const cols = contentColsCount || colsFromCfg || defaultCols;
|
|
820
|
+
|
|
821
|
+
for (let r = 0; r < rows; r++) this._table.addRow(r);
|
|
822
|
+
for (let c = 0; c < cols; c++) this._table.addColumn(c);
|
|
823
|
+
|
|
824
|
+
// restore col widths if exist (values are ratios)
|
|
825
|
+
if (settings && settings.sizes) {
|
|
826
|
+
settings.sizes.forEach((ratio, idx) => {
|
|
827
|
+
if (this._table.colgroup.children[idx]) {
|
|
828
|
+
this._table.colgroup.children[idx].style.width = `${ratio * 100}%`;
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// border toggle (default true if undefined)
|
|
834
|
+
const withBorder =
|
|
835
|
+
(settings?.withBorder) === void 0 ? true : settings?.withBorder;
|
|
836
|
+
|
|
837
|
+
this._table.htmlElement.classList.toggle(this._CSS.withBorder, withBorder);
|
|
838
|
+
|
|
839
|
+
return { rows, cols };
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
_hangEvents() {
|
|
843
|
+
this._container.addEventListener("keydown", (evt) => this._containerKeydown(evt));
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
_containerKeydown(evt) {
|
|
847
|
+
if (evt.keyCode === 13) this._containerEnterPressed(evt);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
_getHoveredSideOfContainer() {
|
|
851
|
+
return this._hoveredCell === this._container ? (this._isBottomOrRight() ? 0 : -1) : 1;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
_isBottomOrRight() {
|
|
855
|
+
return this._hoveredCellSide === "bottom" || this._hoveredCellSide === "right";
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
_containerEnterPressed(evt) {
|
|
859
|
+
if (!(this._table.selectedCell !== null && !evt.shiftKey)) return;
|
|
860
|
+
|
|
861
|
+
const tr = this._table.selectedCell.closest("TR");
|
|
862
|
+
let insertIndex = this._getHoveredSideOfContainer();
|
|
863
|
+
|
|
864
|
+
if (insertIndex === 1) insertIndex = tr.sectionRowIndex + 1;
|
|
865
|
+
|
|
866
|
+
this._table.addRow(insertIndex).cells[0].click();
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// -----------------------------------------------------------------------------
|
|
871
|
+
// Editor.js tool wrapper class (Table tool)
|
|
872
|
+
// -----------------------------------------------------------------------------
|
|
873
|
+
|
|
874
|
+
const TOOLBOX_ICON = `<svg width="18" height="14"><path d="M2.833 8v1.95a1.7 1.7 0 0 0 1.7 1.7h3.45V8h-5.15zm0-2h5.15V2.35h-3.45a1.7 1.7 0 0 0-1.7 1.7V6zm12.3 2h-5.15v3.65h3.45a1.7 1.7 0 0 0 1.7-1.7V8zm0-2V4.05a1.7 1.7 0 0 0-1.7-1.7h-3.45V6h5.15zM4.533.1h8.9a3.95 3.95 0 0 1 3.95 3.95v5.9a3.95 3.95 0 0 1-3.95 3.95h-8.9a3.95 3.95 0 0 1-3.95-3.95v-5.9A3.95 3.95 0 0 1 4.533.1z"/></svg>`;
|
|
875
|
+
|
|
876
|
+
const BORDER_ICON = `<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
877
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 2V8V10V16V18H2H8H10H16H18V16V10V8V2V0H16H10H8H2H0V2ZM8 2L2 2V8H8L8 2ZM8 10H2V16H8L8 10ZM10 16V10H16V16H10ZM10 8V2L16 2V8H10Z"/>
|
|
878
|
+
</svg>`;
|
|
879
|
+
|
|
880
|
+
class TableTool {
|
|
881
|
+
/**
|
|
882
|
+
* Editor.js Tool constructor signature
|
|
883
|
+
* @param {{data:any, config:any, api:any, readOnly:boolean}} param0
|
|
884
|
+
*/
|
|
885
|
+
constructor({ data, config, api, readOnly }) {
|
|
886
|
+
this.api = api;
|
|
887
|
+
this.readOnly = readOnly;
|
|
888
|
+
|
|
889
|
+
this._tableConstructor = new TableConstructor(data, config, api, readOnly);
|
|
890
|
+
|
|
891
|
+
this._CSS = {
|
|
892
|
+
input: "tc-table__inp",
|
|
893
|
+
settingsButton: this.api.styles.settingsButton,
|
|
894
|
+
settingsButtonActive: this.api.styles.settingsButtonActive,
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
this.borderActive = data.settings?.withBorder;
|
|
898
|
+
this.toggleBorderButton = null;
|
|
899
|
+
|
|
900
|
+
ct(this, "toggleBorder", () => {
|
|
901
|
+
this.borderActive = !this.borderActive;
|
|
902
|
+
this.toggleBorderButton.classList.toggle(this._CSS.settingsButtonActive, this.borderActive);
|
|
903
|
+
this._tableConstructor._table._element.classList.toggle(
|
|
904
|
+
this._tableConstructor._CSS.withBorder,
|
|
905
|
+
this.borderActive
|
|
906
|
+
);
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
static get isReadOnlySupported() {
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
static get enableLineBreaks() {
|
|
915
|
+
return true;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
static get sanitize() {
|
|
919
|
+
return { br: true, mark: true };
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
static get toolbox() {
|
|
923
|
+
return { icon: TOOLBOX_ICON, title: "Table" };
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
render() {
|
|
927
|
+
return this._tableConstructor.htmlElement;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Editor.js save(output)
|
|
932
|
+
*/
|
|
933
|
+
save(container) {
|
|
934
|
+
const tableEl = container.querySelector("table");
|
|
935
|
+
|
|
936
|
+
const content = [];
|
|
937
|
+
const rows = tableEl.rows;
|
|
938
|
+
|
|
939
|
+
const colSizeRatios = [];
|
|
940
|
+
const tableWidth = tableEl.offsetWidth;
|
|
941
|
+
|
|
942
|
+
for (let r = 0; r < rows.length; r++) {
|
|
943
|
+
const row = rows[r];
|
|
944
|
+
const tds = Array.from(row.cells);
|
|
945
|
+
|
|
946
|
+
const editableAreas = tds.map((td) => td.querySelector("." + this._CSS.input));
|
|
947
|
+
const images = tds.map((td) => td.querySelector("." + IMAGE_CSS.image));
|
|
948
|
+
|
|
949
|
+
const rowCells = tds.map((td, idx) => {
|
|
950
|
+
if (editableAreas[idx]) {
|
|
951
|
+
return { type: "input", text: editableAreas[idx].innerHTML };
|
|
952
|
+
}
|
|
953
|
+
if (images[idx]) {
|
|
954
|
+
return { type: "image", src: images[idx].getAttribute("src") };
|
|
955
|
+
}
|
|
956
|
+
return undefined;
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
if (r === 0) {
|
|
960
|
+
tds.forEach((td) => colSizeRatios.push(td.offsetWidth / tableWidth));
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
content.push(
|
|
964
|
+
rowCells.map((cell) => {
|
|
965
|
+
if (!cell) return "";
|
|
966
|
+
|
|
967
|
+
switch (cell.type) {
|
|
968
|
+
case "input":
|
|
969
|
+
return cell.text;
|
|
970
|
+
case "image":
|
|
971
|
+
return cell;
|
|
972
|
+
default:
|
|
973
|
+
return "";
|
|
974
|
+
}
|
|
975
|
+
})
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
return {
|
|
980
|
+
settings: {
|
|
981
|
+
sizes: colSizeRatios,
|
|
982
|
+
withBorder: this.borderActive === void 0 ? true : this.borderActive,
|
|
983
|
+
},
|
|
984
|
+
content,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
_isEmpty(container) {
|
|
989
|
+
return !container.textContent.trim();
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
renderSettings() {
|
|
993
|
+
const wrapper = document.createElement("DIV");
|
|
994
|
+
const btn = document.createElement("SPAN");
|
|
995
|
+
|
|
996
|
+
btn.classList.add(this._CSS.settingsButton);
|
|
997
|
+
if (this.borderActive) btn.classList.add(this._CSS.settingsButtonActive);
|
|
998
|
+
|
|
999
|
+
btn.innerHTML = BORDER_ICON;
|
|
1000
|
+
btn.dataset.active = this.borderActive;
|
|
1001
|
+
|
|
1002
|
+
btn.addEventListener("click", () => this.toggleBorder());
|
|
1003
|
+
|
|
1004
|
+
wrapper.appendChild(btn);
|
|
1005
|
+
this.toggleBorderButton = btn;
|
|
1006
|
+
|
|
1007
|
+
return wrapper;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// If you want it as default export (optional):
|
|
1012
|
+
export default TableTool;
|