@liwe3/webcomponents 1.1.0 → 1.1.10

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.
Files changed (56) hide show
  1. package/dist/AIMarkdownEditor.d.ts +35 -0
  2. package/dist/AIMarkdownEditor.d.ts.map +1 -0
  3. package/dist/AIMarkdownEditor.js +412 -0
  4. package/dist/AIMarkdownEditor.js.map +1 -0
  5. package/dist/AITextEditor.d.ts +10 -0
  6. package/dist/AITextEditor.d.ts.map +1 -1
  7. package/dist/AITextEditor.js +63 -27
  8. package/dist/AITextEditor.js.map +1 -1
  9. package/dist/ButtonToolbar.d.ts +35 -0
  10. package/dist/ButtonToolbar.d.ts.map +1 -0
  11. package/dist/ButtonToolbar.js +220 -0
  12. package/dist/ButtonToolbar.js.map +1 -0
  13. package/dist/CheckList.d.ts +31 -0
  14. package/dist/CheckList.d.ts.map +1 -0
  15. package/dist/CheckList.js +336 -0
  16. package/dist/CheckList.js.map +1 -0
  17. package/dist/ChunkUploader.d.ts +22 -0
  18. package/dist/ChunkUploader.d.ts.map +1 -1
  19. package/dist/ChunkUploader.js +245 -103
  20. package/dist/ChunkUploader.js.map +1 -1
  21. package/dist/ComicBalloon.d.ts +82 -0
  22. package/dist/ComicBalloon.d.ts.map +1 -0
  23. package/dist/ComicBalloon.js +346 -0
  24. package/dist/ComicBalloon.js.map +1 -0
  25. package/dist/Dialog.d.ts +102 -0
  26. package/dist/Dialog.d.ts.map +1 -0
  27. package/dist/Dialog.js +299 -0
  28. package/dist/Dialog.js.map +1 -0
  29. package/dist/MarkdownPreview.d.ts +25 -0
  30. package/dist/MarkdownPreview.d.ts.map +1 -0
  31. package/dist/MarkdownPreview.js +147 -0
  32. package/dist/MarkdownPreview.js.map +1 -0
  33. package/dist/ResizableCropper.d.ts +158 -0
  34. package/dist/ResizableCropper.d.ts.map +1 -0
  35. package/dist/ResizableCropper.js +562 -0
  36. package/dist/ResizableCropper.js.map +1 -0
  37. package/dist/SmartSelect.d.ts +1 -0
  38. package/dist/SmartSelect.d.ts.map +1 -1
  39. package/dist/SmartSelect.js +45 -2
  40. package/dist/SmartSelect.js.map +1 -1
  41. package/dist/index.d.ts +16 -9
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +52 -29
  44. package/dist/index.js.map +1 -1
  45. package/package.json +33 -3
  46. package/src/AIMarkdownEditor.ts +568 -0
  47. package/src/AITextEditor.ts +97 -2
  48. package/src/ButtonToolbar.ts +302 -0
  49. package/src/CheckList.ts +438 -0
  50. package/src/ChunkUploader.ts +837 -623
  51. package/src/ComicBalloon.ts +709 -0
  52. package/src/Dialog.ts +510 -0
  53. package/src/MarkdownPreview.ts +213 -0
  54. package/src/ResizableCropper.ts +1099 -0
  55. package/src/SmartSelect.ts +48 -2
  56. package/src/index.ts +110 -47
