diff --git a/apps/lfx-one/package.json b/apps/lfx-one/package.json index 9a14b90c..e1c7bdf3 100644 --- a/apps/lfx-one/package.json +++ b/apps/lfx-one/package.json @@ -53,6 +53,7 @@ "ngx-cookie-service-ssr": "^19.1.2", "pino-http": "^10.5.0", "primeng": "^20.4.0", + "quill": "^2.0.3", "rxjs": "~7.8.2", "snowflake-sdk": "^2.3.1", "tslib": "^2.8.1" diff --git a/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html b/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html index bd500dda..0b1c4bac 100644 --- a/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html +++ b/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html @@ -11,7 +11,7 @@

Basic Information

- This mailing list belongs to: {{ projectName() }} + This mailing list belongs to: {{ projectName() }} - {{ service()?.domain || '' }}

@@ -50,23 +50,19 @@

Basic Information

- - + [style]="{ height: '125px' }" + dataTest="mailing-list-basic-info-description-input"> +

This helps members understand when to use this list.

@if (form().get('description')?.errors?.['required'] && form().get('description')?.touched) {

Description is required

- } - @if (form().get('description')?.errors?.['minlength'] && form().get('description')?.touched) { + } @else if (form().get('description')?.errors?.['minlength'] && form().get('description')?.touched) {

Description must be at least 11 characters

- } - @if (form().get('description')?.errors?.['maxlength'] && form().get('description')?.touched) { + } @else if (form().get('description')?.errors?.['maxlength'] && form().get('description')?.touched) {

Description cannot exceed 500 characters

}
diff --git a/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts b/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts index 51618cc0..6318f43f 100644 --- a/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts +++ b/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts @@ -1,18 +1,21 @@ // Copyright The Linux Foundation and each contributor to LFX. // SPDX-License-Identifier: MIT -import { Component, computed, input, Signal } from '@angular/core'; +import { Component, computed, inject, input, Signal } from '@angular/core'; import { FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ProjectContextService } from '@app/shared/services/project-context.service'; +import { EditorComponent } from '@components/editor/editor.component'; import { InputTextComponent } from '@components/input-text/input-text.component'; -import { TextareaComponent } from '@components/textarea/textarea.component'; import { GroupsIOService } from '@lfx-one/shared/interfaces'; @Component({ selector: 'lfx-mailing-list-basic-info', - imports: [ReactiveFormsModule, InputTextComponent, TextareaComponent], + imports: [ReactiveFormsModule, InputTextComponent, EditorComponent], templateUrl: './mailing-list-basic-info.component.html', }) export class MailingListBasicInfoComponent { + private readonly projectContextService = inject(ProjectContextService); + public readonly form = input.required(); public readonly formValue = input.required>>(); public readonly service = input(null); @@ -20,18 +23,40 @@ export class MailingListBasicInfoComponent { public readonly maxGroupNameLength = input(34); public readonly projectName = computed(() => { - return this.service()?.project_name || 'Your Project'; + return this.service()?.project_name || this.projectContextService.selectedProject()?.name || this.projectContextService.selectedFoundation()?.name || ''; }); public readonly serviceDomain = computed(() => { return this.service()?.domain || 'groups.io'; }); + public readonly isSharedService = computed(() => { + const service = this.service(); + if (!service) return false; + + const currentProjectUid = this.projectContextService.selectedProject()?.uid || this.projectContextService.selectedFoundation()?.uid; + + // If service type is explicitly 'shared', it's a shared service + if (service.type === 'shared') return true; + + // If current project UID doesn't match service project UID, and service is primary, it's being used as shared + if (currentProjectUid !== service.project_uid && service.type === 'primary') return true; + + return false; + }); + public readonly emailPreview = computed(() => { const groupName = (this.formValue()()?.['group_name'] as string) || 'listname'; const prefixValue = this.prefix(); const domain = this.serviceDomain(); - return `${prefixValue}${groupName}@${domain}`; + + // For shared services, include prefix with hyphen + if (this.isSharedService()) { + return `${prefixValue}-${groupName}@${domain}`; + } + + // For non-shared services, no prefix + return `${groupName}@${domain}`; }); public readonly groupNameTooLong = computed(() => { @@ -42,10 +67,15 @@ export class MailingListBasicInfoComponent { public readonly groupNameLengthError = computed(() => { const maxLength = this.maxGroupNameLength(); - const prefixValue = this.prefix(); - if (prefixValue) { - return `Name cannot exceed ${maxLength} characters (prefix "${prefixValue}" uses ${prefixValue.length} of 34 allowed)`; + const prefixValue = this.prefix() + '-'; + + // For shared services, include hyphen in the count + if (this.isSharedService() && prefixValue) { + const prefixWithHyphenLength = prefixValue.length; + return `Name cannot exceed ${maxLength} characters (prefix "${prefixValue}" uses ${prefixWithHyphenLength} of 34 allowed)`; } + + // For non-shared services, no prefix consideration return `Name cannot exceed ${maxLength} characters`; }); } diff --git a/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html b/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html index 9a9cb525..c3207669 100644 --- a/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html +++ b/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html @@ -61,7 +61,7 @@
- {{ mailingList.description || '-' }} + {{ (mailingList.description | stripHtml) || '-' }}
@@ -111,7 +111,7 @@
- {{ mailingList.description || '-' }} + {{ (mailingList.description | stripHtml) || '-' }}
diff --git a/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts b/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts index 5e099c8c..2e3a93a8 100644 --- a/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts +++ b/apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts @@ -13,6 +13,7 @@ import { MailingListTypeLabelPipe } from '@pipes/mailing-list-type-label.pipe'; import { MailingListVisibilitySeverityPipe } from '@pipes/mailing-list-visibility-severity.pipe'; import { RemainingGroupsTooltipPipe } from '@pipes/remaining-groups-tooltip.pipe'; import { SliceLinkedGroupsPipe } from '@pipes/slice-linked-groups.pipe'; +import { StripHtmlPipe } from '@pipes/strip-html.pipe'; import { TooltipModule } from 'primeng/tooltip'; @Component({ @@ -28,6 +29,7 @@ import { TooltipModule } from 'primeng/tooltip'; MailingListTypeLabelPipe, RemainingGroupsTooltipPipe, SliceLinkedGroupsPipe, + StripHtmlPipe, ], templateUrl: './mailing-list-table.component.html', styleUrl: './mailing-list-table.component.scss', diff --git a/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts b/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts index 13b9113b..f0a637bf 100644 --- a/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts +++ b/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts @@ -9,7 +9,7 @@ import { ButtonComponent } from '@components/button/button.component'; import { CardComponent } from '@components/card/card.component'; import { InputTextComponent } from '@components/input-text/input-text.component'; import { SelectComponent } from '@components/select/select.component'; -import { MAILING_LIST_LABEL } from '@lfx-one/shared/constants'; +import { COMMITTEE_LABEL, MAILING_LIST_LABEL } from '@lfx-one/shared/constants'; import { GroupsIOMailingList, MailingListCommittee, ProjectContext } from '@lfx-one/shared/interfaces'; import { FeatureFlagService } from '@services/feature-flag.service'; import { MailingListService } from '@services/mailing-list.service'; @@ -186,7 +186,7 @@ export class MailingListDashboardComponent { value: committee.uid, })); - return [{ label: 'All Committees', value: null }, ...committeeOptions]; + return [{ label: 'All ' + COMMITTEE_LABEL.plural, value: null }, ...committeeOptions]; }); } diff --git a/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html b/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html index edb88001..adb12eba 100644 --- a/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html +++ b/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html @@ -14,25 +14,53 @@

