BolEditor / Documentation

BolEditor Documentation

The most powerful AI-powered rich text editor with zero dependencies, 20+ languages, and 14 built-in plugins. Built for developers who demand excellence.

AI-Powered

Generate content, fix grammar, translate, and more with built-in AI.

20+ Languages

Full i18n support with RTL for Arabic and Hebrew.

Zero Dependencies

Pure vanilla JavaScript. Lightweight and fast.

14 Plugins

Extend functionality with our plugin ecosystem.

Quick Example

Get started with BolEditor in seconds:

<!-- Include CSS -->
<link rel="stylesheet" href="boleditor.min.css">

<!-- Include JavaScript -->
<script src="boleditor.bundle.umd.js"></script>

<!-- Create container -->
<div id="editor"></div>

<!-- Initialize -->
<script>
  const editor = new BOLEDITOR('#editor', {
    theme: 'light',
    lang: 'en',
    placeholder: 'Start writing...'
  });
</script>
// ES Module Import
import BOLEDITOR from 'boleditor';
import 'boleditor/dist/boleditor.min.css';

// Initialize editor
const editor = new BOLEDITOR('#editor', {
  theme: 'light',
  lang: 'en',
  minHeight: '400px',
  placeholder: 'Start writing amazing content...',

  // AI Configuration
  ai: {
    enabled: true,
    endpoint: '/api/ai/generate'
  },

  // Autosave
  autosave: {
    enabled: true,
    interval: 30000,
    key: 'my-editor-content'
  }
});

// Get content
const html = editor.getContent();
const text = editor.getText();
import { useEffect, useRef } from 'react';
import BOLEDITOR from 'boleditor';
import 'boleditor/dist/boleditor.min.css';

function Editor({ onChange, initialContent }) {
  const editorRef = useRef(null);
  const instanceRef = useRef(null);

  useEffect(() => {
    if (!instanceRef.current) {
      instanceRef.current = new BOLEDITOR(editorRef.current, {
        theme: 'light',
        lang: 'en',
        content: initialContent,
        ai: { enabled: true, endpoint: '/api/ai' }
      });

      instanceRef.current.on('change', (content) => {
        onChange?.(content);
      });
    }

    return () => {
      instanceRef.current?.destroy();
    };
  }, []);

  return <div ref={editorRef} />;
}

export default Editor;
<template>
  <div ref="editorContainer"></div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import BOLEDITOR from 'boleditor';
import 'boleditor/dist/boleditor.min.css';

const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);

const editorContainer = ref(null);
let editor = null;

onMounted(() => {
  editor = new BOLEDITOR(editorContainer.value, {
    theme: 'light',
    lang: 'en',
    content: props.modelValue,
    ai: { enabled: true, endpoint: '/api/ai' }
  });

  editor.on('change', (content) => {
    emit('update:modelValue', content);
  });
});

onUnmounted(() => {
  editor?.destroy();
});
</script>
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import BOLEDITOR from 'boleditor';

@Component({
  selector: 'app-editor',
  template: '<div #editorContainer></div>'
})
export class EditorComponent implements AfterViewInit, OnDestroy {
  @ViewChild('editorContainer') container!: ElementRef;
  private editor: any;

  ngAfterViewInit() {
    this.editor = new BOLEDITOR(this.container.nativeElement, {
      theme: 'light',
      lang: 'en',
      minHeight: '400px',
      ai: { enabled: true, endpoint: '/api/ai' }
    });

    this.editor.on('change', (content: string) => {
      console.log('Content changed:', content);
    });
  }

  ngOnDestroy() {
    this.editor?.destroy();
  }

  getContent(): string {
    return this.editor?.getContent() || '';
  }
}
Pro Tip

Use the UMD bundle (boleditor.bundle.umd.js) for browser usage and the ES module for modern bundlers.

Installation

Multiple ways to install BolEditor in your project.

Package Managers

npm install boleditor
yarn add boleditor
pnpm add boleditor

CDN

Use BolEditor directly from a CDN without any build step:

<!-- CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/boleditor/dist/boleditor.min.css">

<!-- JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/boleditor/dist/boleditor.bundle.umd.js"></script>
<!-- CSS -->
<link rel="stylesheet" href="https://unpkg.com/boleditor/dist/boleditor.min.css">

<!-- JavaScript -->
<script src="https://unpkg.com/boleditor/dist/boleditor.bundle.umd.js"></script>

Download

Download the latest release and include files manually:

Quick Start

Get up and running with BolEditor in under 5 minutes.

Basic Setup

Step 1: Include Files

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Editor</title>

    <!-- BolEditor CSS -->
    <link rel="stylesheet" href="boleditor.min.css">
</head>
<body>
    <!-- Editor Container -->
    <div id="editor"></div>

    <!-- BolEditor JS -->
    <script src="boleditor.bundle.umd.js"></script>
</body>
</html>

Step 2: Initialize Editor

// Basic initialization
const editor = new BOLEDITOR('#editor');

// With options
const editor = new BOLEDITOR('#editor', {
    theme: 'light',           // 'light' or 'dark'
    lang: 'en',               // Language code
    rtl: false,               // Right-to-left mode
    minHeight: '400px',       // Minimum height
    maxHeight: '800px',       // Maximum height
    placeholder: 'Start writing...',
    content: '<p>Initial content</p>',

    // AI features
    ai: {
        enabled: true,
        endpoint: '/api/ai/generate'
    },

    // Autosave
    autosave: {
        enabled: true,
        interval: 30000,
        key: 'editor-autosave'
    }
});

Step 3: Get/Set Content

// Get HTML content
const htmlContent = editor.getContent();

// Get plain text
const textContent = editor.getText();

// Set content
editor.setContent('<h1>Hello World</h1><p>This is BolEditor.</p>');

// Clear content
editor.clear();

// Check if empty
const isEmpty = editor.isEmpty();
Note

The editor automatically sanitizes content to prevent XSS attacks. All HTML is cleaned before rendering.

Configuration

Complete configuration options for BolEditor.

Options Reference

Option Type Default Description
theme string 'light' Color theme: 'light' or 'dark'
lang string 'en' Interface language code
rtl boolean false Enable right-to-left mode
minHeight string '300px' Minimum editor height
maxHeight string null Maximum editor height
placeholder string '' Placeholder text when empty
content string '' Initial HTML content
ai.enabled boolean false Enable AI features
ai.endpoint string null AI API endpoint URL
autosave.enabled boolean false Enable autosave
autosave.interval number 30000 Autosave interval in ms

API Reference

Complete list of methods and properties available on the editor instance.

Methods

Content Methods

Method Returns Description
getContent() string Get HTML content
setContent(html) void Set HTML content
getText() string Get plain text content
clear() void Clear all content
isEmpty() boolean Check if editor is empty
getWordCount() number Get word count
getCharCount() number Get character count

Editor Methods

Method Returns Description
setTheme(theme) void Change theme ('light' or 'dark')
setLanguage(lang) void Change interface language
setRTL(enabled) void Enable/disable RTL mode
focus() void Focus the editor
blur() void Remove focus from editor
disable() void Disable editing
enable() void Enable editing
destroy() void Destroy editor instance

Events

Listen to editor events to react to user interactions.

Available Events

