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,617 @@
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
+ var _a;
101
+ (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.setData("text", imageUrl);
102
+ }
103
+ // Handle drop event in the editor
104
+ drop(event) {
105
+ var _a;
106
+ event.preventDefault();
107
+ const imageUrl = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData("text");
108
+ if (imageUrl) {
109
+ this.insertImageInEditor(imageUrl);
110
+ }
111
+ }
112
+ // insertImage(event: any): void {
113
+ // const files: FileList = event.target.files;
114
+ // if (!files || files.length === 0) {
115
+ // console.error('No files selected.');
116
+ // return;
117
+ // }
118
+ // Array.from(files).forEach((file) => {
119
+ // // Check file type
120
+ // if (file.type !== 'image/png' && !file.type.startsWith('image/')) {
121
+ // console.error('Unsupported file type:', file.type);
122
+ // return;
123
+ // }
124
+ // // Read the image file
125
+ // const reader = new FileReader();
126
+ // reader.onload = (e: any) => {
127
+ // const imageUrl = e.target.result;
128
+ // // Add image to the uploaded images list
129
+ // this.uploadedImages.push(imageUrl);
130
+ // // Call insertImageInEditor to add the image to the editor
131
+ // this.insertImageInEditor(imageUrl);
132
+ // };
133
+ // reader.readAsDataURL(file);
134
+ // });
135
+ // } -> for PNG
136
+ removeImage(index) {
137
+ this.uploadedImages.splice(index, 1); // Remove the image from the array
138
+ }
139
+ insertImageInEditor(imageUrl) {
140
+ const editor = document.querySelector('.editor');
141
+ if (!editor) {
142
+ console.error('Editor element not found.');
143
+ return;
144
+ }
145
+ // Create image element
146
+ const img = document.createElement('img');
147
+ img.src = imageUrl;
148
+ img.style.maxWidth = '100%';
149
+ img.style.display = 'block';
150
+ img.style.width = '300px';
151
+ img.style.height = 'auto';
152
+ // Create a wrapper around the image to allow resizing
153
+ const wrapper = document.createElement('div');
154
+ wrapper.style.position = 'relative';
155
+ wrapper.style.display = 'inline-block'; // Ensures the wrapper only takes up space for the image
156
+ wrapper.style.margin = '10px';
157
+ wrapper.style.padding = '5px';
158
+ wrapper.style.border = '1px dashed #ccc'; // Optional border to indicate the image area
159
+ // Append the image to the wrapper
160
+ wrapper.appendChild(img);
161
+ // Append the wrapper to the editor
162
+ editor.appendChild(wrapper);
163
+ // Make the image resizable
164
+ this.makeImageResizable(wrapper, img);
165
+ // Optionally, add a text node to maintain cursor position after appending the image
166
+ const textNode = document.createTextNode('\u200B'); // Invisible character to maintain position
167
+ editor.appendChild(textNode);
168
+ const range = document.createRange();
169
+ range.setStartAfter(wrapper);
170
+ range.collapse(true);
171
+ const selection = window.getSelection();
172
+ if (selection) {
173
+ selection.removeAllRanges();
174
+ selection.addRange(range);
175
+ }
176
+ }
177
+ makeImageResizable(wrapper, img) {
178
+ let isResizing = false;
179
+ let startX = 0;
180
+ let startY = 0;
181
+ let startWidth = 0;
182
+ let startHeight = 0;
183
+ // Create resize handles for the image
184
+ const createResizeHandle = (cursor, positionStyles, resizeCallback) => {
185
+ const handle = document.createElement('div');
186
+ handle.style.position = 'absolute';
187
+ handle.style.width = '10px';
188
+ handle.style.height = '10px';
189
+ handle.style.background = 'rgba(0, 0, 0, 0.5)';
190
+ handle.style.cursor = cursor;
191
+ Object.assign(handle.style, positionStyles);
192
+ handle.addEventListener('mousedown', (resizeEvent) => {
193
+ isResizing = true;
194
+ startX = resizeEvent.clientX;
195
+ startY = resizeEvent.clientY;
196
+ startWidth = img.offsetWidth;
197
+ startHeight = img.offsetHeight;
198
+ const onMouseMove = (moveEvent) => {
199
+ if (isResizing) {
200
+ const deltaX = moveEvent.clientX - startX;
201
+ const deltaY = moveEvent.clientY - startY;
202
+ resizeCallback(deltaX, deltaY);
203
+ }
204
+ };
205
+ const onMouseUp = () => {
206
+ isResizing = false;
207
+ document.removeEventListener('mousemove', onMouseMove);
208
+ document.removeEventListener('mouseup', onMouseUp);
209
+ };
210
+ document.addEventListener('mousemove', onMouseMove);
211
+ document.addEventListener('mouseup', onMouseUp);
212
+ });
213
+ wrapper.appendChild(handle);
214
+ };
215
+ // Add resizing handles to the wrapper for the image
216
+ createResizeHandle('nw-resize', { top: '-5px', left: '-5px' }, (deltaX, deltaY) => {
217
+ img.style.width = `${startWidth - deltaX}px`;
218
+ img.style.height = `${startHeight - deltaY}px`;
219
+ });
220
+ createResizeHandle('se-resize', { bottom: '-5px', right: '-5px' }, (deltaX, deltaY) => {
221
+ img.style.width = `${startWidth + deltaX}px`;
222
+ img.style.height = `${startHeight + deltaY}px`;
223
+ });
224
+ }
225
+ insertImage(event) {
226
+ const input = event.target;
227
+ if (input === null || input === void 0 ? void 0 : input.files) {
228
+ Array.from(input.files).forEach(file => {
229
+ const reader = new FileReader();
230
+ reader.onload = (e) => {
231
+ const imageUrl = e.target.result;
232
+ this.uploadedImages.push(imageUrl); // Store image URL in the array
233
+ };
234
+ reader.readAsDataURL(file);
235
+ });
236
+ }
237
+ }
238
+ convertImage(file) {
239
+ return new Promise((resolve, reject) => {
240
+ if (file.type !== 'image/png') {
241
+ resolve(file); // Return the original file if it's not a PNG
242
+ return;
243
+ }
244
+ const img = new Image();
245
+ const reader = new FileReader();
246
+ reader.onload = (e) => {
247
+ img.src = e.target.result;
248
+ img.onload = () => {
249
+ const canvas = document.createElement('canvas');
250
+ const ctx = canvas.getContext('2d');
251
+ if (!ctx) {
252
+ reject(new Error('Failed to get canvas context'));
253
+ return;
254
+ }
255
+ canvas.width = img.width;
256
+ canvas.height = img.height;
257
+ ctx.drawImage(img, 0, 0);
258
+ canvas.toBlob(blob => {
259
+ if (blob) {
260
+ const jpgFile = new File([blob], file.name.replace(/\.png$/, '.jpg'), { type: 'image/jpeg' });
261
+ resolve(jpgFile);
262
+ }
263
+ else {
264
+ reject(new Error('Failed to convert PNG to JPG'));
265
+ }
266
+ }, 'image/jpeg', 0.95 // Quality factor for JPEG
267
+ );
268
+ };
269
+ };
270
+ reader.onerror = () => reject(new Error('Failed to read the file'));
271
+ reader.readAsDataURL(file);
272
+ });
273
+ }
274
+ // insertImageByDrag(imageUrl: string): void {
275
+ // const editor = document.querySelector('.editor') as HTMLElement;
276
+ // if (!editor) {
277
+ // console.error('Editor element not found.');
278
+ // return;
279
+ // }
280
+ // const img = document.createElement('img');
281
+ // img.src = imageUrl;
282
+ // img.style.maxWidth = '100%';
283
+ // img.style.display = 'block';
284
+ // img.style.width = '300px';
285
+ // img.style.height = 'auto';
286
+ // editor.appendChild(img);
287
+ // }
288
+ insertImageToEditor(event) {
289
+ const file = event.target.files[0];
290
+ if (file) {
291
+ const reader = new FileReader();
292
+ reader.onload = (e) => {
293
+ const imageUrl = e.target.result;
294
+ const editor = document.querySelector('.editor');
295
+ if (!editor) {
296
+ console.error('Editor element not found.');
297
+ return;
298
+ }
299
+ const wrapper = document.createElement('div');
300
+ wrapper.contentEditable = 'false';
301
+ wrapper.style.position = 'relative';
302
+ wrapper.style.display = 'inline-block';
303
+ wrapper.style.border = '1px dashed #ccc';
304
+ wrapper.style.margin = '10px';
305
+ wrapper.style.padding = '5px';
306
+ wrapper.style.borderBlockColor = '#007BFF';
307
+ const img = document.createElement('img');
308
+ img.src = imageUrl;
309
+ img.style.maxWidth = '100%';
310
+ img.style.display = 'block';
311
+ img.style.width = '300px';
312
+ img.style.height = 'auto';
313
+ wrapper.appendChild(img);
314
+ let isResizing = false;
315
+ let startX = 0;
316
+ let startY = 0;
317
+ let startWidth = 0;
318
+ let startHeight = 0;
319
+ const createResizeHandle = (cursor, positionStyles, resizeCallback) => {
320
+ const handle = document.createElement('div');
321
+ handle.style.position = 'absolute';
322
+ handle.style.width = '10px';
323
+ handle.style.height = '10px';
324
+ handle.style.background = 'rgba(0, 0, 0, 0.5)';
325
+ handle.style.cursor = cursor;
326
+ Object.assign(handle.style, positionStyles);
327
+ handle.addEventListener('mousedown', (resizeEvent) => {
328
+ isResizing = true;
329
+ startX = resizeEvent.clientX;
330
+ startY = resizeEvent.clientY;
331
+ startWidth = img.offsetWidth;
332
+ startHeight = img.offsetHeight;
333
+ const onMouseMove = (moveEvent) => {
334
+ if (isResizing) {
335
+ const deltaX = moveEvent.clientX - startX;
336
+ const deltaY = moveEvent.clientY - startY;
337
+ resizeCallback(deltaX, deltaY);
338
+ }
339
+ };
340
+ const onMouseUp = () => {
341
+ isResizing = false;
342
+ document.removeEventListener('mousemove', onMouseMove);
343
+ document.removeEventListener('mouseup', onMouseUp);
344
+ };
345
+ document.addEventListener('mousemove', onMouseMove);
346
+ document.addEventListener('mouseup', onMouseUp);
347
+ });
348
+ wrapper.appendChild(handle);
349
+ };
350
+ createResizeHandle('nw-resize', { top: '-5px', left: '-5px' }, (deltaX, deltaY) => {
351
+ img.style.width = `${startWidth - deltaX}px`;
352
+ img.style.height = `${startHeight - deltaY}px`;
353
+ });
354
+ createResizeHandle('se-resize', { bottom: '-5px', right: '-5px' }, (deltaX, deltaY) => {
355
+ img.style.width = `${startWidth + deltaX}px`;
356
+ img.style.height = `${startHeight + deltaY}px`;
357
+ });
358
+ editor.appendChild(wrapper);
359
+ const textNode = document.createTextNode('\u200B');
360
+ editor.appendChild(textNode);
361
+ const range = document.createRange();
362
+ range.setStartAfter(wrapper);
363
+ range.collapse(true);
364
+ const selection = window.getSelection();
365
+ if (selection) {
366
+ selection.removeAllRanges();
367
+ selection.addRange(range);
368
+ }
369
+ };
370
+ reader.readAsDataURL(file);
371
+ }
372
+ }
373
+ openTableDialog(event) {
374
+ this.rows = 1;
375
+ this.cols = 1;
376
+ const dialogRef = this.dialog.open(this.tableDialog, {
377
+ position: {
378
+ top: `${event.clientY}px`,
379
+ left: `${event.clientX}px`,
380
+ },
381
+ panelClass: 'custom-dialog-container'
382
+ });
383
+ dialogRef.afterClosed().subscribe((result) => {
384
+ if (result) {
385
+ this.insertTable(result.rows, result.cols);
386
+ }
387
+ });
388
+ }
389
+ updatePreview(cols, rows) {
390
+ this.cols = cols;
391
+ this.rows = rows;
392
+ }
393
+ updateSelection(colIndex, rowIndex, dialogRef) {
394
+ this.cols = colIndex;
395
+ this.rows = rowIndex;
396
+ dialogRef.close({ rows: this.rows, cols: this.cols }); // Close dialog with selection
397
+ }
398
+ makeFirstRowHeader() {
399
+ const editor = this.editor.nativeElement;
400
+ const selection = window.getSelection();
401
+ // Check if selection is inside the editor
402
+ if (!this.isSelectionInsideEditor(editor, selection)) {
403
+ alert('Please place the cursor inside the table to make the first row a header.');
404
+ return;
405
+ }
406
+ // Find the closest table to the selection
407
+ const range = selection.getRangeAt(0);
408
+ const selectedNode = range.startContainer;
409
+ const table = this.findClosestTable(selectedNode);
410
+ if (!table) {
411
+ alert('No table found near the cursor.');
412
+ return;
413
+ }
414
+ // Convert the first row cells to header cells
415
+ const firstRow = table.rows[0];
416
+ if (firstRow) {
417
+ for (let i = 0; i < firstRow.cells.length; i++) {
418
+ const cell = firstRow.cells[i];
419
+ const th = document.createElement('th');
420
+ th.innerHTML = cell.innerHTML;
421
+ th.style.textAlign = 'center';
422
+ th.style.backgroundColor = '#F1F1F0';
423
+ th.style.border = '1px solid black';
424
+ th.style.borderCollapse = 'collapse';
425
+ th.style.padding = window.getComputedStyle(cell).padding; // Retain padding
426
+ th.style.width = window.getComputedStyle(cell).width; // Retain width
427
+ th.style.height = window.getComputedStyle(cell).height;
428
+ firstRow.replaceChild(th, cell);
429
+ }
430
+ }
431
+ editor.focus();
432
+ }
433
+ findClosestTable(node) {
434
+ while (node && node !== document) {
435
+ if (node.nodeName === 'TABLE') {
436
+ return node;
437
+ }
438
+ node = node.parentNode;
439
+ }
440
+ return null;
441
+ }
442
+ insertTable(rows, cols) {
443
+ const rowCount = parseInt(rows, 10);
444
+ const colCount = parseInt(cols, 10);
445
+ if (isNaN(rowCount) || isNaN(colCount) || rowCount < 1 || colCount < 1) {
446
+ alert('Please enter valid numbers for rows and columns.');
447
+ return;
448
+ }
449
+ const editor = this.editor.nativeElement;
450
+ const selection = window.getSelection();
451
+ // Ensure selection is inside the editor
452
+ if (!this.isSelectionInsideEditor(editor, selection)) {
453
+ // Append the table at the end if selection is outside editor
454
+ this.appendTable(editor, rowCount, colCount);
455
+ return;
456
+ }
457
+ // Proceed to insert the table if selection is valid
458
+ const tableHTML = this.createTableHTML(rowCount, colCount);
459
+ const range = selection.getRangeAt(0);
460
+ range.deleteContents(); // Remove any selected content
461
+ const tempDiv = document.createElement('div');
462
+ tempDiv.innerHTML = tableHTML;
463
+ range.insertNode(tempDiv.firstChild); // Insert the table
464
+ // Move the cursor to the first cell of the new table
465
+ this.setCursorToFirstCell(tempDiv.firstChild);
466
+ editor.focus();
467
+ }
468
+ // Helper function: Create table HTML
469
+ createTableHTML(rowCount, colCount) {
470
+ let tableHTML = '<table border="1" style="border-collapse: collapse; margin:5px">';
471
+ for (let i = 0; i < rowCount; i++) {
472
+ tableHTML += '<tr>';
473
+ for (let j = 0; j < colCount; j++) {
474
+ tableHTML += `<td style="font-size: 14px; min-width: 1.5em; height: 2em; padding: .4em; border: 1px solid black"
475
+ role="textbox" contenteditable="true"></td>`;
476
+ }
477
+ tableHTML += '</tr>';
478
+ }
479
+ tableHTML += '</table>';
480
+ return tableHTML;
481
+ }
482
+ // Helper function: Append a table at the end of the editor
483
+ appendTable(editor, rowCount, colCount) {
484
+ const tableHTML = this.createTableHTML(rowCount, colCount);
485
+ editor.innerHTML += tableHTML;
486
+ // Set cursor inside the first cell of the new table
487
+ const lastInsertedTable = editor.querySelector('table:last-child');
488
+ this.setCursorToFirstCell(lastInsertedTable);
489
+ editor.focus();
490
+ }
491
+ // Helper function: Check if selection is inside the editor
492
+ isSelectionInsideEditor(editor, selection) {
493
+ if (!selection.rangeCount)
494
+ return false;
495
+ let node = selection.getRangeAt(0).commonAncestorContainer;
496
+ // Traverse up to see if the node belongs to the editor
497
+ while (node) {
498
+ if (node === editor)
499
+ return true;
500
+ node = node.parentNode;
501
+ }
502
+ return false;
503
+ }
504
+ // Helper function: Move cursor to the first cell
505
+ // private setCursorToFirstCell(table: HTMLElement) {
506
+ // const firstCell = table.querySelector('tr:first-child td:first-child') as HTMLElement;
507
+ // if (firstCell) {
508
+ // const newRange = document.createRange();
509
+ // newRange.setStart(firstCell, 0); // Start at the beginning of the cell
510
+ // newRange.collapse(true);
511
+ // const selection = window.getSelection();
512
+ // selection!.removeAllRanges();
513
+ // selection!.addRange(newRange);
514
+ // }
515
+ // }
516
+ setCursorToFirstCell(table) {
517
+ const firstCell = table.querySelector('tr:first-child td:first-child, tr:first-child th:first-child');
518
+ if (firstCell) {
519
+ // Ensure the cell is contenteditable
520
+ firstCell.setAttribute('contenteditable', 'true');
521
+ // Focus the cell
522
+ firstCell.focus();
523
+ // Create a range and place the cursor at the start of the cell
524
+ const newRange = document.createRange();
525
+ newRange.selectNodeContents(firstCell); // Select the contents of the cell
526
+ newRange.collapse(true); // Collapse to the start
527
+ const selection = window.getSelection();
528
+ if (selection) {
529
+ selection.removeAllRanges();
530
+ selection.addRange(newRange);
531
+ }
532
+ }
533
+ }
534
+ insertPageBreak() {
535
+ const pageBreak = `<div style="page-break-after: always;"><br /></div>`;
536
+ document.execCommand('insertHTML', false, pageBreak);
537
+ }
538
+ submitContent() {
539
+ const editor = this.editor.nativeElement;
540
+ const content = editor.innerHTML; // Get the HTML content from the editor
541
+ const plainTextContent = editor.innerText; // Optionally, get plain text content
542
+ // For demonstration, you can log it or process it as needed
543
+ this.onChange(content);
544
+ // Example: You could send this data to a server or use it elsewhere in your application
545
+ // this.yourService.submitContent({ content }); // Replace with your service call
546
+ }
547
+ }
548
+ 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 });
549
+ 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: [
550
+ {
551
+ provide: NG_VALUE_ACCESSOR,
552
+ useExisting: forwardRef(() => RapidTextEditorComponent),
553
+ multi: true
554
+ }
555
+ ], 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"] }] });
556
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorComponent, decorators: [{
557
+ type: Component,
558
+ args: [{ selector: 'rich-text-editor', providers: [
559
+ {
560
+ provide: NG_VALUE_ACCESSOR,
561
+ useExisting: forwardRef(() => RapidTextEditorComponent),
562
+ multi: true
563
+ }
564
+ ], 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"] }]
565
+ }], ctorParameters: function () { return [{ type: i1.MatDialog }, { type: i0.ElementRef }]; }, propDecorators: { contentCapture: [{
566
+ type: Input
567
+ }], contentChange: [{
568
+ type: Output
569
+ }], editor: [{
570
+ type: ViewChild,
571
+ args: ['editor', { static: true }]
572
+ }], tableDialog: [{
573
+ type: ViewChild,
574
+ args: ['tableDialog']
575
+ }] } });
576
+
577
+ class RapidTextEditorModule {
578
+ }
579
+ RapidTextEditorModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
580
+ RapidTextEditorModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorModule, declarations: [RapidTextEditorComponent], imports: [CommonModule,
581
+ MatDialogModule,
582
+ MatIconModule,
583
+ MatDialogModule], exports: [RapidTextEditorComponent] });
584
+ RapidTextEditorModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorModule, imports: [[
585
+ CommonModule,
586
+ MatDialogModule,
587
+ MatIconModule,
588
+ MatDialogModule
589
+ ]] });
590
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: RapidTextEditorModule, decorators: [{
591
+ type: NgModule,
592
+ args: [{
593
+ declarations: [
594
+ RapidTextEditorComponent
595
+ ],
596
+ imports: [
597
+ CommonModule,
598
+ MatDialogModule,
599
+ MatIconModule,
600
+ MatDialogModule
601
+ ],
602
+ exports: [
603
+ RapidTextEditorComponent
604
+ ]
605
+ }]
606
+ }] });
607
+
608
+ /*
609
+ * Public API Surface of rapid-text-editor
610
+ */
611
+
612
+ /**
613
+ * Generated bundle index. Do not edit.
614
+ */
615
+
616
+ export { RapidTextEditorComponent, RapidTextEditorModule, RapidTextEditorService };
617
+ //# sourceMappingURL=rapid-text-editor.mjs.map