tafeel-word-template-letters 1.0.0

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,1306 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, HostListener, Output, Input, Component, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/forms';
4
+ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
5
+ import * as i2 from '@angular/platform-browser';
6
+ import * as mammoth from 'mammoth';
7
+ import * as i3 from '@angular/common';
8
+ import { CommonModule } from '@angular/common';
9
+
10
+ class WordTemplateLettersComponent {
11
+ constructor(fb, sanitizer) {
12
+ this.fb = fb;
13
+ this.sanitizer = sanitizer;
14
+ this.templates = [];
15
+ this.refreshTemplatesRequested = new EventEmitter();
16
+ this.templateTagsRequested = new EventEmitter();
17
+ this.uploadedTemplateAnalyzeRequested = new EventEmitter();
18
+ this.pdfPreviewRequested = new EventEmitter();
19
+ this.wordPreviewRequested = new EventEmitter();
20
+ this.generateLetterRequested = new EventEmitter();
21
+ this.suggestedTags = [];
22
+ this.selectedTemplateName = '';
23
+ this.uploadedTemplateFile = null;
24
+ this.uploadedTemplateName = '';
25
+ this.form = this.fb.group({});
26
+ this.tags = [];
27
+ this.layoutTags = [];
28
+ this.numberFormatControls = {};
29
+ this.pdfPreviewUrl = null;
30
+ this.isPdfPreviewStale = false;
31
+ this.wordHtmlPreview = null;
32
+ this.wordPreviewMessage = '';
33
+ this.signatureFile = null;
34
+ this.fontSize = 12;
35
+ this.fontBold = false;
36
+ this.fontItalic = false;
37
+ this.fontUnderline = false;
38
+ this.bodyToolbarTouched = {
39
+ fontSize: false,
40
+ fontBold: false,
41
+ fontItalic: false,
42
+ fontUnderline: false
43
+ };
44
+ this.lineThickness = 1;
45
+ this.tableRows = 3;
46
+ this.tableColumns = 3;
47
+ this.tableHasHeader = true;
48
+ this.tableHeaderColor = '#f1f3f5';
49
+ this.tablePickerOpenTag = '';
50
+ this.tablePickerHoverRows = 1;
51
+ this.tablePickerHoverColumns = 1;
52
+ this.tablePickerRows = Array.from({ length: 8 }, (_, index) => index + 1);
53
+ this.tablePickerColumns = Array.from({ length: 8 }, (_, index) => index + 1);
54
+ this.isLoadingTemplates = false;
55
+ this.isLoadingTags = false;
56
+ this.isAnalyzingUpload = false;
57
+ this.isPreviewingWordHtml = false;
58
+ this.isPreviewingPdf = false;
59
+ this.isGenerating = false;
60
+ this.errorMessage = '';
61
+ this.tableResizeCursor = '';
62
+ this.pdfPreviewObjectUrl = '';
63
+ this.tableResizeState = null;
64
+ this.wordPreviewTimer = null;
65
+ this.wordPreviewRequestId = 0;
66
+ this.hasPendingPreviewChanges = false;
67
+ }
68
+ ngOnInit() {
69
+ this.refreshTemplatesRequested.emit();
70
+ }
71
+ ngOnDestroy() {
72
+ this.clearWordPreviewTimer();
73
+ this.clearPdfPreview();
74
+ }
75
+ loadTemplates() {
76
+ this.errorMessage = '';
77
+ this.isLoadingTemplates = true;
78
+ this.refreshTemplatesRequested.emit();
79
+ }
80
+ setTemplates(templates) {
81
+ this.templates = templates || [];
82
+ this.isLoadingTemplates = false;
83
+ }
84
+ failTemplates(message = 'Unable to load server templates.') {
85
+ this.errorMessage = message;
86
+ this.isLoadingTemplates = false;
87
+ }
88
+ applyTemplateAnalysis(response) {
89
+ const templateTags = response && response.tags ? response.tags : [];
90
+ this.layoutTags = templateTags.filter(tag => this.isLayoutTag(tag));
91
+ this.tags = templateTags.filter(tag => !this.isLayoutTag(tag) && !tag.startsWith('__'));
92
+ this.suggestedTags = response && response.suggestedTags ? response.suggestedTags : [];
93
+ this.createForm(this.tags);
94
+ this.isLoadingTags = false;
95
+ this.isAnalyzingUpload = false;
96
+ this.previewPdfAfterTemplateReady();
97
+ }
98
+ failTemplateAnalysis(message = 'Unable to read tags from the selected template.') {
99
+ this.errorMessage = message;
100
+ this.isLoadingTags = false;
101
+ this.isAnalyzingUpload = false;
102
+ if (this.uploadedTemplateFile) {
103
+ this.uploadedTemplateFile = null;
104
+ this.uploadedTemplateName = '';
105
+ }
106
+ }
107
+ completePdfPreview(blob) {
108
+ this.isPreviewingPdf = false;
109
+ this.showPdfPreview(blob);
110
+ }
111
+ failPdfPreview(message = 'Unable to preview the letter as PDF.') {
112
+ this.errorMessage = message;
113
+ this.isPreviewingPdf = false;
114
+ }
115
+ completeGeneratedLetter(blob, outputFileName) {
116
+ const fileName = outputFileName || this.activeTemplateName.replace(/\.docx$/i, '_generated.docx');
117
+ this.isGenerating = false;
118
+ this.openBlob(blob, fileName, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
119
+ }
120
+ failGeneratedLetter(message = 'Unable to generate the letter.') {
121
+ this.errorMessage = message;
122
+ this.isGenerating = false;
123
+ }
124
+ completeWordPreview(blob, requestId) {
125
+ this.convertWordBlobToHtml(blob, requestId);
126
+ }
127
+ failWordPreview(requestId, message = 'Unable to update Word preview.') {
128
+ if (requestId !== this.wordPreviewRequestId) {
129
+ return;
130
+ }
131
+ this.isPreviewingWordHtml = false;
132
+ this.wordPreviewMessage = message;
133
+ }
134
+ onTemplateChange() {
135
+ this.uploadedTemplateFile = null;
136
+ this.uploadedTemplateName = '';
137
+ this.resetTemplateState();
138
+ if (!this.selectedTemplateName) {
139
+ return;
140
+ }
141
+ this.isLoadingTags = true;
142
+ this.templateTagsRequested.emit(this.selectedTemplateName);
143
+ }
144
+ resetTemplateState() {
145
+ this.tags = [];
146
+ this.layoutTags = [];
147
+ this.suggestedTags = [];
148
+ this.numberFormatControls = {};
149
+ this.signatureFile = null;
150
+ this.clearPdfPreview();
151
+ this.clearWordPreview();
152
+ this.clearWordPreviewTimer();
153
+ this.form = this.fb.group({});
154
+ this.resetBodyToolbarState();
155
+ this.hasPendingPreviewChanges = false;
156
+ this.errorMessage = '';
157
+ }
158
+ onTemplateFileSelected(event) {
159
+ const input = event.target;
160
+ const file = input.files && input.files.length > 0 ? input.files[0] : null;
161
+ if (!file) {
162
+ return;
163
+ }
164
+ if (!/\.docx$/i.test(file.name)) {
165
+ this.errorMessage = 'Only .docx Word templates are supported.';
166
+ input.value = '';
167
+ return;
168
+ }
169
+ this.resetTemplateState();
170
+ this.uploadedTemplateFile = file;
171
+ this.uploadedTemplateName = file.name;
172
+ this.selectedTemplateName = '';
173
+ this.isAnalyzingUpload = true;
174
+ this.uploadedTemplateAnalyzeRequested.emit(file);
175
+ }
176
+ generateLetter() {
177
+ if (!this.hasActiveTemplate || this.isGenerating) {
178
+ return;
179
+ }
180
+ this.errorMessage = '';
181
+ this.isGenerating = true;
182
+ const outputFileName = this.activeTemplateName.replace(/\.docx$/i, '_generated.docx');
183
+ this.generateLetterRequested.emit(this.createRenderRequest(outputFileName));
184
+ }
185
+ previewPdf() {
186
+ if (!this.hasActiveTemplate || this.isPreviewingPdf) {
187
+ return;
188
+ }
189
+ this.errorMessage = '';
190
+ this.isPreviewingPdf = true;
191
+ this.pdfPreviewRequested.emit(this.createRenderRequest());
192
+ }
193
+ previewPdfAfterTemplateReady() {
194
+ setTimeout(() => {
195
+ if (this.hasActiveTemplate && !this.isPreviewingPdf) {
196
+ this.previewPdf();
197
+ }
198
+ });
199
+ }
200
+ getSuggestedTag(tag) {
201
+ return this.suggestedTags.find(item => item.key.toLowerCase() === tag.toLowerCase());
202
+ }
203
+ trackByTag(index, tag) {
204
+ return tag;
205
+ }
206
+ get hasActiveTemplate() {
207
+ return !!this.selectedTemplateName || !!this.uploadedTemplateFile;
208
+ }
209
+ get activeTemplateName() {
210
+ return this.uploadedTemplateName || this.selectedTemplateName;
211
+ }
212
+ getEditorStyles(tag) {
213
+ const styles = {
214
+ 'direction': 'rtl',
215
+ 'text-align': 'right'
216
+ };
217
+ if (this.isBodyTag(tag)) {
218
+ Object.assign(styles, this.getBodyToolbarStyles());
219
+ }
220
+ return styles;
221
+ }
222
+ getBodyToolbarStyles() {
223
+ const styles = {};
224
+ if (this.bodyToolbarTouched.fontSize) {
225
+ styles['font-size'] = `${this.fontSize || 12}pt`;
226
+ }
227
+ if (this.bodyToolbarTouched.fontBold) {
228
+ styles['font-weight'] = this.fontBold ? '700' : '400';
229
+ }
230
+ if (this.bodyToolbarTouched.fontItalic) {
231
+ styles['font-style'] = this.fontItalic ? 'italic' : 'normal';
232
+ }
233
+ if (this.bodyToolbarTouched.fontUnderline) {
234
+ styles['text-decoration'] = this.fontUnderline ? 'underline' : 'none';
235
+ }
236
+ return styles;
237
+ }
238
+ isNumberField(tag) {
239
+ return /number|no|id|date|count|mobile/i.test(tag);
240
+ }
241
+ supportsHindiNumberFormat(tag) {
242
+ return this.isNumberField(tag) || this.isMultilineField(tag);
243
+ }
244
+ isSystemGeneratedTag(tag) {
245
+ return /^(PageNumber|Page|TotalPages|PageCount)$/i.test(tag);
246
+ }
247
+ isSignatureTag(tag) {
248
+ return /^(SignatureImage|Signature|UserSignatureImage)$/i.test(tag);
249
+ }
250
+ isMultilineField(tag) {
251
+ const normalizedTag = this.normalizeTagName(tag);
252
+ return /^(Body|LetterBody|Description|Notes|Remarks|DecisionText)$/i.test(normalizedTag)
253
+ || /Body$/i.test(normalizedTag);
254
+ }
255
+ isBodyTag(tag) {
256
+ return /^Body$/i.test(this.normalizeTagName(tag));
257
+ }
258
+ isLayoutTag(tag) {
259
+ return this.isSystemGeneratedTag(tag) || this.isSignatureTag(tag);
260
+ }
261
+ onBodyToolbarStyleChange(tag, style) {
262
+ this.bodyToolbarTouched[style] = true;
263
+ const editor = this.getRichEditorElement(tag);
264
+ if (editor && this.isBodyTag(tag)) {
265
+ this.applyBodyToolbarStyles(editor);
266
+ this.syncRichEditorValue(tag, editor);
267
+ }
268
+ this.markPreviewDirty();
269
+ }
270
+ resetBodyToolbarStyles(tag) {
271
+ this.resetBodyToolbarState();
272
+ const editor = this.getRichEditorElement(tag);
273
+ if (editor && this.isBodyTag(tag)) {
274
+ this.clearBodyToolbarStyles(editor);
275
+ this.syncRichEditorValue(tag, editor);
276
+ }
277
+ this.markPreviewDirty();
278
+ }
279
+ onRichTextInput(tag, event) {
280
+ const target = event.target;
281
+ const control = this.form.get(tag);
282
+ if (control) {
283
+ control.setValue(target.innerHTML);
284
+ }
285
+ this.markPreviewDirty();
286
+ }
287
+ onRichTextFocusOut(event) {
288
+ this.onFieldPreviewCommit(event);
289
+ }
290
+ onFieldPreviewCommit(event) {
291
+ if (this.isFocusMovingWithinSameTagField(event)) {
292
+ return;
293
+ }
294
+ if (!this.hasPendingPreviewChanges) {
295
+ return;
296
+ }
297
+ this.scheduleWordHtmlPreview(300, true);
298
+ }
299
+ markPreviewDirty() {
300
+ this.hasPendingPreviewChanges = true;
301
+ if (this.pdfPreviewUrl) {
302
+ this.isPdfPreviewStale = true;
303
+ }
304
+ }
305
+ onRichTextPaste(tag, event) {
306
+ event.preventDefault();
307
+ const clipboardData = event.clipboardData;
308
+ const html = clipboardData ? clipboardData.getData('text/html') : '';
309
+ const text = clipboardData ? clipboardData.getData('text/plain') : '';
310
+ const cleanHtml = html ? this.cleanPastedHtml(html) : this.plainTextToHtml(text);
311
+ this.insertEditorHtml(tag, cleanHtml);
312
+ }
313
+ applyEditorCommand(tag, command, value) {
314
+ const editor = this.getRichEditorElement(tag);
315
+ if (!editor) {
316
+ return;
317
+ }
318
+ editor.focus();
319
+ document.execCommand(command, false, value || null);
320
+ this.syncRichEditorValue(tag, editor);
321
+ }
322
+ applyEditorAlignment(tag, alignment) {
323
+ const editor = this.getRichEditorElement(tag);
324
+ if (!editor) {
325
+ return;
326
+ }
327
+ editor.focus();
328
+ this.ensureEditorBlockStructure(editor, alignment);
329
+ const blocks = this.getSelectedEditorBlocks(editor);
330
+ const targets = blocks.length > 0 ? blocks : this.getEditorBlocks(editor);
331
+ if (targets.length === 0) {
332
+ this.syncRichEditorValue(tag, editor);
333
+ return;
334
+ }
335
+ targets.forEach(block => {
336
+ block.style.textAlign = alignment;
337
+ if (alignment === 'right' || alignment === 'justify') {
338
+ block.dir = 'rtl';
339
+ block.style.direction = 'rtl';
340
+ }
341
+ else if (alignment === 'left') {
342
+ block.dir = 'ltr';
343
+ block.style.direction = 'ltr';
344
+ }
345
+ });
346
+ this.syncRichEditorValue(tag, editor);
347
+ }
348
+ onTableResizeMouseMove(tag, event) {
349
+ if (this.tableResizeState) {
350
+ this.resizeActiveTable(event);
351
+ return;
352
+ }
353
+ const targetCell = this.getResizeTargetCell(event);
354
+ if (!targetCell) {
355
+ this.tableResizeCursor = '';
356
+ return;
357
+ }
358
+ this.tableResizeCursor = this.getResizeDirection(targetCell, event) || '';
359
+ }
360
+ onTableResizeMouseDown(tag, event) {
361
+ const editor = this.getRichEditorElement(tag);
362
+ const targetCell = this.getResizeTargetCell(event);
363
+ if (!editor || !targetCell) {
364
+ return;
365
+ }
366
+ const direction = this.getResizeDirection(targetCell, event);
367
+ if (!direction) {
368
+ return;
369
+ }
370
+ const table = targetCell.closest('table');
371
+ const row = targetCell.parentElement;
372
+ if (!table || !row) {
373
+ return;
374
+ }
375
+ event.preventDefault();
376
+ event.stopPropagation();
377
+ this.tableResizeState = {
378
+ tag,
379
+ editor,
380
+ table,
381
+ rowIndex: row.rowIndex,
382
+ columnIndex: targetCell.cellIndex,
383
+ startX: event.clientX,
384
+ startY: event.clientY,
385
+ startWidth: targetCell.getBoundingClientRect().width,
386
+ startHeight: targetCell.getBoundingClientRect().height,
387
+ direction
388
+ };
389
+ this.tableResizeCursor = direction;
390
+ }
391
+ onDocumentMouseMove(event) {
392
+ if (this.tableResizeState) {
393
+ this.resizeActiveTable(event);
394
+ }
395
+ }
396
+ onDocumentMouseUp() {
397
+ if (!this.tableResizeState) {
398
+ return;
399
+ }
400
+ this.syncTableEditor(this.tableResizeState.tag, this.tableResizeState.editor);
401
+ this.tableResizeState = null;
402
+ this.tableResizeCursor = '';
403
+ }
404
+ clearTableResizeCursor() {
405
+ if (!this.tableResizeState) {
406
+ this.tableResizeCursor = '';
407
+ }
408
+ }
409
+ insertTable(tag) {
410
+ const rows = this.clamp(this.tableRows, 1, 20);
411
+ const columns = this.clamp(this.tableColumns, 1, 8);
412
+ const tableId = `table-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
413
+ const wrapperStyle = 'width:100%;text-align:left;direction:ltr;';
414
+ const tableStyle = 'display:inline-table;border-collapse:collapse;border:1px solid #495057;direction:ltr;margin-top:10px;margin-right:0;margin-bottom:14px;margin-left:0;width:auto;max-width:100%;';
415
+ const cellStyle = 'border:1px solid #495057;direction:rtl;min-width:90px;height:34px;padding:7px 9px;vertical-align:top;';
416
+ const headerStyle = `${cellStyle}background:${this.tableHeaderColor};font-weight:700;`;
417
+ let html = `<div class="template-editor-table-wrapper" data-table-align="left" style="${wrapperStyle}"><table class="template-editor-table" data-template-table-id="${tableId}" data-table-align="left" dir="ltr" style="${tableStyle}"><tbody>`;
418
+ if (this.tableHasHeader) {
419
+ html += '<tr>';
420
+ this.createTableCells(columns, index => `Header ${index + 1}`).forEach(cell => html += `<th contenteditable="true" data-table-header="true" style="${headerStyle}">${cell}</th>`);
421
+ html += '</tr>';
422
+ }
423
+ for (let row = 0; row < rows; row++) {
424
+ html += '<tr>';
425
+ this.createTableCells(columns, () => '<br>').forEach(cell => html += `<td contenteditable="true" style="${cellStyle}">${cell}</td>`);
426
+ html += '</tr>';
427
+ }
428
+ html += '</tbody></table></div><p><br></p>';
429
+ this.insertEditorHtml(tag, html);
430
+ this.focusFirstTableCell(tag, tableId);
431
+ }
432
+ toggleTablePicker(tag) {
433
+ this.tablePickerOpenTag = this.tablePickerOpenTag === tag ? '' : tag;
434
+ this.tablePickerHoverRows = this.clamp(this.tableRows, 1, 8);
435
+ this.tablePickerHoverColumns = this.clamp(this.tableColumns, 1, 8);
436
+ }
437
+ previewTableSize(rows, columns) {
438
+ this.tablePickerHoverRows = rows;
439
+ this.tablePickerHoverColumns = columns;
440
+ }
441
+ isTablePickerCellActive(row, column) {
442
+ return row <= this.tablePickerHoverRows && column <= this.tablePickerHoverColumns;
443
+ }
444
+ insertTableFromPicker(tag, rows, columns) {
445
+ this.tableRows = rows;
446
+ this.tableColumns = columns;
447
+ this.tablePickerOpenTag = '';
448
+ this.insertTable(tag);
449
+ }
450
+ applyCurrentTableHeaderColor(tag) {
451
+ this.tableHasHeader = true;
452
+ this.applyCurrentTableHeader(tag);
453
+ }
454
+ applyCurrentTableHeader(tag) {
455
+ const context = this.getCurrentTableContext(tag);
456
+ if (!context) {
457
+ return;
458
+ }
459
+ const firstRow = context.table.rows[0];
460
+ if (!firstRow) {
461
+ return;
462
+ }
463
+ Array.from(firstRow.cells).forEach(cell => {
464
+ const tableCell = cell;
465
+ if (this.tableHasHeader) {
466
+ tableCell.setAttribute('data-table-header', 'true');
467
+ tableCell.style.backgroundColor = this.tableHeaderColor;
468
+ tableCell.style.fontWeight = '700';
469
+ }
470
+ else {
471
+ tableCell.removeAttribute('data-table-header');
472
+ tableCell.style.removeProperty('background-color');
473
+ tableCell.style.removeProperty('font-weight');
474
+ }
475
+ });
476
+ this.syncTableEditor(tag, context.editor);
477
+ }
478
+ addTableRow(tag) {
479
+ const context = this.getCurrentTableContext(tag);
480
+ if (!context) {
481
+ return;
482
+ }
483
+ const referenceRow = context.row || context.table.rows[context.table.rows.length - 1];
484
+ const columnCount = Math.max(referenceRow ? referenceRow.cells.length : 0, 1);
485
+ const newRow = context.table.insertRow(referenceRow ? referenceRow.rowIndex + 1 : -1);
486
+ for (let index = 0; index < columnCount; index++) {
487
+ const cell = newRow.insertCell(index);
488
+ cell.contentEditable = 'true';
489
+ cell.innerHTML = '<br>';
490
+ }
491
+ this.focusElementContents(newRow.cells[0]);
492
+ this.syncTableEditor(tag, context.editor);
493
+ }
494
+ deleteTableRow(tag) {
495
+ const context = this.getCurrentTableContext(tag);
496
+ if (!context || context.table.rows.length <= 1) {
497
+ return;
498
+ }
499
+ const rowIndex = context.row ? context.row.rowIndex : context.table.rows.length - 1;
500
+ context.table.deleteRow(rowIndex);
501
+ const nextRow = context.table.rows[Math.min(rowIndex, context.table.rows.length - 1)];
502
+ if (nextRow && nextRow.cells.length > 0) {
503
+ this.focusElementContents(nextRow.cells[0]);
504
+ }
505
+ this.syncTableEditor(tag, context.editor);
506
+ }
507
+ addTableColumn(tag) {
508
+ const context = this.getCurrentTableContext(tag);
509
+ if (!context) {
510
+ return;
511
+ }
512
+ const insertIndex = context.cell ? context.cell.cellIndex + 1 : this.getMaxTableColumnCount(context.table);
513
+ Array.from(context.table.rows).forEach(row => {
514
+ const referenceCell = row.cells[Math.max(0, Math.min(insertIndex - 1, row.cells.length - 1))];
515
+ const isHeaderRow = referenceCell && referenceCell.tagName.toLowerCase() === 'th';
516
+ const cell = document.createElement(isHeaderRow ? 'th' : 'td');
517
+ cell.contentEditable = 'true';
518
+ cell.innerHTML = '<br>';
519
+ row.insertBefore(cell, row.cells[insertIndex] || null);
520
+ });
521
+ const focusRow = context.row || context.table.rows[0];
522
+ if (focusRow && focusRow.cells[insertIndex]) {
523
+ this.focusElementContents(focusRow.cells[insertIndex]);
524
+ }
525
+ this.syncTableEditor(tag, context.editor);
526
+ }
527
+ deleteTableColumn(tag) {
528
+ const context = this.getCurrentTableContext(tag);
529
+ if (!context || this.getMaxTableColumnCount(context.table) <= 1) {
530
+ return;
531
+ }
532
+ const deleteIndex = context.cell ? context.cell.cellIndex : this.getMaxTableColumnCount(context.table) - 1;
533
+ Array.from(context.table.rows).forEach(row => {
534
+ if (row.cells[deleteIndex]) {
535
+ row.deleteCell(deleteIndex);
536
+ }
537
+ });
538
+ const focusRow = context.row || context.table.rows[0];
539
+ const focusIndex = Math.max(0, deleteIndex - 1);
540
+ if (focusRow && focusRow.cells[focusIndex]) {
541
+ this.focusElementContents(focusRow.cells[focusIndex]);
542
+ }
543
+ this.syncTableEditor(tag, context.editor);
544
+ }
545
+ deleteCurrentTable(tag) {
546
+ const context = this.getCurrentTableContext(tag);
547
+ if (!context) {
548
+ return;
549
+ }
550
+ const paragraph = document.createElement('p');
551
+ paragraph.innerHTML = '<br>';
552
+ const removableElement = context.wrapper || context.table;
553
+ removableElement.parentNode?.insertBefore(paragraph, removableElement.nextSibling);
554
+ removableElement.remove();
555
+ this.focusElementContents(paragraph);
556
+ this.syncTableEditor(tag, context.editor);
557
+ }
558
+ alignCurrentTable(tag, alignment) {
559
+ const context = this.getCurrentTableContext(tag);
560
+ if (!context) {
561
+ return;
562
+ }
563
+ context.table.style.textAlign = alignment;
564
+ context.table.style.display = 'inline-table';
565
+ context.table.style.float = 'none';
566
+ context.table.dir = 'ltr';
567
+ context.table.style.direction = 'ltr';
568
+ context.table.style.removeProperty('margin');
569
+ context.table.setAttribute('data-table-align', alignment);
570
+ if (context.wrapper) {
571
+ context.wrapper.style.width = '100%';
572
+ context.wrapper.style.textAlign = alignment;
573
+ context.wrapper.setAttribute('data-table-align', alignment);
574
+ }
575
+ if (alignment === 'right') {
576
+ if (context.wrapper) {
577
+ context.wrapper.removeAttribute('dir');
578
+ context.wrapper.style.removeProperty('direction');
579
+ }
580
+ context.table.dir = 'ltr';
581
+ context.table.style.direction = 'ltr';
582
+ context.table.style.marginLeft = '0';
583
+ context.table.style.marginRight = '0';
584
+ }
585
+ else if (alignment === 'center') {
586
+ if (context.wrapper) {
587
+ context.wrapper.removeAttribute('dir');
588
+ context.wrapper.style.removeProperty('direction');
589
+ }
590
+ context.table.dir = 'ltr';
591
+ context.table.style.direction = 'ltr';
592
+ context.table.style.marginLeft = '0';
593
+ context.table.style.marginRight = '0';
594
+ }
595
+ else {
596
+ if (context.wrapper) {
597
+ context.wrapper.removeAttribute('dir');
598
+ context.wrapper.style.removeProperty('direction');
599
+ }
600
+ context.table.dir = 'ltr';
601
+ context.table.style.direction = 'ltr';
602
+ context.table.style.marginLeft = '0';
603
+ context.table.style.marginRight = '0';
604
+ }
605
+ this.syncTableEditor(tag, context.editor);
606
+ }
607
+ insertHeading(tag) {
608
+ this.insertEditorHtml(tag, '<h2>Section Title</h2>');
609
+ }
610
+ insertParagraph(tag) {
611
+ this.insertEditorHtml(tag, '<p>Write paragraph text here.</p>');
612
+ }
613
+ insertBulletList(tag) {
614
+ this.insertEditorHtml(tag, '<ul><li>First point</li><li>Second point</li><li>Third point</li></ul>');
615
+ }
616
+ insertNumberedList(tag) {
617
+ this.insertEditorHtml(tag, '<ol><li>First item</li><li>Second item</li><li>Third item</li></ol>');
618
+ }
619
+ insertSeparator(tag) {
620
+ const thickness = this.clamp(this.lineThickness, 1, 6);
621
+ this.lineThickness = thickness;
622
+ this.insertEditorHtml(tag, `<hr style="border:0;border-top:${thickness}px solid #495057;margin:10px 0;"><p><br></p>`);
623
+ }
624
+ insertArabicOpening(tag) {
625
+ this.insertEditorHtml(tag, '<p dir="rtl" style="text-align: right;">سعادة/ </p><p dir="rtl" style="text-align: right;">السلام عليكم ورحمة الله وبركاته،</p><p dir="rtl" style="text-align: right;">إشارة إلى الموضوع أعلاه، نفيدكم بما يلي:</p>');
626
+ }
627
+ onSignatureSelected(event) {
628
+ const input = event.target;
629
+ this.signatureFile = input.files && input.files.length > 0 ? input.files[0] : null;
630
+ this.scheduleWordHtmlPreview();
631
+ }
632
+ createForm(tags) {
633
+ const group = {};
634
+ tags.forEach(tag => {
635
+ group[tag] = [''];
636
+ if (this.supportsHindiNumberFormat(tag)) {
637
+ this.numberFormatControls[tag] = false;
638
+ }
639
+ });
640
+ this.form = this.fb.group(group);
641
+ }
642
+ insertEditorHtml(tag, html) {
643
+ const control = this.form.get(tag);
644
+ if (!control) {
645
+ return;
646
+ }
647
+ const editor = this.getRichEditorElement(tag);
648
+ if (!editor) {
649
+ control.setValue(`${String(control.value || '')}${html}`);
650
+ return;
651
+ }
652
+ editor.focus();
653
+ this.insertHtmlAtCursor(editor, html);
654
+ this.syncRichEditorValue(tag, editor);
655
+ this.markPreviewDirty();
656
+ }
657
+ focusFirstTableCell(tag, tableId) {
658
+ window.setTimeout(() => {
659
+ const editor = this.getRichEditorElement(tag);
660
+ const table = editor ? editor.querySelector(`table[data-template-table-id="${tableId}"]`) : null;
661
+ const cell = table ? table.querySelector('th,td') : null;
662
+ if (cell) {
663
+ this.focusElementContents(cell);
664
+ }
665
+ });
666
+ }
667
+ getCurrentTableContext(tag) {
668
+ const editor = this.getRichEditorElement(tag);
669
+ if (!editor) {
670
+ return null;
671
+ }
672
+ const selection = window.getSelection();
673
+ const selectedElement = selection && selection.anchorNode && editor.contains(selection.anchorNode)
674
+ ? this.getElementFromSelectionNode(selection.anchorNode)
675
+ : null;
676
+ const cell = selectedElement ? selectedElement.closest('td,th') : null;
677
+ const row = cell ? cell.closest('tr') : null;
678
+ const tableFromSelection = cell ? cell.closest('table') : null;
679
+ const tables = Array.from(editor.querySelectorAll('table'));
680
+ const table = tableFromSelection || tables[tables.length - 1] || null;
681
+ const wrapper = table ? table.closest('.template-editor-table-wrapper') : null;
682
+ return table ? { editor, table, wrapper, row, cell } : null;
683
+ }
684
+ getMaxTableColumnCount(table) {
685
+ return Array.from(table.rows).reduce((max, row) => Math.max(max, row.cells.length), 0);
686
+ }
687
+ focusElementContents(element) {
688
+ element.focus();
689
+ const range = document.createRange();
690
+ range.selectNodeContents(element);
691
+ range.collapse(false);
692
+ const selection = window.getSelection();
693
+ if (selection) {
694
+ selection.removeAllRanges();
695
+ selection.addRange(range);
696
+ }
697
+ }
698
+ syncTableEditor(tag, editor) {
699
+ this.syncRichEditorValue(tag, editor);
700
+ this.markPreviewDirty();
701
+ editor.focus();
702
+ }
703
+ isFocusMovingWithinSameTagField(event) {
704
+ if (!event) {
705
+ return false;
706
+ }
707
+ const currentTarget = event.currentTarget;
708
+ const nextTarget = event.relatedTarget;
709
+ const currentField = currentTarget ? currentTarget.closest('.tag-field') : null;
710
+ return !!currentField && !!nextTarget && currentField.contains(nextTarget);
711
+ }
712
+ insertHtmlAtCursor(editor, html) {
713
+ const selection = window.getSelection();
714
+ if (!selection || selection.rangeCount === 0 || !editor.contains(selection.anchorNode)) {
715
+ editor.insertAdjacentHTML('beforeend', html);
716
+ return;
717
+ }
718
+ const range = selection.getRangeAt(0);
719
+ range.deleteContents();
720
+ const container = document.createElement('div');
721
+ container.innerHTML = html;
722
+ const fragment = document.createDocumentFragment();
723
+ let node;
724
+ let lastNode = null;
725
+ while ((node = container.firstChild)) {
726
+ lastNode = fragment.appendChild(node);
727
+ }
728
+ range.insertNode(fragment);
729
+ if (lastNode) {
730
+ range.setStartAfter(lastNode);
731
+ range.collapse(true);
732
+ selection.removeAllRanges();
733
+ selection.addRange(range);
734
+ }
735
+ }
736
+ cleanPastedHtml(html) {
737
+ const container = document.createElement('div');
738
+ container.innerHTML = String(html || '')
739
+ .replace(/<!--[\s\S]*?-->/g, '')
740
+ .replace(/<style[\s\S]*?<\/style>/gi, '')
741
+ .replace(/<script[\s\S]*?<\/script>/gi, '')
742
+ .replace(/<xml[\s\S]*?<\/xml>/gi, '')
743
+ .replace(/<meta[\s\S]*?>/gi, '')
744
+ .replace(/<link[\s\S]*?>/gi, '')
745
+ .replace(/<\/?o:p[^>]*>/gi, '');
746
+ this.cleanPastedNode(container);
747
+ const cleaned = container.innerHTML.trim();
748
+ return cleaned || this.plainTextToHtml(container.textContent || '');
749
+ }
750
+ cleanPastedNode(node) {
751
+ Array.from(node.childNodes).forEach(child => {
752
+ if (child.nodeType === Node.TEXT_NODE) {
753
+ child.textContent = this.normalizePastedText(child.textContent || '');
754
+ return;
755
+ }
756
+ if (!(child instanceof HTMLElement)) {
757
+ child.parentNode?.removeChild(child);
758
+ return;
759
+ }
760
+ this.cleanPastedNode(child);
761
+ const tagName = child.tagName.toLowerCase();
762
+ if (!this.isAllowedPasteTag(tagName)) {
763
+ this.unwrapElement(child);
764
+ return;
765
+ }
766
+ const hasRtlText = this.containsRtlText(child.textContent || '');
767
+ const direction = hasRtlText && this.isPastedBlockTag(tagName)
768
+ ? 'rtl'
769
+ : this.getAllowedDirection(child.getAttribute('dir'));
770
+ const cleanStyle = this.getCleanPastedStyle(child);
771
+ Array.from(child.attributes).forEach(attribute => child.removeAttribute(attribute.name));
772
+ if (direction) {
773
+ child.setAttribute('dir', direction);
774
+ }
775
+ if (cleanStyle) {
776
+ child.setAttribute('style', cleanStyle);
777
+ }
778
+ if ((tagName === 'span' || tagName === 'font' || tagName === 'a') && !cleanStyle && !direction) {
779
+ this.unwrapElement(child);
780
+ }
781
+ });
782
+ }
783
+ isAllowedPasteTag(tagName) {
784
+ return [
785
+ 'p', 'div', 'br', 'h1', 'h2', 'h3', 'ul', 'ol', 'li',
786
+ 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'strong', 'b',
787
+ 'em', 'i', 'u', 'span', 'font', 'hr', 'a'
788
+ ].indexOf(tagName) >= 0;
789
+ }
790
+ getAllowedDirection(value) {
791
+ const normalized = String(value || '').toLowerCase();
792
+ return normalized === 'rtl' || normalized === 'ltr' ? normalized : '';
793
+ }
794
+ getCleanPastedStyle(element) {
795
+ const style = element.getAttribute('style') || '';
796
+ const rules = [];
797
+ const textAlign = /text-align\s*:\s*(right|center|left|justify)/i.exec(style);
798
+ const direction = /direction\s*:\s*(rtl|ltr)/i.exec(style);
799
+ const fontWeight = /font-weight\s*:\s*(bold|[6-9]00)/i.exec(style);
800
+ const fontStyle = /font-style\s*:\s*italic/i.test(style);
801
+ const textDecoration = /text-decoration[^:]*:\s*[^;]*underline/i.test(style);
802
+ if (this.isPastedBlockTag(element.tagName.toLowerCase()) && this.containsRtlText(element.textContent || '') && (!textAlign || textAlign[1].toLowerCase() !== 'center')) {
803
+ rules.push('text-align: right');
804
+ }
805
+ else if (textAlign) {
806
+ rules.push(`text-align: ${textAlign[1].toLowerCase()}`);
807
+ }
808
+ if (this.isPastedBlockTag(element.tagName.toLowerCase()) && this.containsRtlText(element.textContent || '')) {
809
+ rules.push('direction: rtl');
810
+ }
811
+ else if (direction) {
812
+ rules.push(`direction: ${direction[1].toLowerCase()}`);
813
+ }
814
+ if (fontWeight || element.tagName.toLowerCase() === 'b' || element.tagName.toLowerCase() === 'strong') {
815
+ rules.push('font-weight: bold');
816
+ }
817
+ if (fontStyle || element.tagName.toLowerCase() === 'i' || element.tagName.toLowerCase() === 'em') {
818
+ rules.push('font-style: italic');
819
+ }
820
+ if (textDecoration || element.tagName.toLowerCase() === 'u') {
821
+ rules.push('text-decoration: underline');
822
+ }
823
+ return rules.join('; ');
824
+ }
825
+ normalizePastedText(value) {
826
+ return String(value || '')
827
+ .replace(/\u00a0/g, ' ')
828
+ .replace(/[ \t\r\n\f\v]+/g, ' ');
829
+ }
830
+ isPastedBlockTag(tagName) {
831
+ return ['p', 'div', 'h1', 'h2', 'h3', 'li', 'td', 'th'].indexOf(tagName) >= 0;
832
+ }
833
+ containsRtlText(value) {
834
+ return /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/.test(String(value || ''));
835
+ }
836
+ unwrapElement(element) {
837
+ const parent = element.parentNode;
838
+ if (!parent) {
839
+ return;
840
+ }
841
+ while (element.firstChild) {
842
+ parent.insertBefore(element.firstChild, element);
843
+ }
844
+ parent.removeChild(element);
845
+ }
846
+ plainTextToHtml(text) {
847
+ return String(text || '')
848
+ .replace(/\r\n/g, '\n')
849
+ .split('\n')
850
+ .map(line => `<p>${line ? this.escapeHtml(line) : '<br>'}</p>`)
851
+ .join('');
852
+ }
853
+ escapeHtml(value) {
854
+ return String(value || '')
855
+ .replace(/&/g, '&amp;')
856
+ .replace(/</g, '&lt;')
857
+ .replace(/>/g, '&gt;')
858
+ .replace(/"/g, '&quot;')
859
+ .replace(/'/g, '&#39;');
860
+ }
861
+ getRichEditorElement(tag) {
862
+ return document.getElementById(`tag-${tag}`);
863
+ }
864
+ getSelectedEditorBlocks(editor) {
865
+ const selection = window.getSelection();
866
+ if (!selection || selection.rangeCount === 0 || !editor.contains(selection.anchorNode)) {
867
+ return [];
868
+ }
869
+ const range = selection.getRangeAt(0);
870
+ const blocks = this.getEditorBlocks(editor)
871
+ .filter(block => range.intersectsNode(block));
872
+ if (blocks.length > 0) {
873
+ return blocks;
874
+ }
875
+ const anchorElement = this.getElementFromSelectionNode(selection.anchorNode);
876
+ const closestBlock = anchorElement ? this.closestEditorBlock(anchorElement, editor) : null;
877
+ return closestBlock ? [closestBlock] : [];
878
+ }
879
+ getEditorBlocks(editor) {
880
+ return Array.from(editor.querySelectorAll('p, div, h1, h2, h3, li, td, th'));
881
+ }
882
+ ensureEditorBlockStructure(editor, fallbackAlignment = 'right') {
883
+ const inlineNodes = [];
884
+ Array.from(editor.childNodes).forEach(node => {
885
+ if (this.isIgnorableEditorTextNode(node)) {
886
+ editor.removeChild(node);
887
+ return;
888
+ }
889
+ if (this.isEditorBlockNode(node)) {
890
+ this.wrapInlineEditorNodes(editor, inlineNodes, node, fallbackAlignment);
891
+ return;
892
+ }
893
+ inlineNodes.push(node);
894
+ });
895
+ this.wrapInlineEditorNodes(editor, inlineNodes, null, fallbackAlignment);
896
+ }
897
+ wrapInlineEditorNodes(editor, inlineNodes, beforeNode, fallbackAlignment) {
898
+ if (inlineNodes.length === 0) {
899
+ return;
900
+ }
901
+ const paragraph = document.createElement('p');
902
+ const text = inlineNodes.map(node => node.textContent || '').join('');
903
+ const hasRtlText = this.containsRtlText(text);
904
+ const alignment = fallbackAlignment || (hasRtlText ? 'right' : 'left');
905
+ paragraph.style.textAlign = alignment;
906
+ if (alignment === 'right' || alignment === 'justify' || hasRtlText) {
907
+ paragraph.dir = 'rtl';
908
+ paragraph.style.direction = 'rtl';
909
+ }
910
+ else if (alignment === 'left') {
911
+ paragraph.dir = 'ltr';
912
+ paragraph.style.direction = 'ltr';
913
+ }
914
+ inlineNodes.forEach(node => paragraph.appendChild(node));
915
+ editor.insertBefore(paragraph, beforeNode);
916
+ inlineNodes.length = 0;
917
+ }
918
+ isEditorBlockNode(node) {
919
+ return node instanceof HTMLElement && /^(p|div|h1|h2|h3|ul|ol|li|table|hr)$/i.test(node.tagName);
920
+ }
921
+ isIgnorableEditorTextNode(node) {
922
+ return node.nodeType === Node.TEXT_NODE && !String(node.textContent || '').trim();
923
+ }
924
+ getElementFromSelectionNode(node) {
925
+ if (!node) {
926
+ return null;
927
+ }
928
+ return node instanceof HTMLElement ? node : node.parentElement;
929
+ }
930
+ closestEditorBlock(element, editor) {
931
+ const block = element.closest('p, div, h1, h2, h3, li, td, th');
932
+ return block && editor.contains(block) ? block : null;
933
+ }
934
+ getResizeTargetCell(event) {
935
+ const element = event.target;
936
+ const cell = element ? element.closest('td,th') : null;
937
+ return cell && cell.closest('.rich-body-editor') ? cell : null;
938
+ }
939
+ getResizeDirection(cell, event) {
940
+ const rect = cell.getBoundingClientRect();
941
+ const resizeThreshold = 7;
942
+ const nearRightEdge = Math.abs(event.clientX - rect.right) <= resizeThreshold;
943
+ const nearBottomEdge = Math.abs(event.clientY - rect.bottom) <= resizeThreshold;
944
+ if (nearRightEdge && nearBottomEdge) {
945
+ return 'both';
946
+ }
947
+ if (nearRightEdge) {
948
+ return 'column';
949
+ }
950
+ if (nearBottomEdge) {
951
+ return 'row';
952
+ }
953
+ return null;
954
+ }
955
+ resizeActiveTable(event) {
956
+ if (!this.tableResizeState) {
957
+ return;
958
+ }
959
+ const state = this.tableResizeState;
960
+ if (state.direction === 'column' || state.direction === 'both') {
961
+ const width = Math.max(44, Math.round(state.startWidth + event.clientX - state.startX));
962
+ this.setTableColumnWidth(state.table, state.columnIndex, width);
963
+ }
964
+ if (state.direction === 'row' || state.direction === 'both') {
965
+ const height = Math.max(28, Math.round(state.startHeight + event.clientY - state.startY));
966
+ this.setTableRowHeight(state.table, state.rowIndex, height);
967
+ }
968
+ }
969
+ setTableColumnWidth(table, columnIndex, width) {
970
+ Array.from(table.rows).forEach(row => {
971
+ const cell = row.cells[columnIndex];
972
+ if (!cell) {
973
+ return;
974
+ }
975
+ cell.style.width = `${width}px`;
976
+ cell.style.minWidth = `${width}px`;
977
+ });
978
+ }
979
+ setTableRowHeight(table, rowIndex, height) {
980
+ const row = table.rows[rowIndex];
981
+ if (!row) {
982
+ return;
983
+ }
984
+ row.style.height = `${height}px`;
985
+ Array.from(row.cells).forEach(cell => {
986
+ cell.style.height = `${height}px`;
987
+ cell.style.minHeight = `${height}px`;
988
+ });
989
+ }
990
+ syncRichEditorValue(tag, editor, emitEvent = true) {
991
+ const control = this.form.get(tag);
992
+ if (control) {
993
+ control.setValue(editor.innerHTML, { emitEvent });
994
+ }
995
+ }
996
+ applyBodyToolbarStyles(editor) {
997
+ const styles = this.getBodyToolbarStyles();
998
+ const targets = Array.from(editor.querySelectorAll('p, h1, h2, h3, li, td, th'));
999
+ targets.forEach(target => {
1000
+ if (styles['font-size']) {
1001
+ target.style.fontSize = styles['font-size'];
1002
+ }
1003
+ if (styles['font-style']) {
1004
+ target.style.fontStyle = styles['font-style'];
1005
+ }
1006
+ if (styles['text-decoration']) {
1007
+ target.style.textDecoration = styles['text-decoration'];
1008
+ }
1009
+ if (styles['font-weight'] && (this.fontBold || target.tagName.toLowerCase() !== 'th')) {
1010
+ target.style.fontWeight = styles['font-weight'];
1011
+ }
1012
+ });
1013
+ }
1014
+ clearBodyToolbarStyles(editor) {
1015
+ const targets = Array.from(editor.querySelectorAll('p, h1, h2, h3, li, td, th'));
1016
+ targets.forEach(target => {
1017
+ target.style.removeProperty('font-size');
1018
+ target.style.removeProperty('font-weight');
1019
+ target.style.removeProperty('font-style');
1020
+ target.style.removeProperty('text-decoration');
1021
+ });
1022
+ }
1023
+ resetBodyToolbarState() {
1024
+ this.fontSize = 12;
1025
+ this.fontBold = false;
1026
+ this.fontItalic = false;
1027
+ this.fontUnderline = false;
1028
+ this.bodyToolbarTouched = {
1029
+ fontSize: false,
1030
+ fontBold: false,
1031
+ fontItalic: false,
1032
+ fontUnderline: false
1033
+ };
1034
+ }
1035
+ createTableRow(cells) {
1036
+ return `| ${cells.join(' | ')} |`;
1037
+ }
1038
+ createTableCells(count, valueFactory) {
1039
+ const cells = [];
1040
+ for (let index = 0; index < count; index++) {
1041
+ cells.push(valueFactory(index));
1042
+ }
1043
+ return cells;
1044
+ }
1045
+ clamp(value, min, max) {
1046
+ const parsedValue = Number(value);
1047
+ if (isNaN(parsedValue) || !isFinite(parsedValue)) {
1048
+ return min;
1049
+ }
1050
+ return Math.min(Math.max(Math.floor(parsedValue), min), max);
1051
+ }
1052
+ normalizeTagName(tag) {
1053
+ return (tag || '')
1054
+ .replace(/[{}[\]]/g, '')
1055
+ .trim();
1056
+ }
1057
+ buildTagPayload() {
1058
+ const payload = {};
1059
+ Object.keys(this.form.value || {}).forEach(key => {
1060
+ if (this.isMultilineField(key)) {
1061
+ const editor = this.getRichEditorElement(key);
1062
+ if (editor) {
1063
+ this.ensureEditorBlockStructure(editor);
1064
+ if (this.isBodyTag(key)) {
1065
+ this.applyBodyToolbarStyles(editor);
1066
+ }
1067
+ this.normalizeEditorTablesForExport(editor);
1068
+ this.syncRichEditorValue(key, editor, false);
1069
+ }
1070
+ }
1071
+ payload[key] = this.form.value[key] == null ? '' : String(this.form.value[key]);
1072
+ if (this.supportsHindiNumberFormat(key)) {
1073
+ payload[`__numberFormat:${key}`] = this.numberFormatControls[key] ? 'hindi' : 'english';
1074
+ }
1075
+ });
1076
+ payload['SignatureImage'] = this.signatureFile ? '[Signature Image]' : '';
1077
+ payload['Signature'] = this.signatureFile ? '[Signature Image]' : '';
1078
+ payload['UserSignatureImage'] = this.signatureFile ? '[Signature Image]' : '';
1079
+ return payload;
1080
+ }
1081
+ createRenderRequest(outputFileName) {
1082
+ return {
1083
+ templateName: this.selectedTemplateName,
1084
+ uploadedTemplateFile: this.uploadedTemplateFile,
1085
+ tags: this.buildTagPayload(),
1086
+ outputFileName,
1087
+ signatureFile: this.signatureFile
1088
+ };
1089
+ }
1090
+ normalizeEditorTablesForExport(editor) {
1091
+ const tables = Array.from(editor.querySelectorAll('table'));
1092
+ tables.forEach(table => {
1093
+ const wrapper = table.closest('.template-editor-table-wrapper');
1094
+ const alignment = this.getTableExportAlignment(table, wrapper);
1095
+ if (!alignment) {
1096
+ return;
1097
+ }
1098
+ table.setAttribute('data-table-align', alignment);
1099
+ table.setAttribute('align', alignment);
1100
+ table.setAttribute('dir', 'ltr');
1101
+ table.style.textAlign = alignment;
1102
+ table.style.display = 'inline-table';
1103
+ table.style.direction = 'ltr';
1104
+ table.style.float = 'none';
1105
+ if (wrapper) {
1106
+ wrapper.setAttribute('data-table-align', alignment);
1107
+ wrapper.style.width = '100%';
1108
+ wrapper.style.textAlign = alignment;
1109
+ }
1110
+ });
1111
+ }
1112
+ getTableExportAlignment(table, wrapper) {
1113
+ const wrapperTextAlign = (wrapper?.style.textAlign || '').toLowerCase();
1114
+ if (wrapperTextAlign === 'left' || wrapperTextAlign === 'center' || wrapperTextAlign === 'right') {
1115
+ return wrapperTextAlign;
1116
+ }
1117
+ const wrapperDataAlignment = (wrapper?.getAttribute('data-table-align') || '').toLowerCase();
1118
+ if (wrapperDataAlignment === 'left' || wrapperDataAlignment === 'center' || wrapperDataAlignment === 'right') {
1119
+ return wrapperDataAlignment;
1120
+ }
1121
+ const tableTextAlign = (table.style.textAlign || '').toLowerCase();
1122
+ if (tableTextAlign === 'left' || tableTextAlign === 'center' || tableTextAlign === 'right') {
1123
+ return tableTextAlign;
1124
+ }
1125
+ const tableDataAlignment = (table.getAttribute('data-table-align') || '').toLowerCase();
1126
+ if (tableDataAlignment === 'left' || tableDataAlignment === 'center' || tableDataAlignment === 'right') {
1127
+ return tableDataAlignment;
1128
+ }
1129
+ return null;
1130
+ }
1131
+ showPdfPreview(blob) {
1132
+ this.clearPdfPreview();
1133
+ const pdfBlob = new Blob([blob], { type: 'application/pdf' });
1134
+ this.pdfPreviewObjectUrl = window.URL.createObjectURL(pdfBlob);
1135
+ this.pdfPreviewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(`${this.pdfPreviewObjectUrl}#zoom=page-fit`);
1136
+ this.isPdfPreviewStale = false;
1137
+ this.hasPendingPreviewChanges = false;
1138
+ }
1139
+ scheduleWordHtmlPreview(delay = 650, refreshPdf = false) {
1140
+ if (!this.hasActiveTemplate || this.isLoadingTags || this.isAnalyzingUpload) {
1141
+ return;
1142
+ }
1143
+ if (this.pdfPreviewUrl) {
1144
+ this.isPdfPreviewStale = true;
1145
+ if (refreshPdf) {
1146
+ this.clearWordPreviewTimer();
1147
+ this.wordPreviewTimer = setTimeout(() => this.refreshPdfPreviewAfterFieldChange(delay), delay);
1148
+ }
1149
+ return;
1150
+ }
1151
+ this.clearWordPreviewTimer();
1152
+ this.wordPreviewTimer = setTimeout(() => this.renderWordHtmlPreview(), delay);
1153
+ }
1154
+ refreshPdfPreviewAfterFieldChange(delay) {
1155
+ this.wordPreviewTimer = null;
1156
+ if (!this.hasActiveTemplate || !this.pdfPreviewUrl) {
1157
+ return;
1158
+ }
1159
+ if (this.isPreviewingPdf) {
1160
+ this.wordPreviewTimer = setTimeout(() => this.refreshPdfPreviewAfterFieldChange(delay), delay);
1161
+ return;
1162
+ }
1163
+ this.previewPdf();
1164
+ }
1165
+ renderWordHtmlPreview() {
1166
+ if (!this.hasActiveTemplate) {
1167
+ this.clearWordPreview();
1168
+ return;
1169
+ }
1170
+ const requestId = ++this.wordPreviewRequestId;
1171
+ const outputFileName = this.activeTemplateName.replace(/\.docx$/i, '_preview.docx') || 'template_preview.docx';
1172
+ this.isPreviewingWordHtml = true;
1173
+ this.wordPreviewMessage = '';
1174
+ this.wordPreviewRequested.emit({
1175
+ ...this.createRenderRequest(outputFileName),
1176
+ requestId
1177
+ });
1178
+ }
1179
+ async convertWordBlobToHtml(blob, requestId) {
1180
+ try {
1181
+ const arrayBuffer = await blob.arrayBuffer();
1182
+ const result = await mammoth.convertToHtml({ arrayBuffer }, {
1183
+ convertImage: mammoth.images.dataUri,
1184
+ styleMap: [
1185
+ "p[style-name='Title'] => h1:fresh",
1186
+ "p[style-name='Heading 1'] => h1:fresh",
1187
+ "p[style-name='Heading 2'] => h2:fresh",
1188
+ "p[style-name='Heading 3'] => h3:fresh"
1189
+ ]
1190
+ });
1191
+ if (requestId !== this.wordPreviewRequestId) {
1192
+ return;
1193
+ }
1194
+ this.wordHtmlPreview = this.sanitizer.bypassSecurityTrustHtml(this.decorateWordPreviewHtml(result.value));
1195
+ this.wordPreviewMessage = result.messages && result.messages.length > 0
1196
+ ? 'Quick Word preview updated. Use PDF Preview for exact template layout.'
1197
+ : '';
1198
+ this.hasPendingPreviewChanges = false;
1199
+ }
1200
+ catch {
1201
+ if (requestId === this.wordPreviewRequestId) {
1202
+ this.wordHtmlPreview = null;
1203
+ this.wordPreviewMessage = 'Unable to render Word preview.';
1204
+ }
1205
+ }
1206
+ finally {
1207
+ if (requestId === this.wordPreviewRequestId) {
1208
+ this.isPreviewingWordHtml = false;
1209
+ }
1210
+ }
1211
+ }
1212
+ decorateWordPreviewHtml(html) {
1213
+ return `<div class="word-html-page" dir="rtl">${html || ''}</div>`;
1214
+ }
1215
+ clearWordPreviewTimer() {
1216
+ if (this.wordPreviewTimer) {
1217
+ clearTimeout(this.wordPreviewTimer);
1218
+ this.wordPreviewTimer = null;
1219
+ }
1220
+ }
1221
+ clearWordPreview() {
1222
+ this.wordHtmlPreview = null;
1223
+ this.wordPreviewMessage = '';
1224
+ }
1225
+ clearPdfPreview() {
1226
+ if (this.pdfPreviewObjectUrl) {
1227
+ window.URL.revokeObjectURL(this.pdfPreviewObjectUrl);
1228
+ }
1229
+ this.pdfPreviewObjectUrl = '';
1230
+ this.pdfPreviewUrl = null;
1231
+ this.isPdfPreviewStale = false;
1232
+ }
1233
+ openBlob(blob, fileName, contentType) {
1234
+ const documentBlob = new Blob([blob], { type: contentType });
1235
+ const url = window.URL.createObjectURL(documentBlob);
1236
+ const opened = window.open(url, '_blank');
1237
+ if (!opened) {
1238
+ const link = document.createElement('a');
1239
+ link.href = url;
1240
+ link.download = fileName;
1241
+ document.body.appendChild(link);
1242
+ link.click();
1243
+ document.body.removeChild(link);
1244
+ }
1245
+ setTimeout(() => window.URL.revokeObjectURL(url), 60000);
1246
+ }
1247
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: WordTemplateLettersComponent, deps: [{ token: i1.FormBuilder }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
1248
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.4", type: WordTemplateLettersComponent, isStandalone: false, selector: "masar-word-template-letters", inputs: { templates: "templates" }, outputs: { refreshTemplatesRequested: "refreshTemplatesRequested", templateTagsRequested: "templateTagsRequested", uploadedTemplateAnalyzeRequested: "uploadedTemplateAnalyzeRequested", pdfPreviewRequested: "pdfPreviewRequested", wordPreviewRequested: "wordPreviewRequested", generateLetterRequested: "generateLetterRequested" }, host: { listeners: { "document:mousemove": "onDocumentMouseMove($event)", "document:mouseup": "onDocumentMouseUp()" } }, ngImport: i0, template: "<div class=\"word-template-page\">\r\n <div class=\"page-toolbar\">\r\n <div>\r\n <h1>Word Template Letters</h1>\r\n <p>Generate letters from server templates using Masar data tags.</p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"alert alert-danger\" *ngIf=\"errorMessage\">\r\n {{ errorMessage }}\r\n </div>\r\n\r\n <section class=\"template-panel selector-panel\">\r\n <div class=\"template-select-field\">\r\n <label for=\"templateSelect\">Template</label>\r\n <div class=\"template-select-control\">\r\n <select id=\"templateSelect\" class=\"form-control\" [(ngModel)]=\"selectedTemplateName\" (change)=\"onTemplateChange()\">\r\n <option value=\"\">Select template</option>\r\n <option *ngFor=\"let template of templates\" [value]=\"template.name\">\r\n {{ template.displayName }} - {{ template.name }}\r\n </option>\r\n </select>\r\n <button type=\"button\" class=\"btn btn-outline-secondary icon-action-btn refresh-action-btn\" title=\"Refresh templates\" aria-label=\"Refresh templates\" (click)=\"loadTemplates()\" [disabled]=\"isLoadingTemplates\">\r\n <i class=\"fa fa-sync-alt\" [class.fa-spin]=\"isLoadingTemplates\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"template-upload-field\">\r\n <label for=\"templateUpload\">Upload Word Template</label>\r\n <input id=\"templateUpload\" class=\"form-control\" type=\"file\" accept=\".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document\" (change)=\"onTemplateFileSelected($event)\" />\r\n <span class=\"font-source\" *ngIf=\"uploadedTemplateName\">{{ uploadedTemplateName }}</span>\r\n </div>\r\n\r\n <div class=\"template-summary\" *ngIf=\"hasActiveTemplate\">\r\n <div>\r\n <span class=\"summary-label\">Active Template</span>\r\n <strong>{{ uploadedTemplateName ? 'Uploaded' : 'Server' }}</strong>\r\n </div>\r\n <div>\r\n <span class=\"summary-label\">Data Tags</span>\r\n <strong>{{ tags.length }}</strong>\r\n </div>\r\n <div>\r\n <span class=\"summary-label\">Layout Tags</span>\r\n <strong>{{ layoutTags.length }}</strong>\r\n </div>\r\n <div>\r\n <span class=\"summary-label\">Signature</span>\r\n <strong>{{ signatureFile ? 'Selected' : 'Optional' }}</strong>\r\n </div>\r\n </div>\r\n\r\n <div class=\"template-empty\" *ngIf=\"!isLoadingTemplates && templates.length === 0\">\r\n No .docx templates were found on the server.\r\n </div>\r\n </section>\r\n\r\n <section class=\"template-panel editor-panel\" *ngIf=\"hasActiveTemplate\">\r\n <div class=\"workbench-layout\" *ngIf=\"!isLoadingTags && !isAnalyzingUpload\">\r\n <div class=\"data-entry-panel\">\r\n <form [formGroup]=\"form\" class=\"tags-form\" *ngIf=\"tags.length > 0\">\r\n <div class=\"tag-field\" [class.multiline-field]=\"isMultilineField(tag)\" *ngFor=\"let tag of tags; trackBy: trackByTag\">\r\n <label *ngIf=\"!isMultilineField(tag)\" [for]=\"'tag-' + tag\">\r\n {{ getSuggestedTag(tag)?.label || tag }}\r\n <span>{{ tag }}</span>\r\n </label>\r\n <div class=\"editor-toolbar word-ribbon\" *ngIf=\"isBodyTag(tag)\">\r\n <div class=\"ribbon-group font-ribbon-group\">\r\n <div class=\"ribbon-controls\">\r\n <div class=\"ribbon-row\">\r\n <input class=\"form-control form-control-sm ribbon-size-input\" title=\"Font size\" aria-label=\"Font size\" type=\"number\" min=\"8\" max=\"36\" [(ngModel)]=\"fontSize\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"onBodyToolbarStyleChange(tag, 'fontSize')\" />\r\n <label class=\"ribbon-toggle\" [class.active]=\"fontBold\" title=\"Bold\" aria-label=\"Bold\">\r\n <input type=\"checkbox\" [(ngModel)]=\"fontBold\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"onBodyToolbarStyleChange(tag, 'fontBold')\" />\r\n <i class=\"fas fa-bold\" aria-hidden=\"true\"></i>\r\n </label>\r\n <label class=\"ribbon-toggle\" [class.active]=\"fontItalic\" title=\"Italic\" aria-label=\"Italic\">\r\n <input type=\"checkbox\" [(ngModel)]=\"fontItalic\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"onBodyToolbarStyleChange(tag, 'fontItalic')\" />\r\n <i class=\"fas fa-italic\" aria-hidden=\"true\"></i>\r\n </label>\r\n <label class=\"ribbon-toggle\" [class.active]=\"fontUnderline\" title=\"Underline\" aria-label=\"Underline\">\r\n <input type=\"checkbox\" [(ngModel)]=\"fontUnderline\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"onBodyToolbarStyleChange(tag, 'fontUnderline')\" />\r\n <i class=\"fas fa-underline\" aria-hidden=\"true\"></i>\r\n </label>\r\n <label class=\"ribbon-toggle ribbon-text-toggle\" [class.active]=\"numberFormatControls[tag]\" title=\"Use Hindi numbers\" aria-label=\"Use Hindi numbers\">\r\n <input type=\"checkbox\" [(ngModel)]=\"numberFormatControls[tag]\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"markPreviewDirty()\" />\r\n \u0661\u0662\u0663\r\n </label>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Reset body style changes\" aria-label=\"Reset body style changes\" (click)=\"resetBodyToolbarStyles(tag)\">\r\n <i class=\"fas fa-undo\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"ribbon-group paragraph-ribbon-group\">\r\n <div class=\"ribbon-controls\">\r\n <div class=\"ribbon-row\">\r\n <select class=\"form-control form-control-sm line-thickness-select\" title=\"Line thickness\" aria-label=\"Line thickness\" [(ngModel)]=\"lineThickness\" [ngModelOptions]=\"{standalone: true}\">\r\n <option [ngValue]=\"1\">1px</option>\r\n <option [ngValue]=\"2\">2px</option>\r\n <option [ngValue]=\"3\">3px</option>\r\n <option [ngValue]=\"4\">4px</option>\r\n <option [ngValue]=\"6\">6px</option>\r\n </select>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Line\" aria-label=\"Insert line\" (click)=\"insertSeparator(tag)\">\r\n <i class=\"fas fa-minus\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Align left\" aria-label=\"Align text left\" (click)=\"applyEditorAlignment(tag, 'left')\">\r\n <i class=\"fas fa-align-left\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Align center\" aria-label=\"Align text center\" (click)=\"applyEditorAlignment(tag, 'center')\">\r\n <i class=\"fas fa-align-center\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Align right\" aria-label=\"Align text right\" (click)=\"applyEditorAlignment(tag, 'right')\">\r\n <i class=\"fas fa-align-right\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Justify\" aria-label=\"Justify text\" (click)=\"applyEditorAlignment(tag, 'justify')\">\r\n <i class=\"fas fa-align-justify\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"ribbon-group table-builder table-ribbon-group\">\r\n <div class=\"ribbon-controls\">\r\n <div class=\"ribbon-row\">\r\n <div class=\"table-picker\">\r\n <button type=\"button\" class=\"ribbon-primary-btn table-picker-trigger\" title=\"Insert table\" aria-label=\"Insert table\" (click)=\"toggleTablePicker(tag)\">\r\n <i class=\"fas fa-table\" aria-hidden=\"true\"></i>\r\n <i class=\"fas fa-caret-down table-picker-caret\" aria-hidden=\"true\"></i>\r\n </button>\r\n <div class=\"table-picker-panel\" *ngIf=\"tablePickerOpenTag === tag\">\r\n <div class=\"table-picker-size\">{{ tablePickerHoverRows }} \u00D7 {{ tablePickerHoverColumns }}</div>\r\n <div class=\"table-picker-grid\" role=\"grid\" aria-label=\"Select table size\">\r\n <div class=\"table-picker-row\" *ngFor=\"let row of tablePickerRows\">\r\n <button\r\n type=\"button\"\r\n class=\"table-picker-cell\"\r\n *ngFor=\"let column of tablePickerColumns\"\r\n [class.active]=\"isTablePickerCellActive(row, column)\"\r\n [attr.aria-label]=\"'Insert ' + row + ' by ' + column + ' table'\"\r\n (mouseenter)=\"previewTableSize(row, column)\"\r\n (focus)=\"previewTableSize(row, column)\"\r\n (click)=\"insertTableFromPicker(tag, row, column)\">\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <label class=\"ribbon-toggle ribbon-text-toggle\" [class.active]=\"tableHasHeader\" title=\"Header row\" aria-label=\"Header row\">\r\n <input type=\"checkbox\" [(ngModel)]=\"tableHasHeader\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"applyCurrentTableHeader(tag)\" />\r\n Header\r\n </label>\r\n <label class=\"header-color-picker\" title=\"Header color\" aria-label=\"Header color\">\r\n <input type=\"color\" [(ngModel)]=\"tableHeaderColor\" [ngModelOptions]=\"{standalone: true}\" (change)=\"applyCurrentTableHeaderColor(tag)\" />\r\n </label>\r\n <button type=\"button\" class=\"ribbon-icon-btn table-align-btn\" title=\"Align table left\" aria-label=\"Align table left\" (click)=\"alignCurrentTable(tag, 'left')\">\r\n <i class=\"fas fa-table\" aria-hidden=\"true\"></i><i class=\"fas fa-align-left\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn table-align-btn\" title=\"Align table center\" aria-label=\"Align table center\" (click)=\"alignCurrentTable(tag, 'center')\">\r\n <i class=\"fas fa-table\" aria-hidden=\"true\"></i><i class=\"fas fa-align-center\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn table-align-btn\" title=\"Align table right\" aria-label=\"Align table right\" (click)=\"alignCurrentTable(tag, 'right')\">\r\n <i class=\"fas fa-table\" aria-hidden=\"true\"></i><i class=\"fas fa-align-right\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-danger-btn\" title=\"Delete table\" aria-label=\"Delete table\" (click)=\"deleteCurrentTable(tag)\">\r\n <i class=\"fas fa-trash-alt\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div\r\n *ngIf=\"isMultilineField(tag)\"\r\n [id]=\"'tag-' + tag\"\r\n class=\"form-control rich-body-editor\"\r\n contenteditable=\"true\"\r\n dir=\"rtl\"\r\n [ngStyle]=\"getEditorStyles(tag)\"\r\n [class.table-resize-column]=\"tableResizeCursor === 'column'\"\r\n [class.table-resize-row]=\"tableResizeCursor === 'row'\"\r\n [class.table-resize-both]=\"tableResizeCursor === 'both'\"\r\n (mousemove)=\"onTableResizeMouseMove(tag, $event)\"\r\n (mousedown)=\"onTableResizeMouseDown(tag, $event)\"\r\n (mouseleave)=\"clearTableResizeCursor()\"\r\n (paste)=\"onRichTextPaste(tag, $event)\"\r\n (input)=\"onRichTextInput(tag, $event)\"\r\n (focusout)=\"onRichTextFocusOut($event)\">\r\n </div>\r\n <input *ngIf=\"!isMultilineField(tag)\" [id]=\"'tag-' + tag\" class=\"form-control\" type=\"text\" [formControlName]=\"tag\" (input)=\"markPreviewDirty()\" (blur)=\"onFieldPreviewCommit($event)\" />\r\n <label class=\"number-format\" *ngIf=\"isNumberField(tag) && !isMultilineField(tag)\">\r\n <input type=\"checkbox\" [(ngModel)]=\"numberFormatControls[tag]\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"markPreviewDirty()\" (blur)=\"onFieldPreviewCommit($event)\" />\r\n Use Hindi numbers\r\n </label>\r\n </div>\r\n </form>\r\n\r\n <div class=\"tags-empty-state\" *ngIf=\"tags.length === 0\">\r\n <div>\r\n <strong>No editable tags found</strong>\r\n <span>The selected template does not contain supported tags.</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"settings-layout media-settings\">\r\n <div class=\"settings-group\">\r\n <div class=\"settings-title\">Media</div>\r\n <div class=\"format-field signature-field\">\r\n <label for=\"signatureImage\">Signature Image</label>\r\n <input id=\"signatureImage\" class=\"form-control\" type=\"file\" accept=\"image/png,image/jpeg,image/gif\" (change)=\"onSignatureSelected($event)\" />\r\n <span class=\"file-name\" *ngIf=\"signatureFile\">{{ signatureFile.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"pdf-preview-panel\">\r\n <div class=\"preview-panel-header\">\r\n <div>\r\n <h3>{{ pdfPreviewUrl ? 'PDF Preview' : 'Word Preview' }}</h3>\r\n <span class=\"preview-status\" *ngIf=\"pdfPreviewUrl && isPdfPreviewStale\">Preview needs refresh. Click Preview PDF for the exact updated layout.</span>\r\n <span class=\"preview-status\" *ngIf=\"isPreviewingWordHtml && !pdfPreviewUrl\">Updating Word preview...</span>\r\n <span class=\"preview-status\" *ngIf=\"wordPreviewMessage && !pdfPreviewUrl\">{{ wordPreviewMessage }}</span>\r\n </div>\r\n <div class=\"preview-actions\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary preview-pdf-btn\" (click)=\"previewPdf()\" [disabled]=\"isLoadingTags || isAnalyzingUpload || isPreviewingPdf\">\r\n <i class=\"fa fa-file-pdf\" aria-hidden=\"true\"></i>\r\n {{ isPreviewingPdf ? 'Rendering PDF...' : 'Preview PDF' }}\r\n </button>\r\n <button type=\"button\" class=\"btn btn-primary word-action-btn\" title=\"Generate Word document\" aria-label=\"Generate Word document\" (click)=\"generateLetter()\" [disabled]=\"!hasActiveTemplate || isLoadingTags || isAnalyzingUpload || isGenerating\">\r\n <i class=\"fa fa-file-word\" [class.fa-pulse]=\"isGenerating\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <iframe *ngIf=\"pdfPreviewUrl\" class=\"pdf-frame\" [src]=\"pdfPreviewUrl\" title=\"Letter PDF Preview\"></iframe>\r\n\r\n <div class=\"word-html-preview\" *ngIf=\"!pdfPreviewUrl && wordHtmlPreview\" [innerHTML]=\"wordHtmlPreview\"></div>\r\n\r\n <div class=\"pdf-empty\" *ngIf=\"!pdfPreviewUrl && !wordHtmlPreview\">\r\n {{ isPreviewingWordHtml ? 'Preparing Word preview...' : 'Fill the form to see a quick Word preview.' }}\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"template-empty\" *ngIf=\"isLoadingTags || isAnalyzingUpload\">\r\n Reading template tags...\r\n </div>\r\n </section>\r\n</div>\r\n", styles: [".word-template-page{background:#f5f7f9;min-height:calc(100vh - 64px);padding:24px}.page-toolbar,.panel-header,.toolbar-actions,.panel-actions{align-items:center;display:flex;gap:12px}.page-toolbar,.panel-header{justify-content:space-between}.page-toolbar h1,.panel-header h2{color:#212529;font-size:22px;font-weight:600;line-height:1.25;margin:0}.page-toolbar p,.panel-header p{color:#6c757d;font-size:13px;line-height:1.45;margin:6px 0 0}.toolbar-actions,.panel-actions{flex-wrap:wrap;justify-content:flex-end}.template-panel{background:#fff;border:1px solid #dee2e6;border-radius:8px;box-shadow:0 1px 2px #00000008;margin-top:18px;padding:18px}.editor-panel{display:flex;flex-direction:column;min-height:calc(100vh - 220px)}.selector-panel{align-items:end;display:grid;gap:16px;grid-template-columns:minmax(280px,1fr) minmax(260px,.8fr)}.template-select-control{align-items:center;display:flex;gap:8px}.template-select-control select{min-width:0}.icon-action-btn{align-items:center;display:inline-flex;flex:0 0 auto;height:38px;justify-content:center;min-width:42px;padding-left:10px;padding-right:10px}.refresh-action-btn,.refresh-action-btn:hover,.refresh-action-btn:focus{background:#fff;color:#495057}.refresh-action-btn i{color:#495057!important;font-size:15px}.refresh-action-btn:disabled i{color:#868e96!important}.template-select-field label,.template-upload-field label,.format-field label,.tag-field label{color:#343a40;display:block;font-size:13px;font-weight:600;margin-bottom:6px}.template-summary{border:1px solid #e9ecef;border-radius:8px;display:grid;gap:0;grid-column:1/-1;grid-template-columns:repeat(4,minmax(0,1fr));overflow:hidden}.template-summary>div{background:#f8f9fa;border-right:1px solid #e9ecef;min-width:0;padding:10px 12px}.template-summary>div:last-child{border-right:0}.summary-label,.file-name,.font-source,.tag-field label span{color:#6c757d;display:block;font-size:12px;font-weight:400;line-height:1.35;margin-top:2px;overflow-wrap:anywhere}.template-summary strong{color:#212529;display:block;font-size:14px;margin-top:2px;overflow-wrap:anywhere}.template-empty{color:#6c757d;font-size:13px;padding:18px 0 4px}.workbench-layout{align-items:start;display:grid;flex:1;gap:18px;grid-template-columns:minmax(0,1fr) minmax(0,1fr);min-height:calc(100vh - 270px);width:100%}.pdf-preview-panel,.data-entry-panel{border:1px solid #dee2e6;border-radius:8px;height:100%;min-width:0;padding:14px;width:100%}.pdf-preview-panel{background:#f8f9fa;display:flex;flex-direction:column;min-height:calc(100vh - 300px)}.data-entry-panel{background:#fff;display:flex;flex-direction:column;min-height:calc(100vh - 300px)}.preview-panel-header{align-items:center;display:flex;gap:12px;justify-content:space-between;margin-bottom:12px}.preview-actions{align-items:center;display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.preview-actions .btn{align-items:center;display:inline-flex;gap:7px}.preview-pdf-btn{height:38px;padding-left:12px;padding-right:12px}.preview-pdf-btn i{color:#6c757d;font-size:16px}.word-action-btn{align-items:center;display:inline-flex;flex:0 0 44px;height:38px;justify-content:center;min-width:44px;padding:0}.word-action-btn i{color:#fff;font-size:18px;line-height:1}.preview-panel-header h3{color:#212529;font-size:16px;font-weight:600;line-height:1.3;margin:0}.preview-status{color:#6c757d;display:block;font-size:12px;line-height:1.35;margin-top:3px}.pdf-frame{background:#fff;border:1px solid #ced4da;border-radius:6px;display:block;flex:1;min-height:calc(100vh - 380px);width:100%}.word-html-preview{background:#2b2b2b;border:1px solid #ced4da;border-radius:6px;flex:1;min-height:calc(100vh - 380px);overflow:auto;padding:24px}:host ::ng-deep .word-html-preview .word-html-page{background:#fff;box-shadow:0 2px 10px #00000029;color:#111;direction:rtl;font-family:Arial,Tahoma,sans-serif;font-size:12pt;line-height:1.6;margin:0 auto;min-height:920px;padding:70px 72px;text-align:right;width:min(100%,794px)}:host ::ng-deep .word-html-preview .word-html-page p{margin:0 0 10px}:host ::ng-deep .word-html-preview .word-html-page h1,:host ::ng-deep .word-html-preview .word-html-page h2,:host ::ng-deep .word-html-preview .word-html-page h3{color:#111;font-weight:700;line-height:1.35;margin:0 0 14px}:host ::ng-deep .word-html-preview .word-html-page table{border-collapse:collapse;direction:ltr;margin:10px 0 14px;max-width:100%}:host ::ng-deep .word-html-preview .word-html-page th,:host ::ng-deep .word-html-preview .word-html-page td{border:1px solid #495057;direction:rtl;padding:6px 8px;vertical-align:top}:host ::ng-deep .word-html-preview .word-html-page img{height:auto;max-width:100%}.pdf-empty{align-items:center;background:#fff;border:1px dashed #ced4da;border-radius:6px;color:#6c757d;display:flex;flex:1;font-size:13px;justify-content:center;min-height:calc(100vh - 380px);padding:18px;text-align:center}.settings-layout{display:block;margin-top:16px}.media-settings{border-top:1px solid #dee2e6;padding-top:14px}.settings-group{min-width:0}.settings-title{color:#495057;font-size:13px;font-weight:600;margin-bottom:10px}.format-check,.number-format{align-items:center;color:#495057;display:flex;font-size:13px;gap:7px;margin:0}.signature-field input{height:auto}.tags-form{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin-top:18px;overflow:auto;padding-right:4px}.tags-empty-state{align-items:center;background:#fff;border:1px dashed #ced4da;border-radius:6px;color:#6c757d;display:flex;flex:1;justify-content:center;margin-top:18px;min-height:clamp(360px,46vh,680px);padding:18px;text-align:center}.tags-empty-state strong{color:#343a40;display:block;font-size:15px;margin-bottom:6px}.tags-empty-state span{display:block;font-size:13px}.tag-field{min-width:0}.tag-field.multiline-field{grid-column:1/-1}.tag-field.multiline-field>label{display:none!important}.editor-toolbar{align-items:stretch;background:linear-gradient(#fbfbfb,#f1f3f5);border:1px solid #cfd6de;border-bottom:0;border-radius:4px 4px 0 0;display:flex;flex-wrap:nowrap;gap:0;padding:0}.editor-toolbar button:has(.fa-file-pdf),.editor-toolbar .preview-pdf-btn{display:none!important}.word-ribbon{min-height:46px;overflow:visible}.ribbon-group{align-items:center;border-right:1px solid #cfd6de;display:flex;flex-direction:row;justify-content:center;margin:0;min-width:0;padding:6px 8px;position:relative}.ribbon-group:last-child{border-right:0}.font-ribbon-group,.paragraph-ribbon-group{flex:0 0 auto}.table-ribbon-group{flex:0 0 auto;padding-right:10px}.ribbon-controls{align-items:center;display:flex;flex-wrap:nowrap;gap:5px;min-height:28px}.ribbon-row{align-items:center;display:flex;flex-wrap:nowrap;gap:4px;min-height:28px}.font-ribbon-group .ribbon-controls,.font-ribbon-group .ribbon-row{flex-wrap:nowrap}.font-ribbon-group .ribbon-toggle,.font-ribbon-group .ribbon-icon-btn{flex:0 0 auto}.ribbon-size-input{flex:0 0 54px;height:26px;padding:2px 4px;text-align:center}.line-thickness-select{flex:0 0 64px;height:26px;min-width:64px;padding:2px 4px}.ribbon-icon-btn,.ribbon-primary-btn,.ribbon-danger-btn,.ribbon-command-btn,.ribbon-toggle{align-items:center;background:transparent;border:1px solid transparent;border-radius:2px;color:#343a40;cursor:pointer;display:inline-flex;font-size:12px;gap:3px;height:26px;justify-content:center;line-height:1;min-width:26px;padding:3px 6px;-webkit-user-select:none;user-select:none}.ribbon-icon-btn:hover,.ribbon-command-btn:hover,.ribbon-toggle:hover{background:#e9f2ff;border-color:#9dc5f4}.ribbon-primary-btn{background:#eef5ff;border-color:#b9d5ff;color:#0d47a1}.ribbon-primary-btn:hover{background:#dcecff}.ribbon-danger-btn{color:#b42318}.ribbon-danger-btn:hover{background:#fff1f0;border-color:#ffb4ab}.ribbon-toggle input{display:none}.ribbon-toggle.active{background:#dbeafe;border-color:#7db3f2;color:#0b5cad}.ribbon-text-toggle{font-weight:600;min-width:auto;white-space:nowrap}.ribbon-corner-icon{font-size:9px;margin-left:-1px;margin-top:-8px}.ribbon-command-btn{min-width:52px}.table-align-btn{min-width:42px}.table-align-btn .fa-table{font-size:11px}.header-color-picker{align-items:center;border:1px solid transparent;border-radius:2px;cursor:pointer;display:inline-flex;height:26px;justify-content:center;margin:0;padding:2px;width:30px}.header-color-picker:hover{background:#e9f2ff;border-color:#9dc5f4}.header-color-picker input{border:0;cursor:pointer;height:20px;padding:0;width:24px}.ribbon-field{align-items:center;color:#495057;display:inline-flex;font-size:11px;gap:4px;margin:0;white-space:nowrap}.ribbon-field input{height:26px;padding:2px 4px;text-align:center;width:54px}.table-picker{display:inline-flex;position:relative}.table-picker-trigger{min-width:34px;padding-left:7px;padding-right:7px}.table-picker-caret{font-size:9px;margin-left:1px}.table-picker-panel{background:#fff;border:1px solid #adb5bd;border-radius:3px;box-shadow:0 6px 16px #2125292e;left:0;padding:7px;position:absolute;top:30px;width:148px;z-index:25}.table-picker-size{border-bottom:1px solid #e9ecef;color:#212529;font-size:12px;font-weight:600;line-height:1;margin:0 -7px 6px;padding:0 7px 6px;text-align:center}.table-picker-grid{display:flex;flex-direction:column;gap:2px}.table-picker-row{display:flex;gap:2px}.table-picker-cell{background:#fff;border:1px solid #d0d7de;cursor:pointer;height:13px;min-width:0;padding:0;width:13px}.table-picker-cell:hover,.table-picker-cell:focus,.table-picker-cell.active{background:#dcecff;border-color:#4d94ff;outline:none}.rich-body-editor{background:#fff;border-top-left-radius:0;border-top-right-radius:0;direction:auto;font-family:Arial,Tahoma,sans-serif;font-size:14px;line-height:1.7;min-height:clamp(360px,46vh,680px);overflow:auto;padding:14px;white-space:normal}.rich-body-editor:empty:before{color:#adb5bd;content:\"Start writing the letter body here...\"}.rich-body-editor:focus{border-color:#86b7fe;box-shadow:0 0 0 .2rem #0d6efd26;outline:0}.rich-body-editor p,.rich-body-editor h1,.rich-body-editor h2,.rich-body-editor h3,.rich-body-editor ul,.rich-body-editor ol{margin:0 0 10px}.rich-body-editor h1,.rich-body-editor h2,.rich-body-editor h3{color:#212529;font-weight:700;line-height:1.35}.rich-body-editor h1{font-size:2em}.rich-body-editor h2{font-size:1.75em}.rich-body-editor h3{font-size:1.45em}.rich-body-editor .template-editor-table-wrapper{width:100%}.rich-body-editor table{border:1px solid #495057;border-collapse:collapse;direction:ltr;display:inline-table;margin:10px 0 14px;max-width:100%;table-layout:auto;width:auto}.rich-body-editor th,.rich-body-editor td{border:1px solid #495057;direction:rtl;height:34px;min-width:90px;padding:7px 9px;position:relative;vertical-align:top}.rich-body-editor.table-resize-column,.rich-body-editor.table-resize-column th,.rich-body-editor.table-resize-column td{cursor:col-resize}.rich-body-editor.table-resize-row,.rich-body-editor.table-resize-row th,.rich-body-editor.table-resize-row td{cursor:row-resize}.rich-body-editor.table-resize-both,.rich-body-editor.table-resize-both th,.rich-body-editor.table-resize-both td{cursor:nwse-resize}.rich-body-editor th:focus,.rich-body-editor td:focus{box-shadow:inset 0 0 0 2px #86b7fe;outline:0}.rich-body-editor th{background:#f1f3f5;font-weight:700}.number-format{font-size:12px;margin-top:7px}@media (max-width: 992px){.selector-panel,.workbench-layout{grid-template-columns:1fr}.editor-panel,.workbench-layout{min-height:auto}.template-summary{max-width:100%}}@media (max-width: 768px){.word-template-page{padding:16px}.page-toolbar,.panel-header{align-items:stretch;flex-direction:column}.toolbar-actions,.panel-actions{justify-content:flex-start}.toolbar-actions .btn,.panel-actions .btn{flex:1 1 150px}.style-tools,.table-builder{flex:1 1 100%}.pdf-preview-panel,.pdf-frame,.pdf-empty{min-height:520px}}@media (max-width: 520px){.template-panel{padding:14px}.template-summary,.tags-form{grid-template-columns:1fr}.template-summary>div{border-bottom:1px solid #e9ecef;border-right:0}.template-summary>div:last-child{border-bottom:0}.font-toolbar-field,.size-toolbar-field,.toolbar-toggle-group{flex:1 1 100%}}\n"], dependencies: [{ kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] }); }
1249
+ }
1250
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: WordTemplateLettersComponent, decorators: [{
1251
+ type: Component,
1252
+ args: [{ standalone: false, selector: 'masar-word-template-letters', template: "<div class=\"word-template-page\">\r\n <div class=\"page-toolbar\">\r\n <div>\r\n <h1>Word Template Letters</h1>\r\n <p>Generate letters from server templates using Masar data tags.</p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"alert alert-danger\" *ngIf=\"errorMessage\">\r\n {{ errorMessage }}\r\n </div>\r\n\r\n <section class=\"template-panel selector-panel\">\r\n <div class=\"template-select-field\">\r\n <label for=\"templateSelect\">Template</label>\r\n <div class=\"template-select-control\">\r\n <select id=\"templateSelect\" class=\"form-control\" [(ngModel)]=\"selectedTemplateName\" (change)=\"onTemplateChange()\">\r\n <option value=\"\">Select template</option>\r\n <option *ngFor=\"let template of templates\" [value]=\"template.name\">\r\n {{ template.displayName }} - {{ template.name }}\r\n </option>\r\n </select>\r\n <button type=\"button\" class=\"btn btn-outline-secondary icon-action-btn refresh-action-btn\" title=\"Refresh templates\" aria-label=\"Refresh templates\" (click)=\"loadTemplates()\" [disabled]=\"isLoadingTemplates\">\r\n <i class=\"fa fa-sync-alt\" [class.fa-spin]=\"isLoadingTemplates\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"template-upload-field\">\r\n <label for=\"templateUpload\">Upload Word Template</label>\r\n <input id=\"templateUpload\" class=\"form-control\" type=\"file\" accept=\".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document\" (change)=\"onTemplateFileSelected($event)\" />\r\n <span class=\"font-source\" *ngIf=\"uploadedTemplateName\">{{ uploadedTemplateName }}</span>\r\n </div>\r\n\r\n <div class=\"template-summary\" *ngIf=\"hasActiveTemplate\">\r\n <div>\r\n <span class=\"summary-label\">Active Template</span>\r\n <strong>{{ uploadedTemplateName ? 'Uploaded' : 'Server' }}</strong>\r\n </div>\r\n <div>\r\n <span class=\"summary-label\">Data Tags</span>\r\n <strong>{{ tags.length }}</strong>\r\n </div>\r\n <div>\r\n <span class=\"summary-label\">Layout Tags</span>\r\n <strong>{{ layoutTags.length }}</strong>\r\n </div>\r\n <div>\r\n <span class=\"summary-label\">Signature</span>\r\n <strong>{{ signatureFile ? 'Selected' : 'Optional' }}</strong>\r\n </div>\r\n </div>\r\n\r\n <div class=\"template-empty\" *ngIf=\"!isLoadingTemplates && templates.length === 0\">\r\n No .docx templates were found on the server.\r\n </div>\r\n </section>\r\n\r\n <section class=\"template-panel editor-panel\" *ngIf=\"hasActiveTemplate\">\r\n <div class=\"workbench-layout\" *ngIf=\"!isLoadingTags && !isAnalyzingUpload\">\r\n <div class=\"data-entry-panel\">\r\n <form [formGroup]=\"form\" class=\"tags-form\" *ngIf=\"tags.length > 0\">\r\n <div class=\"tag-field\" [class.multiline-field]=\"isMultilineField(tag)\" *ngFor=\"let tag of tags; trackBy: trackByTag\">\r\n <label *ngIf=\"!isMultilineField(tag)\" [for]=\"'tag-' + tag\">\r\n {{ getSuggestedTag(tag)?.label || tag }}\r\n <span>{{ tag }}</span>\r\n </label>\r\n <div class=\"editor-toolbar word-ribbon\" *ngIf=\"isBodyTag(tag)\">\r\n <div class=\"ribbon-group font-ribbon-group\">\r\n <div class=\"ribbon-controls\">\r\n <div class=\"ribbon-row\">\r\n <input class=\"form-control form-control-sm ribbon-size-input\" title=\"Font size\" aria-label=\"Font size\" type=\"number\" min=\"8\" max=\"36\" [(ngModel)]=\"fontSize\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"onBodyToolbarStyleChange(tag, 'fontSize')\" />\r\n <label class=\"ribbon-toggle\" [class.active]=\"fontBold\" title=\"Bold\" aria-label=\"Bold\">\r\n <input type=\"checkbox\" [(ngModel)]=\"fontBold\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"onBodyToolbarStyleChange(tag, 'fontBold')\" />\r\n <i class=\"fas fa-bold\" aria-hidden=\"true\"></i>\r\n </label>\r\n <label class=\"ribbon-toggle\" [class.active]=\"fontItalic\" title=\"Italic\" aria-label=\"Italic\">\r\n <input type=\"checkbox\" [(ngModel)]=\"fontItalic\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"onBodyToolbarStyleChange(tag, 'fontItalic')\" />\r\n <i class=\"fas fa-italic\" aria-hidden=\"true\"></i>\r\n </label>\r\n <label class=\"ribbon-toggle\" [class.active]=\"fontUnderline\" title=\"Underline\" aria-label=\"Underline\">\r\n <input type=\"checkbox\" [(ngModel)]=\"fontUnderline\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"onBodyToolbarStyleChange(tag, 'fontUnderline')\" />\r\n <i class=\"fas fa-underline\" aria-hidden=\"true\"></i>\r\n </label>\r\n <label class=\"ribbon-toggle ribbon-text-toggle\" [class.active]=\"numberFormatControls[tag]\" title=\"Use Hindi numbers\" aria-label=\"Use Hindi numbers\">\r\n <input type=\"checkbox\" [(ngModel)]=\"numberFormatControls[tag]\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"markPreviewDirty()\" />\r\n \u0661\u0662\u0663\r\n </label>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Reset body style changes\" aria-label=\"Reset body style changes\" (click)=\"resetBodyToolbarStyles(tag)\">\r\n <i class=\"fas fa-undo\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"ribbon-group paragraph-ribbon-group\">\r\n <div class=\"ribbon-controls\">\r\n <div class=\"ribbon-row\">\r\n <select class=\"form-control form-control-sm line-thickness-select\" title=\"Line thickness\" aria-label=\"Line thickness\" [(ngModel)]=\"lineThickness\" [ngModelOptions]=\"{standalone: true}\">\r\n <option [ngValue]=\"1\">1px</option>\r\n <option [ngValue]=\"2\">2px</option>\r\n <option [ngValue]=\"3\">3px</option>\r\n <option [ngValue]=\"4\">4px</option>\r\n <option [ngValue]=\"6\">6px</option>\r\n </select>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Line\" aria-label=\"Insert line\" (click)=\"insertSeparator(tag)\">\r\n <i class=\"fas fa-minus\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Align left\" aria-label=\"Align text left\" (click)=\"applyEditorAlignment(tag, 'left')\">\r\n <i class=\"fas fa-align-left\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Align center\" aria-label=\"Align text center\" (click)=\"applyEditorAlignment(tag, 'center')\">\r\n <i class=\"fas fa-align-center\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Align right\" aria-label=\"Align text right\" (click)=\"applyEditorAlignment(tag, 'right')\">\r\n <i class=\"fas fa-align-right\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn\" title=\"Justify\" aria-label=\"Justify text\" (click)=\"applyEditorAlignment(tag, 'justify')\">\r\n <i class=\"fas fa-align-justify\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"ribbon-group table-builder table-ribbon-group\">\r\n <div class=\"ribbon-controls\">\r\n <div class=\"ribbon-row\">\r\n <div class=\"table-picker\">\r\n <button type=\"button\" class=\"ribbon-primary-btn table-picker-trigger\" title=\"Insert table\" aria-label=\"Insert table\" (click)=\"toggleTablePicker(tag)\">\r\n <i class=\"fas fa-table\" aria-hidden=\"true\"></i>\r\n <i class=\"fas fa-caret-down table-picker-caret\" aria-hidden=\"true\"></i>\r\n </button>\r\n <div class=\"table-picker-panel\" *ngIf=\"tablePickerOpenTag === tag\">\r\n <div class=\"table-picker-size\">{{ tablePickerHoverRows }} \u00D7 {{ tablePickerHoverColumns }}</div>\r\n <div class=\"table-picker-grid\" role=\"grid\" aria-label=\"Select table size\">\r\n <div class=\"table-picker-row\" *ngFor=\"let row of tablePickerRows\">\r\n <button\r\n type=\"button\"\r\n class=\"table-picker-cell\"\r\n *ngFor=\"let column of tablePickerColumns\"\r\n [class.active]=\"isTablePickerCellActive(row, column)\"\r\n [attr.aria-label]=\"'Insert ' + row + ' by ' + column + ' table'\"\r\n (mouseenter)=\"previewTableSize(row, column)\"\r\n (focus)=\"previewTableSize(row, column)\"\r\n (click)=\"insertTableFromPicker(tag, row, column)\">\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <label class=\"ribbon-toggle ribbon-text-toggle\" [class.active]=\"tableHasHeader\" title=\"Header row\" aria-label=\"Header row\">\r\n <input type=\"checkbox\" [(ngModel)]=\"tableHasHeader\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"applyCurrentTableHeader(tag)\" />\r\n Header\r\n </label>\r\n <label class=\"header-color-picker\" title=\"Header color\" aria-label=\"Header color\">\r\n <input type=\"color\" [(ngModel)]=\"tableHeaderColor\" [ngModelOptions]=\"{standalone: true}\" (change)=\"applyCurrentTableHeaderColor(tag)\" />\r\n </label>\r\n <button type=\"button\" class=\"ribbon-icon-btn table-align-btn\" title=\"Align table left\" aria-label=\"Align table left\" (click)=\"alignCurrentTable(tag, 'left')\">\r\n <i class=\"fas fa-table\" aria-hidden=\"true\"></i><i class=\"fas fa-align-left\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn table-align-btn\" title=\"Align table center\" aria-label=\"Align table center\" (click)=\"alignCurrentTable(tag, 'center')\">\r\n <i class=\"fas fa-table\" aria-hidden=\"true\"></i><i class=\"fas fa-align-center\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-icon-btn table-align-btn\" title=\"Align table right\" aria-label=\"Align table right\" (click)=\"alignCurrentTable(tag, 'right')\">\r\n <i class=\"fas fa-table\" aria-hidden=\"true\"></i><i class=\"fas fa-align-right\" aria-hidden=\"true\"></i>\r\n </button>\r\n <button type=\"button\" class=\"ribbon-danger-btn\" title=\"Delete table\" aria-label=\"Delete table\" (click)=\"deleteCurrentTable(tag)\">\r\n <i class=\"fas fa-trash-alt\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div\r\n *ngIf=\"isMultilineField(tag)\"\r\n [id]=\"'tag-' + tag\"\r\n class=\"form-control rich-body-editor\"\r\n contenteditable=\"true\"\r\n dir=\"rtl\"\r\n [ngStyle]=\"getEditorStyles(tag)\"\r\n [class.table-resize-column]=\"tableResizeCursor === 'column'\"\r\n [class.table-resize-row]=\"tableResizeCursor === 'row'\"\r\n [class.table-resize-both]=\"tableResizeCursor === 'both'\"\r\n (mousemove)=\"onTableResizeMouseMove(tag, $event)\"\r\n (mousedown)=\"onTableResizeMouseDown(tag, $event)\"\r\n (mouseleave)=\"clearTableResizeCursor()\"\r\n (paste)=\"onRichTextPaste(tag, $event)\"\r\n (input)=\"onRichTextInput(tag, $event)\"\r\n (focusout)=\"onRichTextFocusOut($event)\">\r\n </div>\r\n <input *ngIf=\"!isMultilineField(tag)\" [id]=\"'tag-' + tag\" class=\"form-control\" type=\"text\" [formControlName]=\"tag\" (input)=\"markPreviewDirty()\" (blur)=\"onFieldPreviewCommit($event)\" />\r\n <label class=\"number-format\" *ngIf=\"isNumberField(tag) && !isMultilineField(tag)\">\r\n <input type=\"checkbox\" [(ngModel)]=\"numberFormatControls[tag]\" [ngModelOptions]=\"{standalone: true}\" (ngModelChange)=\"markPreviewDirty()\" (blur)=\"onFieldPreviewCommit($event)\" />\r\n Use Hindi numbers\r\n </label>\r\n </div>\r\n </form>\r\n\r\n <div class=\"tags-empty-state\" *ngIf=\"tags.length === 0\">\r\n <div>\r\n <strong>No editable tags found</strong>\r\n <span>The selected template does not contain supported tags.</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"settings-layout media-settings\">\r\n <div class=\"settings-group\">\r\n <div class=\"settings-title\">Media</div>\r\n <div class=\"format-field signature-field\">\r\n <label for=\"signatureImage\">Signature Image</label>\r\n <input id=\"signatureImage\" class=\"form-control\" type=\"file\" accept=\"image/png,image/jpeg,image/gif\" (change)=\"onSignatureSelected($event)\" />\r\n <span class=\"file-name\" *ngIf=\"signatureFile\">{{ signatureFile.name }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"pdf-preview-panel\">\r\n <div class=\"preview-panel-header\">\r\n <div>\r\n <h3>{{ pdfPreviewUrl ? 'PDF Preview' : 'Word Preview' }}</h3>\r\n <span class=\"preview-status\" *ngIf=\"pdfPreviewUrl && isPdfPreviewStale\">Preview needs refresh. Click Preview PDF for the exact updated layout.</span>\r\n <span class=\"preview-status\" *ngIf=\"isPreviewingWordHtml && !pdfPreviewUrl\">Updating Word preview...</span>\r\n <span class=\"preview-status\" *ngIf=\"wordPreviewMessage && !pdfPreviewUrl\">{{ wordPreviewMessage }}</span>\r\n </div>\r\n <div class=\"preview-actions\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary preview-pdf-btn\" (click)=\"previewPdf()\" [disabled]=\"isLoadingTags || isAnalyzingUpload || isPreviewingPdf\">\r\n <i class=\"fa fa-file-pdf\" aria-hidden=\"true\"></i>\r\n {{ isPreviewingPdf ? 'Rendering PDF...' : 'Preview PDF' }}\r\n </button>\r\n <button type=\"button\" class=\"btn btn-primary word-action-btn\" title=\"Generate Word document\" aria-label=\"Generate Word document\" (click)=\"generateLetter()\" [disabled]=\"!hasActiveTemplate || isLoadingTags || isAnalyzingUpload || isGenerating\">\r\n <i class=\"fa fa-file-word\" [class.fa-pulse]=\"isGenerating\" aria-hidden=\"true\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <iframe *ngIf=\"pdfPreviewUrl\" class=\"pdf-frame\" [src]=\"pdfPreviewUrl\" title=\"Letter PDF Preview\"></iframe>\r\n\r\n <div class=\"word-html-preview\" *ngIf=\"!pdfPreviewUrl && wordHtmlPreview\" [innerHTML]=\"wordHtmlPreview\"></div>\r\n\r\n <div class=\"pdf-empty\" *ngIf=\"!pdfPreviewUrl && !wordHtmlPreview\">\r\n {{ isPreviewingWordHtml ? 'Preparing Word preview...' : 'Fill the form to see a quick Word preview.' }}\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"template-empty\" *ngIf=\"isLoadingTags || isAnalyzingUpload\">\r\n Reading template tags...\r\n </div>\r\n </section>\r\n</div>\r\n", styles: [".word-template-page{background:#f5f7f9;min-height:calc(100vh - 64px);padding:24px}.page-toolbar,.panel-header,.toolbar-actions,.panel-actions{align-items:center;display:flex;gap:12px}.page-toolbar,.panel-header{justify-content:space-between}.page-toolbar h1,.panel-header h2{color:#212529;font-size:22px;font-weight:600;line-height:1.25;margin:0}.page-toolbar p,.panel-header p{color:#6c757d;font-size:13px;line-height:1.45;margin:6px 0 0}.toolbar-actions,.panel-actions{flex-wrap:wrap;justify-content:flex-end}.template-panel{background:#fff;border:1px solid #dee2e6;border-radius:8px;box-shadow:0 1px 2px #00000008;margin-top:18px;padding:18px}.editor-panel{display:flex;flex-direction:column;min-height:calc(100vh - 220px)}.selector-panel{align-items:end;display:grid;gap:16px;grid-template-columns:minmax(280px,1fr) minmax(260px,.8fr)}.template-select-control{align-items:center;display:flex;gap:8px}.template-select-control select{min-width:0}.icon-action-btn{align-items:center;display:inline-flex;flex:0 0 auto;height:38px;justify-content:center;min-width:42px;padding-left:10px;padding-right:10px}.refresh-action-btn,.refresh-action-btn:hover,.refresh-action-btn:focus{background:#fff;color:#495057}.refresh-action-btn i{color:#495057!important;font-size:15px}.refresh-action-btn:disabled i{color:#868e96!important}.template-select-field label,.template-upload-field label,.format-field label,.tag-field label{color:#343a40;display:block;font-size:13px;font-weight:600;margin-bottom:6px}.template-summary{border:1px solid #e9ecef;border-radius:8px;display:grid;gap:0;grid-column:1/-1;grid-template-columns:repeat(4,minmax(0,1fr));overflow:hidden}.template-summary>div{background:#f8f9fa;border-right:1px solid #e9ecef;min-width:0;padding:10px 12px}.template-summary>div:last-child{border-right:0}.summary-label,.file-name,.font-source,.tag-field label span{color:#6c757d;display:block;font-size:12px;font-weight:400;line-height:1.35;margin-top:2px;overflow-wrap:anywhere}.template-summary strong{color:#212529;display:block;font-size:14px;margin-top:2px;overflow-wrap:anywhere}.template-empty{color:#6c757d;font-size:13px;padding:18px 0 4px}.workbench-layout{align-items:start;display:grid;flex:1;gap:18px;grid-template-columns:minmax(0,1fr) minmax(0,1fr);min-height:calc(100vh - 270px);width:100%}.pdf-preview-panel,.data-entry-panel{border:1px solid #dee2e6;border-radius:8px;height:100%;min-width:0;padding:14px;width:100%}.pdf-preview-panel{background:#f8f9fa;display:flex;flex-direction:column;min-height:calc(100vh - 300px)}.data-entry-panel{background:#fff;display:flex;flex-direction:column;min-height:calc(100vh - 300px)}.preview-panel-header{align-items:center;display:flex;gap:12px;justify-content:space-between;margin-bottom:12px}.preview-actions{align-items:center;display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.preview-actions .btn{align-items:center;display:inline-flex;gap:7px}.preview-pdf-btn{height:38px;padding-left:12px;padding-right:12px}.preview-pdf-btn i{color:#6c757d;font-size:16px}.word-action-btn{align-items:center;display:inline-flex;flex:0 0 44px;height:38px;justify-content:center;min-width:44px;padding:0}.word-action-btn i{color:#fff;font-size:18px;line-height:1}.preview-panel-header h3{color:#212529;font-size:16px;font-weight:600;line-height:1.3;margin:0}.preview-status{color:#6c757d;display:block;font-size:12px;line-height:1.35;margin-top:3px}.pdf-frame{background:#fff;border:1px solid #ced4da;border-radius:6px;display:block;flex:1;min-height:calc(100vh - 380px);width:100%}.word-html-preview{background:#2b2b2b;border:1px solid #ced4da;border-radius:6px;flex:1;min-height:calc(100vh - 380px);overflow:auto;padding:24px}:host ::ng-deep .word-html-preview .word-html-page{background:#fff;box-shadow:0 2px 10px #00000029;color:#111;direction:rtl;font-family:Arial,Tahoma,sans-serif;font-size:12pt;line-height:1.6;margin:0 auto;min-height:920px;padding:70px 72px;text-align:right;width:min(100%,794px)}:host ::ng-deep .word-html-preview .word-html-page p{margin:0 0 10px}:host ::ng-deep .word-html-preview .word-html-page h1,:host ::ng-deep .word-html-preview .word-html-page h2,:host ::ng-deep .word-html-preview .word-html-page h3{color:#111;font-weight:700;line-height:1.35;margin:0 0 14px}:host ::ng-deep .word-html-preview .word-html-page table{border-collapse:collapse;direction:ltr;margin:10px 0 14px;max-width:100%}:host ::ng-deep .word-html-preview .word-html-page th,:host ::ng-deep .word-html-preview .word-html-page td{border:1px solid #495057;direction:rtl;padding:6px 8px;vertical-align:top}:host ::ng-deep .word-html-preview .word-html-page img{height:auto;max-width:100%}.pdf-empty{align-items:center;background:#fff;border:1px dashed #ced4da;border-radius:6px;color:#6c757d;display:flex;flex:1;font-size:13px;justify-content:center;min-height:calc(100vh - 380px);padding:18px;text-align:center}.settings-layout{display:block;margin-top:16px}.media-settings{border-top:1px solid #dee2e6;padding-top:14px}.settings-group{min-width:0}.settings-title{color:#495057;font-size:13px;font-weight:600;margin-bottom:10px}.format-check,.number-format{align-items:center;color:#495057;display:flex;font-size:13px;gap:7px;margin:0}.signature-field input{height:auto}.tags-form{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin-top:18px;overflow:auto;padding-right:4px}.tags-empty-state{align-items:center;background:#fff;border:1px dashed #ced4da;border-radius:6px;color:#6c757d;display:flex;flex:1;justify-content:center;margin-top:18px;min-height:clamp(360px,46vh,680px);padding:18px;text-align:center}.tags-empty-state strong{color:#343a40;display:block;font-size:15px;margin-bottom:6px}.tags-empty-state span{display:block;font-size:13px}.tag-field{min-width:0}.tag-field.multiline-field{grid-column:1/-1}.tag-field.multiline-field>label{display:none!important}.editor-toolbar{align-items:stretch;background:linear-gradient(#fbfbfb,#f1f3f5);border:1px solid #cfd6de;border-bottom:0;border-radius:4px 4px 0 0;display:flex;flex-wrap:nowrap;gap:0;padding:0}.editor-toolbar button:has(.fa-file-pdf),.editor-toolbar .preview-pdf-btn{display:none!important}.word-ribbon{min-height:46px;overflow:visible}.ribbon-group{align-items:center;border-right:1px solid #cfd6de;display:flex;flex-direction:row;justify-content:center;margin:0;min-width:0;padding:6px 8px;position:relative}.ribbon-group:last-child{border-right:0}.font-ribbon-group,.paragraph-ribbon-group{flex:0 0 auto}.table-ribbon-group{flex:0 0 auto;padding-right:10px}.ribbon-controls{align-items:center;display:flex;flex-wrap:nowrap;gap:5px;min-height:28px}.ribbon-row{align-items:center;display:flex;flex-wrap:nowrap;gap:4px;min-height:28px}.font-ribbon-group .ribbon-controls,.font-ribbon-group .ribbon-row{flex-wrap:nowrap}.font-ribbon-group .ribbon-toggle,.font-ribbon-group .ribbon-icon-btn{flex:0 0 auto}.ribbon-size-input{flex:0 0 54px;height:26px;padding:2px 4px;text-align:center}.line-thickness-select{flex:0 0 64px;height:26px;min-width:64px;padding:2px 4px}.ribbon-icon-btn,.ribbon-primary-btn,.ribbon-danger-btn,.ribbon-command-btn,.ribbon-toggle{align-items:center;background:transparent;border:1px solid transparent;border-radius:2px;color:#343a40;cursor:pointer;display:inline-flex;font-size:12px;gap:3px;height:26px;justify-content:center;line-height:1;min-width:26px;padding:3px 6px;-webkit-user-select:none;user-select:none}.ribbon-icon-btn:hover,.ribbon-command-btn:hover,.ribbon-toggle:hover{background:#e9f2ff;border-color:#9dc5f4}.ribbon-primary-btn{background:#eef5ff;border-color:#b9d5ff;color:#0d47a1}.ribbon-primary-btn:hover{background:#dcecff}.ribbon-danger-btn{color:#b42318}.ribbon-danger-btn:hover{background:#fff1f0;border-color:#ffb4ab}.ribbon-toggle input{display:none}.ribbon-toggle.active{background:#dbeafe;border-color:#7db3f2;color:#0b5cad}.ribbon-text-toggle{font-weight:600;min-width:auto;white-space:nowrap}.ribbon-corner-icon{font-size:9px;margin-left:-1px;margin-top:-8px}.ribbon-command-btn{min-width:52px}.table-align-btn{min-width:42px}.table-align-btn .fa-table{font-size:11px}.header-color-picker{align-items:center;border:1px solid transparent;border-radius:2px;cursor:pointer;display:inline-flex;height:26px;justify-content:center;margin:0;padding:2px;width:30px}.header-color-picker:hover{background:#e9f2ff;border-color:#9dc5f4}.header-color-picker input{border:0;cursor:pointer;height:20px;padding:0;width:24px}.ribbon-field{align-items:center;color:#495057;display:inline-flex;font-size:11px;gap:4px;margin:0;white-space:nowrap}.ribbon-field input{height:26px;padding:2px 4px;text-align:center;width:54px}.table-picker{display:inline-flex;position:relative}.table-picker-trigger{min-width:34px;padding-left:7px;padding-right:7px}.table-picker-caret{font-size:9px;margin-left:1px}.table-picker-panel{background:#fff;border:1px solid #adb5bd;border-radius:3px;box-shadow:0 6px 16px #2125292e;left:0;padding:7px;position:absolute;top:30px;width:148px;z-index:25}.table-picker-size{border-bottom:1px solid #e9ecef;color:#212529;font-size:12px;font-weight:600;line-height:1;margin:0 -7px 6px;padding:0 7px 6px;text-align:center}.table-picker-grid{display:flex;flex-direction:column;gap:2px}.table-picker-row{display:flex;gap:2px}.table-picker-cell{background:#fff;border:1px solid #d0d7de;cursor:pointer;height:13px;min-width:0;padding:0;width:13px}.table-picker-cell:hover,.table-picker-cell:focus,.table-picker-cell.active{background:#dcecff;border-color:#4d94ff;outline:none}.rich-body-editor{background:#fff;border-top-left-radius:0;border-top-right-radius:0;direction:auto;font-family:Arial,Tahoma,sans-serif;font-size:14px;line-height:1.7;min-height:clamp(360px,46vh,680px);overflow:auto;padding:14px;white-space:normal}.rich-body-editor:empty:before{color:#adb5bd;content:\"Start writing the letter body here...\"}.rich-body-editor:focus{border-color:#86b7fe;box-shadow:0 0 0 .2rem #0d6efd26;outline:0}.rich-body-editor p,.rich-body-editor h1,.rich-body-editor h2,.rich-body-editor h3,.rich-body-editor ul,.rich-body-editor ol{margin:0 0 10px}.rich-body-editor h1,.rich-body-editor h2,.rich-body-editor h3{color:#212529;font-weight:700;line-height:1.35}.rich-body-editor h1{font-size:2em}.rich-body-editor h2{font-size:1.75em}.rich-body-editor h3{font-size:1.45em}.rich-body-editor .template-editor-table-wrapper{width:100%}.rich-body-editor table{border:1px solid #495057;border-collapse:collapse;direction:ltr;display:inline-table;margin:10px 0 14px;max-width:100%;table-layout:auto;width:auto}.rich-body-editor th,.rich-body-editor td{border:1px solid #495057;direction:rtl;height:34px;min-width:90px;padding:7px 9px;position:relative;vertical-align:top}.rich-body-editor.table-resize-column,.rich-body-editor.table-resize-column th,.rich-body-editor.table-resize-column td{cursor:col-resize}.rich-body-editor.table-resize-row,.rich-body-editor.table-resize-row th,.rich-body-editor.table-resize-row td{cursor:row-resize}.rich-body-editor.table-resize-both,.rich-body-editor.table-resize-both th,.rich-body-editor.table-resize-both td{cursor:nwse-resize}.rich-body-editor th:focus,.rich-body-editor td:focus{box-shadow:inset 0 0 0 2px #86b7fe;outline:0}.rich-body-editor th{background:#f1f3f5;font-weight:700}.number-format{font-size:12px;margin-top:7px}@media (max-width: 992px){.selector-panel,.workbench-layout{grid-template-columns:1fr}.editor-panel,.workbench-layout{min-height:auto}.template-summary{max-width:100%}}@media (max-width: 768px){.word-template-page{padding:16px}.page-toolbar,.panel-header{align-items:stretch;flex-direction:column}.toolbar-actions,.panel-actions{justify-content:flex-start}.toolbar-actions .btn,.panel-actions .btn{flex:1 1 150px}.style-tools,.table-builder{flex:1 1 100%}.pdf-preview-panel,.pdf-frame,.pdf-empty{min-height:520px}}@media (max-width: 520px){.template-panel{padding:14px}.template-summary,.tags-form{grid-template-columns:1fr}.template-summary>div{border-bottom:1px solid #e9ecef;border-right:0}.template-summary>div:last-child{border-bottom:0}.font-toolbar-field,.size-toolbar-field,.toolbar-toggle-group{flex:1 1 100%}}\n"] }]
1253
+ }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.DomSanitizer }], propDecorators: { templates: [{
1254
+ type: Input
1255
+ }], refreshTemplatesRequested: [{
1256
+ type: Output
1257
+ }], templateTagsRequested: [{
1258
+ type: Output
1259
+ }], uploadedTemplateAnalyzeRequested: [{
1260
+ type: Output
1261
+ }], pdfPreviewRequested: [{
1262
+ type: Output
1263
+ }], wordPreviewRequested: [{
1264
+ type: Output
1265
+ }], generateLetterRequested: [{
1266
+ type: Output
1267
+ }], onDocumentMouseMove: [{
1268
+ type: HostListener,
1269
+ args: ['document:mousemove', ['$event']]
1270
+ }], onDocumentMouseUp: [{
1271
+ type: HostListener,
1272
+ args: ['document:mouseup']
1273
+ }] } });
1274
+
1275
+ class WordTemplateLettersModule {
1276
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: WordTemplateLettersModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1277
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.4", ngImport: i0, type: WordTemplateLettersModule, declarations: [WordTemplateLettersComponent], imports: [CommonModule,
1278
+ FormsModule,
1279
+ ReactiveFormsModule], exports: [WordTemplateLettersComponent] }); }
1280
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: WordTemplateLettersModule, imports: [CommonModule,
1281
+ FormsModule,
1282
+ ReactiveFormsModule] }); }
1283
+ }
1284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.4", ngImport: i0, type: WordTemplateLettersModule, decorators: [{
1285
+ type: NgModule,
1286
+ args: [{
1287
+ declarations: [
1288
+ WordTemplateLettersComponent
1289
+ ],
1290
+ imports: [
1291
+ CommonModule,
1292
+ FormsModule,
1293
+ ReactiveFormsModule
1294
+ ],
1295
+ exports: [
1296
+ WordTemplateLettersComponent
1297
+ ]
1298
+ }]
1299
+ }] });
1300
+
1301
+ /**
1302
+ * Generated bundle index. Do not edit.
1303
+ */
1304
+
1305
+ export { WordTemplateLettersComponent, WordTemplateLettersModule };
1306
+ //# sourceMappingURL=tafeel-word-template-letters.mjs.map