Event Callback Data Description
change content (string) Content changed
focus - Editor focused
blur - Editor lost focus
ready - Editor initialized
selectionChange selection object Text selection changed

Usage Example

const editor = new BOLEDITOR('#editor');

// Listen to content changes
editor.on('change', (content) => {
    console.log('Content updated:', content);
    // Auto-save to server
    saveToServer(content);
});

// Editor ready
editor.on('ready', () => {
    console.log('Editor is ready!');
});

// Focus events
editor.on('focus', () => {
    console.log('Editor focused');
});

editor.on('blur', () => {
    console.log('Editor blurred');
});

// Remove event listener
editor.off('change', myCallback);

Theming

Customize the editor appearance with themes and CSS variables.

Built-in Themes

BolEditor comes with two built-in themes:

Light Theme

Clean white background with dark text.

Dark Theme

Dark background with light text.

CSS Variables

Override these CSS variables to customize the editor:

.boleditor {
    /* Colors */
    --bol-primary: #8b5cf6;
    --bol-background: #ffffff;
    --bol-text: #1e293b;
    --bol-border: #e2e8f0;

    /* Toolbar */
    --bol-toolbar-bg: #f8fafc;
    --bol-toolbar-btn-hover: #e2e8f0;

    /* Content */
    --bol-content-bg: #ffffff;
    --bol-placeholder: #94a3b8;

    /* Sizing */
    --bol-border-radius: 8px;
    --bol-font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}

Plugins Overview

BolEditor comes with 14 powerful plugins to extend functionality.

Available Plugins

AI Assistant Plugin

Generate content, fix grammar, translate text, and more with AI-powered assistance.

Configuration

const editor = new BOLEDITOR('#editor', {
    ai: {
        enabled: true,
        endpoint: '/api/ai/generate',

        // Optional: Custom headers
        headers: {
            'Authorization': 'Bearer YOUR_API_KEY'
        },

        // Optional: Custom prompts
        prompts: {
            improve: 'Improve this text:',
            grammar: 'Fix grammar and spelling:',
            translate: 'Translate to {language}:',
            summarize: 'Summarize this text:'
        }
    }
});

AI Features

Content Generation

Generate paragraphs, lists, FAQs, and more.

Grammar Check

Fix spelling and grammar errors.

Translation

Translate content to 50+ languages.

Text Improvement

Make text more engaging and professional.

API Required

The AI plugin requires a backend API endpoint. BolEditor sends requests to your endpoint, allowing you to use any AI provider (OpenAI, Anthropic, etc.).

Slash Commands Plugin

Notion-style "/" commands for quick formatting, inserting blocks, and executing actions.

Configuration

const editor = new BOLEDITOR('#editor', {
    slashCommands: {
        enabled: true,
        trigger: '/',  // Character to trigger menu
        placeholder: 'Type a command...'
    }
});

// User types "/" to see command menu
// Available commands: /h1, /h2, /h3, /bullet, /numbered,
// /quote, /code, /divider, /image, /table, /todo
const editor = new BOLEDITOR('#editor', {
    slashCommands: {
        enabled: true,
        commands: [
            // Built-in commands
            { name: 'heading1', label: 'Heading 1', icon: 'bi-type-h1', shortcut: '/h1' },
            { name: 'heading2', label: 'Heading 2', icon: 'bi-type-h2', shortcut: '/h2' },
            { name: 'bullet', label: 'Bullet List', icon: 'bi-list-ul', shortcut: '/bullet' },
            { name: 'numbered', label: 'Numbered List', icon: 'bi-list-ol', shortcut: '/numbered' },
            { name: 'quote', label: 'Quote Block', icon: 'bi-quote', shortcut: '/quote' },
            { name: 'code', label: 'Code Block', icon: 'bi-code', shortcut: '/code' },
            { name: 'divider', label: 'Divider', icon: 'bi-dash-lg', shortcut: '/divider' },

            // Custom commands
            {
                name: 'callout',
                label: 'Callout Box',
                icon: 'bi-info-circle',
                shortcut: '/callout',
                action: (editor) => {
                    editor.insertHTML('<div class="callout">Enter text...</div>');
                }
            },
            {
                name: 'youtube',
                label: 'YouTube Video',
                icon: 'bi-youtube',
                shortcut: '/youtube',
                action: (editor) => {
                    const url = prompt('Enter YouTube URL:');
                    if (url) {
                        const videoId = extractYouTubeId(url);
                        editor.insertHTML(`<iframe src="https://youtube.com/embed/${videoId}"></iframe>`);
                    }
                }
            }
        ]
    }
});
const editor = new BOLEDITOR('#editor', {
    slashCommands: {
        enabled: true,

        // Categorize commands
        categories: [
            {
                name: 'Basic Blocks',
                commands: ['heading1', 'heading2', 'heading3', 'paragraph']
            },
            {
                name: 'Lists',
                commands: ['bullet', 'numbered', 'todo']
            },
            {
                name: 'Media',
                commands: ['image', 'video', 'embed']
            },
            {
                name: 'Advanced',
                commands: ['table', 'code', 'quote', 'divider']
            }
        ],

        // Styling
        menuStyle: {
            maxHeight: '300px',
            width: '280px',
            position: 'below'  // 'below' or 'above'
        },

        // Events
        onSelect: (command, editor) => {
            console.log(`Command selected: ${command.name}`);
            analytics.track('slash_command_used', { command: command.name });
        },

        onOpen: () => console.log('Slash menu opened'),
        onClose: () => console.log('Slash menu closed')
    }
});

// Programmatically trigger slash menu
editor.openSlashMenu();

// Register command at runtime
editor.registerSlashCommand({
    name: 'timestamp',
    label: 'Insert Timestamp',
    icon: 'bi-clock',
    shortcut: '/time',
    action: (editor) => {
        editor.insertText(new Date().toLocaleString());
    }
});

Built-in Commands

Command Shortcut Description
heading1/h1Large heading
heading2/h2Medium heading
heading3/h3Small heading
bullet/bullet, /ulBullet list
numbered/numbered, /olNumbered list
todo/todo, /taskCheckbox list
quote/quoteBlock quote
code/codeCode block
divider/divider, /hrHorizontal line
image/image, /imgInsert image
table/tableInsert table
embed/embedEmbed URL
Pro Tip

Users can type "/" followed by any letters to filter commands. For example, "/h" shows all heading options.

Markdown Plugin

Live markdown shortcuts, import/export, and real-time preview support.

Configuration

const editor = new BOLEDITOR('#editor', {
    markdown: {
        enabled: true,

        // Live conversion while typing
        livePreview: true,

        // Supported syntax
        syntax: {
            bold: true,        // **text** or __text__
            italic: true,      // *text* or _text_
            strikethrough: true, // ~~text~~
            code: true,        // `code` or ```block```
            headings: true,    // # ## ### etc.
            lists: true,       // - or 1.
            links: true,       // [text](url)
            images: true,      // ![alt](url)
            blockquotes: true, // > quote
            horizontalRule: true, // --- or ***
            tables: true       // | col | col |
        },

        // GFM (GitHub Flavored Markdown)
        gfm: true,

        // Convert on paste
        convertOnPaste: true
    }
});
// Import Markdown content
const markdownContent = `
# Welcome to BolEditor

This is **bold** and this is *italic*.

## Features
- Easy to use
- Powerful API
- Zero dependencies

\`\`\`javascript
const editor = new BOLEDITOR('#editor');
\`\`\`

> This is a blockquote

| Name | Value |
|------|-------|
| A    | 1     |
| B    | 2     |
`;

