{"id":50799039,"url":"https://github.com/markrahimi/mandoo-editor","last_synced_at":"2026-06-12T17:30:54.188Z","repository":{"id":363433033,"uuid":"1259245439","full_name":"markrahimi/mandoo-editor","owner":"markrahimi","description":"A modern WYSIWYG editor for React \u0026 Next.js","archived":false,"fork":false,"pushed_at":"2026-06-08T21:39:32.000Z","size":94,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-08T23:21:31.877Z","etag":null,"topics":["block-editor","contenteditable","dark-mode","nextjs","react-editor","rich-text","typescript","wysiwyg"],"latest_commit_sha":null,"homepage":"https://mandooeditor.markrahimi.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/markrahimi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-04T10:17:29.000Z","updated_at":"2026-06-08T21:51:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/markrahimi/mandoo-editor","commit_stats":null,"previous_names":["markrahimi/mandoo-editor"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/markrahimi/mandoo-editor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markrahimi%2Fmandoo-editor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markrahimi%2Fmandoo-editor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markrahimi%2Fmandoo-editor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markrahimi%2Fmandoo-editor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/markrahimi","download_url":"https://codeload.github.com/markrahimi/mandoo-editor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markrahimi%2Fmandoo-editor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34256180,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["block-editor","contenteditable","dark-mode","nextjs","react-editor","rich-text","typescript","wysiwyg"],"created_at":"2026-06-12T17:30:51.762Z","updated_at":"2026-06-12T17:30:54.179Z","avatar_url":"https://github.com/markrahimi.png","language":"TypeScript","funding_links":["https://ko-fi.com/E1E11W0EQP"],"categories":[],"sub_categories":[],"readme":"# MandooEditor\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://mandooeditor.markrahimi.com\"\u003e\n    \u003cimg src=\"./logo.svg\" width=\"80\" alt=\"MandooEditor logo\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eA modern, lightweight WYSIWYG rich text editor for React \u0026 Next.js\u003c/strong\u003e\u003cbr/\u003e\n  Feature-flagged · Fully typed · Zero runtime dependencies · \u0026lt;400KB\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/mandoo-editor\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/mandoo-editor?color=6366f1\u0026label=npm\" alt=\"npm version\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/mandoo-editor\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/mandoo-editor?color=0ea5e9\" alt=\"downloads\" /\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/TypeScript-100%25-3178c6\" alt=\"TypeScript\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/React-18%2B-0ea5e9\" alt=\"React\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Next.js-15%2B-000\" alt=\"Next.js\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT-22c55e\" alt=\"license\" /\u003e\n\u003c/p\u003e\n\n---\n\n## Features\n\n| Feature              | Description                                                                       |\n| -------------------- | --------------------------------------------------------------------------------- |\n| **Visual editor**    | `contenteditable` WYSIWYG — no iframe, no Flash                                   |\n| **Block mode**       | Drag-and-drop block editor with per-block type selector                           |\n| **Text / HTML mode** | Raw HTML editing with syntax highlighting                                         |\n| **HTML \u0026 Markdown**  | `onChange` fires in whichever format you choose                                   |\n| **Feature flags**    | Enable or disable every toolbar button individually                               |\n| **Media upload**     | Wire any S3 / MinIO / custom API — just pass callbacks                            |\n| **Plugins**          | Link checker, tables, image editor, history, YouTube embed, subscript/superscript |\n| **Fully typed**      | End-to-end TypeScript with imperative ref handle                                  |\n| **Zero deps**        | No runtime dependencies beyond React                                              |\n\n---\n\n## Installation\n\n```bash\nnpm install mandoo-editor\n# or\nyarn add mandoo-editor\n# or\npnpm add mandoo-editor\n```\n\n\u003e **⚠️ Required — add this import wherever you use the editor:**\n\n```tsx\nimport 'mandoo-editor/styles';\n```\n\nAdd it in your layout, page, or component — wherever `MandooEditor` is rendered. Without it the editor has no styling.\n\n---\n\n## Quick Start\n\n```tsx\n\"use client\";\n\nimport MandooEditor from \"mandoo-editor\";\n\nexport default function MyPage() {\n  return (\n    \u003cMandooEditor\n      defaultValue=\"\u003cp\u003eStart writing...\u003c/p\u003e\"\n      onChange={(html) =\u003e console.log(html)}\n      height={400}\n    /\u003e\n  );\n}\n```\n\n---\n\n## API Reference\n\n### Props\n\n| Prop           | Type                      | Default                     | Description                              |\n| -------------- | ------------------------- | --------------------------- | ---------------------------------------- |\n| `value`        | `string`                  | —                           | Controlled HTML value                    |\n| `defaultValue` | `string`                  | `''`                        | Uncontrolled initial HTML value          |\n| `onChange`     | `(value: string) =\u003e void` | —                           | Fires on every change with current value |\n| `outputFormat` | `'html' \\| 'markdown'`    | `'html'`                    | Format for `onChange` and `getValue()`   |\n| `placeholder`  | `string`                  | `'Start writing…'`          | Placeholder shown when empty             |\n| `tabs`         | `TabId[]`                 | `['visual','text','block']` | Which tabs to display                    |\n| `defaultTab`   | `TabId`                   | `'visual'`                  | Initially active tab                     |\n| `features`     | `Features`                | all enabled                 | Granular toolbar feature flags           |\n| `plugins`      | `Plugins`                 | none                        | Optional plugin flags                    |\n| `media`        | `MediaConfig`             | —                           | File upload / library config             |\n| `theme`        | `'classic' \\| 'modern'`   | `'classic'`                 | Visual theme                             |\n| `colorScheme`  | `'light' \\| 'dark'`       | `'light'`                   | Color scheme                             |\n| `defaultDir`   | `'rtl' \\| 'ltr'`          | —                           | Default text direction for the editor    |\n| `height`       | `number`                  | `400`                       | Min height of editor content area (px)   |\n| `className`    | `string`                  | —                           | Extra CSS class on root element          |\n| `apiToken`     | `string`                  | —                           | Token for future paid pro features       |\n\n### Imperative Handle (ref)\n\n```tsx\nimport { useRef } from \"react\";\nimport MandooEditor, { MandooEditorHandle } from \"mandoo-editor\";\n\nconst ref = useRef\u003cMandooEditorHandle\u003e(null);\n\n// Methods:\nref.current?.getValue(); // → string (respects outputFormat)\nref.current?.getHTML(); // → raw HTML string\nref.current?.getMarkdown(); // → Markdown string\nref.current?.setValue(html); // set content programmatically\nref.current?.focus(); // focus the editor\nref.current?.clear(); // clear content\n```\n\n---\n\n## Form Integration\n\nMandooEditor outputs HTML or Markdown. There are two ways to use it in a form:\n\n### Option 1 — `name` prop (native forms, FormData, Server Actions)\n\nAdd a `name` prop and a hidden `\u003cinput\u003e` is automatically rendered. Works with any form library or native HTML form submission.\n\n```tsx\n// Native HTML form\n\u003cform action=\"/api/save\" method=\"POST\"\u003e\n  \u003cMandooEditor name=\"content\" outputFormat=\"html\" /\u003e\n  \u003cbutton type=\"submit\"\u003eSave\u003c/button\u003e\n\u003c/form\u003e\n\n// Next.js Server Action\nasync function save(formData: FormData) {\n  'use server';\n  const content = formData.get('content'); // ← HTML or Markdown\n}\n\n\u003cform action={save}\u003e\n  \u003cMandooEditor name=\"content\" outputFormat=\"markdown\" /\u003e\n  \u003cbutton type=\"submit\"\u003eSave\u003c/button\u003e\n\u003c/form\u003e\n```\n\n### Option 2 — `onChange` (controlled state, react-hook-form, Zustand…)\n\n```tsx\n// useState\nconst [content, setContent] = useState('');\n\u003cMandooEditor onChange={setContent} outputFormat=\"html\" /\u003e\n\n// react-hook-form\nconst { setValue } = useForm();\n\u003cMandooEditor onChange={(v) =\u003e setValue('content', v)} outputFormat=\"markdown\" /\u003e\n\n// Zustand / Redux\n\u003cMandooEditor onChange={(v) =\u003e dispatch(setContent(v))} /\u003e\n```\n\n---\n\n## Feature Flags\n\nDisable any toolbar button by setting its flag to `false`:\n\n```tsx\n\u003cMandooEditor\n  features={{\n    // Disable specific buttons\n    strikethrough: false,\n    align: false,\n    charMap: false,\n    help: false,\n    // All others remain enabled\n  }}\n/\u003e\n```\n\nFull list of flags: `bold`, `italic`, `strikethrough`, `lists`, `blockquote`, `hr`, `align`, `link`, `code`, `direction`, `fullscreen`, `kitchenSink`, `underline`, `justify`, `foreColor`, `pasteAsText`, `removeFormat`, `charMap`, `indent`, `undo`, `help`, `media`, `subscript`, `superscript`\n\n---\n\n## Code Formatting\n\nThe `code` feature adds a **Code** button to the toolbar. It has two modes depending on the selection:\n\n| Context | Result |\n|---|---|\n| Text selected | Wraps in inline `\u003ccode\u003e` |\n| No selection / cursor in a block | Converts block to `\u003cpre\u003e` (code block) |\n| Click again inside `\u003ccode\u003e` or `\u003cpre\u003e` | Removes the formatting |\n\nBoth `\u003ccode\u003e` and `\u003cpre\u003e` share the same visual style — monospace font, subtle background from `--me-textarea-bg`, and a matching border — so inline and block code look like a family.\n\n```tsx\n// Disable the code button\n\u003cMandooEditor features={{ code: false }} /\u003e\n```\n\n---\n\n## RTL / LTR Direction\n\nThe `direction` feature adds **RTL** and **LTR** toggle buttons to the toolbar. Direction is applied per block — each paragraph or heading can have its own direction independently.\n\n| Action | Result |\n|---|---|\n| Click RTL | Sets `dir=\"rtl\" style=\"direction:rtl; text-align:right\"` on the current block |\n| Click LTR | Sets `dir=\"ltr\" style=\"direction:ltr; text-align:left\"` on the current block |\n| Click the active button again | Removes direction from the block (toggle off) |\n\nOne button is always highlighted: the active block's direction, or `defaultDir` if set, or LTR by default.\n\n```tsx\n// RTL-first editor (e.g. Persian / Arabic content)\n\u003cMandooEditor defaultDir=\"rtl\" /\u003e\n\n// Disable the direction buttons entirely\n\u003cMandooEditor features={{ direction: false }} /\u003e\n```\n\nDirection is stored inline in the HTML output so it renders correctly anywhere, without requiring the editor's stylesheet:\n\n```html\n\u003cp dir=\"rtl\" style=\"direction: rtl; text-align: right;\"\u003eمتن فارسی\u003c/p\u003e\n\u003cp dir=\"ltr\" style=\"direction: ltr; text-align: left;\"\u003eEnglish paragraph\u003c/p\u003e\n```\n\n---\n\n## Plugins\n\n```tsx\n\u003cMandooEditor\n  plugins={{\n    linkChecker: true, // Validate URLs when inserting links\n    spellChecker: true, // Browser-native spell check\n    tables: true, // Insert \u0026 edit tables\n    imageEditor: true, // Crop/resize images before upload\n    history: true, // Edit history with restore\n    youtube: true, // Embed YouTube videos by URL\n  }}\n/\u003e\n```\n\n---\n\n## Media Upload\n\nWire any storage backend — S3, MinIO, Cloudflare R2, or your own API:\n\n```tsx\n\u003cMandooEditor\n  media={{\n    accept: \"image/*,video/*\",\n    maxSize: 10 * 1024 * 1024, // 10 MB\n\n    async onUpload(file) {\n      const fd = new FormData();\n      fd.append(\"file\", file);\n      const res = await fetch(\"/api/upload\", { method: \"POST\", body: fd });\n      return res.json(); // { url: string, name?: string, alt?: string }\n    },\n\n    async onListFiles() {\n      const res = await fetch(\"/api/media\");\n      return res.json(); // MediaFile[]\n    },\n  }}\n/\u003e\n```\n\n### MinIO / S3 Server Route (Next.js App Router)\n\n```ts\n// app/api/upload/route.ts\nimport { Client } from \"minio\"; // npm install minio\nimport { NextRequest, NextResponse } from \"next/server\";\n\nconst minio = new Client({\n  endPoint: process.env.MINIO_ENDPOINT!,\n  useSSL: true,\n  accessKey: process.env.MINIO_ACCESS_KEY!,\n  secretKey: process.env.MINIO_SECRET_KEY!,\n});\n\nexport async function POST(req: NextRequest) {\n  const form = await req.formData();\n  const file = form.get(\"file\") as File;\n  const buf = Buffer.from(await file.arrayBuffer());\n  const name = `uploads/${Date.now()}-${file.name}`;\n  await minio.putObject(process.env.MINIO_BUCKET!, name, buf, buf.length, {\n    \"Content-Type\": file.type,\n  });\n  const url = await minio.presignedGetObject(\n    process.env.MINIO_BUCKET!,\n    name,\n    604800\n  );\n  return NextResponse.json({ url, name: file.name });\n}\n```\n\n---\n\n## Output Formats\n\n```tsx\n// HTML output (default)\n\u003cMandooEditor\n  outputFormat=\"html\"\n  onChange={(html) =\u003e {\n    // \"\u003cp\u003eHello \u003cstrong\u003eworld\u003c/strong\u003e\u003c/p\u003e\"\n    console.log(html);\n  }}\n/\u003e\n\n// Markdown output\n\u003cMandooEditor\n  outputFormat=\"markdown\"\n  onChange={(md) =\u003e {\n    // \"Hello **world**\"\n    console.log(md);\n  }}\n/\u003e\n```\n\n---\n\n## Tabs Configuration\n\n```tsx\n// Only show Visual and Text tabs (no Block editor)\n\u003cMandooEditor tabs={['visual', 'text']} /\u003e\n\n// Start on Block tab\n\u003cMandooEditor defaultTab=\"block\" /\u003e\n\n// Only Block editor\n\u003cMandooEditor tabs={['block']} /\u003e\n```\n\n---\n\n## Theming\n\n### Built-in themes\n\nMandooEditor ships with two visual themes and two color schemes — mix and match any combination:\n\n```tsx\n// Classic theme (default) — dense toolbar, serif content font\n\u003cMandooEditor theme=\"classic\" colorScheme=\"light\" /\u003e\n\n// Classic dark\n\u003cMandooEditor theme=\"classic\" colorScheme=\"dark\" /\u003e\n\n// Modern theme — minimal toolbar, rounded corners, sans-serif content font\n\u003cMandooEditor theme=\"modern\" colorScheme=\"light\" /\u003e\n\n// Modern dark\n\u003cMandooEditor theme=\"modern\" colorScheme=\"dark\" /\u003e\n```\n\n### CSS customization\n\nEvery color, radius, and font in MandooEditor is driven by CSS custom properties set on the root container. You can override any of them from your own CSS:\n\n```css\n/* globals.css or any stylesheet loaded after mandoo-editor/styles */\n.mandoo-editor-container {\n  --me-accent: #e11d48;            /* links, active buttons, focus rings */\n  --me-container-radius: 4px;      /* outer border radius */\n  --me-content-font: 'Vazirmatn', sans-serif; /* content area font */\n}\n```\n\nYou can also scope overrides to a specific theme or color scheme:\n\n```css\n/* Only affect the modern theme */\n.mandoo-editor-container[data-mandoo-theme=\"modern\"] {\n  --me-accent: #7c3aed;\n  --me-toolbar-bg: #fafafa;\n}\n\n/* Only affect dark mode */\n.mandoo-editor-container[data-mandoo-scheme=\"dark\"] {\n  --me-bg: #18181b;\n  --me-border: #27272a;\n}\n```\n\n### Full list of CSS variables\n\n| Variable | Controls | Classic light default |\n|---|---|---|\n| `--me-bg` | Editor \u0026 modal background | `#ffffff` |\n| `--me-border` | All borders | `#dddddd` |\n| `--me-color` | UI text | `#444444` |\n| `--me-color-strong` | Headings, modal titles | `#23282d` |\n| `--me-toolbar-bg` | Toolbar row background | `#ebebeb` |\n| `--me-tools-bg` | Media/tabs bar background | `#f1f1f1` |\n| `--me-btn-hover` | Button hover background | `#d5d5d5` |\n| `--me-btn-active` | Active/pressed button background | `#b8b8b8` |\n| `--me-btn-bg` | Inactive button background | `#f3f5f6` |\n| `--me-statusbar-bg` | Status bar background | `#ebebeb` |\n| `--me-textarea-bg` | HTML textarea background | `#f9f9f9` |\n| `--me-modal-bg` | Modal body background | `#ffffff` |\n| `--me-modal-header-bg` | Modal header background | `#f1f1f1` |\n| `--me-accent` | Links, focus rings, active state | `#0073aa` |\n| `--me-muted` | Placeholder, counts, labels | `#888888` |\n| `--me-sep` | Toolbar separators | `#cccccc` |\n| `--me-content-color` | Content area text | `#333333` |\n| `--me-content-font` | Content area font family | `Georgia, serif` |\n| `--me-btn-size` | Toolbar button width \u0026 height | `26px` |\n| `--me-btn-radius` | Toolbar button border radius | `2px` |\n| `--me-container-radius` | Outer container border radius | `0px` |\n\n---\n\n## Pro Features (Coming Soon)\n\nThe following features require an `apiToken` and will be available in a future paid tier:\n\n- **Export to PDF** — one-click export via Mandoo cloud API\n- **Word Import/Export** — read and write `.docx` files\n- **AI Assistant** — chat with AI to rewrite, summarise, or extend content\n\n```tsx\n// Reserve your token now — setting it has no effect until pro plugins are released\n\u003cMandooEditor apiToken=\"mk_live_...\" /\u003e\n```\n\n---\n\n## Token Infrastructure\n\n```ts\nimport { mandooFetch, validateToken } from \"mandoo-editor\";\n\n// Validate a token format\nconst valid = validateToken(\"mk_live_abc123...\");\n\n// Call Mandoo API (for pro features)\nconst result = await mandooFetch(\n  \"/export/pdf\",\n  { method: \"POST\", body: fd },\n  {\n    token: \"mk_live_...\",\n    baseUrl: \"https://api.mandooeditor.com/v1\", // optional override\n  }\n);\n```\n\n---\n\n## TypeScript Types\n\n```ts\nimport type {\n  MandooEditorProps,\n  MandooEditorHandle,\n  Features,\n  Plugins,\n  MediaConfig,\n  MediaFile,\n  MediaUploadResult,\n  TabId,\n  OutputFormat,\n  TokenConfig,\n} from \"mandoo-editor\";\n```\n\n---\n\n## Links\n\n|                |                                                                                                  |\n| -------------- | ------------------------------------------------------------------------------------------------ |\n| 🌍 **Website** | [mandooeditor.markrahimi.com](https://mandooeditor.markrahimi.com)                               |\n| 📦 **npm**     | [npmjs.com/package/mandoo-editor](https://www.npmjs.com/package/mandoo-editor)                   |\n| 🐙 **GitHub**  | [github.com/markrahimi/mandoo-editor](https://github.com/markrahimi/mandoo-editor)               |\n| 🐛 **Issues**  | [github.com/markrahimi/mandoo-editor/issues](https://github.com/markrahimi/mandoo-editor/issues) |\n| ☕ **Support** | [ko-fi.com/markrahimi](https://ko-fi.com/E1E11W0EQP)                                             |\n| 👤 **Author**  | [markrahimi.com](https://markrahimi.com)                                                         |\n\n---\n\n## License\n\nMIT © [Mohammad Ali Rahimi](https://markrahimi.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkrahimi%2Fmandoo-editor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarkrahimi%2Fmandoo-editor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkrahimi%2Fmandoo-editor/lists"}