package/src/Dialog.ts ADDED
@@ -0,0 +1,510 @@
1
+ /**
2
+ * Dialog Web Component
3
+ * A customizable dialog with HTML body, custom buttons, and modal support
4
+ */
5
+
6
+ export type DialogButton = {
7
+ label : string;
8
+ backgroundColor? : string;
9
+ onclick : ( dialog? : DialogElement ) => void;
10
+ };
11
+
12
+ export type DialogConfig = {
13
+ title? : string; // Default: "Dialog"
14
+ body : string; // HTML content
15
+ buttons? : DialogButton[];
16
+ modal? : boolean; // If true, dims background and prevents interaction outside dialog
17
+ escToClose? : boolean; // If true, Esc key closes dialog
18
+ clickToClose? : boolean; // If true, clicking outside closes dialog (like cancel)
19
+ fxAppear? : 'none' | 'fade' | 'slide'; // Animation effect when dialog appears (default: 'none')
20
+ fxSpeed? : number; // Animation duration in milliseconds (default: 1000)
21
+ onclose? : () => void;
22
+ };
23
+
24
+ export class DialogElement extends HTMLElement {
25
+ declare shadowRoot : ShadowRoot;
26
+ private config : DialogConfig = {
27
+ title: 'Dialog',
28
+ body: '',
29
+ modal: true,
30
+ escToClose: true,
31
+ clickToClose: true,
32
+ fxAppear: 'none',
33
+ fxSpeed: 1000,
34
+ };
35
+ private backdrop? : HTMLElement;
36
+ private escKeyHandler? : ( e : KeyboardEvent ) => void;
37
+ private eventsBound : boolean = false;
38
+
39
+ constructor () {
40
+ super();
41
+ this.attachShadow( { mode: 'open' } );
42
+ }
43
+
44
+ connectedCallback () : void {
45
+ this.render();
46
+ this.setupKeyboardListeners();
47
+ }
48
+
49
+ disconnectedCallback () : void {
50
+ this.removeKeyboardListeners();
51
+ if ( this.backdrop ) {
52
+ this.backdrop.remove();
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Shows the dialog with the given configuration
58
+ */
59
+ show ( config : DialogConfig ) : void {
60
+ this.config = { ...this.config, ...config };
61
+ this.render();
62
+ this.setupKeyboardListeners();
63
+
64
+ // Create backdrop if modal is enabled
65
+ if ( this.config.modal ) {
66
+ this.createBackdrop();
67
+ }
68
+
69
+ // Add opening animation class
70
+ requestAnimationFrame( () => {
71
+ requestAnimationFrame( () => {
72
+ const dialog = this.shadowRoot.querySelector( '.dialog-container' ) as HTMLElement;
73
+ if ( dialog ) {
74
+ dialog.classList.add( 'show' );
75
+ }
76
+ if ( this.backdrop ) {
77
+ // Use inline style instead of class since backdrop is outside shadow DOM
78
+ this.backdrop.style.opacity = '1';
79
+ }
80
+ } );
81
+ } );
82
+ }
83
+
84
+ /**
85
+ * Closes the dialog
86
+ */
87
+ close () : void {
88
+ const dialog = this.shadowRoot.querySelector( '.dialog-container' ) as HTMLElement;
89
+
90
+ // Add closing animation
91
+ if ( dialog ) {
92
+ dialog.classList.add( 'closing' );
93
+ }
94
+ if ( this.backdrop ) {
95
+ this.backdrop.remove();
96
+ this.backdrop = undefined;
97
+ }
98
+
99
+ // Wait for animation to complete
100
+ const animationDuration = this.config.fxAppear === 'none' ? 0 : ( this.config.fxSpeed || 1000 );
101
+ setTimeout( () => {
102
+ this.removeKeyboardListeners();
103
+
104
+ this.dispatchEvent( new CustomEvent( 'close' ) );
105
+ if ( this.config.onclose ) {
106
+ this.config.onclose();
107
+ }
108
+ this.remove();
109
+ }, animationDuration );
110
+ }
111
+
112
+ /**
113
+ * Creates the modal backdrop
114
+ */
115
+ private createBackdrop () : void {
116
+ if ( this.backdrop ) return;
117
+
118
+ this.backdrop = document.createElement( 'div' );
119
+ this.backdrop.className = 'dialog-backdrop';
120
+ this.backdrop.style.position = 'fixed';
121
+ this.backdrop.style.top = '0';
122
+ this.backdrop.style.left = '0';
123
+ this.backdrop.style.width = '100%';
124
+ this.backdrop.style.height = '100%';
125
+ this.backdrop.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
126
+ this.backdrop.style.backdropFilter = 'blur(4px)';
127
+ ( this.backdrop.style as any ).webkitBackdropFilter = 'blur(4px)'; // Safari support
128
+ this.backdrop.style.zIndex = '99998';
129
+ this.backdrop.style.opacity = '0';
130
+ this.backdrop.style.transition = 'opacity 0.3s ease';
131
+
132
+ // Handle click outside to close
133
+ if ( this.config.clickToClose ) {
134
+ this.backdrop.addEventListener( 'click', () => {
135
+ this.close();
136
+ } );
137
+ }
138
+
139
+ document.body.appendChild( this.backdrop );
140
+ }
141
+
142
+ /**
143
+ * Sets up keyboard event listeners
144
+ */
145
+ private setupKeyboardListeners () : void {
146
+ if ( this.config.escToClose ) {
147
+ this.escKeyHandler = ( e : KeyboardEvent ) => {
148
+ if ( e.key === 'Escape' ) {
149
+ this.close();
150
+ }
151
+ };
152
+ document.addEventListener( 'keydown', this.escKeyHandler );
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Removes keyboard event listeners
158
+ */
159
+ private removeKeyboardListeners () : void {
160
+ if ( this.escKeyHandler ) {
161
+ document.removeEventListener( 'keydown', this.escKeyHandler );
162
+ this.escKeyHandler = undefined;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Binds all event listeners
168
+ */
169
+ private bindEvents () : void {
170
+ // Only bind events once to prevent duplicate event listeners
171
+ if ( this.eventsBound ) return;
172
+ this.eventsBound = true;
173
+
174
+ // Handle button clicks
175
+ this.shadowRoot.addEventListener( 'click', ( e ) => {
176
+ const target = e.target as HTMLElement;
177
+
178
+ if ( target.closest( '.close-button' ) ) {
179
+ this.close();
180
+ } else if ( target.closest( '.dialog-button' ) ) {
181
+ const buttonIndex = ( target.closest( '.dialog-button' ) as HTMLElement ).dataset.index;
182
+ if ( buttonIndex !== undefined ) {
183
+ const button = this.config.buttons?.[parseInt( buttonIndex, 10 )];
184
+ if ( button && button.onclick ) {
185
+ button.onclick( this );
186
+ }
187
+ }
188
+ }
189
+ } );
190
+
191
+ // Prevent scroll propagation to body when scrolling within dialog
192
+ const dialogContainer = this.shadowRoot.querySelector( '.dialog-container' ) as HTMLElement;
193
+ if ( dialogContainer ) {
194
+ dialogContainer.addEventListener( 'wheel', ( e ) => {
195
+ e.stopPropagation();
196
+ }, { passive: true } );
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Renders the component
202
+ */
203
+ private render () : void {
204
+ const title = this.config.title || 'Dialog';
205
+ const buttons = this.config.buttons || [];
206
+ const animationSpeed = this.config.fxSpeed || 1000;
207
+ const animationDuration = `${animationSpeed}ms`;
208
+ const fxClass = this.config.fxAppear === 'fade'
209
+ ? 'fx-fade'
210
+ : this.config.fxAppear === 'slide'
211
+ ? 'fx-slide'
212
+ : '';
213
+
214
+ // Determine button layout:
215
+ // - If only 1 button, put it on the right
216
+ // - If 2+ buttons, put first on left, rest on right
217
+ const firstButton = buttons.length > 1 ? buttons[0] : null;
218
+ const rightButtons = buttons.length === 1 ? buttons : buttons.slice( 1 );
219
+
220
+ this.shadowRoot.innerHTML = `
221
+ <style>
222
+ :host {
223
+ display: block;
224
+ font-family: var(--font-family, var(--liwe3-font-family, Ubuntu, sans-serif));
225
+ font-size: var(--font-size, 14px);
226
+ }
227
+
228
+ .dialog-container {
229
+ position: fixed;
230
+ top: 50%;
231
+ left: 50%;
232
+ transform: translate(-50%, -50%);
233
+ min-width: 400px;
234
+ max-width: 600px;
235
+ max-height: 80vh;
236
+ background: var(--dialog-background, var(--liwe3-surface-raised, white));
237
+ border-radius: var(--dialog-border-radius, var(--liwe3-border-radius, 8px));
238
+ box-shadow: var(--dialog-shadow, 0 10px 40px rgba(0, 0, 0, 0.2));
239
+ z-index: 99999;
240
+ display: flex;
241
+ flex-direction: column;
242
+ opacity: 1;
243
+ font-family: var(--font-family, var(--liwe3-font-family, Ubuntu, sans-serif));
244
+ }
245
+
246
+ /* Fade animation */
247
+ .dialog-container.fx-fade {
248
+ opacity: 0;
249
+ transition: opacity ${animationDuration} ease;
250
+ }
251
+
252
+ .dialog-container.fx-fade.show {
253
+ opacity: 1;
254
+ }
255
+
256
+ .dialog-container.fx-fade.closing {
257
+ opacity: 0;
258
+ }
259
+
260
+ /* Slide animation */
261
+ .dialog-container.fx-slide {
262
+ transform: translate(-50%, -100%);
263
+ opacity: 0;
264
+ transition: transform ${animationDuration} cubic-bezier(0.16, 1, 0.3, 1), opacity ${animationDuration} ease;
265
+ }
266
+
267
+ .dialog-container.fx-slide.show {
268
+ transform: translate(-50%, -50%);
269
+ opacity: 1;
270
+ }
271
+
272
+ .dialog-container.fx-slide.closing {
273
+ transform: translate(-50%, -100%);
274
+ opacity: 0;
275
+ }
276
+
277
+ .dialog-header {
278
+ padding: 0;
279
+ border-bottom: none;
280
+ flex-shrink: 0;
281
+ }
282
+
283
+ .dialog-title {
284
+ margin: 0;
285
+ padding: 20px 24px;
286
+ font-size: 20px;
287
+ font-weight: 600;
288
+ color: var(--dialog-title-color, var(--liwe3-text-inverse, white));
289
+ background: var(--dialog-title-background, linear-gradient(135deg,
290
+ var(--liwe3-mode1-500, #667eea),
291
+ var(--liwe3-mode4-500, #9f7aea)
292
+ ));
293
+ border-bottom: 1px solid var(--dialog-border-color, var(--liwe3-border-default, #e0e0e0));
294
+ }
295
+
296
+ .dialog-body {
297
+ padding: 24px;
298
+ overflow-y: auto;
299
+ flex: 1;
300
+ color: var(--dialog-text-color, var(--liwe3-text-mode2, #555));
301
+ line-height: 1.6;
302
+ }
303
+
304
+ .dialog-footer {
305
+ padding: 16px 24px;
306
+ border-top: 1px solid var(--dialog-border-color, var(--liwe3-border-default, #e0e0e0));
307
+ display: flex;
308
+ justify-content: space-between;
309
+ align-items: center;
310
+ gap: 12px;
311
+ flex-shrink: 0;
312
+ background: var(--dialog-background, var(--liwe3-surface-raised, white));
313
+ }
314
+
315
+ .footer-left {
316
+ display: flex;
317
+ gap: 12px;
318
+ }
319
+
320
+ .footer-right {
321
+ display: flex;
322
+ gap: 12px;
323
+ margin-left: auto;
324
+ }
325
+
326
+ .dialog-button {
327
+ padding: 8px 20px;
328
+ border: 1px solid var(--dialog-button-border-color, var(--liwe3-border-default, #ccc));
329
+ border-radius: var(--dialog-button-border-radius, var(--liwe3-border-radius, 4px));
330
+ background: var(--dialog-button-background, var(--liwe3-surface-raised, white));
331
+ color: var(--dialog-button-color, var(--liwe3-text-mode1, #333));
332
+ font-size: 14px;
333
+ cursor: pointer;
334
+ transition: all 0.2s;
335
+ font-family: inherit;
336
+ font-weight: 500;
337
+ }
338
+
339
+ .dialog-button:hover {
340
+ background-color: var(--dialog-button-hover-background, var(--liwe3-hover-overlay, #f8f9fa));
341
+ border-color: var(--dialog-button-hover-border-color, var(--liwe3-border-strong, #999));
342
+ }
343
+
344
+ .dialog-button:active {
345
+ background-color: var(--dialog-button-active-background, var(--liwe3-active-overlay, #e9ecef));
346
+ }
347
+
348
+ .close-button {
349
+ padding: 8px 20px;
350
+ border: 1px solid var(--dialog-close-border-color, var(--liwe3-border-default, #ccc));
351
+ border-radius: var(--dialog-button-border-radius, var(--liwe3-border-radius, 4px));
352
+ background: var(--dialog-close-background, var(--liwe3-surface-raised, white));
353
+ color: var(--dialog-close-color, var(--liwe3-text-mode1, #333));
354
+ font-size: 14px;
355
+ cursor: pointer;
356
+ transition: all 0.2s;
357
+ font-family: inherit;
358
+ font-weight: 500;
359
+ }
360
+
361
+ .close-button:hover {
362
+ background-color: var(--dialog-close-hover-background, var(--liwe3-hover-overlay, #f8f9fa));
363
+ border-color: var(--dialog-close-hover-border-color, var(--liwe3-border-strong, #999));
364
+ }
365
+
366
+ @media (max-width: 768px) {
367
+ .dialog-container {
368
+ min-width: 90vw;
369
+ max-width: 90vw;
370
+ }
371
+ }
372
+ </style>
373
+
374
+ <div class="dialog-container ${fxClass}">
375
+ <div class="dialog-header">
376
+ <h2 class="dialog-title">${title}</h2>
377
+ </div>
378
+
379
+ <div class="dialog-body">
380
+ ${this.config.body}
381
+ </div>
382
+
383
+ <div class="dialog-footer">
384
+ ${
385
+ firstButton
386
+ ? `
387
+ <div class="footer-left">
388
+ <button
389
+ class="dialog-button"
390
+ data-index="0"
391
+ style="${firstButton.backgroundColor ? `background-color: ${firstButton.backgroundColor}; color: white; border-color: ${firstButton.backgroundColor};` : ''}"
392
+ >
393
+ ${firstButton.label}
394
+ </button>
395
+ </div>
396
+ `
397
+ : ''
398
+ }
399
+
400
+ <div class="footer-right">
401
+ ${
402
+ rightButtons.map( ( button, index ) => `
403
+ <button
404
+ class="dialog-button"
405
+ data-index="${firstButton ? index + 1 : index}"
406
+ style="${button.backgroundColor ? `background-color: ${button.backgroundColor}; color: white; border-color: ${button.backgroundColor};` : ''}"
407
+ >
408
+ ${button.label}
409
+ </button>
410
+ ` ).join( '' )
411
+ }
412
+ ${buttons.length === 0 ? '<button class="close-button">Close</button>' : ''}
413
+ </div>
414
+ </div>
415
+ </div>
416
+ `;
417
+
418
+ this.bindEvents();
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Conditionally defines the custom element if in a browser environment.
424
+ */
425
+ const defineDialog = ( tagName : string = 'liwe3-dialog' ) : void => {
426
+ if ( typeof window !== 'undefined' && !window.customElements.get( tagName ) ) {
427
+ customElements.define( tagName, DialogElement );
428
+ }
429
+ };
430
+
431
+ // Auto-register with default tag name
432
+ defineDialog();
433
+
434
+ /**
435
+ * Container ID for dialogs
436
+ */
437
+ const DIALOG_CONTAINER_ID = 'liwe3-dialog-container';
438
+
439
+ /**
440
+ * Gets or creates the dialog container element
441
+ */
442
+ const getDialogContainer = () : HTMLElement => {
443
+ let container = document.getElementById( DIALOG_CONTAINER_ID );
444
+
445
+ if ( !container ) {
446
+ container = document.createElement( 'div' );
447
+ container.id = DIALOG_CONTAINER_ID;
448
+ container.style.position = 'fixed';
449
+ container.style.zIndex = '99999';
450
+ container.style.pointerEvents = 'none';
451
+ document.body.appendChild( container );
452
+ }
453
+
454
+ return container;
455
+ };
456
+
457
+ /**
458
+ * Shows a dialog with the given configuration.
459
+ * This is the recommended way to display dialogs.
460
+ *
461
+ * @param config - The dialog configuration
462
+ * @returns The dialog element instance
463
+ *
464
+ * @example
465
+ * ```typescript
466
+ * import { dialogAdd } from '@liwe3/webcomponents';
467
+ *
468
+ * dialogAdd({
469
+ * title: 'Delete File',
470
+ * body: '<p>Are you sure you want to delete this file? This action cannot be undone.</p>',
471
+ * buttons: [
472
+ * {
473
+ * label: 'Delete',
474
+ * backgroundColor: '#dc3545',
475
+ * onclick: (dialog) => {
476
+ * console.log('File deleted');
477
+ * dialog.close();
478
+ * }
479
+ * },
480
+ * {
481
+ * label: 'Cancel',
482
+ * onclick: (dialog) => {
483
+ * console.log('Cancelled');
484
+ * dialog.close();
485
+ * }
486
+ * }
487
+ * ],
488
+ * modal: true,
489
+ * escToClose: true,
490
+ * clickToClose: true
491
+ * });
492
+ * ```
493
+ */
494
+ const dialogAdd = ( config : DialogConfig ) : DialogElement => {
495
+ const container = getDialogContainer();
496
+ const dialog = document.createElement( 'liwe3-dialog' ) as DialogElement;
497
+
498
+ // Allow pointer events on individual dialogs
499
+ dialog.style.pointerEvents = 'auto';
500
+
501
+ // Show the dialog with the provided config
502
+ dialog.show( config );
503
+
504
+ // Add to container
505
+ container.appendChild( dialog );
506
+
507
+ return dialog;
508
+ };
509
+
510
+ export { defineDialog, dialogAdd };
@@ -0,0 +1,213 @@
1
+ /**
2
+ * MarkdownPreview Web Component
3
+ * Renders markdown content using a dynamically loaded library
4
+ */
5
+
6
+ export class MarkdownPreviewElement extends HTMLElement {
7
+ declare shadowRoot: ShadowRoot;
8
+ private _libUrl: string = 'https://cdn.jsdelivr.net/npm/marked@4.3.0/marked.min.js';
9
+ private _value: string = '';
10
+ private _isLibLoaded: boolean = false;
11
+ private _isLoadingLib: boolean = false;
12
+ private container!: HTMLElement;
13
+
14
+ static get observedAttributes() {
15
+ return ['lib-url', 'value'];
16
+ }
17
+
18
+ constructor() {
19
+ super();
20
+ this.attachShadow({ mode: 'open' });
21
+ this.render();
22
+ }
23
+
24
+ connectedCallback() {
25
+ this.loadLibrary();
26
+ }
27
+
28
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
29
+ if (oldValue === newValue) return;
30
+
31
+ if (name === 'lib-url') {
32
+ this._libUrl = newValue;
33
+ this._isLibLoaded = false; // Reset loaded state if URL changes
34
+ this.loadLibrary();
35
+ } else if (name === 'value') {
36
+ // Only update internal value if it differs, to avoid loops if we were to reflect
37
+ if (this._value !== newValue) {
38
+ this.value = newValue;
39
+ }
40
+ }
41
+ }
42
+
43
+ get libUrl(): string {
44
+ return this._libUrl;
45
+ }
46
+
47
+ set libUrl(value: string) {
48
+ this.setAttribute('lib-url', value);
49
+ }
50
+
51
+ get value(): string {
52
+ return this._value;
53
+ }
54
+
55
+ set value(content: string) {
56
+ this._value = content;
57
+ this.updateContent();
58
+ // We do NOT reflect to attribute to avoid performance issues with large content
59
+ }
60
+
61
+ private render() {
62
+ this.shadowRoot.innerHTML = `
63
+ <style>
64
+ :host {
65
+ display: block;
66
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
67
+ line-height: 1.6;
68
+ color: #333;
69
+ }
70
+ .markdown-body {
71
+ box-sizing: border-box;
72
+ min-width: 200px;
73
+ max-width: 980px;
74
+ margin: 0 auto;
75
+ padding: 15px;
76
+ }
77
+ img {
78
+ max-width: 100%;
79
+ }
80
+ pre {
81
+ background-color: #f6f8fa;
82
+ border-radius: 6px;
83
+ padding: 16px;
84
+ overflow: auto;
85
+ }
86
+ code {
87
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
88
+ font-size: 85%;
89
+ background-color: rgba(175, 184, 193, 0.2);
90
+ padding: 0.2em 0.4em;
91
+ border-radius: 6px;
92
+ }
93
+ pre code {
94
+ background-color: transparent;
95
+ padding: 0;
96
+ }
97
+ blockquote {
98
+ border-left: 0.25em solid #d0d7de;
99
+ color: #656d76;
100
+ padding: 0 1em;
101
+ margin: 0;
102
+ }
103
+ table {
104
+ border-spacing: 0;
105
+ border-collapse: collapse;
106
+ display: block;
107
+ width: max-content;
108
+ max-width: 100%;
109
+ overflow: auto;
110
+ }
111
+ table th,
112
+ table td {
113
+ padding: 6px 13px;
114
+ border: 1px solid #d0d7de;
115
+ }
116
+ table tr {
117
+ background-color: #ffffff;
118
+ border-top: 1px solid #d8dee4;
119
+ }
120
+ table tr:nth-child(2n) {
121
+ background-color: #f6f8fa;
122
+ }
123
+ </style>
124
+ <div class="markdown-body" id="content"></div>
125
+ `;
126
+ this.container = this.shadowRoot.getElementById('content') as HTMLElement;
127
+ }
128
+
129
+ private async loadLibrary() {
130
+ if (this._isLibLoaded || this._isLoadingLib) return;
131
+
132
+ // Check if marked is already available globally
133
+ if (typeof (window as any).marked === 'function' || (typeof (window as any).marked === 'object' && typeof (window as any).marked.parse === 'function')) {
134
+ this._isLibLoaded = true;
135
+ this.updateContent();
136
+ return;
137
+ }
138
+
139
+ this._isLoadingLib = true;
140
+
141
+ try {
142
+ const script = document.createElement('script');
143
+ script.src = this._libUrl;
144
+ script.onload = () => {
145
+ // Double check if marked is available
146
+ if (!(window as any).marked) {
147
+ console.error('MarkdownPreview: Library loaded but window.marked is undefined');
148
+ this.container.innerHTML = `<div style="color: red;">Error: Markdown library loaded but not found. Try a different URL.</div>`;
149
+ this._isLoadingLib = false;
150
+ return;
151
+ }
152
+ this._isLibLoaded = true;
153
+ this._isLoadingLib = false;
154
+ this.updateContent();
155
+ this.dispatchEvent(new CustomEvent('library-loaded'));
156
+ };
157
+ script.onerror = () => {
158
+ this._isLoadingLib = false;
159
+ console.error(`Failed to load markdown library from ${this._libUrl}`);
160
+ this.container.innerHTML = `<div style="color: red;">Error loading markdown library</div>`;
161
+ };
162
+ document.head.appendChild(script);
163
+ } catch (e) {
164
+ this._isLoadingLib = false;
165
+ console.error(e);
166
+ }
167
+ }
168
+
169
+ private updateContent() {
170
+ if (!this._isLibLoaded) {
171
+ if (!this._isLoadingLib) {
172
+ this.loadLibrary();
173
+ }
174
+ return;
175
+ }
176
+
177
+ const marked = (window as any).marked;
178
+ if (!marked) {
179
+ console.error('Marked library loaded but not found in window');
180
+ return;
181
+ }
182
+
183
+ try {
184
+ // Handle both function style (older marked) and object style (newer marked)
185
+ const parse = typeof marked === 'function' ? marked : marked.parse;
186
+ if (typeof parse === 'function') {
187
+ const html = parse(this._value);
188
+ // If it returns a promise (async), handle it
189
+ if (html instanceof Promise) {
190
+ html.then((res: string) => {
191
+ this.container.innerHTML = res;
192
+ });
193
+ } else {
194
+ this.container.innerHTML = html;
195
+ }
196
+ } else {
197
+ console.error('Marked parse function not found');
198
+ this.container.innerHTML = `<div style="color: red;">Error: marked.parse not found</div>`;
199
+ }
200
+ } catch (e) {
201
+ console.error('Error parsing markdown:', e);
202
+ this.container.innerHTML = `<div style="color: red;">Error parsing markdown</div>`;
203
+ }
204
+ }
205
+ }
206
+
207
+ export const defineMarkdownPreview = (tagName: string = 'liwe3-markdown-preview') => {
208
+ if (typeof window !== 'undefined' && !customElements.get(tagName)) {
209
+ customElements.define(tagName, MarkdownPreviewElement);
210
+ }
211
+ };
212
+
213
+ defineMarkdownPreview();