// Set markdown content (auto-converts to HTML)
editor.setMarkdown(markdownContent);

// Get content as Markdown
const md = editor.getMarkdown();
console.log(md);

// Export to Markdown file
editor.exportMarkdown('document.md');

// Import from file input
document.getElementById('mdFile').addEventListener('change', async (e) => {
    const file = e.target.files[0];
    const text = await file.text();
    editor.setMarkdown(text);
});
# Live Markdown Shortcuts

## Text Formatting
**bold text**        → Bold (or Ctrl+B)
*italic text*        → Italic (or Ctrl+I)
~~strikethrough~~    → Strikethrough
`inline code`        → Inline code
[link](url)          → Hyperlink

## Headings (type at start of line)
# Heading 1          → H1 (or Ctrl+1)
## Heading 2         → H2 (or Ctrl+2)
### Heading 3        → H3 (or Ctrl+3)

## Lists (type at start of line)
- item               → Bullet list
* item               → Bullet list
1. item              → Numbered list
- [ ] task           → Todo/checkbox
- [x] done           → Checked todo

## Blocks
> quote              → Blockquote
```                  → Code block (type language after)
---                  → Horizontal rule
***                  → Horizontal rule

## Tables
| Col 1 | Col 2 |
|-------|-------|
| A     | B     |

## Images
![alt text](image-url)

Markdown Shortcuts Table

Type This Get This Keyboard
# textHeading 1Ctrl+1
## textHeading 2Ctrl+2
**text**BoldCtrl+B
*text*ItalicCtrl+I
~~text~~StrikethroughCtrl+Shift+S
`code`CodeCtrl+`
- item• Bullet list-
1. item1. Numbered list-
> quoteBlockquoteCtrl+Shift+Q
---Horizontal rule-

Media Manager Plugin

Drag & drop uploads, image resizing, galleries, and media library management.

Configuration

const editor = new BOLEDITOR('#editor', {
    media: {
        enabled: true,

        // Upload configuration
        upload: {
            endpoint: '/api/upload',
            maxFileSize: 10 * 1024 * 1024, // 10MB
            allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],

            // Headers for upload request
            headers: {
                'Authorization': 'Bearer YOUR_TOKEN'
            }
        },

        // Image handling
        images: {
            resize: true,
            maxWidth: 1920,
            maxHeight: 1080,
            quality: 0.85,
            lazyLoad: true
        },

        // Drag & drop
        dragDrop: {
            enabled: true,
            overlay: true,
            overlayText: 'Drop files here'
        },

        // Paste images from clipboard
        pasteImages: true
    }
});
const editor = new BOLEDITOR('#editor', {
    media: {
        enabled: true,

        // Custom upload handler
        uploadHandler: async (file, progressCallback) => {
            const formData = new FormData();
            formData.append('file', file);

            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();

                xhr.upload.addEventListener('progress', (e) => {
                    if (e.lengthComputable) {
                        const percent = (e.loaded / e.total) * 100;
                        progressCallback(percent);
                    }
                });

                xhr.addEventListener('load', () => {
                    if (xhr.status === 200) {
                        const response = JSON.parse(xhr.responseText);
                        resolve({
                            url: response.url,
                            alt: response.filename,
                            width: response.width,
                            height: response.height
                        });
                    } else {
                        reject(new Error('Upload failed'));
                    }
                });

                xhr.addEventListener('error', () => reject(new Error('Network error')));

                xhr.open('POST', '/api/upload');
                xhr.setRequestHeader('Authorization', 'Bearer TOKEN');
                xhr.send(formData);
            });
        },

        // Events
        onUploadStart: (file) => console.log('Starting upload:', file.name),
        onUploadProgress: (file, percent) => console.log(`${file.name}: ${percent}%`),
        onUploadComplete: (file, result) => console.log('Uploaded:', result.url),
        onUploadError: (file, error) => console.error('Upload failed:', error)
    }
});
const editor = new BOLEDITOR('#editor', {
    media: {
        enabled: true,

        // Media library/gallery
        library: {
            enabled: true,
            endpoint: '/api/media',  // GET endpoint for media list

            // Pagination
            perPage: 20,

            // Search
            searchable: true,

            // Filters
            filters: ['images', 'videos', 'documents'],

            // Custom fetch function
            fetchMedia: async (page, search, filter) => {
                const response = await fetch(
                    `/api/media?page=${page}&search=${search}&type=${filter}`
                );
                const data = await response.json();

                return {
                    items: data.items.map(item => ({
                        id: item.id,
                        url: item.url,
                        thumbnail: item.thumbnail,
                        name: item.filename,
                        type: item.mime_type,
                        size: item.size,
                        createdAt: item.created_at
                    })),
                    total: data.total,
                    hasMore: data.has_more
                };
            }
        },

        // Image editing
        editing: {
            crop: true,
            rotate: true,
            flip: true,
            filters: true,  // Basic filters (brightness, contrast)
            resize: true
        }
    }
});

// Open media library programmatically
editor.openMediaLibrary();

// Insert image programmatically
editor.insertImage({
    src: 'https://example.com/image.jpg',
    alt: 'Description',
    width: 800,
    height: 600
});

Image Toolbar

When an image is selected, a contextual toolbar appears with these options:

Resize

Drag handles or enter exact dimensions.

Alignment

Left, center, right, or float.

Link

Make image clickable with a link.

Caption

Add caption text below image.

Mentions & Tags Plugin

@mentions for users and #hashtags with autocomplete and custom data sources.

Configuration

const editor = new BOLEDITOR('#editor', {
    mentions: {
        enabled: true,

        // @mentions configuration
        users: {
            trigger: '@',
            data: [
                { id: 1, name: 'John Doe', avatar: '/avatars/john.jpg', username: 'johndoe' },
                { id: 2, name: 'Jane Smith', avatar: '/avatars/jane.jpg', username: 'janesmith' },
                { id: 3, name: 'Bob Wilson', avatar: '/avatars/bob.jpg', username: 'bobwilson' }
            ],
            searchKey: 'name',  // Field to search
            displayKey: 'name', // Field to display
            insertKey: 'username', // Field to insert
            limit: 10
        },

        // #hashtags configuration
        tags: {
            trigger: '#',
            data: [
                { id: 1, name: 'javascript', count: 1250 },
                { id: 2, name: 'react', count: 890 },
                { id: 3, name: 'webdev', count: 2100 }
            ],
            searchKey: 'name',
            displayKey: 'name',
            limit: 10
        }
    }
});
const editor = new BOLEDITOR('#editor', {
    mentions: {
        enabled: true,

        users: {
            trigger: '@',

            // Async data fetching
            fetch: async (query) => {
                const response = await fetch(`/api/users/search?q=${encodeURIComponent(query)}`);
                const users = await response.json();

                return users.map(user => ({
                    id: user.id,
                    name: user.full_name,
                    username: user.username,
                    avatar: user.profile_image,
                    role: user.role
                }));
            },

            // Debounce API calls
            debounce: 300,

            // Minimum characters before searching
            minChars: 2,

            // Cache results
            cache: true,
            cacheDuration: 60000 // 1 minute
        },

        tags: {
            trigger: '#',

            fetch: async (query) => {
                const response = await fetch(`/api/tags/search?q=${encodeURIComponent(query)}`);
                return response.json();
            },

            // Allow creating new tags
            allowNew: true,
            createNew: async (tagName) => {
                const response = await fetch('/api/tags', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ name: tagName })
                });
                return response.json();
            }
        }
    }
});

