Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ yarn add mandoo-editor
pnpm add mandoo-editor
```

Then import the styles once (e.g. in your root layout):
> **⚠️ Required — add this import wherever you use the editor:**

```tsx
import "mandoo-editor/styles";
import 'mandoo-editor/styles';
```

Add it in your layout, page, or component — wherever `MandooEditor` is rendered. Without it the editor has no styling.

---

## Quick Start
Expand Down Expand Up @@ -115,6 +117,50 @@ ref.current?.clear(); // clear content

---

## Form Integration

MandooEditor outputs HTML or Markdown. There are two ways to use it in a form:

### Option 1 — `name` prop (native forms, FormData, Server Actions)

Add a `name` prop and a hidden `<input>` is automatically rendered. Works with any form library or native HTML form submission.

```tsx
// Native HTML form
<form action="/api/save" method="POST">
<MandooEditor name="content" outputFormat="html" />
<button type="submit">Save</button>
</form>

// Next.js Server Action
async function save(formData: FormData) {
'use server';
const content = formData.get('content'); // ← HTML or Markdown
}

<form action={save}>
<MandooEditor name="content" outputFormat="markdown" />
<button type="submit">Save</button>
</form>
```

### Option 2 — `onChange` (controlled state, react-hook-form, Zustand…)

```tsx
// useState
const [content, setContent] = useState('');
<MandooEditor onChange={setContent} outputFormat="html" />

// react-hook-form
const { setValue } = useForm();
<MandooEditor onChange={(v) => setValue('content', v)} outputFormat="markdown" />

// Zustand / Redux
<MandooEditor onChange={(v) => dispatch(setContent(v))} />
```

---

## Feature Flags

Disable any toolbar button by setting its flag to `false`:
Expand Down Expand Up @@ -310,7 +356,7 @@ import type {
| | |
| -------------- | ------------------------------------------------------------------------------------------------ |
| 🌍 **Website** | [mandooeditor.markrahimi.com](https://mandooeditor.markrahimi.com) |
| 📦 **npm** | [npmjs.com/package/mandoo-editor](https://www.npmjs.com/package/mandoo-editor) |
| 📦 **npm** | [npmjs.com/package/mandoo-editor](https://www.npmjs.com/package/mandoo-editor) |
| 🐙 **GitHub** | [github.com/markrahimi/mandoo-editor](https://github.com/markrahimi/mandoo-editor) |
| 🐛 **Issues** | [github.com/markrahimi/mandoo-editor/issues](https://github.com/markrahimi/mandoo-editor/issues) |
| ☕ **Support** | [ko-fi.com/markrahimi](https://ko-fi.com/E1E11W0EQP) |
Expand Down
33 changes: 22 additions & 11 deletions src/MandooEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, {
useImperativeHandle,
forwardRef,
} from 'react';
import styles from './styles/mandoo.module.css';

import Toolbar from './toolbar/Toolbar';
import VisualEditor from './editor/VisualEditor';
import TextEditor from './editor/TextEditor';
Expand Down Expand Up @@ -43,6 +43,7 @@ const MandooEditor = forwardRef<MandooEditorHandle, MandooEditorProps>(function
features: featuresProp,
media,
plugins = {},
name,
height = DEFAULT_HEIGHT,
className,
},
Expand Down Expand Up @@ -241,19 +242,19 @@ const MandooEditor = forwardRef<MandooEditorHandle, MandooEditorProps>(function
return (
<div
className={[
styles['mandoo-editor-container'],
state.isFullscreen ? styles['mandoo-fullscreen'] : '',
'mandoo-editor-container',
state.isFullscreen ? 'mandoo-fullscreen' : '',
className ?? '',
].filter(Boolean).join(' ')}
id="mandoo-editor-container"
>
{/* Tools bar: Add Media + Tab buttons */}
<div className={styles['mandoo-editor-tools']} id="mandoo-editor-tools">
<div className={styles['mandoo-media-buttons']}>
<div className={'mandoo-editor-tools'} id="mandoo-editor-tools">
<div className={'mandoo-media-buttons'}>
{features.media && (
<button
type="button"
className={styles['btn-add-media']}
className={'btn-add-media'}
title="Add Media"
onClick={() => {
if (!media) {
Expand All @@ -275,7 +276,7 @@ const MandooEditor = forwardRef<MandooEditorHandle, MandooEditorProps>(function
{plugins.tables && (
<button
type="button"
className={styles['btn-add-media']}
className={'btn-add-media'}
title="Insert Table"
onClick={() => { saveRange(); setShowTableModal(true); }}
>
Expand All @@ -285,7 +286,7 @@ const MandooEditor = forwardRef<MandooEditorHandle, MandooEditorProps>(function
{plugins.youtube && (
<button
type="button"
className={styles['btn-add-media']}
className={'btn-add-media'}
title="Embed YouTube Video"
onClick={() => { saveRange(); setShowYoutubeModal(true); }}
style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}
Expand Down Expand Up @@ -320,10 +321,10 @@ const MandooEditor = forwardRef<MandooEditorHandle, MandooEditorProps>(function
/>
)}

<div className={styles['mandoo-editor-tabs']} role="tablist">
<div className={'mandoo-editor-tabs'} role="tablist">
{tabs.map(tab => (
<button key={tab} type="button" role="tab" aria-selected={activeTab === tab}
className={`${styles['mandoo-tab-btn']} ${activeTab === tab ? styles['mandoo-tab-active'] : ''}`}
className={`${'mandoo-tab-btn'} ${activeTab === tab ? 'mandoo-tab-active' : ''}`}
onClick={() => switchTo(tab)}>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
</button>
Expand All @@ -332,7 +333,7 @@ const MandooEditor = forwardRef<MandooEditorHandle, MandooEditorProps>(function
</div>

{/* Editor wrap */}
<div className={styles['mandoo-editor-wrap']} id="mandoo-editor-wrap">
<div className={'mandoo-editor-wrap'} id="mandoo-editor-wrap">
{activeTab === 'block' ? (
<BlockEditor
html={htmlValue}
Expand Down Expand Up @@ -404,6 +405,16 @@ const MandooEditor = forwardRef<MandooEditorHandle, MandooEditorProps>(function
)}
</div>

{/* Hidden input for native form / FormData / server actions integration */}
{name && (
<input
type="hidden"
name={name}
value={outputFormat === 'markdown' ? htmlToMarkdown(htmlValue) : htmlValue}
readOnly
/>
)}

{/* ── Watermark ─────────────────────────────────────────────────────── */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '3px 8px', background: '#f8fafc', borderTop: '1px solid #f1f5f9' }}>
<a href="https://mandooeditor.markrahimi.com" target="_blank" rel="noopener noreferrer"
Expand Down
Loading
Loading