Skip to content

Conversation

@asithade
Copy link
Contributor

@asithade asithade commented Jan 5, 2026

Summary

Enhance mailing list description handling with rich text editing capabilities and proper HTML content management.

Changes

  1. Rich Text Editor Integration

    • Replace textarea with Quill editor component for mailing list descriptions
    • Add Quill dependency for WYSIWYG editing capabilities
    • Implement editor component wrapper for consistent styling
  2. HTML Content Handling

    • Create strip-html pipe to sanitize HTML content for display in tables
    • Add HTML-aware validators (htmlRequired, htmlMinLength, htmlMaxLength)
    • Update description display to show plain text in table views
  3. Mailing List UX Improvements

    • Enhanced project context display in basic info
    • Improved shared service handling with correct prefix logic
    • Better email preview for shared vs non-shared services
    • Enhanced empty state when no services are configured
    • Updated committee filter label to use dynamic terminology

Technical Details

  • New pipe: apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • New component: apps/lfx-one/src/app/shared/components/editor/
  • Updated validators: packages/shared/src/validators/mailing-list.validators.ts
  • Added dependency: quill@^2.0.3

Testing

  • ✅ Verified rich text editing in create/edit flows
  • ✅ Confirmed HTML stripping in table displays
  • ✅ Validated description length constraints
  • ✅ Tested shared service prefix logic
  • ✅ All linting and type checks passed
  • ✅ Build successful

JIRA

LFXV2-950

Generated with Claude Code

- Add Quill rich text editor for mailing list descriptions
- Create strip-html pipe for table display sanitization
- Implement HTML-aware validators (htmlRequired, htmlMinLength, htmlMaxLength)
- Enhance shared service handling with correct prefix logic
- Improve empty state when no services configured
- Update committee filter labels to use dynamic terminology

LFXV2-950

Signed-off-by: Asitha de Silva <[email protected]>
Copilot AI review requested due to automatic review settings January 5, 2026 20:04
@asithade asithade requested a review from jordane as a code owner January 5, 2026 20:04
@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new rich-text editor component and strip-html utility/pipe, HTML-aware validators, shared-service naming and form changes in mailing-list flows, updates templates to use the editor and sanitized descriptions, adjusts interfaces to relax required fields, and adds a quill dependency.

Changes

Cohort / File(s) Summary
Editor component
apps/lfx-one/src/app/shared/components/editor/editor.component.ts, apps/lfx-one/src/app/shared/components/editor/editor.component.html, apps/lfx-one/src/app/shared/components/editor/editor.component.scss
New lfx-editor component wrapping a rich-text editor with reactive-form bindings, toolbar options, text/selection output events, and styling.
Strip-HTML pipe & util
apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts, packages/shared/src/utils/html-utils.ts, packages/shared/src/utils/index.ts
New stripHtml utility (regex + entity decode) exported from utils; StripHtmlPipe wraps it for template use.
HTML validators
packages/shared/src/validators/mailing-list.validators.ts
New validators: htmlRequiredValidator, htmlMinLengthValidator, htmlMaxLengthValidator that validate plain text after stripping HTML.
Mailing List basic info
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts, .../mailing-list-basic-info.component.html
Replaced textarea with editor; added ProjectContextService injection; added isSharedService computed; updated emailPreview and groupNameLengthError to include shared-service prefix handling; owner display now includes service domain.
Mailing List table
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts, .../mailing-list-table.component.html
Imported StripHtmlPipe and applied it in templates to strip HTML from descriptions before fallback rendering.
Mailing List manage
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts, .../mailing-list-manage.component.html
Added "No Services" empty state branch; replaced MessageComponent with CardComponent; switched description validation to HTML validators; refactored shared-service creation and prefix/group_name computation; prepareMailingListData signature changed to accept service object.
Mailing List dashboard
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
Default committee option label now uses COMMITTEE_LABEL.plural constant instead of hardcoded text.
Shared interfaces
packages/shared/src/interfaces/mailing-list.interface.ts
CreateGroupsIOServiceRequest fields domain, group_name, and public made optional (nullable/undefined).
Dependency
apps/lfx-one/package.json
Added dependency: quill ^2.0.3.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Editor as EditorComponent
    participant Form as ReactiveForm
    participant Validators as HTMLValidators
    participant Manage as MailingListManage
    participant API as GroupsIO/MailingListService

    User->>Editor: type/paste HTML description
    Editor->>Form: emit text change
    Form->>Validators: validate description (stripHtml -> length/required)
    Validators-->>Form: validation result

    alt form valid & user submits
        Form->>Manage: submit payload (includes service selection)
        Manage->>Manage: determine isSharedService & compute prefix
        alt needs shared service
            Manage->>API: create shared service (with prefix)
            API-->>Manage: service created (uid)
        end
        Manage->>API: prepareMailingListData(service) & create mailing list
        API-->>User: creation success
    else validation error
        Form-->>User: show validation errors
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main focus of the changeset: implementing a rich text editor and HTML handling for mailing list descriptions, which are the primary changes across all modified files.
Description check ✅ Passed The description is well-organized and directly relates to the changeset, detailing the rich text editor integration, HTML handling, UX improvements, and technical artifacts that align with the actual code changes.
Linked Issues check ✅ Passed All code changes successfully implement the requirements from LFXV2-950: rich text editor integration with Quill dependency, HTML stripping utilities and pipes, HTML-aware validators, improved UX components, and all specified technical artifacts are present.
Out of Scope Changes check ✅ Passed All changes are directly related to the LFXV2-950 objectives: editor component, HTML utilities/validators, mailing list UI updates, and supporting infrastructure. No out-of-scope modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/LFXV2-950

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Fix all issues with AI Agents 🤖
In
@apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts:
- Around line 68-80: The computed property groupNameLengthError incorrectly
double-counts the hyphen and treats prefixValue as truthy even when prefix() is
empty; fix by basing the conditional on prefix() (not prefixValue) and compute
the prefix-with-hyphen length only when prefix() is non-empty (prefix().length +
1), otherwise treat it as 0; ensure prefixValue used in the message includes the
hyphen only when prefix() is non-empty so the displayed "uses X of 34 allowed"
count and text are accurate.