// Events
editor.on('mention', (data) => {
    console.log('User mentioned:', data);
    // Notify user, send email, etc.
    notifyUser(data.userId, 'You were mentioned in a document');
});

editor.on('tag', (data) => {
    console.log('Tag used:', data);
});
const editor = new BOLEDITOR('#editor', {
    mentions: {
        enabled: true,

        users: {
            trigger: '@',
            fetch: fetchUsers,

            // Custom dropdown item template
            renderItem: (user) => `
                <div class="mention-item">
                    <img src="${user.avatar}" alt="${user.name}" class="mention-avatar">
                    <div class="mention-info">
                        <span class="mention-name">${user.name}</span>
                        <span class="mention-username">@${user.username}</span>
                    </div>
                    ${user.verified ? '<span class="verified-badge">✓</span>' : ''}
                </div>
            `,

            // Custom inserted mention template
            renderMention: (user) => `
                <span
                    class="mention"
                    data-user-id="${user.id}"
                    data-username="${user.username}"
                    contenteditable="false">
                    @${user.name}
                </span>
            `,

            // Custom styling class
            menuClass: 'custom-mention-menu',
            itemClass: 'custom-mention-item',
            activeClass: 'custom-mention-active'
        }
    }
});

// Get all mentions from content
const mentions = editor.getMentions();
console.log(mentions);
// [{ id: 1, name: 'John Doe', username: 'johndoe' }, ...]

// Get all tags from content
const tags = editor.getTags();
console.log(tags);
// ['javascript', 'react', 'webdev']
Accessibility

The mentions dropdown is fully keyboard accessible. Use arrow keys to navigate, Enter to select, and Escape to close.

Export Plugin

Export content to PDF, Word (DOCX), Markdown, HTML, and plain text formats.

Configuration

const editor = new BOLEDITOR('#editor', {
    export: {
        enabled: true,
        formats: ['pdf', 'docx', 'md', 'html', 'txt']
    }
});

// Export to different formats
editor.exportPDF('document.pdf');
editor.exportWord('document.docx');
editor.exportMarkdown('document.md');
editor.exportHTML('document.html');
editor.exportText('document.txt');

// Get raw content without downloading
const pdfBlob = await editor.toPDF();
const wordBlob = await editor.toWord();
const markdown = editor.toMarkdown();
const html = editor.toHTML();
const text = editor.toText();
const editor = new BOLEDITOR('#editor', {
    export: {
        enabled: true,

        pdf: {
            // Page setup
            pageSize: 'A4',        // 'A4', 'Letter', 'Legal', or custom { width, height }
            orientation: 'portrait', // 'portrait' or 'landscape'

            // Margins (in mm)
            margins: {
                top: 20,
                right: 20,
                bottom: 20,
                left: 20
            },

            // Header/Footer
            header: {
                text: 'My Document',
                fontSize: 10,
                alignment: 'center'
            },
            footer: {
                text: 'Page {page} of {pages}',
                fontSize: 10,
                alignment: 'center'
            },

            // Styling
            fontSize: 12,
            fontFamily: 'Helvetica',
            lineHeight: 1.5,

            // Images
            imageQuality: 0.92,
            embedImages: true,

            // Metadata
            metadata: {
                title: 'Document Title',
                author: 'Author Name',
                subject: 'Subject',
                keywords: ['keyword1', 'keyword2']
            }
        }
    }
});

// Export with custom options (override defaults)
await editor.exportPDF('report.pdf', {
    pageSize: 'Letter',
    orientation: 'landscape',
    header: { text: 'Quarterly Report Q4 2024' },
    footer: { text: 'Confidential - Page {page}' }
});
const editor = new BOLEDITOR('#editor', {
    export: {
        enabled: true,

        word: {
            // Document properties
            title: 'Document Title',
            creator: 'BolEditor',
            description: 'Created with BolEditor',

            // Page setup
            pageSize: {
                width: 12240,  // twips (8.5 inches)
                height: 15840  // twips (11 inches)
            },

            // Margins (in twips, 1440 twips = 1 inch)
            margins: {
                top: 1440,
                right: 1440,
                bottom: 1440,
                left: 1440
            },

            // Styles
            styles: {
                heading1: { fontSize: 32, bold: true, color: '2E74B5' },
                heading2: { fontSize: 26, bold: true, color: '2E74B5' },
                paragraph: { fontSize: 22, lineSpacing: 276 }
            },

            // Include styles from editor
            preserveStyles: true,

            // Table of contents
            tableOfContents: false
        }
    }
});

// Export with callback
editor.exportWord('document.docx', {
    title: 'My Report'
}).then(() => {
    console.log('Export complete!');
}).catch((error) => {
    console.error('Export failed:', error);
});

Supported Formats

Format Method Description
PDF exportPDF(filename) Portable Document Format with full styling
DOCX exportWord(filename) Microsoft Word compatible document
Markdown exportMarkdown(filename) GitHub-flavored Markdown
HTML exportHTML(filename) Standalone HTML with inline styles
Text exportText(filename) Plain text without formatting

Collaboration Plugin

Real-time collaborative editing with presence indicators, live cursors, and conflict resolution.

Configuration

const editor = new BOLEDITOR('#editor', {
    collaboration: {
        enabled: true,

        // WebSocket endpoint
        url: 'wss://your-server.com/collab',

        // Document identifier
        documentId: 'doc-123',

        // Current user info
        user: {
            id: 'user-456',
            name: 'John Doe',
            color: '#8b5cf6',  // Cursor color
            avatar: '/avatars/john.jpg'
        },

        // Reconnection
        reconnect: true,
        reconnectInterval: 1000,
        maxReconnectAttempts: 10,

        // Sync settings
        debounce: 100,  // Debounce changes before sending

        // Events
        onConnect: () => console.log('Connected to collaboration server'),
        onDisconnect: () => console.log('Disconnected from server'),
        onUserJoin: (user) => console.log(`${user.name} joined`),
        onUserLeave: (user) => console.log(`${user.name} left`),
        onError: (error) => console.error('Collaboration error:', error)
    }
});
// Node.js WebSocket Server Example (using ws library)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

// Store document state and connected users
const documents = new Map();
const users = new Map();