{{ isEditMode() ? 'Edit Mailing List' : 'Create Mailing List' }}

+ @if (hasNoServices()) { + + +
+
+ +
+
+ +
+
+ + +

Mailing list service hasn't been set up yet for this project. To create a mailing list for your project, contact support.

- - - - - - - + +
+ + +
+
+
+
+ } @else { + + + + + + + - - -
- - @if (!hasNoServices()) { + + +
+
@@ -80,33 +108,11 @@

}

- } - -
- - - @if (hasNoServices()) { - - - -
- Mailing List Service Not Configured - - To create a mailing list, a Groups.io service must be set up for this project or its parent project. Please - - open a support ticket - - to request the service configuration. - -
-
-
- } @else { + +
+ + [prefix]="servicePrefix()" [maxGroupNameLength]="maxGroupNameLength()"> - } - - + + - - - - - + + + + + - - - -
-

Step 3: People & Groups Component

-

Coming next

-
-
-
+ + + +
+

Step 3: People & Groups Component

+

Coming next

+
+
+
+
-
-
- + + + }
diff --git a/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts b/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts index 62bb4a08..7de3a2cf 100644 --- a/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts +++ b/apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts @@ -6,12 +6,12 @@ import { toObservable, toSignal } from '@angular/core/rxjs-interop'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ButtonComponent } from '@components/button/button.component'; -import { MessageComponent } from '@components/message/message.component'; +import { CardComponent } from '@components/card/card.component'; import { MAILING_LIST_TOTAL_STEPS } from '@lfx-one/shared/constants'; import { MailingListAudienceAccess, MailingListType } from '@lfx-one/shared/enums'; import { CreateGroupsIOServiceRequest, CreateMailingListRequest, GroupsIOMailingList, GroupsIOService, MailingListCommittee } from '@lfx-one/shared/interfaces'; import { markFormControlsAsTouched } from '@lfx-one/shared/utils'; -import { announcementVisibilityValidator } from '@lfx-one/shared/validators'; +import { announcementVisibilityValidator, htmlMaxLengthValidator, htmlMinLengthValidator, htmlRequiredValidator } from '@lfx-one/shared/validators'; import { MailingListService } from '@services/mailing-list.service'; import { ProjectContextService } from '@services/project-context.service'; import { ProjectService } from '@services/project.service'; @@ -24,7 +24,7 @@ import { MailingListSettingsComponent } from '../components/mailing-list-setting @Component({ selector: 'lfx-mailing-list-manage', - imports: [ReactiveFormsModule, RouterLink, ButtonComponent, MessageComponent, StepperModule, MailingListBasicInfoComponent, MailingListSettingsComponent], + imports: [ReactiveFormsModule, RouterLink, ButtonComponent, CardComponent, StepperModule, MailingListBasicInfoComponent, MailingListSettingsComponent], templateUrl: './mailing-list-manage.component.html', styleUrl: './mailing-list-manage.component.scss', }) @@ -54,20 +54,23 @@ export class MailingListManageComponent { // Parent service tracking for shared service creation public readonly parentService = signal(null); - public readonly needsSharedServiceCreation = computed(() => this.parentService() !== null && this.availableServices().length === 0); + public readonly needsSharedServiceCreation = computed( + () => this.parentService() !== null && this.availableServices().filter((service) => service.type === 'shared').length === 0 + ); // Prefix calculation for shared services public readonly servicePrefix = computed(() => { if (this.needsSharedServiceCreation()) { const project = this.project(); - return project ? `${this.cleanSlug(project.slug)}-` : ''; + return project ? `${this.cleanSlug(project.slug)}` : ''; } + return this.selectedService()?.prefix || ''; }); // Max group name length accounting for prefix (total max is 34) public readonly maxGroupNameLength = computed(() => { - const prefix = this.servicePrefix(); + const prefix = this.servicePrefix() + '-'; return 34 - prefix.length; }); @@ -127,8 +130,8 @@ export class MailingListManageComponent { serviceCreation$ .pipe( switchMap((newService: GroupsIOService | null) => { - const serviceUid = newService?.uid ?? this.selectedService()?.uid ?? ''; - const data = this.prepareMailingListData(serviceUid); + const service = newService ?? this.selectedService(); + const data = this.prepareMailingListData(service); return this.isEditMode() ? this.mailingListService.updateMailingList(this.mailingListId()!, data) : this.mailingListService.createMailingList(data); }) @@ -165,7 +168,7 @@ export class MailingListManageComponent { { // Step 1: Basic Information group_name: new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(34), Validators.pattern(/^[a-zA-Z0-9_-]+$/)]), - description: new FormControl('', [Validators.required, Validators.minLength(11), Validators.maxLength(500)]), + description: new FormControl('', [htmlRequiredValidator(), htmlMinLengthValidator(11), htmlMaxLengthValidator(500)]), // Step 2: Settings audience_access: new FormControl(MailingListAudienceAccess.PUBLIC, [Validators.required]), @@ -263,16 +266,20 @@ export class MailingListManageComponent { } } - private prepareMailingListData(serviceUid: string): CreateMailingListRequest { + private prepareMailingListData(service: GroupsIOService | null): CreateMailingListRequest { const formValue = this.form().value; + const prefix = this.servicePrefix() || this.cleanSlug(this.project()?.slug || ''); + const groupName = service?.type === 'primary' ? formValue.group_name : `${prefix}-${formValue.group_name}`; + return { - group_name: formValue.group_name, + group_name: groupName, public: formValue.public, type: formValue.type, audience_access: formValue.audience_access, description: formValue.description || '', - service_uid: serviceUid, + service_uid: service?.uid ?? '', committees: formValue.committees?.length > 0 ? formValue.committees : undefined, + title: formValue.group_name, }; } @@ -350,11 +357,9 @@ export class MailingListManageComponent { const serviceData: CreateGroupsIOServiceRequest = { type: 'shared', - prefix: `${this.cleanSlug(project.slug)}-`, + prefix: `${this.cleanSlug(project.slug)}`, project_uid: project.uid, domain: parent.domain, - group_name: parent.group_name, - public: true, }; return this.mailingListService.createService(serviceData); diff --git a/apps/lfx-one/src/app/shared/components/editor/editor.component.html b/apps/lfx-one/src/app/shared/components/editor/editor.component.html new file mode 100644 index 00000000..c5e39339 --- /dev/null +++ b/apps/lfx-one/src/app/shared/components/editor/editor.component.html @@ -0,0 +1,54 @@ + + + + + @if (showToolbar()) { + + + + + + + + + + + + + + + + + + + + + + + + + + + } @else { + + + + } + diff --git a/apps/lfx-one/src/app/shared/components/editor/editor.component.scss b/apps/lfx-one/src/app/shared/components/editor/editor.component.scss new file mode 100644 index 00000000..fc7a7d7e --- /dev/null +++ b/apps/lfx-one/src/app/shared/components/editor/editor.component.scss @@ -0,0 +1,11 @@ +/* Copyright The Linux Foundation and each contributor to LFX. */ +/* SPDX-License-Identifier: MIT */ + +::ng-deep { + .p-editor { + .p-editor-content, + .p-editor-content * { + @apply text-sm; + } + } +} diff --git a/apps/lfx-one/src/app/shared/components/editor/editor.component.ts b/apps/lfx-one/src/app/shared/components/editor/editor.component.ts new file mode 100644 index 00000000..a9be4c4f --- /dev/null +++ b/apps/lfx-one/src/app/shared/components/editor/editor.component.ts @@ -0,0 +1,49 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +import { Component, input, output } from '@angular/core'; +import { FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { EditorModule, EditorSelectionChangeEvent, EditorTextChangeEvent } from 'primeng/editor'; + +type ToolbarOption = string | Record; + +@Component({ + selector: 'lfx-editor', + imports: [EditorModule, ReactiveFormsModule], + templateUrl: './editor.component.html', + styleUrl: './editor.component.scss', +}) +export class EditorComponent { + // Required inputs + public readonly form = input.required(); + public readonly control = input.required(); + + // Optional inputs + public readonly placeholder = input(''); + public readonly style = input>({ height: '200px' }); + public readonly styleClass = input(''); + public readonly readonly = input(false); + public readonly dataTest = input(); + + // Toolbar configuration - default includes requested options + public readonly showToolbar = input(true); + public readonly toolbarOptions = input([ + ['bold', 'italic', 'underline', 'strike'], + [{ list: 'ordered' }, { list: 'bullet' }], + [{ align: ['center', 'right', 'justify'] }], + ['link'], + ['clean'], + ]); + + // Events + public readonly onTextChange = output(); + public readonly onSelectionChange = output(); + + protected handleTextChange(event: EditorTextChangeEvent): void { + this.onTextChange.emit(event); + } + + protected handleSelectionChange(event: EditorSelectionChangeEvent): void { + this.onSelectionChange.emit(event); + } +} diff --git a/apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts b/apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts new file mode 100644 index 00000000..4f96b394 --- /dev/null +++ b/apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts @@ -0,0 +1,14 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +import { Pipe, PipeTransform } from '@angular/core'; +import { stripHtml } from '@lfx-one/shared'; + +@Pipe({ + name: 'stripHtml', +}) +export class StripHtmlPipe implements PipeTransform { + public transform(value: string | null | undefined): string { + return stripHtml(value); + } +} diff --git a/packages/shared/src/interfaces/mailing-list.interface.ts b/packages/shared/src/interfaces/mailing-list.interface.ts index 868d8673..538d329f 100644 --- a/packages/shared/src/interfaces/mailing-list.interface.ts +++ b/packages/shared/src/interfaces/mailing-list.interface.ts @@ -149,15 +149,15 @@ export interface CreateGroupsIOServiceRequest { /** Service type */ type: string; /** Domain for the service */ - domain: string; + domain?: string; /** Prefix for mailing lists (optional) */ prefix?: string; /** Associated project UID */ project_uid: string; /** Groups.io group name */ - group_name: string; + group_name?: string; /** Whether the service is publicly accessible */ - public: boolean; + public?: boolean; } /** diff --git a/packages/shared/src/utils/html-utils.ts b/packages/shared/src/utils/html-utils.ts new file mode 100644 index 00000000..82947efb --- /dev/null +++ b/packages/shared/src/utils/html-utils.ts @@ -0,0 +1,39 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +/** + * Strips HTML tags and decodes common HTML entities from a string. + * This function works in both browser and Node.js (SSR) environments. + * + * @param html - The HTML string to strip tags from + * @returns Plain text with HTML tags removed and entities decoded + * + * @example + * ```typescript + * stripHtml('

Hello & World

') + * // Returns: "Hello & World" + * + * stripHtml(null) + * // Returns: "" + * ``` + */ +export function stripHtml(html: string | null | undefined): string { + if (!html) return ''; + + return ( + html + // Remove HTML tags + .replace(/<[^>]*>/g, '') + // Decode common HTML entities + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/'/g, "'") + .replace(/'/g, "'") + // Trim whitespace + .trim() + ); +} diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index d5eeb19a..e10418b8 100644 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -5,6 +5,7 @@ export * from './color.utils'; export * from './date-time.utils'; export * from './file.utils'; export * from './form.utils'; +export * from './html-utils'; export * from './meeting.utils'; export * from './rsvp-calculator.util'; export * from './string.utils'; diff --git a/packages/shared/src/validators/mailing-list.validators.ts b/packages/shared/src/validators/mailing-list.validators.ts index e6afd4d4..b90ea328 100644 --- a/packages/shared/src/validators/mailing-list.validators.ts +++ b/packages/shared/src/validators/mailing-list.validators.ts @@ -4,6 +4,7 @@ import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; import { MailingListType } from '../enums/mailing-list.enum'; +import { stripHtml } from '../utils/html-utils'; /** * Validator to ensure announcement mailing lists have public visibility @@ -22,3 +23,65 @@ export function announcementVisibilityValidator(): ValidatorFn { return null; }; } + +/** + * Validator for minimum length of HTML content (strips tags before counting) + * @param minLength - Minimum character count for plain text content + */ +export function htmlMinLengthValidator(minLength: number): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + if (!value) return null; // Let required validator handle empty values + + const plainText = stripHtml(value); + if (plainText.length < minLength) { + return { + minlength: { + requiredLength: minLength, + actualLength: plainText.length, + }, + }; + } + + return null; + }; +} + +/** + * Validator for maximum length of HTML content (strips tags before counting) + * @param maxLength - Maximum character count for plain text content + */ +export function htmlMaxLengthValidator(maxLength: number): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + if (!value) return null; + + const plainText = stripHtml(value); + if (plainText.length > maxLength) { + return { + maxlength: { + requiredLength: maxLength, + actualLength: plainText.length, + }, + }; + } + + return null; + }; +} + +/** + * Validator for required HTML content (checks if plain text is not empty) + */ +export function htmlRequiredValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + const plainText = stripHtml(value); + + if (!plainText || plainText.length === 0) { + return { required: true }; + } + + return null; + }; +} diff --git a/yarn.lock b/yarn.lock index d610fb03..08df7fdf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9547,6 +9547,13 @@ __metadata: languageName: node linkType: hard +"fast-diff@npm:^1.3.0": + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29 + languageName: node + linkType: hard + "fast-glob@npm:3.3.3, fast-glob@npm:^3.3.2": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" @@ -11759,6 +11766,7 @@ __metadata: prettier-plugin-organize-imports: "npm:^4.2.0" prettier-plugin-tailwindcss: "npm:^0.6.14" primeng: "npm:^20.4.0" + quill: "npm:^2.0.3" rxjs: "npm:~7.8.2" snowflake-sdk: "npm:^2.3.1" tailwindcss: "npm:^3.4.17" @@ -11945,6 +11953,13 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:^4.17.21": + version: 4.17.22 + resolution: "lodash-es@npm:4.17.22" + checksum: 10c0/5f28a262183cca43e08c580622557f393cb889386df2d8adf7c852bfdff7a84c5e629df5aa6c5c6274e83b38172f239d3e4e72e1ad27352d9ae9766627338089 + languageName: node + linkType: hard + "lodash.camelcase@npm:^4.3.0": version: 4.3.0 resolution: "lodash.camelcase@npm:4.3.0" @@ -11952,6 +11967,13 @@ __metadata: languageName: node linkType: hard +"lodash.clonedeep@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.clonedeep@npm:4.5.0" + checksum: 10c0/2caf0e4808f319d761d2939ee0642fa6867a4bbf2cfce43276698828380756b99d4c4fa226d881655e6ac298dd453fe12a5ec8ba49861777759494c534936985 + languageName: node + linkType: hard + "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -11980,6 +12002,13 @@ __metadata: languageName: node linkType: hard +"lodash.isequal@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f + languageName: node + linkType: hard + "lodash.isinteger@npm:^4.0.4": version: 4.0.4 resolution: "lodash.isinteger@npm:4.0.4" @@ -13358,6 +13387,13 @@ __metadata: languageName: node linkType: hard +"parchment@npm:^3.0.0": + version: 3.0.0 + resolution: "parchment@npm:3.0.0" + checksum: 10c0/83cc55756a899d6769e42b345ae55738f7e93f9027bb0095f518bc75b81d44ba2b69a76bc25f259974d972f8e250066419c3f92e3ded7518f144fc9c1b47430d + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -14198,6 +14234,29 @@ __metadata: languageName: node linkType: hard +"quill-delta@npm:^5.1.0": + version: 5.1.0 + resolution: "quill-delta@npm:5.1.0" + dependencies: + fast-diff: "npm:^1.3.0" + lodash.clonedeep: "npm:^4.5.0" + lodash.isequal: "npm:^4.5.0" + checksum: 10c0/a99462b96177f4559e5a659be0f51bbfe090c11b61c53aa19afabd3fdf8a6495173bbacd84b75acce680ed7c157a024907e74ff077ddd6a135b4da15bf71ada2 + languageName: node + linkType: hard + +"quill@npm:^2.0.3": + version: 2.0.3 + resolution: "quill@npm:2.0.3" + dependencies: + eventemitter3: "npm:^5.0.1" + lodash-es: "npm:^4.17.21" + parchment: "npm:^3.0.0" + quill-delta: "npm:^5.1.0" + checksum: 10c0/9897468b3e2b0fbf9c91471deea745d7b6494f866cb8caace63267769b2c4c6128e49da0988c4ed64f1a91a171fbf91c84009b663b1256a3caca0755204bb6b5 + languageName: node + linkType: hard + "randombytes@npm:^2.1.0": version: 2.1.0 resolution: "randombytes@npm:2.1.0"