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: ![alt](url)

 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:
dwindown
2025-11-15 20:05:50 +07:00
parent 550b3b69ef
commit 4471cd600f
45 changed files with 9194 additions and 508 deletions

View File

@@ -175,7 +175,210 @@ WooNooW enforces a mobilefirst 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.