wss.on('connection', (ws) => {
    let currentUser = null;
    let currentDoc = null;

    ws.on('message', (message) => {
        const data = JSON.parse(message);

        switch (data.type) {
            case 'join':
                currentUser = data.user;
                currentDoc = data.documentId;

                // Add user to document
                if (!documents.has(currentDoc)) {
                    documents.set(currentDoc, { content: '', users: new Set() });
                }
                documents.get(currentDoc).users.add(currentUser.id);
                users.set(currentUser.id, ws);

                // Send current state
                ws.send(JSON.stringify({
                    type: 'init',
                    content: documents.get(currentDoc).content,
                    users: Array.from(documents.get(currentDoc).users)
                }));

                // Broadcast user joined
                broadcast(currentDoc, { type: 'user_join', user: currentUser }, ws);
                break;

            case 'update':
                // Apply operation and broadcast
                documents.get(currentDoc).content = data.content;
                broadcast(currentDoc, {
                    type: 'update',
                    operations: data.operations,
                    userId: currentUser.id
                }, ws);
                break;

            case 'cursor':
                // Broadcast cursor position
                broadcast(currentDoc, {
                    type: 'cursor',
                    userId: currentUser.id,
                    position: data.position
                }, ws);
                break;
        }
    });

    ws.on('close', () => {
        if (currentDoc && currentUser) {
            documents.get(currentDoc)?.users.delete(currentUser.id);
            users.delete(currentUser.id);
            broadcast(currentDoc, { type: 'user_leave', user: currentUser });
        }
    });
});

function broadcast(docId, message, exclude = null) {
    const doc = documents.get(docId);
    if (!doc) return;

    doc.users.forEach(userId => {
        const ws = users.get(userId);
        if (ws && ws !== exclude && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify(message));
        }
    });
}
const editor = new BOLEDITOR('#editor', {
    collaboration: {
        enabled: true,
        url: 'wss://your-server.com/collab',
        documentId: 'doc-123',
        user: { id: 'user-1', name: 'John', color: '#8b5cf6' },

        // Presence/cursor settings
        presence: {
            // Show other users' cursors
            cursors: true,
            cursorLabel: true,  // Show name above cursor

            // Show selection highlights
            selections: true,

            // Cursor idle timeout (hide after inactivity)
            idleTimeout: 60000,

            // Custom cursor rendering
            renderCursor: (user) => `
                <div class="remote-cursor" style="background: ${user.color}">
                    <span class="cursor-label">${user.name}</span>
                </div>
            `
        },

        // Awareness (user status)
        awareness: {
            // Broadcast user status
            status: 'active',  // 'active', 'idle', 'away'

            // Custom awareness data
            custom: {
                currentSection: 'introduction',
                viewportPosition: 0
            }
        }
    }
});

// Get connected users
const connectedUsers = editor.getCollaborators();
console.log(connectedUsers);
// [{ id: 'user-2', name: 'Jane', color: '#06b6d4', status: 'active' }]

// Update awareness
editor.setAwareness({
    status: 'idle',
    currentSection: 'conclusion'
});

// Listen for awareness changes
editor.on('awarenessChange', (users) => {
    updateUsersList(users);
});
Server Required

Real-time collaboration requires a WebSocket server. BolEditor uses operational transformation (OT) for conflict resolution. You can use existing solutions like Y.js, ShareDB, or implement your own.

Comments Plugin

Inline commenting system with threaded replies, resolution workflow, and @mentions integration.

Configuration

const editor = new BOLEDITOR('#editor', {
    comments: {
        enabled: true,

        // Comment features
        allowReplies: true,
        allowResolve: true,
        allowDelete: true,
        maxDepth: 3,  // Max reply depth

        // Styling
        highlightColor: 'rgba(255, 213, 79, 0.3)',

        // Current user (required)
        currentUser: {
            id: 'user-123',
            name: 'John Doe',
            avatar: '/avatars/john.jpg'
        },

        // Callbacks
        onCommentAdd: (comment) => {
            console.log('Comment added:', comment);
            // Save to your backend
            saveComment(comment);
        },

        onCommentReply: (comment, reply) => {
            console.log('Reply added:', reply);
            saveReply(comment.id, reply);
        },

        onCommentResolve: (comment) => {
            console.log('Comment resolved:', comment);
            markResolved(comment.id);
        },

        onCommentDelete: (comment) => {
            console.log('Comment deleted:', comment);
            deleteComment(comment.id);
        }
    }
});
const editor = new BOLEDITOR('#editor', {
    comments: {
        enabled: true,
        currentUser: { id: 'user-1', name: 'John Doe' }
    },

    // @mentions configuration
    mentions: {
        enabled: true,
        trigger: '@',

        // Static user list
        data: [
            { id: 1, name: 'John Doe', username: 'john', avatar: '/avatars/john.jpg' },
            { id: 2, name: 'Jane Smith', username: 'jane', avatar: '/avatars/jane.jpg' },
            { id: 3, name: 'Bob Wilson', username: 'bob', avatar: '/avatars/bob.jpg' }
        ],

        // Or fetch from API
        fetch: async (query) => {
            const response = await fetch(`/api/users/search?q=${query}`);
            return response.json();
        },

        // Debounce API calls
        debounce: 200,
        minChars: 2,
        limit: 10,

        // Callback when user is mentioned
        onMention: (user) => {
            console.log('User mentioned:', user);
            notifyUser(user.id, 'You were mentioned');
        }
    },

    // #hashtags configuration
    hashtags: {
        enabled: true,
        trigger: '#',
        allowNew: true,  // Allow creating new tags

        data: [
            { name: 'important', count: 150 },
            { name: 'todo', count: 89 },
            { name: 'review', count: 45 }
        ],

        onHashtag: (tag) => {
            console.log('Tag used:', tag);
        }
    }
});
// Get all comments
const comments = editor.getComments();
console.log(comments);
// [{ id, text, content, author, createdAt, replies, resolved }]

// Get all mentions in the document
const mentions = editor.getMentions();
console.log(mentions);
// [{ value: 'john', text: '@John Doe' }, ...]

// Get all hashtags in the document
const tags = editor.getHashtags();
console.log(tags);
// [{ value: 'important', text: '#important' }, ...]

// Programmatically add a comment
editor.addComment({
    text: 'Selected text to comment on',
    content: 'This is my comment',
    author: { id: 'user-1', name: 'John' }
});

// Resolve a comment
editor.resolveComment('comment-123');

// Delete a comment
editor.deleteComment('comment-123');

// Open comments sidebar
editor.openCommentsSidebar();

// Listen for comment events
editor.on('comment:add', (comment) => {
    console.log('New comment:', comment);
});

editor.on('comment:reply', (comment, reply) => {
    console.log('New reply on:', comment.id);
});

editor.on('mention', (user) => {
    // Send notification to mentioned user
    sendNotification(user.id, {
        type: 'mention',
        message: `You were mentioned by ${currentUser.name}`
    });
});

Keyboard Shortcuts

Shortcut Action
Ctrl+Alt+MAdd comment on selection
@Trigger mentions dropdown
#Trigger hashtags dropdown
Arrow Up/DownNavigate dropdown
Enter / TabSelect mention/tag
EscapeClose dropdown
Pro Tip

Comments work seamlessly with the Collaboration plugin. When multiple users are editing, comments sync in real-time and notifications are sent to mentioned users.

React Integration

Complete guide to using BolEditor with React, including hooks, TypeScript support, and best practices.

Installation

npm install boleditor
# or
yarn add boleditor

Basic Component

import { useEffect, useRef, useState } from 'react';
import BOLEDITOR from 'boleditor';
import 'boleditor/dist/boleditor.min.css';

function BolEditor({
    initialContent = '',
    onChange,
    theme = 'light',
    placeholder = 'Start writing...'
}) {
    const containerRef = useRef(null);
    const editorRef = useRef(null);
    const [isReady, setIsReady] = useState(false);

    useEffect(() => {
        if (!containerRef.current || editorRef.current) return;

        // Initialize editor
        editorRef.current = new BOLEDITOR(containerRef.current, {
            theme,
            placeholder,
            content: initialContent,
            ai: { enabled: true, endpoint: '/api/ai' },
            autosave: { enabled: true, interval: 30000, key: 'draft' }
        });

        // Set up change handler
        editorRef.current.on('change', (content) => {
            onChange?.(content);
        });

        editorRef.current.on('ready', () => {
            setIsReady(true);
        });

        // Cleanup
        return () => {
            editorRef.current?.destroy();
            editorRef.current = null;
        };
    }, []);

    // Update theme when prop changes
    useEffect(() => {
        editorRef.current?.setTheme(theme);
    }, [theme]);

    return (
        <div className="boleditor-wrapper">
            <div ref={containerRef} />
            {!isReady && <div className="loading">Loading editor...</div>}
        </div>
    );
}

export default BolEditor;

// Usage
function App() {
    const [content, setContent] = useState('');

    const handleSave = async () => {
        await fetch('/api/posts', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ content })
        });
    };

    return (
        <div>
            <BolEditor
                initialContent="<p>Hello World</p>"
                onChange={setContent}
                theme="light"
            />
            <button onClick={handleSave}>Save</button>
        </div>
    );
}
import { useEffect, useRef, useState, useCallback } from 'react';
import BOLEDITOR from 'boleditor';
import 'boleditor/dist/boleditor.min.css';

// Custom hook for BolEditor
function useBolEditor(options = {}) {
    const containerRef = useRef(null);
    const editorRef = useRef(null);
    const [isReady, setIsReady] = useState(false);
    const [wordCount, setWordCount] = useState(0);

    useEffect(() => {
        if (!containerRef.current) return;

        editorRef.current = new BOLEDITOR(containerRef.current, {
            theme: 'light',
            ...options
        });

        editorRef.current.on('ready', () => setIsReady(true));
        editorRef.current.on('change', () => {
            setWordCount(editorRef.current.getWordCount());
        });

        return () => {
            editorRef.current?.destroy();
        };
    }, []);

    const getContent = useCallback(() => {
        return editorRef.current?.getContent() || '';
    }, []);

    const setContent = useCallback((html) => {
        editorRef.current?.setContent(html);
    }, []);

    const clear = useCallback(() => {
        editorRef.current?.clear();
    }, []);

    const focus = useCallback(() => {
        editorRef.current?.focus();
    }, []);

    const exportPDF = useCallback((filename) => {
        editorRef.current?.exportPDF(filename);
    }, []);

    return {
        containerRef,
        editorRef,
        isReady,
        wordCount,
        getContent,
        setContent,
        clear,
        focus,
        exportPDF
    };
}

// Usage
function Editor() {
    const {
        containerRef,
        isReady,
        wordCount,
        getContent,
        exportPDF
    } = useBolEditor({
        placeholder: 'Write something amazing...',
        ai: { enabled: true, endpoint: '/api/ai' }
    });

    return (
        <div>
            <div className="toolbar">
                <span>{wordCount} words</span>
                <button onClick={() => exportPDF('document.pdf')}>
                    Export PDF
                </button>
            </div>
            <div ref={containerRef} />
        </div>
    );
}

export { useBolEditor };
export default Editor;
import { useEffect, useRef, useState, FC } from 'react';
import BOLEDITOR, { BolEditorOptions, BolEditorInstance } from 'boleditor';
import 'boleditor/dist/boleditor.min.css';

interface EditorProps {
    initialContent?: string;
    onChange?: (content: string) => void;
    onReady?: (editor: BolEditorInstance) => void;
    theme?: 'light' | 'dark';
    placeholder?: string;
    className?: string;
}

const BolEditorComponent: FC<EditorProps> = ({
    initialContent = '',
    onChange,
    onReady,
    theme = 'light',
    placeholder = 'Start writing...',
    className = ''
}) => {
    const containerRef = useRef<HTMLDivElement>(null);
    const editorRef = useRef<BolEditorInstance | null>(null);
    const [isReady, setIsReady] = useState(false);

    useEffect(() => {
        if (!containerRef.current || editorRef.current) return;

        const options: BolEditorOptions = {
            theme,
            placeholder,
            content: initialContent,
            ai: {
                enabled: true,
                endpoint: '/api/ai/generate'
            }
        };

        editorRef.current = new BOLEDITOR(containerRef.current, options);

        editorRef.current.on('change', (content: string) => {
            onChange?.(content);
        });

        editorRef.current.on('ready', () => {
            setIsReady(true);
            onReady?.(editorRef.current!);
        });

        return () => {
            editorRef.current?.destroy();
            editorRef.current = null;
        };
    }, []);

    useEffect(() => {
        if (editorRef.current && theme) {
            editorRef.current.setTheme(theme);
        }
    }, [theme]);

    return (
        <div className={`boleditor-react ${className}`}>
            <div ref={containerRef} />
        </div>
    );
};

export default BolEditorComponent;

// Type definitions (boleditor.d.ts)
declare module 'boleditor' {
    export interface BolEditorOptions {
        theme?: 'light' | 'dark';
        lang?: string;
        rtl?: boolean;
        placeholder?: string;
        content?: string;
        minHeight?: string;
        maxHeight?: string;
        ai?: {
            enabled: boolean;
            endpoint: string;
            headers?: Record<string, string>;
        };
        autosave?: {
            enabled: boolean;
            interval: number;
            key: string;
        };
    }

    export interface BolEditorInstance {
        getContent(): string;
        setContent(html: string): void;
        getText(): string;
        clear(): void;
        isEmpty(): boolean;
        getWordCount(): number;
        setTheme(theme: 'light' | 'dark'): void;
        focus(): void;
        blur(): void;
        destroy(): void;
        on(event: string, callback: Function): void;
        off(event: string, callback: Function): void;
        exportPDF(filename: string): Promise<void>;
        exportWord(filename: string): Promise<void>;
    }

    export default class BOLEDITOR {
        constructor(
            element: string | HTMLElement,
            options?: BolEditorOptions
        );
    }
}

Form Integration

import { useForm, Controller } from 'react-hook-form';
import BolEditor from './BolEditor';

function PostForm() {
    const { control, handleSubmit, formState: { errors } } = useForm({
        defaultValues: {
            title: '',
            content: ''
        }
    });

    const onSubmit = async (data) => {
        await fetch('/api/posts', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
    };

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <input {...register('title', { required: true })} />

            <Controller
                name="content"
                control={control}
                rules={{ required: 'Content is required' }}
                render={({ field }) => (
                    <BolEditor
                        initialContent={field.value}
                        onChange={field.onChange}
                    />
                )}
            />
            {errors.content && <span>{errors.content.message}</span>}

            <button type="submit">Publish</button>
        </form>
    );
}
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
import BolEditor from './BolEditor';

const validationSchema = Yup.object({
    title: Yup.string().required('Title is required'),
    content: Yup.string()
        .required('Content is required')
        .min(100, 'Content must be at least 100 characters')
});

