feat: Complete markdown syntax refinement and variable protection
✅ New cleaner syntax implemented: - [card:type] instead of [card type='type'] - [button:style](url)Text[/button] instead of [button url='...' style='...'] - Standard markdown images:  ✅ Variable protection from markdown parsing: - Variables with underscores (e.g., {order_items_table}) now protected - HTML comment placeholders prevent italic/bold parsing - All variables render correctly in preview ✅ Button rendering fixes: - Buttons work in Visual mode inside cards - Buttons work in Preview mode - Button clicks prevented in visual editor - Proper styling for solid and outline buttons ✅ Backward compatibility: - Old syntax still supported - No breaking changes ✅ Bug fixes: - Fixed order_item_table → order_items_table naming - Fixed button regex to match across newlines - Added button/image parsing to parseMarkdownBasics - Prevented button clicks on .button and .button-outline classes 📚 Documentation: - NEW_MARKDOWN_SYNTAX.md - Complete user guide - MARKDOWN_SYNTAX_AND_VARIABLES.md - Technical analysis
This commit is contained in:
205
PROJECT_SOP.md
205
PROJECT_SOP.md
@@ -175,7 +175,210 @@ WooNooW enforces a mobile‑first responsive standard across all SPA interfaces
|
||||
|
||||
These rules ensure consistent UX across device classes while maintaining WooNooW's design hierarchy.
|
||||
|
||||
### 5.8 Mobile Contextual Header Pattern
|
||||
### 5.8 Dialog Behavior Pattern
|
||||
|
||||
WooNooW uses **Radix UI Dialog** with specific patterns for preventing accidental dismissal.
|
||||
|
||||
**Core Principle:** Prevent outside-click and escape-key dismissal for dialogs with unsaved changes or complex editing.
|
||||
|
||||
**Dialog Types:**
|
||||
|
||||
| Type | Outside Click | Escape Key | Use Case | Example |
|
||||
|------|---------------|------------|----------|---------|
|
||||
| **Informational** | ✅ Allow | ✅ Allow | Simple info, confirmations | Alert dialogs |
|
||||
| **Quick Edit** | ✅ Allow | ✅ Allow | Single field edits | Rename, quick settings |
|
||||
| **Heavy Edit** | ❌ Prevent | ❌ Prevent | Multi-field forms, rich content | Email builder, template editor |
|
||||
| **Destructive** | ❌ Prevent | ❌ Prevent | Delete confirmations with input | Delete with confirmation text |
|
||||
|
||||
**Implementation:**
|
||||
|
||||
```typescript
|
||||
// Heavy Edit Dialog - Prevent accidental dismissal
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
onEscapeKeyDown={(e) => e.preventDefault()}
|
||||
>
|
||||
{/* Dialog content */}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsOpen(false)}>
|
||||
{__('Cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
{__('Save Changes')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
// Quick Edit Dialog - Allow dismissal
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent>
|
||||
{/* Simple content */}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
1. ✅ **Prevent dismissal** when:
|
||||
- Dialog contains unsaved form data
|
||||
- User is editing rich content (WYSIWYG, code editor)
|
||||
- Dialog has multiple steps or complex state
|
||||
- Action is destructive and requires confirmation
|
||||
|
||||
2. ✅ **Allow dismissal** when:
|
||||
- Dialog is purely informational
|
||||
- Single field with auto-save
|
||||
- No data loss risk
|
||||
- Quick actions (view, select)
|
||||
|
||||
3. ✅ **Always provide explicit close buttons**:
|
||||
- Cancel button to close without saving
|
||||
- Save button to commit changes
|
||||
- X button in header (Radix default)
|
||||
|
||||
**Examples:**
|
||||
|
||||
- ❌ Prevent: `admin-spa/src/components/EmailBuilder/EmailBuilder.tsx` - Block edit dialog
|
||||
- ❌ Prevent: Template editor dialogs with rich content
|
||||
- ✅ Allow: Simple confirmation dialogs
|
||||
- ✅ Allow: View-only information dialogs
|
||||
|
||||
**Best Practice:**
|
||||
|
||||
When in doubt, **prevent dismissal** for editing dialogs. It's better to require explicit Cancel/Save than risk data loss.
|
||||
|
||||
**Responsive Dialog/Drawer Pattern:**
|
||||
|
||||
For settings pages and forms, use **ResponsiveDialog** component that automatically switches between Dialog (desktop) and Drawer (mobile):
|
||||
|
||||
```typescript
|
||||
import { ResponsiveDialog } from '@/components/ui/responsive-dialog';
|
||||
|
||||
<ResponsiveDialog
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
title={__('Edit Settings')}
|
||||
description={__('Configure your settings')}
|
||||
footer={
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setIsOpen(false)}>
|
||||
{__('Cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
{__('Save')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{/* Form content */}
|
||||
</ResponsiveDialog>
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- **Desktop (≥768px)**: Shows centered Dialog
|
||||
- **Mobile (<768px)**: Shows bottom Drawer for better reachability
|
||||
|
||||
**Component:** `admin-spa/src/components/ui/responsive-dialog.tsx`
|
||||
|
||||
### 5.9 Settings Page Layout Pattern
|
||||
|
||||
WooNooW enforces a **consistent layout pattern** for all settings pages to ensure predictable UX and maintainability.
|
||||
|
||||
**Core Principle:** All settings pages MUST use `SettingsLayout` component with contextual header.
|
||||
|
||||
**Implementation Pattern:**
|
||||
|
||||
```typescript
|
||||
import { SettingsLayout } from './components/SettingsLayout';
|
||||
|
||||
export default function MySettingsPage() {
|
||||
const [settings, setSettings] = useState({...});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const handleSave = async () => {
|
||||
// Save logic
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SettingsLayout
|
||||
title={__('Page Title')}
|
||||
description={__('Page description')}
|
||||
isLoading={true}
|
||||
>
|
||||
<div className="animate-pulse h-64 bg-muted rounded-lg"></div>
|
||||
</SettingsLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsLayout
|
||||
title={__('Page Title')}
|
||||
description={__('Page description')}
|
||||
onSave={handleSave}
|
||||
saveLabel={__('Save Changes')}
|
||||
>
|
||||
{/* Settings content - automatically boxed with max-w-5xl */}
|
||||
<SettingsCard title={__('Section Title')}>
|
||||
{/* Form fields */}
|
||||
</SettingsCard>
|
||||
</SettingsLayout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**SettingsLayout Props:**
|
||||
|
||||
| Prop | Type | Required | Description |
|
||||
|------|------|----------|-------------|
|
||||
| `title` | `string \| ReactNode` | Yes | Page title shown in contextual header |
|
||||
| `description` | `string` | No | Subtitle/description below title |
|
||||
| `onSave` | `() => Promise<void>` | No | Save handler - shows Save button in header |
|
||||
| `saveLabel` | `string` | No | Custom label for save button (default: "Save changes") |
|
||||
| `isLoading` | `boolean` | No | Shows loading state |
|
||||
| `action` | `ReactNode` | No | Custom action buttons (e.g., Back button) |
|
||||
|
||||
**Layout Behavior:**
|
||||
|
||||
1. **Contextual Header** (Mobile + Desktop)
|
||||
- Shows page title and description
|
||||
- Shows Save button if `onSave` provided
|
||||
- Shows custom actions if `action` provided
|
||||
- Sticky at top of page
|
||||
|
||||
2. **Content Area**
|
||||
- Automatically boxed with `max-w-5xl mx-auto`
|
||||
- Responsive padding and spacing
|
||||
- Consistent with other admin pages
|
||||
|
||||
3. **No Inline Header**
|
||||
- When using `onSave` or `action`, inline header is hidden
|
||||
- Title/description only appear in contextual header
|
||||
- Saves vertical space
|
||||
|
||||
**Rules for Settings Pages:**
|
||||
|
||||
1. ✅ **Always use SettingsLayout** - Never create custom layout
|
||||
2. ✅ **Pass title/description to layout** - Don't render inline headers
|
||||
3. ✅ **Use onSave for save actions** - Don't render save buttons in content
|
||||
4. ✅ **Use SettingsCard for sections** - Consistent card styling
|
||||
5. ✅ **Show loading state** - Use `isLoading` prop during data fetch
|
||||
6. ❌ **Never use full-width layout** - Content is always boxed
|
||||
7. ❌ **Never duplicate save buttons** - One save button in header only
|
||||
|
||||
**Examples:**
|
||||
|
||||
- ✅ Good: `admin-spa/src/routes/Settings/Customers.tsx`
|
||||
- ✅ Good: `admin-spa/src/routes/Settings/Notifications/Staff.tsx`
|
||||
- ✅ Good: `admin-spa/src/routes/Settings/Notifications/Customer.tsx`
|
||||
|
||||
**Files:**
|
||||
- Layout component: `admin-spa/src/routes/Settings/components/SettingsLayout.tsx`
|
||||
- Card component: `admin-spa/src/routes/Settings/components/SettingsCard.tsx`
|
||||
|
||||
### 5.9 Mobile Contextual Header Pattern
|
||||
|
||||
WooNooW implements a **dual-header system** for mobile-first UX, ensuring actionable pages have consistent navigation and action buttons.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user