From ea3b6a7c772795211d282ce772ef0898d267dabd Mon Sep 17 00:00:00 2001 From: Nic Luckie Date: Wed, 24 Sep 2025 11:47:55 -0400 Subject: [PATCH] refactor(ui): remove Markdown rendering from ConfirmDialog component - Removed Markdown rendering capability from ConfirmDialog component - Removed marked library dependency from package.json and lockfile - Updated all component usages to use plain text descriptions - Converted irreversible action warnings from Markdown to plain text - Simplified component API by removing descriptionMarkdown prop - Updated ConfirmDialog README to reflect simplified implementation - Retained DOMPurify dependency for other components that need it - Updated en.json translations to remove Markdown formatting --- web/package.json | 1 - web/pnpm-lock.yaml | 10 --- web/src/components/ConfirmDialog/README.md | 86 ++++++------------- web/src/components/ConfirmDialog/index.tsx | 27 +----- web/src/components/MemoActionMenu.tsx | 2 +- .../Settings/AccessTokenSection.tsx | 2 +- web/src/components/Settings/MemberSection.tsx | 2 +- .../Settings/UserSessionsSection.tsx | 2 +- .../components/Settings/WebhookSection.tsx | 6 +- web/src/locales/en.json | 25 +++--- 10 files changed, 53 insertions(+), 110 deletions(-) diff --git a/web/package.json b/web/package.json index 3d193c70f..df874bbe1 100644 --- a/web/package.json +++ b/web/package.json @@ -40,7 +40,6 @@ "leaflet": "^1.9.4", "lodash-es": "^4.17.21", "lucide-react": "^0.486.0", - "marked": "^16.3.0", "mermaid": "^11.11.0", "mime": "^4.1.0", "mobx": "^6.13.7", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 3c2ed4a28..66fd93471 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -104,9 +104,6 @@ importers: lucide-react: specifier: ^0.486.0 version: 0.486.0(react@18.3.1) - marked: - specifier: ^16.3.0 - version: 16.3.0 mermaid: specifier: ^11.11.0 version: 11.11.0 @@ -2731,11 +2728,6 @@ packages: engines: {node: '>= 18'} hasBin: true - marked@16.3.0: - resolution: {integrity: sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==} - engines: {node: '>= 20'} - hasBin: true - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -6232,8 +6224,6 @@ snapshots: marked@15.0.12: {} - marked@16.3.0: {} - math-intrinsics@1.1.0: {} mdn-data@2.0.14: {} diff --git a/web/src/components/ConfirmDialog/README.md b/web/src/components/ConfirmDialog/README.md index cd9cfc324..98d41f8df 100644 --- a/web/src/components/ConfirmDialog/README.md +++ b/web/src/components/ConfirmDialog/README.md @@ -2,7 +2,7 @@ ## Overview -`ConfirmDialog` standardizes confirmation flows across the app. It replaces ad‑hoc `window.confirm` usage with an accessible, themeable dialog that supports asynchronous operations and optional Markdown descriptions. +`ConfirmDialog` standardizes confirmation flows across the app. It replaces ad‑hoc `window.confirm` usage with an accessible, themeable dialog that supports asynchronous operations. ## Key Features @@ -11,19 +11,15 @@ - Blocks dismissal while async confirm is pending - Clear separation of title (action) vs description (context) -### 2. Markdown Support -- Optional `descriptionMarkdown` renders localized Markdown -- Sanitized via `DOMPurify` after `marked` parsing - -### 3. Async-Aware +### 2. Async-Aware - Accepts sync or async `onConfirm` - Auto-closes on resolve; remains open on error for retry / toast -### 4. Internationalization Ready +### 3. Internationalization Ready - All labels / text provided by caller through i18n hook - Supports interpolation for dynamic context -### 5. Minimal Surface, Easy Extension +### 4. Minimal Surface, Easy Extension - Lightweight API (few required props) - Style hook via `.container` class (SCSS module) @@ -32,14 +28,12 @@ ``` ConfirmDialog ├── State: loading (tracks pending confirm action) -├── Markdown pipeline: marked → DOMPurify → safe HTML (if descriptionMarkdown) ├── Dialog primitives: Header (title + description), Footer (buttons) └── External control: parent owns open state via onOpenChange ``` ## Usage -### Basic ```tsx import { useTranslate } from "@/utils/i18n"; import ConfirmDialog from "@/components/ConfirmDialog"; @@ -47,28 +41,14 @@ import ConfirmDialog from "@/components/ConfirmDialog"; const t = useTranslate(); ; -``` - -### With Markdown Description & Interpolation -```tsx -; ``` @@ -79,25 +59,24 @@ const t = useTranslate(); | `open` | `boolean` | Yes | `true` (visible) / `false` (hidden) | | `onOpenChange` | `(open: boolean) => void` | Yes | Callback receiving next state; should update parent state | | `title` | `React.ReactNode` | Yes | Short localized action summary (text / node) | -| `description` | `React.ReactNode` | No | Plain content; ignored if `descriptionMarkdown` provided | -| `descriptionMarkdown` | `string` | No | Localized Markdown string (sanitized) | +| `description` | `React.ReactNode` | No | Optional contextual message | | `confirmLabel` | `string` | Yes | Non-empty localized action text (1–2 words) | | `cancelLabel` | `string` | Yes | Localized cancel label | -| `onConfirm` | `() => void \| Promise` | Yes | Sync or async handler; resolve = close, reject = stay open | -| `confirmVariant` | `"default" \| "destructive"` | No | Defaults to `"default"`; use `"destructive"` for irreversible actions | +| `onConfirm` | `() => void | Promise` | Yes | Sync or async handler; resolve = close, reject = stay open | +| `confirmVariant` | `"default" | "destructive"` | No | Defaults to `"default"`; use `"destructive"` for irreversible actions | ## Benefits vs Previous Implementation ### Before (window.confirm / ad‑hoc dialogs) - Blocking native prompt, inconsistent styling - No async progress handling -- No Markdown / rich formatting +- No rich formatting - Hard to localize consistently ### After (ConfirmDialog) - Unified styling + accessibility semantics - Async-safe with loading state shielding -- Markdown or plain description flexibility +- Plain description flexibility - i18n-first via externalized labels ## Technical Implementation Details @@ -105,25 +84,18 @@ const t = useTranslate(); ### Async Handling ```tsx const handleConfirm = async () => { - setLoading(true); - try { - await onConfirm(); // resolve -> close - onOpenChange(false); - } catch (e) { - console.error(e); // remain open for retry - } finally { - setLoading(false); - } + setLoading(true); + try { + await onConfirm(); // resolve -> close + onOpenChange(false); + } catch (e) { + console.error(e); // remain open for retry + } finally { + setLoading(false); + } }; ``` -### Markdown Sanitization -```tsx -const descriptionHtml = descriptionMarkdown - ? DOMPurify.sanitize(String(marked.parse(descriptionMarkdown))) - : null; -``` - ### Close Guard ```tsx !loading && onOpenChange(next)} /> @@ -131,13 +103,11 @@ const descriptionHtml = descriptionMarkdown ## Browser / Environment Support - Works anywhere the existing `Dialog` primitives work (modern browsers) -- Requires DOM for Markdown + sanitization (not SSR executed unless guarded) - No ResizeObserver / layout dependencies ## Performance Considerations -1. Markdown parse + sanitize runs only when `descriptionMarkdown` changes -2. Minimal renders: loading state toggles once per confirm attempt -3. No portal churn—relies on underlying dialog infra +1. Minimal renders: loading state toggles once per confirm attempt +2. No portal churn—relies on underlying dialog infra ## Future Enhancements 1. Severity icon / header accent diff --git a/web/src/components/ConfirmDialog/index.tsx b/web/src/components/ConfirmDialog/index.tsx index f1b594b3d..3145c2b76 100644 --- a/web/src/components/ConfirmDialog/index.tsx +++ b/web/src/components/ConfirmDialog/index.tsx @@ -1,5 +1,3 @@ -import DOMPurify from "dompurify"; -import { marked } from "marked"; import * as React from "react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; @@ -11,10 +9,8 @@ export interface ConfirmDialogProps { onOpenChange: (open: boolean) => void; /** Title content (plain text or React nodes) */ title: React.ReactNode; - /** Optional description as React nodes (ignored if descriptionMarkdown provided) */ + /** Optional description (plain text or React nodes) */ description?: React.ReactNode; - /** Optional description in Markdown. Sanitized & rendered as HTML if provided */ - descriptionMarkdown?: string; /** Confirm / primary action button label */ confirmLabel: string; /** Cancel button label */ @@ -26,8 +22,8 @@ export interface ConfirmDialogProps { } /** - * Accessible confirmation dialog with optional Markdown description. - * - Renders description from either React nodes or sanitized Markdown + * Accessible confirmation dialog. + * - Renders optional description content * - Prevents closing while async confirm action is in-flight * - Minimal opinionated styling; leverages existing UI primitives */ @@ -36,7 +32,6 @@ export default function ConfirmDialog({ onOpenChange, title, description, - descriptionMarkdown, confirmLabel, cancelLabel, onConfirm, @@ -59,26 +54,12 @@ export default function ConfirmDialog({ } }; - // Prepare sanitized HTML if Markdown was provided, memoized for performance - const descriptionHtml = React.useMemo(() => { - return typeof descriptionMarkdown === "string" ? DOMPurify.sanitize(String(marked.parse(descriptionMarkdown))) : null; - }, [descriptionMarkdown]); - return ( !loading && onOpenChange(o)}> {title} - {/* - Rendering sanitized Markdown as HTML. - This is considered safe because DOMPurify removes any potentially dangerous content. - Ensure that Markdown input is trusted or validated upstream. - */} - {descriptionHtml ? ( - - ) : description ? ( - {description} - ) : null} + {description ? {description} : null}