function PostForm() {
    return (
        <Formik
            initialValues={{ title: '', content: '' }}
            validationSchema={validationSchema}
            onSubmit={async (values) => {
                await fetch('/api/posts', {
                    method: 'POST',
                    body: JSON.stringify(values)
                });
            }}
        >
            {({ setFieldValue, values, errors, touched }) => (
                <Form>
                    <Field name="title" placeholder="Post title" />

                    <BolEditor
                        initialContent={values.content}
                        onChange={(content) => setFieldValue('content', content)}
                    />
                    {touched.content && errors.content && (
                        <div className="error">{errors.content}</div>
                    )}

                    <button type="submit">Submit</button>
                </Form>
            )}
        </Formik>
    );
}

Vue.js Integration

Complete guide to using BolEditor with Vue 3, including Composition API, v-model support, and TypeScript.

Installation

npm install boleditor
# or
yarn add boleditor

Vue 3 Component

<!-- BolEditor.vue -->
<template>
    <div class="boleditor-vue">
        <div ref="editorContainer"></div>
        <div v-if="!isReady" class="loading">Loading...</div>
    </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import BOLEDITOR from 'boleditor';
import 'boleditor/dist/boleditor.min.css';

const props = defineProps({
    modelValue: { type: String, default: '' },
    theme: { type: String, default: 'light' },
    placeholder: { type: String, default: 'Start writing...' },
    aiEnabled: { type: Boolean, default: true },
    aiEndpoint: { type: String, default: '/api/ai' }
});

const emit = defineEmits(['update:modelValue', 'ready', 'focus', 'blur']);

const editorContainer = ref(null);
const isReady = ref(false);
let editor = null;

onMounted(() => {
    editor = new BOLEDITOR(editorContainer.value, {
        theme: props.theme,
        placeholder: props.placeholder,
        content: props.modelValue,
        ai: {
            enabled: props.aiEnabled,
            endpoint: props.aiEndpoint
        }
    });

    editor.on('change', (content) => {
        emit('update:modelValue', content);
    });

    editor.on('ready', () => {
        isReady.value = true;
        emit('ready', editor);
    });

    editor.on('focus', () => emit('focus'));
    editor.on('blur', () => emit('blur'));
});

onUnmounted(() => {
    editor?.destroy();
});

// Watch for theme changes
watch(() => props.theme, (newTheme) => {
    editor?.setTheme(newTheme);
});

// Expose methods
defineExpose({
    getContent: () => editor?.getContent(),
    setContent: (html) => editor?.setContent(html),
    clear: () => editor?.clear(),
    focus: () => editor?.focus(),
    exportPDF: (filename) => editor?.exportPDF(filename),
    getWordCount: () => editor?.getWordCount()
});
</script>

<style scoped>
.boleditor-vue {
    position: relative;
}
.loading {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.8);
}
</style>
<!-- BolEditor.vue (Options API) -->
<template>
    <div ref="editorContainer"></div>
</template>

<script>
import BOLEDITOR from 'boleditor';
import 'boleditor/dist/boleditor.min.css';

export default {
    name: 'BolEditor',

    props: {
        modelValue: { type: String, default: '' },
        theme: { type: String, default: 'light' },
        placeholder: { type: String, default: '' }
    },

    emits: ['update:modelValue', 'ready'],

    data() {
        return {
            editor: null
        };
    },

    mounted() {
        this.editor = new BOLEDITOR(this.$refs.editorContainer, {
            theme: this.theme,
            placeholder: this.placeholder,
            content: this.modelValue
        });

        this.editor.on('change', (content) => {
            this.$emit('update:modelValue', content);
        });

        this.editor.on('ready', () => {
            this.$emit('ready', this.editor);
        });
    },

    beforeUnmount() {
        this.editor?.destroy();
    },

    watch: {
        theme(newTheme) {
            this.editor?.setTheme(newTheme);
        }
    },

    methods: {
        getContent() {
            return this.editor?.getContent();
        },
        setContent(html) {
            this.editor?.setContent(html);
        },
        clear() {
            this.editor?.clear();
        }
    }
};
</script>
<!-- BolEditor.vue (TypeScript) -->
<template>
    <div ref="editorContainer"></div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue';
import BOLEDITOR from 'boleditor';
import type { BolEditorInstance, BolEditorOptions } from 'boleditor';
import 'boleditor/dist/boleditor.min.css';

interface Props {
    modelValue?: string;
    theme?: 'light' | 'dark';
    placeholder?: string;
    aiEnabled?: boolean;
    aiEndpoint?: string;
}

const props = withDefaults(defineProps<Props>(), {
    modelValue: '',
    theme: 'light',
    placeholder: 'Start writing...',
    aiEnabled: true,
    aiEndpoint: '/api/ai'
});

const emit = defineEmits<{
    'update:modelValue': [value: string];
    'ready': [editor: BolEditorInstance];
    'focus': [];
    'blur': [];
}>();

const editorContainer = ref<HTMLDivElement | null>(null);
let editor: BolEditorInstance | null = null;

onMounted(() => {
    if (!editorContainer.value) return;

    const options: BolEditorOptions = {
        theme: props.theme,
        placeholder: props.placeholder,
        content: props.modelValue,
        ai: {
            enabled: props.aiEnabled,
            endpoint: props.aiEndpoint
        }
    };

    editor = new BOLEDITOR(editorContainer.value, options);

    editor.on('change', (content: string) => {
        emit('update:modelValue', content);
    });

    editor.on('ready', () => {
        emit('ready', editor!);
    });
});

onUnmounted(() => {
    editor?.destroy();
});

watch(() => props.theme, (newTheme) => {
    editor?.setTheme(newTheme);
});

// Expose methods for parent component
defineExpose({
    getContent: (): string => editor?.getContent() ?? '',
    setContent: (html: string): void => editor?.setContent(html),
    clear: (): void => editor?.clear(),
    focus: (): void => editor?.focus(),
    getWordCount: (): number => editor?.getWordCount() ?? 0
});
</script>

Usage

<template>
    <div class="post-editor">
        <input v-model="title" placeholder="Post title" />

        <BolEditor
            v-model="content"
            :theme="isDarkMode ? 'dark' : 'light'"
            placeholder="Write your post..."
            @ready="onEditorReady"
            ref="editorRef"
        />

        <div class="actions">
            <span>{{ wordCount }} words</span>
            <button @click="exportPDF">Export PDF</button>
            <button @click="save">Save</button>
        </div>
    </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import BolEditor from './components/BolEditor.vue';

const title = ref('');
const content = ref('');
const isDarkMode = ref(false);
const editorRef = ref(null);
const wordCount = ref(0);

const onEditorReady = (editor) => {
    console.log('Editor is ready!');
};

const exportPDF = () => {
    editorRef.value?.exportPDF('my-post.pdf');
};

const save = async () => {
    await fetch('/api/posts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            title: title.value,
            content: content.value
        })
    });
};
</script>

Angular Integration

Complete guide to using BolEditor with Angular, including reactive forms, services, and NgModule setup.

Installation

npm install boleditor

# Add CSS to angular.json styles array:
# "styles": ["node_modules/boleditor/dist/boleditor.min.css"]

Angular Component

// boleditor.component.ts
import {
    Component,
    ElementRef,
    ViewChild,
    AfterViewInit,
    OnDestroy,
    Input,
    Output,
    EventEmitter,
    forwardRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import BOLEDITOR from 'boleditor';

@Component({
    selector: 'app-boleditor',
    template: '<div #editorContainer></div>',
    providers: [{
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => BolEditorComponent),
        multi: true
    }]
})
export class BolEditorComponent implements AfterViewInit, OnDestroy, ControlValueAccessor {
    @ViewChild('editorContainer') container!: ElementRef;