In
@apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts:
- Around line 267-282: prepareMailingListData currently assumes the service
parameter is defined and directly accesses service.type and service.uid; make it
defensive by treating service as optional: if the passed service is falsy, fall
back to this.selectedService() (or an explicit default object) before using it,
and change direct accesses to safe accesses (e.g., use the resolvedService
variable and service?.uid ?? '' pattern) so groupName and service_uid
computation never dereference undefined.

In @apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts:
- Around line 10-18: The transform method in StripHtmlPipe uses
document.createElement which breaks SSR; update transform(value: string | null |
undefined) to first check if typeof document !== 'undefined' and only then use
document.createElement('div') and extract textContent, otherwise fall back to a
safe server-side HTML-stripping approach (e.g., a simple regex strip like
value.replace(/<[^>]+>/g, '') or other non-DOM parsing) so the pipe returns
plain text both on server and client; keep the XSS rationale comment noting this
is a false positive because the element is never attached to the DOM.
🧹 Nitpick comments (2)
apps/lfx-one/src/app/shared/components/editor/editor.component.scss (1)

4-11: LGTM - Editor content styling with Tailwind.

The styling correctly applies text-sm to editor content using Tailwind's @apply directive.

Note: ::ng-deep is deprecated but still necessary for penetrating third-party component encapsulation. Consider migrating to global styles or CSS custom properties when PrimeNG provides better theming hooks.

packages/shared/src/validators/mailing-list.validators.ts (1)

13-18: Consider SSR compatibility for DOMParser usage.

DOMParser is a browser API that may not be available during server-side rendering. If this code runs in an SSR context, it will throw an error.

Consider adding a fallback for SSR environments:

🔎 Proposed fix with SSR fallback
 function stripHtml(html: string): string {
   if (!html) return '';
-  // Remove HTML tags and decode HTML entities
-  const doc = new DOMParser().parseFromString(html, 'text/html');
-  return doc.body.textContent?.trim() || '';
+  // Remove HTML tags and decode HTML entities
+  if (typeof DOMParser !== 'undefined') {
+    const doc = new DOMParser().parseFromString(html, 'text/html');
+    return doc.body.textContent?.trim() || '';
+  }
+  // Fallback for SSR: simple regex-based stripping
+  return html.replace(/<[^>]*>/g, '').trim();
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a6f4173 and 2f42b04.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (14)
  • apps/lfx-one/package.json
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.html
  • apps/lfx-one/src/app/shared/components/editor/editor.component.scss
  • apps/lfx-one/src/app/shared/components/editor/editor.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • packages/shared/src/interfaces/mailing-list.interface.ts
  • packages/shared/src/validators/mailing-list.validators.ts
🧰 Additional context used
📓 Path-based instructions (9)
apps/lfx-one/src/**/*.component.html

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.component.html: Use data-testid naming convention: [section]-[component]-[element] for hierarchical structure
Use flex + flex-col + gap-* instead of space-y-* for spacing

Files:

  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
  • apps/lfx-one/src/app/shared/components/editor/editor.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html
{apps,packages}/**/*.{ts,tsx,js,html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx,js,html,scss,css}: License headers are required on all source files
Always prepend 'Generated with Claude Code (https://claude.ai/code)' if Claude Code assisted with the code

Files:

  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
  • apps/lfx-one/src/app/shared/components/editor/editor.component.scss
  • packages/shared/src/interfaces/mailing-list.interface.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.html
  • packages/shared/src/validators/mailing-list.validators.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html
apps/lfx-one/**/*.{ts,tsx,html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier configuration with Tailwind integration in lfx-one app

Files:

  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
  • apps/lfx-one/src/app/shared/components/editor/editor.component.scss
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html
apps/lfx-one/src/**/*.{html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Tailwind CSS with PrimeUI plugin and LFX colors for styling

Files:

  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
  • apps/lfx-one/src/app/shared/components/editor/editor.component.scss
  • apps/lfx-one/src/app/shared/components/editor/editor.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript interfaces instead of union types for better maintainability

Files:

  • packages/shared/src/interfaces/mailing-list.interface.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • packages/shared/src/validators/mailing-list.validators.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.ts
packages/shared/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

All shared types, interfaces, and constants must be centralized in @lfx-one/shared package

Files:

  • packages/shared/src/interfaces/mailing-list.interface.ts
  • packages/shared/src/validators/mailing-list.validators.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: Do not nest ternary expressions
Always use async/await for promises in TypeScript/JavaScript
Use camelCase for variable and function names

Files:

  • packages/shared/src/interfaces/mailing-list.interface.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • packages/shared/src/validators/mailing-list.validators.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.ts
apps/lfx-one/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.{ts,tsx}: Use zoneless change detection with signals in Angular 19
Always use direct imports for standalone components - no barrel exports
Use ESLint and Prettier for code quality, following Angular-specific ESLint rules

Files:

  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.ts
apps/lfx-one/src/**/*.component.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.component.ts: All PrimeNG components must be wrapped in LFX components for UI library independence
Add data-testid attributes when creating new components for reliable test targeting

Files:

  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.ts
🧠 Learnings (1)
📚 Learning: 2025-12-10T23:20:29.281Z
Learnt from: asithade
Repo: linuxfoundation/lfx-v2-ui PR: 206
File: apps/lfx-one/src/app/modules/meetings/components/agenda-template-selector/agenda-template-selector.component.ts:11-15
Timestamp: 2025-12-10T23:20:29.281Z
Learning: For Angular 19+ projects, standalone defaults to true for components, directives, and pipes when not explicitly declared. Therefore, components with an imports array in the Component decorator do not require an explicit standalone: true declaration to be standalone. In reviews, verify that a component without an explicit standalone flag and with an imports array will behave as a standalone component according to Angular 19+ defaults. If you need to enforce explicit standalone behavior for clarity or consistency, you can still add standalone: true, but it is not required by default in Angular 19+. Ensure this guidance is considered when reviewing any component.ts files across the codebase.

Applied to files:

  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/components/editor/editor.component.ts
🧬 Code graph analysis (4)
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts (1)
packages/shared/src/constants/committees.constants.ts (1)
  • COMMITTEE_LABEL (17-20)
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts (2)
packages/shared/src/validators/mailing-list.validators.ts (3)
  • htmlRequiredValidator (87-98)
  • htmlMinLengthValidator (42-59)
  • htmlMaxLengthValidator (65-82)
packages/shared/src/interfaces/mailing-list.interface.ts (2)
  • GroupsIOService (37-68)
  • CreateMailingListRequest (123-142)
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts (2)
apps/lfx-one/src/app/shared/components/editor/editor.component.ts (1)
  • Component (10-49)
packages/shared/src/interfaces/mailing-list.interface.ts (1)
  • GroupsIOService (37-68)
apps/lfx-one/src/app/shared/components/editor/editor.component.ts (1)
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts (1)
  • Component (11-81)
🪛 ast-grep (0.40.3)
apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts

[warning] 14-14: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: temp.innerHTML = value
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 14-14: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: temp.innerHTML = value
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (16)
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts (2)

12-12: LGTM - Using shared constant for dynamic terminology.

Good use of the COMMITTEE_LABEL constant for configurable UI labels.


189-189: LGTM - Dynamic label for committee filter.

Using COMMITTEE_LABEL.plural ensures consistent terminology across the application.

apps/lfx-one/src/app/shared/components/editor/editor.component.ts (1)

10-49: LGTM - Well-structured editor wrapper component.

The component properly wraps PrimeNG's Editor with Angular 19 signal-based inputs/outputs. The toolbar configuration is sensible, and the event handlers correctly propagate changes.

Note: The dataTest input is correctly exposed for test targeting. As per coding guidelines, ensure the template applies this as data-testid attribute for reliable test targeting.

apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts (1)

33-46: LGTM - Clear shared service detection logic.

The isSharedService computed property correctly identifies shared services by checking both explicit 'shared' type and the case where a primary service is used cross-project.

apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts (1)

16-16: LGTM - StripHtmlPipe integration.

Clean addition of the pipe import and registration for sanitizing HTML descriptions in the table display.

Also applies to: 32-32

apps/lfx-one/package.json (1)

56-56: LGTM - Quill dependency added for rich text editor support.

The addition of quill@^2.0.3 is appropriate for the new EditorComponent wrapping PrimeNG's Editor. The caret constraint ensures compatibility with future patch and minor releases.

apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html (1)

64-64: LGTM!

The stripHtml pipe is correctly applied to sanitize rich text content for table display, with appropriate fallback to '-' for empty descriptions. The pattern is consistently used in both Maintainer and PMO views.

Also applies to: 114-114

packages/shared/src/interfaces/mailing-list.interface.ts (1)

148-161: LGTM!

Making domain, group_name, and public optional aligns with the shared service creation flow where only type, prefix, project_uid, and domain are provided. This supports the new service creation logic in MailingListManageComponent.

apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.html (2)

53-59: LGTM!

The lfx-editor component correctly replaces the textarea for rich text editing. The fixed height and form binding look appropriate.


61-67: Validation error display correctly uses @else if pattern.

The else-if chaining ensures only one error message is shown at a time, which provides a cleaner UX compared to stacking multiple error messages.

apps/lfx-one/src/app/shared/components/editor/editor.component.html (1)

1-54: LGTM! Well-structured editor wrapper component.

The component properly wraps PrimeNG's p-editor following the coding guideline to wrap PrimeNG components for UI library independence. Good use of aria-labels for accessibility on toolbar buttons.

apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html (1)

17-45: LGTM! Good empty state implementation.

The empty state provides clear guidance when no mailing list services are configured, with appropriate styling using flex flex-col gap-* pattern per coding guidelines. The external link properly includes target="_blank" and rel="noopener noreferrer" for security.

packages/shared/src/validators/mailing-list.validators.ts (1)

42-59: LGTM! Well-designed HTML-aware validators.

The validators correctly follow Angular's validator conventions by returning error objects matching the built-in minlength/maxlength/required formats. This ensures seamless integration with the template error handling that checks errors?.['minlength'], etc.

Also applies to: 65-82, 87-98

apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts (3)

169-169: LGTM! HTML validators correctly applied.

The description control now uses htmlRequiredValidator(), htmlMinLengthValidator(11), and htmlMaxLengthValidator(500) which properly validate the plain text content length after stripping HTML tags.


60-73: Prefix handling logic is consistent.

The prefix now excludes the trailing hyphen in servicePrefix() (line 63), but it's correctly added:

  • When calculating maxGroupNameLength (line 71): const prefix = this.servicePrefix() + '-';
  • When constructing groupName in prepareMailingListData (line 270): `${prefix}-${formValue.group_name}`

This keeps the prefix clean for storage while ensuring correct length calculations and name construction.


57-57: Submit flow is unreachable when both parentService() is null and availableServices() is empty due to form visibility control.

The hasNoServices() computed signal (line 53) returns true when no services are available, and the template wraps the entire form/stepper in @if (hasNoServices()) { ... } @else { ... }. This means when availableServices is empty, the form is completely hidden and the onSubmit() button is not accessible, regardless of the parentService value. Therefore, the createSharedService() call at line 126 cannot be reached in the scenario where both parentService() is null and availableServices() is empty.

However, the logic at line 57 remains potentially confusing. Consider clarifying the intent of needsSharedServiceCreation with a comment, or adjust the condition to be more explicit about the shared service creation requirement (e.g., this.parentService() !== null alone, if the empty services case should never reach submission).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements rich text editing capabilities for mailing list descriptions, replacing plain textarea with a Quill-based WYSIWYG editor. It also enhances shared service handling with improved prefix logic, updates the user experience for service configuration states, and adds HTML-aware validators to properly handle rich content.

Key changes:

  • Integrated Quill editor (v2.0.3) with a reusable editor component wrapper for WYSIWYG description editing
  • Added HTML sanitization via strip-html pipe and HTML-aware validators (htmlRequired, htmlMinLength, htmlMaxLength)
  • Improved shared service creation logic and prefix handling for mailing list email addresses

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
yarn.lock Added Quill editor and related dependencies (quill, quill-delta, parchment, lodash-es, fast-diff)
apps/lfx-one/package.json Added quill@^2.0.3 as a dependency
packages/shared/src/validators/mailing-list.validators.ts Added HTML-aware validators and internal stripHtml utility function
packages/shared/src/interfaces/mailing-list.interface.ts Made domain, group_name, and public fields optional in CreateGroupsIOServiceRequest
apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts New pipe for stripping HTML tags from content for table display
apps/lfx-one/src/app/shared/components/editor/ New reusable Quill editor component with toolbar configuration
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/ Updated service prefix logic, shared service creation, and form validation with HTML validators
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/ Updated committee filter label to use dynamic terminology
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/ Applied strip-html pipe to description display in tables
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/ Enhanced project context display, improved email preview for shared services, and integrated rich text editor

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to 72
const prefix = this.servicePrefix() + '-';
return 34 - prefix.length;
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The maxGroupNameLength calculation always adds a hyphen to the prefix, even for non-shared services. This could lead to incorrect length calculations for primary services that don't have a prefix. The hyphen should only be added when calculating for shared services.

Suggested change
const prefix = this.servicePrefix() + '-';
return 34 - prefix.length;
const prefix = this.servicePrefix();
const effectivePrefix = prefix ? `${prefix}-` : '';
return 34 - effectivePrefix.length;

Copilot uses AI. Check for mistakes.
Address critical PR review comments from #218:

- Create shared stripHtml utility for SSR compatibility
  - Implements regex-based HTML stripping that works in both browser and Node.js
  - Replaces DOM-based approaches (document.createElement, DOMParser)
  - Prevents SSR compilation errors during build

- Update StripHtmlPipe to use shared utility
  - Removes browser-only document.createElement() usage
  - Ensures mailing list table descriptions render correctly in SSR

- Update validators to use shared utility
  - Removes DOMParser-based implementation
  - Centralizes HTML stripping logic in one location

- Add null safety to prepareMailingListData method
  - Update signature to accept GroupsIOService | null
  - Use optional chaining for service.type and service.uid
  - Prevents runtime errors when service is null

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Asitha de Silva <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Fix all issues with AI Agents 🤖
In
@apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts:
- Around line 33-46: The shared-service detection in the computed
isSharedService incorrectly treats a primary service as shared when
currentProjectUid is undefined; update the logic in
mailing-list-basic-info.component.ts (computed isSharedService) to handle
missing project context by first deriving currentProjectUid safely from
projectContextService.selectedProject() or selectedFoundation() and if still
undefined return false (or only perform the currentProjectUid !==
service.project_uid check when currentProjectUid is defined), keeping the
existing explicit check for service.type === 'shared' and the
primary-service-as-shared check guarded by a defined currentProjectUid.

In @apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts:
- Around line 7-14: The Pipe decorator for StripHtmlPipe is missing standalone:
true which prevents importing it into standalone components; update the @Pipe
metadata for the StripHtmlPipe class (the decorator above the StripHtmlPipe
declaration that contains name: 'stripHtml') to include standalone: true so the
pipe can be used in component imports; no changes to the transform method or
stripHtml usage are needed, just add the standalone flag to the @Pipe decorator.
♻️ Duplicate comments (3)
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts (1)

68-80: Partial fix applied; truthy check issue remains.

The double-counting issue from the previous review has been partially addressed—line 74 no longer adds +1 to the length. However, the condition on line 73 still checks prefixValue (which is this.prefix() + '-') for truthiness. When prefix() returns an empty string, prefixValue becomes "-", which is truthy, causing a confusing error message: "prefix "-" uses 1 of 34 allowed".

🔎 Recommended fix
   public readonly groupNameLengthError = computed(() => {
     const maxLength = this.maxGroupNameLength();
-    const prefixValue = this.prefix() + '-';
+    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)`;
+    if (this.isSharedService() && prefixValue.length > 0) {
+      const prefixWithHyphenLength = prefixValue.length + 1; // +1 for the hyphen separator
+      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`;
   });
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts (2)

72-75: Fix maxGroupNameLength calculation for primary services.

The calculation always adds a hyphen to the prefix (this.servicePrefix() + '-'), but primary services don't use a prefix in the group name (line 272). This incorrectly reduces the maximum allowed length by 1 character for primary services.

🔎 Proposed fix
 public readonly maxGroupNameLength = computed(() => {
-  const prefix = this.servicePrefix() + '-';
-  return 34 - prefix.length;
+  const service = this.selectedService();
+  if (service?.type === 'primary') {
+    return 34; // No prefix for primary services
+  }
+  const prefix = this.servicePrefix();
+  return 34 - prefix.length - 1; // -1 for the hyphen separator
 });

282-282: Fix title field to use the prefixed group name.

The title field is set to the raw formValue.group_name, but line 275 shows that groupName includes the prefix for shared services. This creates an inconsistency where the title won't match the actual group name for shared services.

🔎 Proposed fix
     return {
       group_name: groupName,
       public: formValue.public,
       type: formValue.type,
       audience_access: formValue.audience_access,
       description: formValue.description || '',
       service_uid: service?.uid ?? '',
       committees: formValue.committees?.length > 0 ? formValue.committees : undefined,
-      title: formValue.group_name,
+      title: groupName,
     };
🧹 Nitpick comments (1)
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts (1)

25-27: LGTM: Fallback chain handles missing project names.

The computed property correctly falls back through available project/foundation names with proper optional chaining.

Optional: consider line breaking for readability

The line is quite long (117+ characters). If you prefer, you could break it for readability:

 public readonly projectName = computed(() => {
-  return this.service()?.project_name || this.projectContextService.selectedProject()?.name || this.projectContextService.selectedFoundation()?.name || '';
+  return (
+    this.service()?.project_name ||
+    this.projectContextService.selectedProject()?.name ||
+    this.projectContextService.selectedFoundation()?.name ||
+    ''
+  );
 });
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2f42b04 and 11967f5.

📒 Files selected for processing (6)
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • packages/shared/src/utils/html-utils.ts
  • packages/shared/src/utils/index.ts
  • packages/shared/src/validators/mailing-list.validators.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript interfaces instead of union types for better maintainability

Files:

  • packages/shared/src/utils/index.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • packages/shared/src/utils/html-utils.ts
  • packages/shared/src/validators/mailing-list.validators.ts
packages/shared/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

All shared types, interfaces, and constants must be centralized in @lfx-one/shared package

Files:

  • packages/shared/src/utils/index.ts
  • packages/shared/src/utils/html-utils.ts
  • packages/shared/src/validators/mailing-list.validators.ts
{apps,packages}/**/*.{ts,tsx,js,html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx,js,html,scss,css}: License headers are required on all source files
Always prepend 'Generated with Claude Code (https://claude.ai/code)' if Claude Code assisted with the code

Files:

  • packages/shared/src/utils/index.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • packages/shared/src/utils/html-utils.ts
  • packages/shared/src/validators/mailing-list.validators.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: Do not nest ternary expressions
Always use async/await for promises in TypeScript/JavaScript
Use camelCase for variable and function names

Files:

  • packages/shared/src/utils/index.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
  • packages/shared/src/utils/html-utils.ts
  • packages/shared/src/validators/mailing-list.validators.ts
apps/lfx-one/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.{ts,tsx}: Use zoneless change detection with signals in Angular 19
Always use direct imports for standalone components - no barrel exports
Use ESLint and Prettier for code quality, following Angular-specific ESLint rules

Files:

  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
apps/lfx-one/src/**/*.component.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.component.ts: All PrimeNG components must be wrapped in LFX components for UI library independence
Add data-testid attributes when creating new components for reliable test targeting

Files:

  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
apps/lfx-one/**/*.{ts,tsx,html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier configuration with Tailwind integration in lfx-one app

Files:

  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
  • apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts
🧠 Learnings (1)
📚 Learning: 2025-12-10T23:20:29.281Z
Learnt from: asithade
Repo: linuxfoundation/lfx-v2-ui PR: 206
File: apps/lfx-one/src/app/modules/meetings/components/agenda-template-selector/agenda-template-selector.component.ts:11-15
Timestamp: 2025-12-10T23:20:29.281Z
Learning: For Angular 19+ projects, standalone defaults to true for components, directives, and pipes when not explicitly declared. Therefore, components with an imports array in the Component decorator do not require an explicit standalone: true declaration to be standalone. In reviews, verify that a component without an explicit standalone flag and with an imports array will behave as a standalone component according to Angular 19+ defaults. If you need to enforce explicit standalone behavior for clarity or consistency, you can still add standalone: true, but it is not required by default in Angular 19+. Ensure this guidance is considered when reviewing any component.ts files across the codebase.

Applied to files:

  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts
🧬 Code graph analysis (3)
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts (3)
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts (1)
  • Component (25-367)
apps/lfx-one/src/app/shared/components/editor/editor.component.ts (1)
  • Component (10-49)
packages/shared/src/interfaces/mailing-list.interface.ts (1)
  • GroupsIOService (37-68)
apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts (1)
packages/shared/src/utils/html-utils.ts (1)
  • stripHtml (20-39)
packages/shared/src/validators/mailing-list.validators.ts (1)
packages/shared/src/utils/html-utils.ts (1)
  • stripHtml (20-39)
🔇 Additional comments (10)
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-basic-info/mailing-list-basic-info.component.ts (3)

4-4: LGTM: Import changes align with editor integration.

The addition of inject, ProjectContextService, and EditorComponent imports correctly supports the new rich text editor and shared service logic.

Also applies to: 6-7, 13-13


17-18: LGTM: Correct dependency injection pattern.

The use of inject() to initialize the projectContextService follows Angular 19+ best practices.


48-60: LGTM: Email preview correctly handles shared service prefixes.

The conditional logic properly formats email addresses for shared vs. non-shared services, aligning with the PR objectives.

packages/shared/src/utils/html-utils.ts (1)

20-39: LGTM! SSR-compatible HTML stripping implementation.

The regex-based approach correctly works in both browser and SSR environments, resolving the previous SSR compatibility concerns. The function appropriately handles null/undefined inputs and decodes common HTML entities.

Note: The regex tag stripping is basic and won't handle all edge cases (e.g., malformed HTML, < in attributes), and entity decoding is limited to common entities. However, this is sufficient for the stated use case of stripping HTML for table displays and validation.

packages/shared/src/utils/index.ts (1)

8-8: LGTM!

The export follows the established pattern and correctly exposes the new HTML utilities to consumers of the shared package.

apps/lfx-one/src/app/shared/pipes/strip-html.pipe.ts (1)

11-13: LGTM! SSR compatibility issue resolved.

The implementation now delegates to the shared stripHtml utility, which uses regex-based stripping that works correctly in both browser and SSR environments. This resolves the previous SSR compatibility concerns with document.createElement.

packages/shared/src/validators/mailing-list.validators.ts (1)

31-87: LGTM! Well-implemented HTML-aware validators.

The three new validators correctly:

  • Strip HTML tags before validating plain text content
  • Return standard Angular validator error formats
  • Handle null/undefined inputs appropriately
  • Delegate empty value handling to the required validator (for min/max length validators)

The implementation properly separates concerns by delegating HTML stripping to the shared utility.

apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts (3)

57-59: LGTM! Correct shared service creation logic.

The logic now correctly uses AND (&&) to check both conditions:

  • Parent service exists, AND
  • No shared services are available

This filters specifically for shared services, ensuring shared service creation only happens when needed. The previous concern about OR vs AND logic has been properly addressed.


171-171: LGTM! Correct HTML-aware validation.

The description field now properly uses the HTML-aware validators (htmlRequiredValidator, htmlMinLengthValidator, htmlMaxLengthValidator) which strip HTML tags before validating plain text content length. This ensures validation works correctly with the rich text editor.


269-284: LGTM! Null-safe service parameter handling.

The method signature now correctly accepts GroupsIOService | null, and uses optional chaining (service?.type, service?.uid) throughout to safely handle null values. This addresses the previous concern about potential undefined access errors.

@asithade asithade merged commit fee0fef into main Jan 5, 2026
6 checks passed
@asithade asithade deleted the feat/LFXV2-950 branch January 5, 2026 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants