@kubex/zinc 1.0.5 → 1.0.6
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/dist/custom-elements.json +172 -8
- package/dist/vscode.html-custom-data.json +8 -1
- package/dist/web-types.json +21 -2
- package/dist/zn.d.ts +63 -37
- package/dist/zn.min.js +320 -241
- package/package.json +1 -1
- package/src/components/bulk-actions/bulk-actions.component.ts +152 -84
- package/src/components/bulk-actions/bulk-actions.scss +2 -0
- package/src/components/chart/chart.component.ts +14 -12
- package/src/components/cols/cols.component.ts +7 -3
- package/src/components/cols/cols.scss +22 -14
- package/src/components/content-block/content-block.component.ts +102 -36
- package/src/components/data-table/data-table.component.ts +43 -18
- package/src/components/inline-edit/inline-edit.component.ts +24 -3
- package/src/components/inline-edit/inline-edit.scss +11 -1
- package/src/components/item/item.scss +9 -17
- package/src/components/settings-container/settings-container.scss +11 -4
- package/src/components/style/style.component.ts +7 -1
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import {classMap} from "lit/directives/class-map.js";
|
|
2
2
|
import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
|
|
3
3
|
import {FormControlController} from "../../internal/form";
|
|
4
|
+
import {litToHTML} from "../../utilities/lit-to-html";
|
|
4
5
|
import {property, query} from 'lit/decorators.js';
|
|
5
6
|
import ZincElement from '../../internal/zinc-element';
|
|
7
|
+
import ZnButton from "../button";
|
|
8
|
+
import ZnInput from "../input";
|
|
9
|
+
import ZnOption from "../option";
|
|
10
|
+
import ZnSelect from "../select";
|
|
11
|
+
import type {ZnChangeEvent} from "../../events/zn-change";
|
|
12
|
+
import type {ZnInputEvent} from "../../events/zn-input";
|
|
6
13
|
|
|
7
14
|
import styles from './bulk-actions.scss';
|
|
8
15
|
|
|
@@ -29,7 +36,10 @@ export interface BulkActionItem {
|
|
|
29
36
|
* @status experimental
|
|
30
37
|
* @since 1.0
|
|
31
38
|
*
|
|
32
|
-
* @dependency zn-
|
|
39
|
+
* @dependency zn-button
|
|
40
|
+
* @dependency zn-input
|
|
41
|
+
* @dependency zn-option
|
|
42
|
+
* @dependency zn-select
|
|
33
43
|
*
|
|
34
44
|
* @event zn-event-name - Emitted as an example.
|
|
35
45
|
*
|
|
@@ -42,9 +52,15 @@ export interface BulkActionItem {
|
|
|
42
52
|
*/
|
|
43
53
|
export default class ZnBulkActions extends ZincElement {
|
|
44
54
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
55
|
+
static dependencies = {
|
|
56
|
+
'zn-button': ZnButton,
|
|
57
|
+
'zn-input': ZnInput,
|
|
58
|
+
'zn-option': ZnOption,
|
|
59
|
+
'zn-select': ZnSelect,
|
|
60
|
+
};
|
|
45
61
|
|
|
46
62
|
@query('.bulk-actions') container: HTMLDivElement;
|
|
47
|
-
@query('.add-rule') addRule:
|
|
63
|
+
@query('.add-rule') addRule: ZnSelect;
|
|
48
64
|
@query('input#main-input') input: HTMLInputElement;
|
|
49
65
|
|
|
50
66
|
@property() name: string;
|
|
@@ -73,12 +89,13 @@ export default class ZnBulkActions extends ZincElement {
|
|
|
73
89
|
<div class="${classMap({
|
|
74
90
|
'bulk-actions': true
|
|
75
91
|
})}">
|
|
76
|
-
<select class="add-rule" @change="${this._addRule}">
|
|
77
|
-
<option value="">Select Filter</option>
|
|
92
|
+
<zn-select class="add-rule" placeholder="Select Filter" @zn-change="${this._addRule}">
|
|
78
93
|
${this.actions.map((item: BulkActionItem) => html`
|
|
79
|
-
<option value="${item.id}"
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
<zn-option value="${item.id}">
|
|
95
|
+
${item.name.charAt(0).toUpperCase() + item.name.slice(1)}
|
|
96
|
+
</zn-option>`)}
|
|
97
|
+
</zn-select>
|
|
98
|
+
<input id="main-input" name="${this.name}" value="${this.value}" hidden>
|
|
82
99
|
</div>
|
|
83
100
|
`;
|
|
84
101
|
}
|
|
@@ -95,8 +112,9 @@ export default class ZnBulkActions extends ZincElement {
|
|
|
95
112
|
this.value = btoa(JSON.stringify(data));
|
|
96
113
|
}
|
|
97
114
|
|
|
98
|
-
private _addRule(event: Event | null, value: string = "") {
|
|
99
|
-
const
|
|
115
|
+
private _addRule(event: Event | null, value: string = "", pos?: number) {
|
|
116
|
+
const target = event?.target as ZnSelect;
|
|
117
|
+
const id = value ? value : target?.value || '';
|
|
100
118
|
if (id === '') return;
|
|
101
119
|
|
|
102
120
|
const filter = this.actions.find(item => item.id === id);
|
|
@@ -109,104 +127,154 @@ export default class ZnBulkActions extends ZincElement {
|
|
|
109
127
|
value: ''
|
|
110
128
|
});
|
|
111
129
|
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
select.addEventListener('change', (e: Event) => this._changeRule(uniqueId, e));
|
|
125
|
-
select.classList.add('query-builder__key');
|
|
130
|
+
const keySelect = html`
|
|
131
|
+
<zn-select class="query-builder__key"
|
|
132
|
+
@zn-change="${(e: ZnChangeEvent) => this._changeRule(uniqueId, e)}"
|
|
133
|
+
value="${filter.id}">
|
|
134
|
+
${this.actions.map((item: BulkActionItem) => html`
|
|
135
|
+
<zn-option value="${item.id}">
|
|
136
|
+
${item.name.charAt(0).toUpperCase() + item.name.slice(1)}
|
|
137
|
+
</zn-option>
|
|
138
|
+
`)}
|
|
139
|
+
</zn-select>
|
|
140
|
+
`;
|
|
126
141
|
|
|
127
|
-
|
|
142
|
+
const input = this._createInput(uniqueId, filter);
|
|
143
|
+
|
|
144
|
+
const remove = html`
|
|
145
|
+
<zn-button class="query-builder__remove"
|
|
146
|
+
icon="delete"
|
|
147
|
+
icon-size="24"
|
|
148
|
+
color="transparent"
|
|
149
|
+
size="square"
|
|
150
|
+
@click="${(e: Event) => this._removeRule(uniqueId, e)}">
|
|
151
|
+
</zn-button>`;
|
|
152
|
+
|
|
153
|
+
const wrapper = html`
|
|
154
|
+
<div class="query-builder__wrapper">
|
|
155
|
+
${input}
|
|
156
|
+
${remove}
|
|
157
|
+
</div>
|
|
158
|
+
`;
|
|
128
159
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
} else if (filter.type === 'bool' || filter.type === 'boolean') {
|
|
142
|
-
input = document.createElement('select');
|
|
143
|
-
const option1 = document.createElement('option');
|
|
144
|
-
option1.value = '1';
|
|
145
|
-
option1.text = 'True';
|
|
146
|
-
input.appendChild(option1);
|
|
147
|
-
const option2 = document.createElement('option');
|
|
148
|
-
option2.value = '0';
|
|
149
|
-
option2.text = 'False';
|
|
150
|
-
input.appendChild(option2);
|
|
151
|
-
input.addEventListener('change', (e: Event) => this._updateValue(uniqueId, e));
|
|
152
|
-
} else if (filter.type === 'number') {
|
|
153
|
-
input = document.createElement('input');
|
|
154
|
-
input.setAttribute('type', 'number');
|
|
155
|
-
input.addEventListener('input', (e: Event) => this._updateValue(uniqueId, e));
|
|
156
|
-
} else if (filter.type === 'date') {
|
|
157
|
-
input = document.createElement('input');
|
|
158
|
-
input.setAttribute('type', 'date');
|
|
159
|
-
input.addEventListener('input', (e: Event) => this._updateValue(uniqueId, e));
|
|
160
|
+
const rowElement = html`
|
|
161
|
+
<div id="${uniqueId}" class="query-builder__row">
|
|
162
|
+
${keySelect}
|
|
163
|
+
${wrapper}
|
|
164
|
+
</div>
|
|
165
|
+
`;
|
|
166
|
+
|
|
167
|
+
const row = litToHTML<HTMLDivElement>(rowElement);
|
|
168
|
+
if (!row) return;
|
|
169
|
+
|
|
170
|
+
if (pos !== undefined) {
|
|
171
|
+
this.container.insertBefore(row, this.container.children[pos]);
|
|
160
172
|
} else {
|
|
161
|
-
|
|
162
|
-
input.setAttribute('type', 'text');
|
|
163
|
-
input.addEventListener('input', (e: Event) => this._updateValue(uniqueId, e));
|
|
173
|
+
this.container.insertBefore(row, this.addRule);
|
|
164
174
|
}
|
|
165
175
|
|
|
166
|
-
|
|
167
|
-
const wrapper = document.createElement('div');
|
|
168
|
-
wrapper.classList.add('query-builder__wrapper');
|
|
169
|
-
wrapper.appendChild(input);
|
|
170
|
-
row.appendChild(wrapper);
|
|
171
|
-
|
|
172
|
-
const remove = document.createElement('zn-button');
|
|
173
|
-
remove.setAttribute('icon', 'delete');
|
|
174
|
-
remove.setAttribute('icon-size', '24');
|
|
175
|
-
remove.setAttribute('color', 'transparent');
|
|
176
|
-
remove.setAttribute('size', 'square');
|
|
177
|
-
remove.addEventListener('click', (e: Event) => this._removeRule(uniqueId, e));
|
|
178
|
-
remove.classList.add('query-builder__remove');
|
|
179
|
-
row.appendChild(remove);
|
|
180
|
-
|
|
181
|
-
this.container.insertBefore(row, this.addRule);
|
|
176
|
+
// Reset back to default placeholder
|
|
182
177
|
this.addRule.value = '';
|
|
178
|
+
this.addRule.displayLabel = '';
|
|
179
|
+
if (this.addRule.selectedOptions?.length) {
|
|
180
|
+
this.addRule.selectedOptions[0].selected = false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
183
|
this._handleChange();
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
private
|
|
186
|
+
private _createInput(uniqueId: string, filter: BulkActionItem): ZnSelect | ZnInput | null {
|
|
187
|
+
if (filter.options) {
|
|
188
|
+
const input = html`
|
|
189
|
+
<zn-select class="query-builder__value"
|
|
190
|
+
@zn-change="${(e: ZnChangeEvent) => this._updateValue(uniqueId, e)}">
|
|
191
|
+
${Object.keys(filter.options).map(key => html`
|
|
192
|
+
<zn-option value="${key}">
|
|
193
|
+
${filter.options ? filter.options[key] : ''}
|
|
194
|
+
</zn-option>
|
|
195
|
+
`)}
|
|
196
|
+
</zn-select>`;
|
|
197
|
+
const el = litToHTML<ZnSelect>(input);
|
|
198
|
+
if (el) {
|
|
199
|
+
// initialize with current selection
|
|
200
|
+
this._updateValue(uniqueId, {target: el} as unknown as Event);
|
|
201
|
+
}
|
|
202
|
+
return el;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (filter.type === 'bool' || filter.type === 'boolean') {
|
|
206
|
+
const input = html`
|
|
207
|
+
<zn-select class="query-builder__value"
|
|
208
|
+
@zn-change="${(e: ZnChangeEvent) => this._updateValue(uniqueId, e)}">
|
|
209
|
+
<zn-option value="1">True</zn-option>
|
|
210
|
+
<zn-option value="0">False</zn-option>
|
|
211
|
+
</zn-select>`;
|
|
212
|
+
return litToHTML<ZnSelect>(input);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (filter.type === 'number') {
|
|
216
|
+
const input = html`
|
|
217
|
+
<zn-input type="number"
|
|
218
|
+
class="query-builder__value"
|
|
219
|
+
@zn-input="${(e: ZnInputEvent) => this._updateValue(uniqueId, e)}">
|
|
220
|
+
</zn-input>`;
|
|
221
|
+
return litToHTML<ZnInput>(input);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (filter.type === 'date') {
|
|
225
|
+
const input = html`
|
|
226
|
+
<zn-input type="date"
|
|
227
|
+
class="query-builder__value"
|
|
228
|
+
@zn-input="${(e: ZnInputEvent) => this._updateValue(uniqueId, e)}">
|
|
229
|
+
</zn-input>`;
|
|
230
|
+
return litToHTML<ZnInput>(input);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const input = html`
|
|
234
|
+
<zn-input type="text"
|
|
235
|
+
class="query-builder__value"
|
|
236
|
+
@zn-input="${(e: ZnInputEvent) => this._updateValue(uniqueId, e)}">
|
|
237
|
+
</zn-input>`;
|
|
238
|
+
return litToHTML<ZnInput>(input);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private _updateValue(id: string, event: Event | { target: ZnSelect | ZnInput | HTMLDivElement }) {
|
|
187
242
|
const filter = this._selectedRules.get(id);
|
|
188
243
|
if (!filter) return;
|
|
189
244
|
|
|
190
|
-
const input = event.target as
|
|
191
|
-
filter.value = input.value;
|
|
245
|
+
const input = event.target as ZnSelect | ZnInput;
|
|
246
|
+
filter.value = input.value as string;
|
|
192
247
|
|
|
193
248
|
this._selectedRules.set(id, filter);
|
|
194
249
|
this._handleChange();
|
|
195
250
|
}
|
|
196
251
|
|
|
197
|
-
private
|
|
252
|
+
private _getRulePosition(id: string): number {
|
|
253
|
+
const rules = this.container.querySelectorAll('.query-builder__row');
|
|
254
|
+
let position: number = -1;
|
|
255
|
+
rules.forEach((item, index) => {
|
|
256
|
+
if ((item as HTMLDivElement).id === id) {
|
|
257
|
+
position = index;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
return position;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private _changeRule(id: string, event: ZnChangeEvent) {
|
|
198
264
|
// remove the element from the dom
|
|
199
|
-
const
|
|
200
|
-
|
|
265
|
+
const pos: number = this._getRulePosition(id);
|
|
266
|
+
const select = event.target as ZnSelect;
|
|
267
|
+
select.popup.active = false;
|
|
268
|
+
select?.parentElement?.remove();
|
|
201
269
|
// recreate the element based on the selected value;
|
|
202
|
-
this._removeRule(id, event);
|
|
203
|
-
this._addRule(event);
|
|
270
|
+
this._removeRule(id, event as unknown as Event);
|
|
271
|
+
this._addRule(event as unknown as Event, '', pos);
|
|
204
272
|
}
|
|
205
273
|
|
|
206
274
|
private _removeRule(id: string, event: Event) {
|
|
207
275
|
this._selectedRules.delete(id);
|
|
208
|
-
const button = event.target as
|
|
209
|
-
button.
|
|
276
|
+
const button = event.target as ZnButton;
|
|
277
|
+
button?.closest('.query-builder__row')?.remove();
|
|
210
278
|
this._handleChange();
|
|
211
279
|
}
|
|
212
280
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
|
|
2
|
+
import {property} from 'lit/decorators.js';
|
|
3
3
|
import ApexCharts from "apexcharts";
|
|
4
4
|
import ZincElement from '../../internal/zinc-element';
|
|
5
5
|
|
|
@@ -26,21 +26,22 @@ export default class ZnChart extends ZincElement {
|
|
|
26
26
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
27
27
|
|
|
28
28
|
@property() type: 'area' | 'bar' | 'line' = 'bar';
|
|
29
|
-
@property({
|
|
30
|
-
@property({
|
|
29
|
+
@property({type: Array}) data: any[] = [];
|
|
30
|
+
@property({type: Array}) categories: string | string[] = '';
|
|
31
31
|
|
|
32
32
|
@property() xAxis: string;
|
|
33
|
-
@property({
|
|
33
|
+
@property({type: Number, attribute: 'd-size'}) datapointSize: number = 1;
|
|
34
|
+
@property({type: Boolean}) stacked = false;
|
|
34
35
|
|
|
35
36
|
// Live
|
|
36
|
-
@property({
|
|
37
|
-
@property({
|
|
38
|
-
@property({
|
|
39
|
-
@property({
|
|
37
|
+
@property({type: Boolean}) live = false;
|
|
38
|
+
@property({attribute: 'data-url'}) dataUrl = '';
|
|
39
|
+
@property({attribute: 'live-interval', type: Number}) liveInterval = 1000;
|
|
40
|
+
@property({type: Number, reflect: true}) height = 300;
|
|
40
41
|
|
|
41
|
-
@property({
|
|
42
|
+
@property({attribute: 'enable-animations', type: Boolean}) enableAnimations = false;
|
|
42
43
|
|
|
43
|
-
@property({
|
|
44
|
+
@property({attribute: 'y-axis-append'}) yAxisAppend: string;
|
|
44
45
|
|
|
45
46
|
private chart: ApexCharts;
|
|
46
47
|
|
|
@@ -54,10 +55,11 @@ export default class ZnChart extends ZincElement {
|
|
|
54
55
|
animations: this.enableAnimations,
|
|
55
56
|
chart: {
|
|
56
57
|
type: this.type,
|
|
57
|
-
toolbar: {
|
|
58
|
+
toolbar: {show: false},
|
|
58
59
|
foreColor: theme == 'dark' ? "rgb(149, 163, 184)" : "rgb(123, 112, 151)",
|
|
59
60
|
height: this.height + 'px',
|
|
60
61
|
fontFamily: '"Inter", sans',
|
|
62
|
+
stacked: this.stacked,
|
|
61
63
|
zoom: {
|
|
62
64
|
enabled: false
|
|
63
65
|
},
|
|
@@ -25,7 +25,6 @@ import styles from './cols.scss';
|
|
|
25
25
|
export default class ZnCols extends ZincElement {
|
|
26
26
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
27
27
|
|
|
28
|
-
|
|
29
28
|
@property({reflect: true, attribute: 'layout'}) layout: string = '';
|
|
30
29
|
|
|
31
30
|
@property({attribute: 'mc', type: Number, reflect: true}) maxColumns: number = 0;
|
|
@@ -35,7 +34,11 @@ export default class ZnCols extends ZincElement {
|
|
|
35
34
|
@property({type: Boolean}) border: boolean = false;
|
|
36
35
|
|
|
37
36
|
@property({type: Boolean}) pad: boolean;
|
|
37
|
+
|
|
38
|
+
@property({type: Boolean}) divide: boolean = false;
|
|
39
|
+
|
|
38
40
|
@property({attribute: 'pad-x', type: Boolean}) padX: boolean;
|
|
41
|
+
|
|
39
42
|
@property({attribute: 'pad-y', type: Boolean}) padY: boolean;
|
|
40
43
|
|
|
41
44
|
render() {
|
|
@@ -47,7 +50,7 @@ export default class ZnCols extends ZincElement {
|
|
|
47
50
|
|
|
48
51
|
this.layout = layout.join(',');
|
|
49
52
|
|
|
50
|
-
this.maxColumns =
|
|
53
|
+
this.maxColumns = layout.reduce((a, b) => a + b, 0);
|
|
51
54
|
|
|
52
55
|
const prefix = 'zn-col-';
|
|
53
56
|
this.querySelectorAll(':scope > *').forEach((element: HTMLElement, index) => {
|
|
@@ -66,7 +69,8 @@ export default class ZnCols extends ZincElement {
|
|
|
66
69
|
'cols--pad': this.pad,
|
|
67
70
|
'cols--pad-x': this.padX,
|
|
68
71
|
'cols--pad-y': this.padY,
|
|
69
|
-
|
|
72
|
+
'cols--divide': this.divide,
|
|
73
|
+
[`cols--layout-${this.layout.replaceAll(',', '')}`]: !!this.layout,
|
|
70
74
|
[`cols--mc-${this.maxColumns}`]: !!this.maxColumns,
|
|
71
75
|
})}">
|
|
72
76
|
<slot></slot>
|
|
@@ -26,20 +26,10 @@
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
&--mc-3 {
|
|
34
|
-
--col-width: 30%;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
&--mc-4 {
|
|
38
|
-
--col-width: 23%;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
&--mc-5 {
|
|
42
|
-
--col-width: 18%;
|
|
29
|
+
@for $i from 1 through 12 {
|
|
30
|
+
&--mc-#{$i} {
|
|
31
|
+
--col-width: calc(100% / #{$i + 1});
|
|
32
|
+
}
|
|
43
33
|
}
|
|
44
34
|
|
|
45
35
|
&--layout-121 {
|
|
@@ -54,6 +44,24 @@
|
|
|
54
44
|
}
|
|
55
45
|
}
|
|
56
46
|
|
|
47
|
+
&--divide {
|
|
48
|
+
::slotted(*:not(:last-child)) {
|
|
49
|
+
position: relative;
|
|
50
|
+
|
|
51
|
+
&:after {
|
|
52
|
+
content: "";
|
|
53
|
+
position: absolute;
|
|
54
|
+
display: block;
|
|
55
|
+
width: 1px;
|
|
56
|
+
top: 0;
|
|
57
|
+
height: 100%;
|
|
58
|
+
max-height: 100%;
|
|
59
|
+
right: calc((var(--zn-spacing-small) / 2) * -1);
|
|
60
|
+
background-color: rgb(var(--zn-border-color));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
&--no-gap {
|
|
58
66
|
gap: 0;
|
|
59
67
|
}
|
|
@@ -28,6 +28,7 @@ export default class ContentBlock extends ZincElement {
|
|
|
28
28
|
|
|
29
29
|
@property({type: Boolean, reflect: true}) outbound = false;
|
|
30
30
|
@property({type: Boolean, reflect: true}) short = false;
|
|
31
|
+
@property({attribute: 'default-display', reflect: true}) defaultDisplay: 'text' | 'html' = 'text';
|
|
31
32
|
|
|
32
33
|
@queryAssignedNodes({slot: 'html', flatten: true}) htmlNodes!: Node[];
|
|
33
34
|
|
|
@@ -37,49 +38,82 @@ export default class ContentBlock extends ZincElement {
|
|
|
37
38
|
|
|
38
39
|
private _textRows: TextRow[] = [];
|
|
39
40
|
|
|
41
|
+
private _themeChangeObserver?: MutationObserver;
|
|
42
|
+
|
|
40
43
|
connectedCallback() {
|
|
41
44
|
super.connectedCallback();
|
|
42
45
|
this.iframe.then((iframe) => {
|
|
43
|
-
|
|
46
|
+
const rebuild = () => {
|
|
44
47
|
const div = this.htmlNodes[0] as HTMLDivElement;
|
|
45
|
-
if (div)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
if (!div) return;
|
|
49
|
+
|
|
50
|
+
let convertedHtml = div?.innerHTML ?? '';
|
|
51
|
+
convertedHtml = convertedHtml.replace(/ /g, ' ');
|
|
52
|
+
convertedHtml = convertedHtml.replace(/</g, '<');
|
|
53
|
+
convertedHtml = convertedHtml.replace(/>/g, '>');
|
|
54
|
+
convertedHtml = convertedHtml.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
55
|
+
|
|
56
|
+
const themeAttr = document.documentElement.getAttribute('t') || document.body.getAttribute('t') || '';
|
|
57
|
+
|
|
58
|
+
// Pull stylesheet links from host to inherit Zinc styles
|
|
59
|
+
const linkTags = Array.from(document.head.querySelectorAll('link[rel="stylesheet"]'))
|
|
60
|
+
.map((element: Element) => {
|
|
61
|
+
const href = (element as HTMLLinkElement).getAttribute('href') || (element as HTMLLinkElement).href || '';
|
|
62
|
+
return href ? `<link rel="stylesheet" href="${href}">` : '';
|
|
63
|
+
})
|
|
64
|
+
.join('');
|
|
65
|
+
|
|
66
|
+
const baseStyles = `<style>
|
|
67
|
+
html, body {
|
|
68
|
+
background-color: var(--zn-panel-background-color);
|
|
69
|
+
container-type: size;
|
|
70
|
+
margin: 0;
|
|
71
|
+
}
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const originalPosition = iframe.style.position;
|
|
62
|
-
|
|
63
|
-
// Make iframe render but stay hidden
|
|
64
|
-
iframe.style.display = 'block';
|
|
65
|
-
iframe.style.visibility = 'hidden';
|
|
66
|
-
iframe.style.position = 'absolute';
|
|
67
|
-
|
|
68
|
-
setTimeout(() => {
|
|
69
|
-
const iframeBody = iframe.contentDocument?.body;
|
|
70
|
-
if (iframeBody) {
|
|
71
|
-
const height = iframeBody.scrollHeight;
|
|
72
|
-
iframe.style.height = `${height + 30}px`;
|
|
73
|
+
hr {
|
|
74
|
+
height: 0;
|
|
75
|
+
border: none;
|
|
76
|
+
border-top: 1px solid rgb(var(--zn-border-color));
|
|
73
77
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
|
|
79
|
+
code, kbd, samp, pre, .mono {
|
|
80
|
+
font-family: var(--zn-font-family-mono),sans-serif;
|
|
81
|
+
font-size: 1em;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
small {
|
|
85
|
+
font-size: 80%;
|
|
86
|
+
}
|
|
87
|
+
</style>`;
|
|
88
|
+
|
|
89
|
+
const head = `${linkTags}${baseStyles}`;
|
|
90
|
+
const htmlAttrs = `${themeAttr ? ` t="${themeAttr}"` : ''}`;
|
|
91
|
+
const baseHref = document.baseURI || window.location.href;
|
|
92
|
+
|
|
93
|
+
iframe.srcdoc = `<!doctype html><html${htmlAttrs}><head><base href="${baseHref}" target="_blank">${head}</head><body>${convertedHtml}</body></html>`;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
rebuild();
|
|
97
|
+
|
|
98
|
+
// Observe theme changes on both attribute and theme classes on html and body
|
|
99
|
+
this._themeChangeObserver = new MutationObserver(() => rebuild());
|
|
100
|
+
this._themeChangeObserver.observe(document.documentElement, {attributes: true, attributeFilter: ['t', 'class']});
|
|
101
|
+
this._themeChangeObserver.observe(document.body, {attributes: true, attributeFilter: ['t', 'class']});
|
|
102
|
+
|
|
103
|
+
const doc = iframe.contentDocument;
|
|
104
|
+
if (!doc) return;
|
|
105
|
+
|
|
106
|
+
iframe.style.height = (75 + doc.body.scrollHeight) + 'px';
|
|
107
|
+
iframe.style.width = '100%';
|
|
108
|
+
iframe.style.border = '0';
|
|
80
109
|
});
|
|
81
110
|
}
|
|
82
111
|
|
|
112
|
+
disconnectedCallback() {
|
|
113
|
+
super.disconnectedCallback();
|
|
114
|
+
this._themeChangeObserver?.disconnect();
|
|
115
|
+
}
|
|
116
|
+
|
|
83
117
|
private _collapseContent(e: Event) {
|
|
84
118
|
const target = e.target as HTMLElement;
|
|
85
119
|
const headerClick = target.classList.contains('content-block-header') ||
|
|
@@ -130,6 +164,7 @@ export default class ContentBlock extends ZincElement {
|
|
|
130
164
|
const hasTextSlot = this.hasSlotController.test('text');
|
|
131
165
|
const hasHtmlSlot = this.hasSlotController.test('html');
|
|
132
166
|
const showActions = hasTextSlot && hasHtmlSlot;
|
|
167
|
+
const initialShowHtml = hasHtmlSlot && (!hasTextSlot || this.defaultDisplay === 'html');
|
|
133
168
|
|
|
134
169
|
return html`
|
|
135
170
|
<zn-panel flush tabbed class="${classMap({
|
|
@@ -167,10 +202,10 @@ export default class ContentBlock extends ZincElement {
|
|
|
167
202
|
</zn-header>
|
|
168
203
|
|
|
169
204
|
<zn-sp no-gap>
|
|
170
|
-
<iframe class="hidden" title="Content block HTML preview"></iframe>
|
|
205
|
+
<iframe class="${classMap({'hidden': !initialShowHtml})}" title="Content block HTML preview"></iframe>
|
|
171
206
|
<slot class="html-content" name="html" style="display: none;"></slot>
|
|
172
207
|
<slot name="text" style="display: none;"></slot>
|
|
173
|
-
<div class="text-content">
|
|
208
|
+
<div class="${classMap({'text-content': true, 'hidden': initialShowHtml})}">
|
|
174
209
|
${text.map((section) => html`
|
|
175
210
|
${section.type === 'reply' ? html`
|
|
176
211
|
<div class="toggle-reply" @click="${this.showReply}">...</div>` : ''}
|
|
@@ -220,6 +255,7 @@ export default class ContentBlock extends ZincElement {
|
|
|
220
255
|
if (row.startsWith('Sent from')) type = 'reply';
|
|
221
256
|
if (row.trim() === '' && previousType === 'reply') type = 'reply';
|
|
222
257
|
if (type === 'reply' && row.startsWith('>')) row = row.substring(1).trim();
|
|
258
|
+
row = this.transformLineForImages(row);
|
|
223
259
|
|
|
224
260
|
if (previousType === type) {
|
|
225
261
|
if (textRows.length === 0) {
|
|
@@ -239,6 +275,36 @@ export default class ContentBlock extends ZincElement {
|
|
|
239
275
|
return this._textRows;
|
|
240
276
|
}
|
|
241
277
|
|
|
278
|
+
private transformLineForImages(line: string): string {
|
|
279
|
+
let out = line;
|
|
280
|
+
|
|
281
|
+
// Replace Markdown image syntax  for http(s)
|
|
282
|
+
const mdHttpImg = /!\[([^\]]*)\]\((https?:\/\/[^\s)]+?\.(?:png|jpe?g|gif|webp|bmp|svg))\)/gi;
|
|
283
|
+
out = out.replace(mdHttpImg, (_match, alt, url) => {
|
|
284
|
+
return `<img src="${url}" alt="${alt || 'image'}" loading="lazy" style="max-width:100%;max-height:500px;">`;
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Replace Markdown image syntax with data URI (base64)
|
|
288
|
+
const mdDataImg = /!\[([^\]]*)\]\((data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+)\)/gi;
|
|
289
|
+
out = out.replace(mdDataImg, (_match, alt, url) => {
|
|
290
|
+
return `<img src="${url}" alt="${alt || 'image'}" loading="lazy" style="max-width:100%;max-height:500px;">`;
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Replace bare http(s) image URLs with <img>
|
|
294
|
+
const httpImg = /(https?:\/\/[^\s]+?\.(?:png|jpe?g|gif|webp|bmp|svg))(?!\S)/gi;
|
|
295
|
+
out = out.replace(httpImg, (_m, url) => {
|
|
296
|
+
return `<img src="${url}" alt="image" loading="lazy" style="max-width:100%;max-height:500px;">`;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Replace bare data URI images with <img>
|
|
300
|
+
const dataImg = /(data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+)(?!\S)/gi;
|
|
301
|
+
out = out.replace(dataImg, (_m, url) => {
|
|
302
|
+
return `<img src="${url}" alt="image" loading="lazy" style="max-width:100%;max-height:500px;">`;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
|
|
242
308
|
showReply(e: MouseEvent) {
|
|
243
309
|
const target = e.target as HTMLElement;
|
|
244
310
|
const nextElement = target.nextElementSibling;
|