    @Input() theme: 'light' | 'dark' = 'light';
    @Input() placeholder: string = 'Start writing...';
    @Input() aiEnabled: boolean = true;
    @Input() aiEndpoint: string = '/api/ai';

    @Output() ready = new EventEmitter<any>();
    @Output() focus = new EventEmitter<void>();
    @Output() blur = new EventEmitter<void>();

    private editor: any;
    private onChange: (value: string) => void = () => {};
    private onTouched: () => void = () => {};
    private initialContent: string = '';

    ngAfterViewInit(): void {
        this.editor = new BOLEDITOR(this.container.nativeElement, {
            theme: this.theme,
            placeholder: this.placeholder,
            content: this.initialContent,
            ai: {
                enabled: this.aiEnabled,
                endpoint: this.aiEndpoint
            }
        });

        this.editor.on('change', (content: string) => {
            this.onChange(content);
        });

        this.editor.on('ready', () => {
            this.ready.emit(this.editor);
        });

        this.editor.on('focus', () => this.focus.emit());
        this.editor.on('blur', () => {
            this.onTouched();
            this.blur.emit();
        });
    }

    ngOnDestroy(): void {
        this.editor?.destroy();
    }

    // ControlValueAccessor implementation
    writeValue(value: string): void {
        if (this.editor) {
            this.editor.setContent(value || '');
        } else {
            this.initialContent = value || '';
        }
    }

    registerOnChange(fn: (value: string) => void): void {
        this.onChange = fn;
    }

    registerOnTouched(fn: () => void): void {
        this.onTouched = fn;
    }

    setDisabledState(isDisabled: boolean): void {
        if (this.editor) {
            isDisabled ? this.editor.disable() : this.editor.enable();
        }
    }

    // Public methods
    getContent(): string {
        return this.editor?.getContent() || '';
    }

    clear(): void {
        this.editor?.clear();
    }

    exportPDF(filename: string): void {
        this.editor?.exportPDF(filename);
    }
}
// post-form.component.ts
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { PostService } from './post.service';

@Component({
    selector: 'app-post-form',
    template: `
        <form [formGroup]="postForm" (ngSubmit)="onSubmit()">
            <input formControlName="title" placeholder="Post title" />
            <div *ngIf="postForm.get('title')?.errors?.['required']">
                Title is required
            </div>

            <app-boleditor
                formControlName="content"
                [theme]="theme"
                placeholder="Write your post..."
                (ready)="onEditorReady($event)"
            ></app-boleditor>
            <div *ngIf="postForm.get('content')?.errors?.['required']">
                Content is required
            </div>
            <div *ngIf="postForm.get('content')?.errors?.['minlength']">
                Content must be at least 100 characters
            </div>

            <button type="submit" [disabled]="postForm.invalid">
                Publish
            </button>
        </form>
    `
})
export class PostFormComponent {
    postForm: FormGroup;
    theme: 'light' | 'dark' = 'light';

    constructor(
        private fb: FormBuilder,
        private postService: PostService
    ) {
        this.postForm = this.fb.group({
            title: ['', [Validators.required, Validators.minLength(5)]],
            content: ['', [Validators.required, Validators.minLength(100)]]
        });
    }

    onEditorReady(editor: any): void {
        console.log('Editor initialized', editor);
    }

    async onSubmit(): Promise<void> {
        if (this.postForm.valid) {
            await this.postService.createPost(this.postForm.value);
        }
    }
}
// boleditor.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

export interface EditorState {
    content: string;
    wordCount: number;
    isDirty: boolean;
    lastSaved: Date | null;
}

@Injectable({
    providedIn: 'root'
})
export class BolEditorService {
    private stateSubject = new BehaviorSubject<EditorState>({
        content: '',
        wordCount: 0,
        isDirty: false,
        lastSaved: null
    });

    state$ = this.stateSubject.asObservable();
    private autosaveInterval: any;

    updateContent(content: string, wordCount: number): void {
        this.stateSubject.next({
            ...this.stateSubject.value,
            content,
            wordCount,
            isDirty: true
        });
    }

    markSaved(): void {
        this.stateSubject.next({
            ...this.stateSubject.value,
            isDirty: false,
            lastSaved: new Date()
        });
    }

    startAutosave(saveCallback: () => Promise<void>, interval: number = 30000): void {
        this.stopAutosave();
        this.autosaveInterval = setInterval(async () => {
            if (this.stateSubject.value.isDirty) {
                await saveCallback();
                this.markSaved();
            }
        }, interval);
    }

    stopAutosave(): void {
        if (this.autosaveInterval) {
            clearInterval(this.autosaveInterval);
        }
    }
}

// Usage in component
@Component({...})
export class EditorPageComponent implements OnInit, OnDestroy {
    constructor(private editorService: BolEditorService) {}

    ngOnInit(): void {
        this.editorService.startAutosave(
            () => this.saveToServer(),
            30000
        );
    }

    ngOnDestroy(): void {
        this.editorService.stopAutosave();
    }

    onContentChange(content: string): void {
        const wordCount = content.split(/\s+/).filter(Boolean).length;
        this.editorService.updateContent(content, wordCount);
    }

    async saveToServer(): Promise<void> {
        // Save logic
    }
}

Examples

Complete working examples for common use cases.

Blog Editor

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Blog Editor</title>
    <link rel="stylesheet" href="boleditor.min.css">
    <style>
        .editor-container {
            max-width: 800px;
            margin: 2rem auto;
            padding: 0 1rem;
        }
        .publish-btn {
            margin-top: 1rem;
            padding: 0.75rem 2rem;
            background: #8b5cf6;
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div class="editor-container">
        <input type="text" id="title" placeholder="Post title...">
        <div id="editor"></div>
        <button class="publish-btn" onclick="publish()">Publish</button>
    </div>

    <script src="boleditor.bundle.umd.js"></script>
    <script>
        const editor = new BOLEDITOR('#editor', {
            theme: 'light',
            minHeight: '400px',
            placeholder: 'Write your blog post...',
            ai: {
                enabled: true,
                endpoint: '/api/ai/generate'
            },
            autosave: {
                enabled: true,
                interval: 30000,
                key: 'blog-draft'
            }
        });

        async function publish() {
            const title = document.getElementById('title').value;
            const content = editor.getContent();

            const response = await fetch('/api/posts', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ title, content })
            });

            if (response.ok) {
                alert('Post published!');
                editor.clear();
            }
        }
    </script>
</body>
</html>

Changelog

Version history and release notes.

v1.0.0 - Initial Release

Release Date: November 2025

Features

  • Core rich text editing with full formatting
  • 14 built-in plugins
  • AI-powered content generation
  • 20+ language translations
  • RTL support
  • Light and dark themes
  • Zero dependencies
  • Full keyboard navigation
  • Mobile-friendly responsive design

Plugins Included

  • AI Assistant
  • Slash Commands
  • Markdown Support
  • Media Manager
  • Mentions & Tags
  • Smart Paste
  • Templates
  • Export (PDF, Word, MD)
  • Floating Toolbar
  • Content Blocks
  • SEO Analyzer
  • Collaboration
  • Comments
  • Font Controls