rapid-text-editor 1.0.4

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,615 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, EventEmitter, forwardRef, Component, Input, Output, ViewChild, NgModule } from '@angular/core';
3
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
4
+ import * as i1 from '@angular/material/dialog';
5
+ import { MatDialogModule } from '@angular/material/dialog';
6
+ import * as i2 from '@angular/material/icon';
7
+ import { MatIconModule } from '@angular/material/icon';
8
+ import * as i3 from '@angular/common';
9
+ import { CommonModule } from '@angular/common';
10
+
11
+ class RapidTextEditorService {
12
+ constructor() { }
13
+ }
14
+ RapidTextEditorService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
15
+ RapidTextEditorService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorService, providedIn: 'root' });
16
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorService, decorators: [{
17
+ type: Injectable,
18
+ args: [{
19
+ providedIn: 'root'
20
+ }]
21
+ }], ctorParameters: function () { return []; } });
22
+
23
+ class RapidTextEditorComponent {
24
+ constructor(dialog, el) {
25
+ this.dialog = dialog;
26
+ this.el = el;
27
+ this.contentCapture = false;
28
+ this.contentChange = new EventEmitter();
29
+ this.editorContent = '';
30
+ this.rows = 1;
31
+ this.cols = 1;
32
+ this.grid = Array(49).fill(0);
33
+ this.Math = Math;
34
+ this.uploadedImages = [];
35
+ //Two Way Data Binding Functionality
36
+ this.onChange = (content) => { };
37
+ this.onTouched = () => { };
38
+ console.log('Build -1');
39
+ }
40
+ ngOnInit() {
41
+ if (this.contentCapture) {
42
+ console.log('Content capture is enabled.');
43
+ }
44
+ else {
45
+ console.log('Content capture is disabled.');
46
+ }
47
+ }
48
+ ngAfterViewInit() {
49
+ const editor = this.el.nativeElement.querySelector('.editor');
50
+ if (!editor) {
51
+ console.error('Editor element not found.');
52
+ }
53
+ }
54
+ onContentChange() {
55
+ if (this.contentCapture) {
56
+ const editor = this.el.nativeElement.querySelector('.editor');
57
+ if (editor) {
58
+ this.editorContent = editor.innerHTML;
59
+ console.log('Captured Content (Child):', this.editorContent);
60
+ this.contentChange.emit(this.editorContent);
61
+ }
62
+ else {
63
+ console.error('Editor element not found.');
64
+ }
65
+ }
66
+ }
67
+ getContent() {
68
+ return this.editorContent;
69
+ }
70
+ // Called by Angular to update the value programmatically
71
+ writeValue(content) {
72
+ if (this.editor && this.editor.nativeElement) {
73
+ this.editor.nativeElement.innerHTML = content || '';
74
+ }
75
+ }
76
+ // Registers the onChange callback to notify Angular of content changes
77
+ registerOnChange(fn) {
78
+ this.onChange = fn;
79
+ }
80
+ // Registers the onTouched callback for blur events
81
+ registerOnTouched(fn) {
82
+ this.onTouched = fn;
83
+ }
84
+ onBlur() {
85
+ this.onTouched();
86
+ }
87
+ format(command) {
88
+ document.execCommand(command, false, '');
89
+ }
90
+ setHeading(event) {
91
+ const value = event.target.value;
92
+ document.execCommand('formatBlock', false, value);
93
+ }
94
+ //Image Section
95
+ allowDrop(event) {
96
+ event.preventDefault();
97
+ }
98
+ // Handle image drag start
99
+ drag(event, imageUrl) {
100
+ event.dataTransfer?.setData("text", imageUrl);
101
+ }
102
+ // Handle drop event in the editor
103
+ drop(event) {
104
+ event.preventDefault();
105
+ const imageUrl = event.dataTransfer?.getData("text");
106
+ if (imageUrl) {
107
+ this.insertImageInEditor(imageUrl);
108
+ }
109
+ }
110
+ // insertImage(event: any): void {
111
+ // const files: FileList = event.target.files;
112
+ // if (!files || files.length === 0) {
113
+ // console.error('No files selected.');
114
+ // return;
115
+ // }
116
+ // Array.from(files).forEach((file) => {
117
+ // // Check file type
118
+ // if (file.type !== 'image/png' && !file.type.startsWith('image/')) {
119
+ // console.error('Unsupported file type:', file.type);
120
+ // return;
121
+ // }
122
+ // // Read the image file
123
+ // const reader = new FileReader();
124
+ // reader.onload = (e: any) => {
125
+ // const imageUrl = e.target.result;
126
+ // // Add image to the uploaded images list
127
+ // this.uploadedImages.push(imageUrl);
128
+ // // Call insertImageInEditor to add the image to the editor
129
+ // this.insertImageInEditor(imageUrl);
130
+ // };
131
+ // reader.readAsDataURL(file);
132
+ // });
133
+ // } -> for PNG
134
+ removeImage(index) {
135
+ this.uploadedImages.splice(index, 1); // Remove the image from the array
136
+ }
137
+ insertImageInEditor(imageUrl) {
138
+ const editor = document.querySelector('.editor');
139
+ if (!editor) {
140
+ console.error('Editor element not found.');
141
+ return;
142
+ }
143
+ // Create image element
144
+ const img = document.createElement('img');
145
+ img.src = imageUrl;
146
+ img.style.maxWidth = '100%';
147
+ img.style.display = 'block';
148
+ img.style.width = '300px';
149
+ img.style.height = 'auto';
150
+ // Create a wrapper around the image to allow resizing
151
+ const wrapper = document.createElement('div');
152
+ wrapper.style.position = 'relative';
153
+ wrapper.style.display = 'inline-block'; // Ensures the wrapper only takes up space for the image
154
+ wrapper.style.margin = '10px';
155
+ wrapper.style.padding = '5px';
156
+ wrapper.style.border = '1px dashed #ccc'; // Optional border to indicate the image area
157
+ // Append the image to the wrapper
158
+ wrapper.appendChild(img);
159
+ // Append the wrapper to the editor
160
+ editor.appendChild(wrapper);
161
+ // Make the image resizable
162
+ this.makeImageResizable(wrapper, img);
163
+ // Optionally, add a text node to maintain cursor position after appending the image
164
+ const textNode = document.createTextNode('\u200B'); // Invisible character to maintain position
165
+ editor.appendChild(textNode);
166
+ const range = document.createRange();
167
+ range.setStartAfter(wrapper);
168
+ range.collapse(true);
169
+ const selection = window.getSelection();
170
+ if (selection) {
171
+ selection.removeAllRanges();
172
+ selection.addRange(range);
173
+ }
174
+ }
175
+ makeImageResizable(wrapper, img) {
176
+ let isResizing = false;
177
+ let startX = 0;
178
+ let startY = 0;
179
+ let startWidth = 0;
180
+ let startHeight = 0;
181
+ // Create resize handles for the image
182
+ const createResizeHandle = (cursor, positionStyles, resizeCallback) => {
183
+ const handle = document.createElement('div');
184
+ handle.style.position = 'absolute';
185
+ handle.style.width = '10px';
186
+ handle.style.height = '10px';
187
+ handle.style.background = 'rgba(0, 0, 0, 0.5)';
188
+ handle.style.cursor = cursor;
189
+ Object.assign(handle.style, positionStyles);
190
+ handle.addEventListener('mousedown', (resizeEvent) => {
191
+ isResizing = true;
192
+ startX = resizeEvent.clientX;
193
+ startY = resizeEvent.clientY;
194
+ startWidth = img.offsetWidth;
195
+ startHeight = img.offsetHeight;
196
+ const onMouseMove = (moveEvent) => {
197
+ if (isResizing) {
198
+ const deltaX = moveEvent.clientX - startX;
199
+ const deltaY = moveEvent.clientY - startY;
200
+ resizeCallback(deltaX, deltaY);
201
+ }
202
+ };
203
+ const onMouseUp = () => {
204
+ isResizing = false;
205
+ document.removeEventListener('mousemove', onMouseMove);
206
+ document.removeEventListener('mouseup', onMouseUp);
207
+ };
208
+ document.addEventListener('mousemove', onMouseMove);
209
+ document.addEventListener('mouseup', onMouseUp);
210
+ });
211
+ wrapper.appendChild(handle);
212
+ };
213
+ // Add resizing handles to the wrapper for the image
214
+ createResizeHandle('nw-resize', { top: '-5px', left: '-5px' }, (deltaX, deltaY) => {
215
+ img.style.width = `${startWidth - deltaX}px`;
216
+ img.style.height = `${startHeight - deltaY}px`;
217
+ });
218
+ createResizeHandle('se-resize', { bottom: '-5px', right: '-5px' }, (deltaX, deltaY) => {
219
+ img.style.width = `${startWidth + deltaX}px`;
220
+ img.style.height = `${startHeight + deltaY}px`;
221
+ });
222
+ }
223
+ insertImage(event) {
224
+ const input = event.target;
225
+ if (input?.files) {
226
+ Array.from(input.files).forEach(file => {
227
+ const reader = new FileReader();
228
+ reader.onload = (e) => {
229
+ const imageUrl = e.target.result;
230
+ this.uploadedImages.push(imageUrl); // Store image URL in the array
231
+ };
232
+ reader.readAsDataURL(file);
233
+ });
234
+ }
235
+ }
236
+ convertImage(file) {
237
+ return new Promise((resolve, reject) => {
238
+ if (file.type !== 'image/png') {
239
+ resolve(file); // Return the original file if it's not a PNG
240
+ return;
241
+ }
242
+ const img = new Image();
243
+ const reader = new FileReader();
244
+ reader.onload = (e) => {
245
+ img.src = e.target.result;
246
+ img.onload = () => {
247
+ const canvas = document.createElement('canvas');
248
+ const ctx = canvas.getContext('2d');
249
+ if (!ctx) {
250
+ reject(new Error('Failed to get canvas context'));
251
+ return;
252
+ }
253
+ canvas.width = img.width;
254
+ canvas.height = img.height;
255
+ ctx.drawImage(img, 0, 0);
256
+ canvas.toBlob(blob => {
257
+ if (blob) {
258
+ const jpgFile = new File([blob], file.name.replace(/\.png$/, '.jpg'), { type: 'image/jpeg' });
259
+ resolve(jpgFile);
260
+ }
261
+ else {
262
+ reject(new Error('Failed to convert PNG to JPG'));
263
+ }
264
+ }, 'image/jpeg', 0.95 // Quality factor for JPEG
265
+ );
266
+ };
267
+ };
268
+ reader.onerror = () => reject(new Error('Failed to read the file'));
269
+ reader.readAsDataURL(file);
270
+ });
271
+ }
272
+ // insertImageByDrag(imageUrl: string): void {
273
+ // const editor = document.querySelector('.editor') as HTMLElement;
274
+ // if (!editor) {
275
+ // console.error('Editor element not found.');
276
+ // return;
277
+ // }
278
+ // const img = document.createElement('img');
279
+ // img.src = imageUrl;
280
+ // img.style.maxWidth = '100%';
281
+ // img.style.display = 'block';
282
+ // img.style.width = '300px';
283
+ // img.style.height = 'auto';
284
+ // editor.appendChild(img);
285
+ // }
286
+ insertImageToEditor(event) {
287
+ const file = event.target.files[0];
288
+ if (file) {
289
+ const reader = new FileReader();
290
+ reader.onload = (e) => {
291
+ const imageUrl = e.target.result;
292
+ const editor = document.querySelector('.editor');
293
+ if (!editor) {
294
+ console.error('Editor element not found.');
295
+ return;
296
+ }
297
+ const wrapper = document.createElement('div');
298
+ wrapper.contentEditable = 'false';
299
+ wrapper.style.position = 'relative';
300
+ wrapper.style.display = 'inline-block';
301
+ wrapper.style.border = '1px dashed #ccc';
302
+ wrapper.style.margin = '10px';
303
+ wrapper.style.padding = '5px';
304
+ wrapper.style.borderBlockColor = '#007BFF';
305
+ const img = document.createElement('img');
306
+ img.src = imageUrl;
307
+ img.style.maxWidth = '100%';
308
+ img.style.display = 'block';
309
+ img.style.width = '300px';
310
+ img.style.height = 'auto';
311
+ wrapper.appendChild(img);
312
+ let isResizing = false;
313
+ let startX = 0;
314
+ let startY = 0;
315
+ let startWidth = 0;
316
+ let startHeight = 0;
317
+ const createResizeHandle = (cursor, positionStyles, resizeCallback) => {
318
+ const handle = document.createElement('div');
319
+ handle.style.position = 'absolute';
320
+ handle.style.width = '10px';
321
+ handle.style.height = '10px';
322
+ handle.style.background = 'rgba(0, 0, 0, 0.5)';
323
+ handle.style.cursor = cursor;
324
+ Object.assign(handle.style, positionStyles);
325
+ handle.addEventListener('mousedown', (resizeEvent) => {
326
+ isResizing = true;
327
+ startX = resizeEvent.clientX;
328
+ startY = resizeEvent.clientY;
329
+ startWidth = img.offsetWidth;
330
+ startHeight = img.offsetHeight;
331
+ const onMouseMove = (moveEvent) => {
332
+ if (isResizing) {
333
+ const deltaX = moveEvent.clientX - startX;
334
+ const deltaY = moveEvent.clientY - startY;
335
+ resizeCallback(deltaX, deltaY);
336
+ }
337
+ };
338
+ const onMouseUp = () => {
339
+ isResizing = false;
340
+ document.removeEventListener('mousemove', onMouseMove);
341
+ document.removeEventListener('mouseup', onMouseUp);
342
+ };
343
+ document.addEventListener('mousemove', onMouseMove);
344
+ document.addEventListener('mouseup', onMouseUp);
345
+ });
346
+ wrapper.appendChild(handle);
347
+ };
348
+ createResizeHandle('nw-resize', { top: '-5px', left: '-5px' }, (deltaX, deltaY) => {
349
+ img.style.width = `${startWidth - deltaX}px`;
350
+ img.style.height = `${startHeight - deltaY}px`;
351
+ });
352
+ createResizeHandle('se-resize', { bottom: '-5px', right: '-5px' }, (deltaX, deltaY) => {
353
+ img.style.width = `${startWidth + deltaX}px`;
354
+ img.style.height = `${startHeight + deltaY}px`;
355
+ });
356
+ editor.appendChild(wrapper);
357
+ const textNode = document.createTextNode('\u200B');
358
+ editor.appendChild(textNode);
359
+ const range = document.createRange();
360
+ range.setStartAfter(wrapper);
361
+ range.collapse(true);
362
+ const selection = window.getSelection();
363
+ if (selection) {
364
+ selection.removeAllRanges();
365
+ selection.addRange(range);
366
+ }
367
+ };
368
+ reader.readAsDataURL(file);
369
+ }
370
+ }
371
+ openTableDialog(event) {
372
+ this.rows = 1;
373
+ this.cols = 1;
374
+ const dialogRef = this.dialog.open(this.tableDialog, {
375
+ position: {
376
+ top: `${event.clientY}px`,
377
+ left: `${event.clientX}px`,
378
+ },
379
+ panelClass: 'custom-dialog-container'
380
+ });
381
+ dialogRef.afterClosed().subscribe((result) => {
382
+ if (result) {
383
+ this.insertTable(result.rows, result.cols);
384
+ }
385
+ });
386
+ }
387
+ updatePreview(cols, rows) {
388
+ this.cols = cols;
389
+ this.rows = rows;
390
+ }
391
+ updateSelection(colIndex, rowIndex, dialogRef) {
392
+ this.cols = colIndex;
393
+ this.rows = rowIndex;
394
+ dialogRef.close({ rows: this.rows, cols: this.cols }); // Close dialog with selection
395
+ }
396
+ makeFirstRowHeader() {
397
+ const editor = this.editor.nativeElement;
398
+ const selection = window.getSelection();
399
+ // Check if selection is inside the editor
400
+ if (!this.isSelectionInsideEditor(editor, selection)) {
401
+ alert('Please place the cursor inside the table to make the first row a header.');
402
+ return;
403
+ }
404
+ // Find the closest table to the selection
405
+ const range = selection.getRangeAt(0);
406
+ const selectedNode = range.startContainer;
407
+ const table = this.findClosestTable(selectedNode);
408
+ if (!table) {
409
+ alert('No table found near the cursor.');
410
+ return;
411
+ }
412
+ // Convert the first row cells to header cells
413
+ const firstRow = table.rows[0];
414
+ if (firstRow) {
415
+ for (let i = 0; i < firstRow.cells.length; i++) {
416
+ const cell = firstRow.cells[i];
417
+ const th = document.createElement('th');
418
+ th.innerHTML = cell.innerHTML;
419
+ th.style.textAlign = 'center';
420
+ th.style.backgroundColor = '#F1F1F0';
421
+ th.style.border = '1px solid black';
422
+ th.style.borderCollapse = 'collapse';
423
+ th.style.padding = window.getComputedStyle(cell).padding; // Retain padding
424
+ th.style.width = window.getComputedStyle(cell).width; // Retain width
425
+ th.style.height = window.getComputedStyle(cell).height;
426
+ firstRow.replaceChild(th, cell);
427
+ }
428
+ }
429
+ editor.focus();
430
+ }
431
+ findClosestTable(node) {
432
+ while (node && node !== document) {
433
+ if (node.nodeName === 'TABLE') {
434
+ return node;
435
+ }
436
+ node = node.parentNode;
437
+ }
438
+ return null;
439
+ }
440
+ insertTable(rows, cols) {
441
+ const rowCount = parseInt(rows, 10);
442
+ const colCount = parseInt(cols, 10);
443
+ if (isNaN(rowCount) || isNaN(colCount) || rowCount < 1 || colCount < 1) {
444
+ alert('Please enter valid numbers for rows and columns.');
445
+ return;
446
+ }
447
+ const editor = this.editor.nativeElement;
448
+ const selection = window.getSelection();
449
+ // Ensure selection is inside the editor
450
+ if (!this.isSelectionInsideEditor(editor, selection)) {
451
+ // Append the table at the end if selection is outside editor
452
+ this.appendTable(editor, rowCount, colCount);
453
+ return;
454
+ }
455
+ // Proceed to insert the table if selection is valid
456
+ const tableHTML = this.createTableHTML(rowCount, colCount);
457
+ const range = selection.getRangeAt(0);
458
+ range.deleteContents(); // Remove any selected content
459
+ const tempDiv = document.createElement('div');
460
+ tempDiv.innerHTML = tableHTML;
461
+ range.insertNode(tempDiv.firstChild); // Insert the table
462
+ // Move the cursor to the first cell of the new table
463
+ this.setCursorToFirstCell(tempDiv.firstChild);
464
+ editor.focus();
465
+ }
466
+ // Helper function: Create table HTML
467
+ createTableHTML(rowCount, colCount) {
468
+ let tableHTML = '<table border="1" style="border-collapse: collapse; margin:5px">';
469
+ for (let i = 0; i < rowCount; i++) {
470
+ tableHTML += '<tr>';
471
+ for (let j = 0; j < colCount; j++) {
472
+ tableHTML += `<td style="font-size: 14px; min-width: 1.5em; height: 2em; padding: .4em; border: 1px solid black"
473
+ role="textbox" contenteditable="true"></td>`;
474
+ }
475
+ tableHTML += '</tr>';
476
+ }
477
+ tableHTML += '</table>';
478
+ return tableHTML;
479
+ }
480
+ // Helper function: Append a table at the end of the editor
481
+ appendTable(editor, rowCount, colCount) {
482
+ const tableHTML = this.createTableHTML(rowCount, colCount);
483
+ editor.innerHTML += tableHTML;
484
+ // Set cursor inside the first cell of the new table
485
+ const lastInsertedTable = editor.querySelector('table:last-child');
486
+ this.setCursorToFirstCell(lastInsertedTable);
487
+ editor.focus();
488
+ }
489
+ // Helper function: Check if selection is inside the editor
490
+ isSelectionInsideEditor(editor, selection) {
491
+ if (!selection.rangeCount)
492
+ return false;
493
+ let node = selection.getRangeAt(0).commonAncestorContainer;
494
+ // Traverse up to see if the node belongs to the editor
495
+ while (node) {
496
+ if (node === editor)
497
+ return true;
498
+ node = node.parentNode;
499
+ }
500
+ return false;
501
+ }
502
+ // Helper function: Move cursor to the first cell
503
+ // private setCursorToFirstCell(table: HTMLElement) {
504
+ // const firstCell = table.querySelector('tr:first-child td:first-child') as HTMLElement;
505
+ // if (firstCell) {
506
+ // const newRange = document.createRange();
507
+ // newRange.setStart(firstCell, 0); // Start at the beginning of the cell
508
+ // newRange.collapse(true);
509
+ // const selection = window.getSelection();
510
+ // selection!.removeAllRanges();
511
+ // selection!.addRange(newRange);
512
+ // }
513
+ // }
514
+ setCursorToFirstCell(table) {
515
+ const firstCell = table.querySelector('tr:first-child td:first-child, tr:first-child th:first-child');
516
+ if (firstCell) {
517
+ // Ensure the cell is contenteditable
518
+ firstCell.setAttribute('contenteditable', 'true');
519
+ // Focus the cell
520
+ firstCell.focus();
521
+ // Create a range and place the cursor at the start of the cell
522
+ const newRange = document.createRange();
523
+ newRange.selectNodeContents(firstCell); // Select the contents of the cell
524
+ newRange.collapse(true); // Collapse to the start
525
+ const selection = window.getSelection();
526
+ if (selection) {
527
+ selection.removeAllRanges();
528
+ selection.addRange(newRange);
529
+ }
530
+ }
531
+ }
532
+ insertPageBreak() {
533
+ const pageBreak = `<div style="page-break-after: always;"><br /></div>`;
534
+ document.execCommand('insertHTML', false, pageBreak);
535
+ }
536
+ submitContent() {
537
+ const editor = this.editor.nativeElement;
538
+ const content = editor.innerHTML; // Get the HTML content from the editor
539
+ const plainTextContent = editor.innerText; // Optionally, get plain text content
540
+ // For demonstration, you can log it or process it as needed
541
+ this.onChange(content);
542
+ // Example: You could send this data to a server or use it elsewhere in your application
543
+ // this.yourService.submitContent({ content }); // Replace with your service call
544
+ }
545
+ }
546
+ RapidTextEditorComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorComponent, deps: [{ token: i1.MatDialog }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
547
+ RapidTextEditorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: RapidTextEditorComponent, selector: "rich-text-editor", inputs: { contentCapture: "contentCapture" }, outputs: { contentChange: "contentChange" }, providers: [
548
+ {
549
+ provide: NG_VALUE_ACCESSOR,
550
+ useExisting: forwardRef(() => RapidTextEditorComponent),
551
+ multi: true
552
+ }
553
+ ], viewQueries: [{ propertyName: "editor", first: true, predicate: ["editor"], descendants: true, static: true }, { propertyName: "tableDialog", first: true, predicate: ["tableDialog"], descendants: true }], ngImport: i0, template: "<div class=\"rich-text-editor\">\r\n <!-- Toolbar -->\r\n <div class=\"toolbar\">\r\n \r\n <select class=\"select-wrapper\" (change)=\"setHeading($event)\">\r\n <option value=\"P\">Paragraph</option>\r\n <option value=\"H1\">H1</option>\r\n <option value=\"H2\">H2</option>\r\n <option value=\"H3\">H3</option>\r\n <option value=\"H4\">H4</option>\r\n <option value=\"H5\">H5</option>\r\n <option value=\"H6\">H6</option>\r\n </select>\r\n <button (click)=\"format('bold')\" aria-label=\"Bold\">\r\n <mat-icon>format_bold</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('italic')\" aria-label=\"Italic\">\r\n <mat-icon>format_italic</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('underline')\" aria-label=\"Underline\">\r\n <mat-icon>format_underline</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('strikethrough')\" aria-label=\"Strikethrough\">\r\n <mat-icon>strikethrough_s</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('justifyLeft')\" aria-label=\"Align Left\">\r\n <mat-icon>format_align_left</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('justifyCenter')\" aria-label=\"Center\">\r\n <mat-icon>format_align_center</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('justifyRight')\" aria-label=\"Align Right\">\r\n <mat-icon>format_align_right</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('justifyFull')\" aria-label=\"Justify\">\r\n <mat-icon>format_align_justify</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('insertUnorderedList')\" aria-label=\"Unordered List\">\r\n <mat-icon>format_list_bulleted</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('insertOrderedList')\" aria-label=\"Ordered List\">\r\n <mat-icon>format_list_numbered</mat-icon>\r\n </button>\r\n \r\n \r\n \r\n <button color=\"secondary\" aria-label=\"Insert Image\" style=\"position: relative; display: inline-flex; align-items: center; justify-content: center;\">\r\n <input\r\n type=\"file\" \r\n (change)=\"insertImageToEditor($event)\"\r\n style=\"position: absolute; left: -50%; top: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer;\"\r\n aria-hidden=\"true\" \r\n />\r\n \r\n <mat-icon style=\"pointer-events: none;\">insert_photo_outlined</mat-icon>\r\n </button>\r\n \r\n \r\n <button (click)=\"openTableDialog($event)\" color=\"secondary\" aria-label=\"Choose table rows and columns\">\r\n <mat-icon>border_all</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"makeFirstRowHeader()\" color=\"secondary\" aria-label=\"Make Header\">\r\n <img style=\"width: 26px; height: 46px; margin-bottom: 3px;\" src=\"../../../assets/H.svg\" alt=\"Apply Header to table\">\r\n </button>\r\n \r\n </div>\r\n \r\n <!-- Editable Area -->\r\n <!-- (keypress)=\"onContentChange()\" need to be included -->\r\n <div class=\"editor\" contenteditable=\"true\" #editor (input)=\"submitContent()\" (blur)=\"onBlur()\" (drop)=\"drop($event)\" (dragover)=\"allowDrop($event)\" >\r\n <!-- Your content goes here --> \r\n </div>\r\n \r\n </div>\r\n \r\n <button mat-stroked-button class=\"submit\" (click)=\"submitContent()\">Submit</button>\r\n \r\n \r\n <ng-template #tableDialog let-dialogRef=\"dialogRef\">\r\n <h2 mat-dialog-title>Choose Table Size</h2>\r\n <mat-dialog-content>\r\n <div class=\"grid-container\">\r\n <div\r\n *ngFor=\"let cell of grid; let i = index\"\r\n [ngClass]=\"{\r\n 'grid-item': true,\r\n 'highlighted': i % 7 < cols && Math.floor(i / 7) < rows\r\n }\"\r\n (mouseenter)=\"updatePreview(i % 7 + 1, Math.floor(i / 7) + 1)\"\r\n (click)=\"updateSelection(i % 7 + 1, Math.floor(i / 7) + 1, dialogRef)\"\r\n ></div>\r\n </div>\r\n <p>{{ rows }} x {{ cols }} </p>\r\n </mat-dialog-content>\r\n </ng-template>\r\n \r\n \r\n <input type=\"file\" (change)=\"insertImage($event)\" accept=\"image/png,image/*\" multiple />\r\n \r\n \r\n <div class=\"uploaded-images\" *ngIf=\"uploadedImages.length > 0\">\r\n <div *ngFor=\"let imageUrl of uploadedImages; let i = index\" class=\"image-preview\">\r\n <img [src]=\"imageUrl\" alt=\"Uploaded Image\" \r\n draggable=\"true\" \r\n (dragstart)=\"drag($event, imageUrl)\" />\r\n \r\n <button class=\"remove-btn\" (click)=\"removeImage(i)\">Remove</button>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ", styles: ["@charset \"UTF-8\";.rich-text-editor{border:1px solid #ccc;border-radius:5px}.editor{min-height:300px;border:1px solid #ccc;padding:5px;overflow-y:auto;font-family:Calibri;outline:none!important;font-size:14px}button{width:20px;height:20px;background:none;border:none;margin-left:5px;cursor:pointer}button[mat-icon-button]{margin:0 4px;width:20px!important;height:20px;background:none;border:none}.small-icon-button{width:30px;height:30px;padding:4px}.small-icon-button mat-icon{font-size:16px}button[mat-icon-button]{position:relative;overflow:hidden}input[type=file]{padding:5px;cursor:pointer;margin-left:10px}.fileInput{display:flex;justify-content:center;align-items:center}.select-wrapper{display:inline-block;position:relative;width:150px}.select-wrapper select{appearance:none;width:100%;padding:10px 40px 10px 15px;font-size:16px;color:#333;background-color:#f4f4f4;border:1px solid #ddd;border-radius:5px;cursor:pointer;outline:none}.select-wrapper:after{content:\"\\25bc\";position:absolute;top:50%;right:15px;transform:translateY(-50%);pointer-events:none;color:#777;font-size:12px}.select-wrapper select:hover{background-color:#e9e9e9;border-color:#bbb}.select-wrapper select:focus{border-color:#007bff;background-color:#fff}.select-wrapper option{padding:8px;font-size:16px;color:#333;background-color:#fff}.toolbar{display:flex;flex-wrap:wrap;align-items:center;background-color:#f4f4f4;padding:10px;border-radius:8px;box-shadow:0 2px 8px #0000001a;gap:10px}.select-wrapper{padding:8px;font-size:14px;border:1px solid #ddd;border-radius:5px;cursor:pointer;background-color:#fff}.select-wrapper:focus{outline:none;border-color:#007bff}button{border:none;border-radius:5px;padding:8px;cursor:pointer;display:flex;align-items:center;justify-content:center}button:hover{color:#007bff}button:active{background-color:#0056b3}mat-icon{font-size:20px;color:inherit}input[type=number]{width:60px;padding:5px;font-size:14px;border:1px solid #ddd;border-radius:5px;text-align:center}input[type=file]{border:1px solid #ddd;border-radius:5px;padding:5px;font-size:14px;background-color:#fff;cursor:pointer}input[type=file]:hover{border-color:#007bff}.table{width:100px;height:30px;color:#007bff!important;font-weight:700;border:1px solid #007bff}.table:hover{background-color:#0056b3;color:#fff!important}.submit{margin-top:10px;width:100px;height:30px;color:#007bff!important;font-weight:700;border:1px solid #007bff}.submit:hover{border:none;background-color:#0056b3;color:#fff!important}.custom-dialog-container{width:auto;max-width:200px}.grid-container{display:grid;grid-template-columns:repeat(7,20px);grid-gap:5px;gap:5px}.grid-item{width:20px;height:20px;border:1px solid #ddd}.highlighted{background-color:#2196f3}div[contenteditable=false]{display:inline-block;position:relative;resize:both;overflow:hidden;border:1px dashed #ccc;margin:5px}div[contenteditable=false]:hover{border-color:#007bff}div[contenteditable=false] img{display:block;width:100%;height:auto}.uploaded-images{display:flex;flex-wrap:wrap;margin-bottom:20px}.image-preview{margin:10px;padding:5px;border:1px dashed #ccc;cursor:pointer}.image-preview img{max-width:100px;max-height:100px;object-fit:cover}.editor{border:1px solid #ccc;min-height:300px;padding:20px;position:relative}\n"], components: [{ type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], directives: [{ type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
554
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorComponent, decorators: [{
555
+ type: Component,
556
+ args: [{ selector: 'rich-text-editor', providers: [
557
+ {
558
+ provide: NG_VALUE_ACCESSOR,
559
+ useExisting: forwardRef(() => RapidTextEditorComponent),
560
+ multi: true
561
+ }
562
+ ], template: "<div class=\"rich-text-editor\">\r\n <!-- Toolbar -->\r\n <div class=\"toolbar\">\r\n \r\n <select class=\"select-wrapper\" (change)=\"setHeading($event)\">\r\n <option value=\"P\">Paragraph</option>\r\n <option value=\"H1\">H1</option>\r\n <option value=\"H2\">H2</option>\r\n <option value=\"H3\">H3</option>\r\n <option value=\"H4\">H4</option>\r\n <option value=\"H5\">H5</option>\r\n <option value=\"H6\">H6</option>\r\n </select>\r\n <button (click)=\"format('bold')\" aria-label=\"Bold\">\r\n <mat-icon>format_bold</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('italic')\" aria-label=\"Italic\">\r\n <mat-icon>format_italic</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('underline')\" aria-label=\"Underline\">\r\n <mat-icon>format_underline</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('strikethrough')\" aria-label=\"Strikethrough\">\r\n <mat-icon>strikethrough_s</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('justifyLeft')\" aria-label=\"Align Left\">\r\n <mat-icon>format_align_left</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('justifyCenter')\" aria-label=\"Center\">\r\n <mat-icon>format_align_center</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('justifyRight')\" aria-label=\"Align Right\">\r\n <mat-icon>format_align_right</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('justifyFull')\" aria-label=\"Justify\">\r\n <mat-icon>format_align_justify</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('insertUnorderedList')\" aria-label=\"Unordered List\">\r\n <mat-icon>format_list_bulleted</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"format('insertOrderedList')\" aria-label=\"Ordered List\">\r\n <mat-icon>format_list_numbered</mat-icon>\r\n </button>\r\n \r\n \r\n \r\n <button color=\"secondary\" aria-label=\"Insert Image\" style=\"position: relative; display: inline-flex; align-items: center; justify-content: center;\">\r\n <input\r\n type=\"file\" \r\n (change)=\"insertImageToEditor($event)\"\r\n style=\"position: absolute; left: -50%; top: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer;\"\r\n aria-hidden=\"true\" \r\n />\r\n \r\n <mat-icon style=\"pointer-events: none;\">insert_photo_outlined</mat-icon>\r\n </button>\r\n \r\n \r\n <button (click)=\"openTableDialog($event)\" color=\"secondary\" aria-label=\"Choose table rows and columns\">\r\n <mat-icon>border_all</mat-icon>\r\n </button>\r\n \r\n <button (click)=\"makeFirstRowHeader()\" color=\"secondary\" aria-label=\"Make Header\">\r\n <img style=\"width: 26px; height: 46px; margin-bottom: 3px;\" src=\"../../../assets/H.svg\" alt=\"Apply Header to table\">\r\n </button>\r\n \r\n </div>\r\n \r\n <!-- Editable Area -->\r\n <!-- (keypress)=\"onContentChange()\" need to be included -->\r\n <div class=\"editor\" contenteditable=\"true\" #editor (input)=\"submitContent()\" (blur)=\"onBlur()\" (drop)=\"drop($event)\" (dragover)=\"allowDrop($event)\" >\r\n <!-- Your content goes here --> \r\n </div>\r\n \r\n </div>\r\n \r\n <button mat-stroked-button class=\"submit\" (click)=\"submitContent()\">Submit</button>\r\n \r\n \r\n <ng-template #tableDialog let-dialogRef=\"dialogRef\">\r\n <h2 mat-dialog-title>Choose Table Size</h2>\r\n <mat-dialog-content>\r\n <div class=\"grid-container\">\r\n <div\r\n *ngFor=\"let cell of grid; let i = index\"\r\n [ngClass]=\"{\r\n 'grid-item': true,\r\n 'highlighted': i % 7 < cols && Math.floor(i / 7) < rows\r\n }\"\r\n (mouseenter)=\"updatePreview(i % 7 + 1, Math.floor(i / 7) + 1)\"\r\n (click)=\"updateSelection(i % 7 + 1, Math.floor(i / 7) + 1, dialogRef)\"\r\n ></div>\r\n </div>\r\n <p>{{ rows }} x {{ cols }} </p>\r\n </mat-dialog-content>\r\n </ng-template>\r\n \r\n \r\n <input type=\"file\" (change)=\"insertImage($event)\" accept=\"image/png,image/*\" multiple />\r\n \r\n \r\n <div class=\"uploaded-images\" *ngIf=\"uploadedImages.length > 0\">\r\n <div *ngFor=\"let imageUrl of uploadedImages; let i = index\" class=\"image-preview\">\r\n <img [src]=\"imageUrl\" alt=\"Uploaded Image\" \r\n draggable=\"true\" \r\n (dragstart)=\"drag($event, imageUrl)\" />\r\n \r\n <button class=\"remove-btn\" (click)=\"removeImage(i)\">Remove</button>\r\n </div>\r\n </div>\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ", styles: ["@charset \"UTF-8\";.rich-text-editor{border:1px solid #ccc;border-radius:5px}.editor{min-height:300px;border:1px solid #ccc;padding:5px;overflow-y:auto;font-family:Calibri;outline:none!important;font-size:14px}button{width:20px;height:20px;background:none;border:none;margin-left:5px;cursor:pointer}button[mat-icon-button]{margin:0 4px;width:20px!important;height:20px;background:none;border:none}.small-icon-button{width:30px;height:30px;padding:4px}.small-icon-button mat-icon{font-size:16px}button[mat-icon-button]{position:relative;overflow:hidden}input[type=file]{padding:5px;cursor:pointer;margin-left:10px}.fileInput{display:flex;justify-content:center;align-items:center}.select-wrapper{display:inline-block;position:relative;width:150px}.select-wrapper select{appearance:none;width:100%;padding:10px 40px 10px 15px;font-size:16px;color:#333;background-color:#f4f4f4;border:1px solid #ddd;border-radius:5px;cursor:pointer;outline:none}.select-wrapper:after{content:\"\\25bc\";position:absolute;top:50%;right:15px;transform:translateY(-50%);pointer-events:none;color:#777;font-size:12px}.select-wrapper select:hover{background-color:#e9e9e9;border-color:#bbb}.select-wrapper select:focus{border-color:#007bff;background-color:#fff}.select-wrapper option{padding:8px;font-size:16px;color:#333;background-color:#fff}.toolbar{display:flex;flex-wrap:wrap;align-items:center;background-color:#f4f4f4;padding:10px;border-radius:8px;box-shadow:0 2px 8px #0000001a;gap:10px}.select-wrapper{padding:8px;font-size:14px;border:1px solid #ddd;border-radius:5px;cursor:pointer;background-color:#fff}.select-wrapper:focus{outline:none;border-color:#007bff}button{border:none;border-radius:5px;padding:8px;cursor:pointer;display:flex;align-items:center;justify-content:center}button:hover{color:#007bff}button:active{background-color:#0056b3}mat-icon{font-size:20px;color:inherit}input[type=number]{width:60px;padding:5px;font-size:14px;border:1px solid #ddd;border-radius:5px;text-align:center}input[type=file]{border:1px solid #ddd;border-radius:5px;padding:5px;font-size:14px;background-color:#fff;cursor:pointer}input[type=file]:hover{border-color:#007bff}.table{width:100px;height:30px;color:#007bff!important;font-weight:700;border:1px solid #007bff}.table:hover{background-color:#0056b3;color:#fff!important}.submit{margin-top:10px;width:100px;height:30px;color:#007bff!important;font-weight:700;border:1px solid #007bff}.submit:hover{border:none;background-color:#0056b3;color:#fff!important}.custom-dialog-container{width:auto;max-width:200px}.grid-container{display:grid;grid-template-columns:repeat(7,20px);grid-gap:5px;gap:5px}.grid-item{width:20px;height:20px;border:1px solid #ddd}.highlighted{background-color:#2196f3}div[contenteditable=false]{display:inline-block;position:relative;resize:both;overflow:hidden;border:1px dashed #ccc;margin:5px}div[contenteditable=false]:hover{border-color:#007bff}div[contenteditable=false] img{display:block;width:100%;height:auto}.uploaded-images{display:flex;flex-wrap:wrap;margin-bottom:20px}.image-preview{margin:10px;padding:5px;border:1px dashed #ccc;cursor:pointer}.image-preview img{max-width:100px;max-height:100px;object-fit:cover}.editor{border:1px solid #ccc;min-height:300px;padding:20px;position:relative}\n"] }]
563
+ }], ctorParameters: function () { return [{ type: i1.MatDialog }, { type: i0.ElementRef }]; }, propDecorators: { contentCapture: [{
564
+ type: Input
565
+ }], contentChange: [{
566
+ type: Output
567
+ }], editor: [{
568
+ type: ViewChild,
569
+ args: ['editor', { static: true }]
570
+ }], tableDialog: [{
571
+ type: ViewChild,
572
+ args: ['tableDialog']
573
+ }] } });
574
+
575
+ class RapidTextEditorModule {
576
+ }
577
+ RapidTextEditorModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
578
+ RapidTextEditorModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorModule, declarations: [RapidTextEditorComponent], imports: [CommonModule,
579
+ MatDialogModule,
580
+ MatIconModule,
581
+ MatDialogModule], exports: [RapidTextEditorComponent] });
582
+ RapidTextEditorModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorModule, imports: [[
583
+ CommonModule,
584
+ MatDialogModule,
585
+ MatIconModule,
586
+ MatDialogModule
587
+ ]] });
588
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorModule, decorators: [{
589
+ type: NgModule,
590
+ args: [{
591
+ declarations: [
592
+ RapidTextEditorComponent
593
+ ],
594
+ imports: [
595
+ CommonModule,
596
+ MatDialogModule,
597
+ MatIconModule,
598
+ MatDialogModule
599
+ ],
600
+ exports: [
601
+ RapidTextEditorComponent
602
+ ]
603
+ }]
604
+ }] });
605
+
606
+ /*
607
+ * Public API Surface of rapid-text-editor
608
+ */
609
+
610
+ /**
611
+ * Generated bundle index. Do not edit.
612
+ */
613
+
614
+ export { RapidTextEditorComponent, RapidTextEditorModule, RapidTextEditorService };
615
+ //# sourceMappingURL=rapid-text-editor.mjs.map