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() || '';
}
}
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();
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
Content generation, grammar fixes, translations
Notion-style "/" commands for quick actions
Live markdown shortcuts and import/export
Drag & drop uploads, image resizing
@mentions and #hashtags with autocomplete
Clean paste from Word, Excel, Google Docs
15+ pre-built templates
Export to PDF, Word, Markdown, HTML
Context-aware toolbar on selection
Gutenberg-style drag & drop blocks
Keyword analysis and readability scores
Real-time editing with presence
Inline comments with replies
Advanced typography with Google Fonts
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.
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 | /h1 | Large heading |
heading2 | /h2 | Medium heading |
heading3 | /h3 | Small heading |
bullet | /bullet, /ul | Bullet list |
numbered | /numbered, /ol | Numbered list |
todo | /todo, /task | Checkbox list |
quote | /quote | Block quote |
code | /code | Code block |
divider | /divider, /hr | Horizontal line |
image | /image, /img | Insert image |
table | /table | Insert table |
embed | /embed | Embed URL |
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, // 
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

Markdown Shortcuts Table
| Type This | Get This | Keyboard |
|---|---|---|
# text | Heading 1 | Ctrl+1 |
## text | Heading 2 | Ctrl+2 |
**text** | Bold | Ctrl+B |
*text* | Italic | Ctrl+I |
~~text~~ | Ctrl+Shift+S | |
`code` | Code | Ctrl+` |
- item | • Bullet list | - |
1. item | 1. Numbered list | - |
> quote | Blockquote | Ctrl+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']
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);
});
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+M | Add comment on selection |
@ | Trigger mentions dropdown |
# | Trigger hashtags dropdown |
Arrow Up/Down | Navigate dropdown |
Enter / Tab | Select mention/tag |
Escape | Close dropdown |
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