import { Component, Input, OnInit } from '@angular/core'; import { Validators } from '@angular/forms'; import { SpinnerService } from '@core/services/spinner.service'; import { TokenGroup } from '@core/typings/ckeditor.typing'; import { AdHocReportingUI } from '@core/typings/ui/ad-hoc-reporting.typing'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { CommunicationVisibility } from '@features/communications/communications.typing'; import { FormAudience } from '@features/configure-forms/form.typing'; import { DocumentTemplateService } from '@features/document-templates/document-template.service'; import { ReferenceFieldsResolver } from '@features/reference-fields/reference-fields.resolver'; import { ReferenceFieldsService } from '@features/reference-fields/services/reference-fields.service'; import { CopyEmailModalResponse, EmailDetail, EmailEditableChunk, EmailMergeModel, EmailTemplateCopyForUI, SimpleEmail } from '@features/system-emails/email.typing'; import { CKEditorTokenConfig, defaultCKEditorConfig, DuplicateCheckValidator, EmailValidator, ExistingGenericFile, FileService, FileUploadRequest, GenericFile, maxFileSizeForEmailAttachments, simpleCkEditorConfig, TypeSafeFormBuilder, TypeSafeFormGroup } from '@yourcause/common'; import { AnalyticsService, EventType } from '@yourcause/common/analytics'; import { I18nService } from '@yourcause/common/i18n'; import { LogService } from '@yourcause/common/logging'; import { YCModalComponent } from '@yourcause/common/modals'; import { NotifierService } from '@yourcause/common/notifier'; import { clone } from 'lodash'; import { EmailService } from '../email.service'; interface CopyEmailFormGroup { translationLanguage: string; subject: string; emailUsage: string; content: string; cc: string[]; bcc: string[]; allowCcEdit: boolean; documentTemplateId: number; applicantCanView: boolean; documentVisibility: CommunicationVisibility; } @Component({ selector: 'gc-copy-email-modal', templateUrl: './copy-email-modal.component.html', styleUrls: ['./copy-email-modal.component.scss'] }) export class CopyEmailModalComponent extends YCModalComponent implements OnInit { @Input() isTranslation: boolean; @Input() email: SimpleEmail; attachments: GenericFile[]; originalAttachments: GenericFile[]; maxAttachmentsFileSize = maxFileSizeForEmailAttachments; documentTemplateOptions = this.documentTemplateService.documentTemplateOptions; showBcc = false; translationMap: { [langId: string]: EmailTemplateCopyForUI; } = {}; formGroup: TypeSafeFormGroup; chunks: EmailEditableChunk; AudienceType = FormAudience; originalTemplate = ''; headerText: string; subHeaderText: string; primaryButtonText: string; langKeys = this.clientSettingsService.get('selectedLanguages') .filter((lang) => { return lang !== this.clientSettingsService.defaultLanguage; }); createText = this.i18n.translate('common:linkCreate', {}, 'Create'); altCkeditorOptions: CKEditorTokenConfig; unModifiedEmail: SimpleEmail; showCheckboxForCcEdit = false; tokenGroups: TokenGroup[]; ckEditorConfig: CKEDITOR.config; constructor ( private logger: LogService, private referenceFieldsService: ReferenceFieldsService, private spinnerService: SpinnerService, private notifier: NotifierService, private i18n: I18nService, private clientSettingsService: ClientSettingsService, private formBuilder: TypeSafeFormBuilder, private emailService: EmailService, private documentTemplateService: DocumentTemplateService, private analyticsService: AnalyticsService, private referenceFieldsResolver: ReferenceFieldsResolver, private fileService: FileService ) { super(); } get type () { return this.email ? this.email.emailNotificationType : null; } get branding () { return this.clientSettingsService.get('clientBranding'); } get emailDetail (): EmailDetail { return this.emailService.templateMap[this.type]; } get clientLanguages () { return this.clientSettingsService.get('selectedLanguages'); } get clientDefaultLanguage () { return this.clientSettingsService.defaultLanguage; } async ngOnInit () { this.spinnerService.startSpinner(); // this is needed for tokens await this.referenceFieldsResolver.resolve(); await this.setTemplateMap(); this.setCkEditorConfig(); if (this.email) { this.unModifiedEmail = clone(this.email); } this.email = { ...this.email }; // On Init, we can assume these attachments are existing and not generic const emailAttachments = this.email.attachments as ExistingGenericFile[]; this.attachments = emailAttachments.map((a) => { return { fileName: a.fileName, fileUploadId: a.fileUploadId, accessURL: a.accessURL, fileSize: a.fileSize }; }); this.originalAttachments = clone(this.attachments); this.setFormGroup(); this.setTranslationMap(); this.spinnerService.stopSpinner(); } setCkEditorConfig () { const removeButtons = simpleCkEditorConfig.removeButtons.split(','); this.ckEditorConfig = { ...simpleCkEditorConfig, removeButtons: removeButtons.filter((action) => { return action !== 'Image'; }).join(',') }; } async handleFileUpload (fileUploadRequest: FileUploadRequest) { this.spinnerService.startSpinner(); const fileURL = await this.emailService.uploadEmailImage(fileUploadRequest); fileUploadRequest.handleComplete(fileURL); this.spinnerService.stopSpinner(); } async setTemplateMap () { try { await this.emailService.setTemplateMap( this.type, false, this.clientDefaultLanguage ); const body = this.email.body || this.emailDetail.template; this.chunks = this.emailService.getEmailAsEditable(body); this.originalTemplate = this.chunks.editableChunk; this.setTokenGroups(); this.setAltCkeditor(); this.setModalText(); } catch (e) { this.logger.error(e); this.notifier.error(this.i18n.translate( 'GLOBAL:textErrorLoadingEmail', {}, 'There was an error loading the email' )); this.spinnerService.stopSpinner(); this.onCancel(); } } setTokenGroups () { const tokens = this.emailDetail.tokens; const mergeModelType = this.emailService.getEmailMergeModel( this.emailDetail.email.emailNotificationType ); let tokenGroups: TokenGroup[] = [{ groupDisplay: 'Standard', tokens: tokens.map((token) => { return { display: token.name, value: token.token.replace('{{', '').replace('}}', '') }; }) }]; switch (mergeModelType) { case EmailMergeModel.Application: case EmailMergeModel.ApplicationForm: case EmailMergeModel.Award: case EmailMergeModel.Payment: const categoryTokenGroups = this.referenceFieldsService.getCategoryTokenGroups( 'ReferenceField', AdHocReportingUI.Usage.TOKENS ); tokenGroups = [ ...tokenGroups, ...categoryTokenGroups ]; break; } this.tokenGroups = tokenGroups; } setAltCkeditor () { const ckEditorConfig = { extraPlugins: 'yc-token', tokenStart: '{{', tokenEnd: '}}', tokenGroups: this.tokenGroups }; this.altCkeditorOptions = Object.assign({ toolbar: [{ name: 'Tokens', items: ['CreateToken'] }], keystrokes: [ [ 13 /* Enter */, 'blur'], [ CKEDITOR.SHIFT + 13 /* Shift + Enter */, 'blur' ] ] }, ckEditorConfig, defaultCKEditorConfig); } async downloadFile ( outputEmit: { url: string; fileName: string; }) { const { url, fileName } = outputEmit; this.spinnerService.startSpinner(); await this.fileService.downloadUrlAs(url, fileName); this.spinnerService.stopSpinner(); } setModalText () { if (this.isTranslation) { this.headerText = this.i18n.translate('common:textTranslation', {}, 'Translation'); this.subHeaderText = this.email.title; this.primaryButtonText = this.createText; } else if (this.email.isEdit) { this.headerText = this.i18n.translate('GLOBAL:hdrEditCopy', {}, 'Edit Copy'); this.subHeaderText = this.email.title; this.primaryButtonText = this.i18n.translate('common:btnSave', {}, 'Save'); } else { this.headerText = this.i18n.translate('GLOBAL:hdrCreateCopy', {}, 'Create Copy'); this.subHeaderText = this.email.title; this.primaryButtonText = this.createText; } } setFormGroup () { let translationLanguage = ''; if (this.isTranslation) { translationLanguage = [ this.clientSettingsService.defaultLanguage, Validators.required ] as any; } let cc: string[] = []; let bcc: string[] = []; let allowCcEdit = false; let documentTemplateId: number; let applicantCanView = true; let documentVisibility = CommunicationVisibility.ALL_GRANT_MANAGERS; if (this.email.supportsCarbonCopy) { cc = this.email.isEdit ? this.email.ccEmails : []; bcc = this.email.isEdit ? this.email.bccEmails : []; this.showBcc = bcc.length > 0; allowCcEdit = this.email.allowCarbonCopyUpdates; this.showCheckboxForCcEdit = this.emailService.getShowCheckboxForCcEdit(this.type); } if (this.email.allowDocumentTemplates) { const existing = this.email.documentTemplates.length ? this.email.documentTemplates[0] : null; if (existing) { documentTemplateId = existing.documentTemplateId; applicantCanView = this.email.audienceType === this.AudienceType.APPLICANT ? true : existing.applicantCanView; documentVisibility = existing.documentVisibility; } } this.formGroup = this.formBuilder.group({ subject: this.email.subject, content: this.originalTemplate, translationLanguage, emailUsage: this.email.description, cc: [cc, EmailValidator()], bcc: [bcc, EmailValidator()], allowCcEdit, documentTemplateId, applicantCanView, documentVisibility }, { validator: [ DuplicateCheckValidator( 'cc', 'bcc' ) ] }); } async languageOptionChange (langId: string) { const translation = this.translationMap[langId]; let email; if (translation) { email = translation; } else { email = await this.emailService.getClientTranslatedTemplate(langId, this.email.id); email.title = email.title || this.email.title; } this.email = { ...this.email, ...email }; this.unModifiedEmail = clone(this.email); const body = this.email.body || this.emailDetail.template; this.chunks = this.emailService.getEmailAsEditable(body); this.originalTemplate = this.chunks.editableChunk; this.formGroup.get('content').setValue(this.originalTemplate); } documentTemplateChanged () { const documentTemplateId = this.formGroup.value.documentTemplateId; if (documentTemplateId) { let applicantCanView = this.formGroup.value.applicantCanView; // if applicant type email, applicantCanView is always true if (this.email.audienceType === this.AudienceType.APPLICANT) { applicantCanView = true; } this.email.documentTemplates = [{ documentTemplateId, applicantCanView, documentVisibility: this.formGroup.value.documentVisibility }]; } else { this.email.documentTemplates = []; } this.setTranslationMap(); } updateMap ( key: T, data: SimpleEmail[T] ) { if (data !== this.email[key]) { this.email[key] = data; this.setTranslationMap(); } } setTranslationMap () { const { subject, title, templateToSave } = this.stripHTML(); const languageId = this.formGroup.value.translationLanguage || this.clientDefaultLanguage; const template: EmailTemplateCopyForUI = { name: this.formGroup.value.subject, description: this.formGroup.value.emailUsage, subject, emailNotificationTypeId: this.email.emailNotificationType, title, body: templateToSave, default: false, languageId, // for creating templates defaultLanguageId: this.clientDefaultLanguage, ccEmails: this.email.ccEmails, bccEmails: this.email.bccEmails, attachments: this.email.attachments, allowCarbonCopyUpdates: this.email.allowCarbonCopyUpdates, documentTemplates: this.email.documentTemplates }; this.translationMap = { ...this.translationMap, ['' + languageId]: template }; } undoChanges () { this.formGroup.get('content').setValue(this.originalTemplate); this.formGroup.get('emailUsage').setValue(this.unModifiedEmail.description); this.formGroup.get('allowCcEdit').setValue( this.unModifiedEmail.allowCarbonCopyUpdates ); this.formGroup.get('documentTemplateId').setValue( this.unModifiedEmail.documentTemplates[0]?.documentTemplateId ?? null ); this.formGroup.get('applicantCanView').setValue( this.unModifiedEmail.documentTemplates[0]?.applicantCanView ?? false ); this.formGroup.get('documentVisibility').setValue( this.unModifiedEmail.documentTemplates[0]?.documentVisibility ?? null ); this.formGroup.get('cc').setValue(this.unModifiedEmail.ccEmails); this.formGroup.get('bcc').setValue(this.unModifiedEmail.bccEmails); this.attachments = clone(this.originalAttachments); this.email = clone(this.unModifiedEmail); this.analyticsService.emitEvent({ eventName: 'Undo email changes', eventType: EventType.Click, extras: null }); } createCopy () { const templates: EmailTemplateCopyForUI[] = []; Object.keys(this.translationMap).forEach((key) => { templates.push(this.translationMap[key]); }); this.closeModal.emit({templates, attachments: this.attachments}); this.analyticsService.emitEvent({ eventName: 'Create email copy', eventType: EventType.Click, extras: null }); } private stripHTML () { this.chunks.editableChunk = this.formGroup.value.content; const templateToSave = this.emailService.reassembleEditableEmail(this.chunks); const titleDiv = document.createElement('div'); titleDiv.innerHTML = this.email.title; const title = titleDiv.innerText; const subjectDiv = document.createElement('div'); subjectDiv.innerHTML = this.email.subject; const subject = subjectDiv.innerText; return { subject, title, templateToSave }; } addAttachment (payload: {attachment: GenericFile}) { this.attachments = [ ...this.attachments, payload.attachment ]; this.updateMap('attachments', this.attachments as ExistingGenericFile[]); } removeAttachment (index: number) { this.attachments = [ ...this.attachments.slice(0, index), ...this.attachments.slice(index + 1) ]; this.updateMap('attachments', this.attachments as ExistingGenericFile[]); } onCancel () { this.closeModal.emit(); } }