npm package: @jitword/collab-editor
Description: A comprehensive collaborative editor SDK with real-time collaboration features. Drop it into any Vue application and get a full-featured editor with Notion-like experience.
Author: JitWord
Latest version: 1.0.4

# @jitword/collab-editor

A comprehensive, production-ready collaborative editor SDK built with Vue 3 and Tiptap. Drop it into any Vue application and get a full-featured editor with real-time collaboration support.

[![npm version](https://img.shields.io/npm/v/@jitword/collab-editor.svg)](https://www.npmjs.com/package/@jitword/collab-editor)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

## ✨ Features

- 📝 **Rich Text Editing** - Full WYSIWYG editor with Notion-like experience
- 👥 **Real-time Collaboration** - Built-in collaborative editing support
- 🎨 **Categorized Toolbar** - Organized tools (Home, Insert, View, Export)
- 📋 **Table of Contents** - Auto-generated document outline with smooth scrolling
- 📊 **Word Count** - Real-time paragraph and character statistics
- 🤖 **AI Integration Ready** - Events for AI writing, version control, and sharing
- ⌨️ **Keyboard Shortcuts** - Command+S (Mac) / Ctrl+S (Windows) to save
- 📱 **Responsive Design** - Works seamlessly on desktop, tablet, and mobile
- 🎯 **Highly Configurable** - Control every feature via props
- 🔌 **Event-Driven** - Emit events for business logic integration
- 💅 **Beautiful UI** - Based on Arco Design system
- ✨ **Built-in Modals** - Share, AI Writer, and Document Management modals included
- 🚀 **Out-of-the-Box** - Share and Read Mode work automatically

## 🏗️ Architecture Principles

🔐 **Security**: API keys and authentication stay in your code  
🔌 **Flexible**: Supports any collaboration backend (Yjs, Socket.io, custom, etc.)  
📦 **Lightweight**: SDK doesn't include specific network libraries  
🎯 **Clear Responsibilities**: SDK handles UI, you handle data

## 📦 Installation

```bash
# Using npm
npm install @jitword/collab-editor vue

# Using yarn
yarn add @jitword/collab-editor vue

# Using pnpm
pnpm add @jitword/collab-editor vue
```

**Note:** `@arco-design/web-vue` is a peer dependency and will be installed automatically. All collaboration dependencies (`yjs`, `y-websocket`, `@tiptap/extension-collaboration`) are bundled in the package.

## 🚀 Quick Start

### 1. Import Styles and Register Components

```javascript
// main.js
import { createApp } from 'vue'
import ArcoVue from '@arco-design/web-vue'
import '@arco-design/web-vue/dist/arco.css'
import '@jitword/collab-editor/dist/style.css'
import App from './App.vue'

const app = createApp(App)
app.use(ArcoVue)
app.mount('#app')
```

### 2. Basic Usage (Out-of-the-Box)

The simplest way to use the editor with built-in features:

```vue
<template>
  <CollabEditorApp
    v-model="content"
    :config="editorConfig"
    :onlineUsers="onlineUsers"
    :currentDocumentId="currentDocId"
    @ai-generate="handleAIGenerate"
    @editor-ready="handleEditorReady"
    style="height: 100vh;"
  />
</template>

<script setup>
import { ref } from 'vue'
import { CollabEditorApp, NotionKit } from '@jitword/collab-editor'

const content = ref({
  type: 'doc',
  content: [
    {
      type: 'paragraph',
      content: [{ type: 'text', text: 'Start editing...' }]
    }
  ]
})

const onlineUsers = ref([
  { id: 1, name: 'Alice', color: '3b82f6' },
  { id: 2, name: 'Bob', color: '10b981' }
])

const currentDocId = ref('doc-123')

const editorConfig = {
  appTitle: 'My Collaborative Editor',
  documentTitle: 'Untitled Document',
  roomName: 'my-room', // For share modal display
  enableAI: true,
  enableShare: true,
  enableReadMode: true,
  enableDocumentList: true,
  showOnlineUsers: true,
  extensions: [NotionKit]
}

// Handle AI generation (only business logic needed)
const handleAIGenerate = async ({ prompt, editor }) => {
  // Call your AI API
  const result = await callYourAIService(prompt)
  // Insert into editor
  editor.commands.insertContent(result)
}

const handleEditorReady = (editor) => {
  console.log('Editor ready:', editor)
}
</script>
```

**What you get automatically:**
- ✅ Share button → Opens built-in share modal with QR code
- ✅ AI button → Opens built-in AI writer modal
- ✅ Read Mode button → Opens `/read/{docId}` in new tab
- ✅ Online users display in header
- ✅ All UI components styled and ready

## 📚 API Reference

### Props

#### `v-model` (Object | String)
The editor content. Can be ProseMirror JSON or HTML string.

```javascript
const content = ref({
  type: 'doc',
  content: [/* ... */]
})
```

#### `config` (Object)
Editor configuration object.

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `appTitle` | String | `'JitWord 协同文档'` | Application title (desktop) |
| `appTitleShort` | String | `'JitWord'` | Short title (mobile) |
| `documentTitle` | String | `'未命名文档'` | Current document title |
| `enableAI` | Boolean | `true` | Show AI writing button |
| `enableVersions` | Boolean | `true` | Show version history button |
| `enableShare` | Boolean | `true` | Show share button |
| `enableReadMode` | Boolean | `true` | Show read mode button |
| `enableAISettings` | Boolean | `true` | Show AI settings button |
| `enableDocumentList` | Boolean | `false` | Show document list button |
| `enableCreateDocument` | Boolean | `false` | Show create document button |
| `createDocumentTooltip` | String | `'新建文档'` | Tooltip for create button |
| `showOnlineUsers` | Boolean | `true` | Display online users |
| `hideToc` | Boolean | `false` | Hide table of contents |
| `hideFooter` | Boolean | `false` | Hide footer (word count) |
| `hideHeader` | Boolean | `false` | Hide header bar |
| `hideToolbar` | Boolean | `false` | Hide formatting toolbar |
| `hideBubbleMenu` | Boolean | `false` | Hide bubble menu |
| `editable` | Boolean | `true` | Allow editing |
| `locale` | String | `'en'` | Editor locale |
| `theme` | String | `'light'` | Editor theme |
| `isMobile` | Boolean | `false` | Mobile mode |
| `isTablet` | Boolean | `false` | Tablet mode |
| `aiWriterBusy` | Boolean | `false` | AI writer loading state |
| `extensions` | Array | `[NotionKit]` | Tiptap extensions |
| `uploadFn` | Function | `null` | Custom file upload handler |
| `defaultCategory` | String | `'home'` | Default toolbar category |
| `pageWidth` | Number | `210` | Page width (mm) |
| `pageHeight` | Number | `297` | Page height (mm) |
| `marginTop` | Number | `25.4` | Top margin (mm) |
| `marginRight` | Number | `31.7` | Right margin (mm) |
| `marginBottom` | Number | `25.4` | Bottom margin (mm) |
| `marginLeft` | Number | `31.7` | Left margin (mm) |
| `readMode` | Boolean | `false` | Enable read-only mode (hides toolbar, shows simple header) |
| `readModeBadgeText` | String | `'Read-only Mode'` | Badge text shown in read mode |

**Built-in Modal Configuration:**

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `useBuiltinModals` | Boolean | `true` | Enable all built-in modals |
| `roomName` | String | `''` | Room name (shown in share modal) |
| `shareConfig` | Object | `{}` | Share modal configuration (see below) |
| `aiConfig` | Object | `{}` | AI modal configuration (see below) |
| `aiSettingsConfig` | Object | `{}` | AI Settings modal configuration (see below) |
| `versionConfig` | Object | `{}` | Version History modal configuration (see below) |
| `docsConfig` | Object | `{}` | Document drawer configuration (see below) |
| `getReadModeUrl` | Function | `null` | Custom read mode URL generator |
| `onReadModeClick` | Function | `null` | Custom read mode handler |

#### `onlineUsers` (Array)
Array of online users for collaboration display.

```javascript
const onlineUsers = ref([
  { id: 1, name: 'Alice', color: '3b82f6' }, // No # prefix
  { id: 2, name: 'Bob', color: '10b981' }
])
```

#### `documents` (Array)
Array of documents for built-in document drawer.

```javascript
const documents = ref([
  { id: 'doc-1', name: 'My Document', updatedAt: Date.now() },
  { id: 'doc-2', name: 'Another Doc', updatedAt: Date.now() - 3600000 }
])
```

#### `currentDocumentId` (String)
Current document ID (required for read mode and document drawer).

```javascript
const currentDocumentId = ref('doc-123')
```

#### `isTrashView` (Boolean)
Whether document drawer is showing trash view.

#### `documentsLoading` (Boolean)
Loading state for document drawer.

### Events

#### Editor Events

| Event | Payload | Description |
|-------|---------|-------------|
| `update:modelValue` | `content` | Emitted when content changes |
| `editor-ready` | `editor` | Emitted when editor is initialized |

#### Header Button Events

| Event | Payload | Description |
|-------|---------|-------------|
| `ai-click` | `{ editor }` | AI writing button clicked (custom mode) |
| `version-click` | `{ editor }` | Version history button clicked |
| `share-click` | `{ editor }` | Share button clicked (custom mode) |
| `ai-settings-click` | `{ editor }` | AI settings button clicked |
| `read-mode-click` | `{ editor }` | Read mode button clicked (custom mode) |
| `document-list-click` | `{ editor }` | Document list button clicked (custom mode) |
| `create-document-click` | `{ editor }` | Create document button clicked |
| `mobile-menu-select` | `value` | Mobile menu item selected |

#### Built-in Modal Events

| Event | Payload | Description |
|-------|---------|-------------|
| `ai-generate` | `{ prompt, editor }` | User requested AI generation |
| `toggle-trash` | - | Toggle trash view in document drawer |
| `select-document` | `docId` | Document selected from drawer |
| `create-document` | - | Create new document requested |
| `delete-document` | `docId` | Soft delete document (move to trash) |
| `restore-document` | `docId` | Restore document from trash |
| `permanent-delete-document` | `docId` | Permanently delete document |
| `rename-document` | `docId, newName, onSuccess` | Rename document |
| `save` | `{ editor, content, currentDocumentId }` | Save triggered by keyboard shortcut (Cmd+S / Ctrl+S) |
| `version-restore` | `content` | Version restored, update editor content |
| `version-create` | `{ documentId, content, title, description, isAutoSave, author }` | Version created successfully |
| `version-update` | `{ documentId, versionId, title, description }` | Version metadata updated |
| `version-delete` | `{ documentId, versionId, version }` | Version deleted |
| `version-preview` | `{ documentId, versionId, version, content }` | Version previewed |

### Slots

#### `#headerLeftActions`
Add custom actions to the left side of the header (after built-in buttons).

```vue
<CollabEditorApp>
  <template #headerLeftActions>
    <a-button @click="customAction">Custom</a-button>
  </template>
</CollabEditorApp>
```

#### `#headerMiddleActions`
Add custom actions to the middle of the header (after version button).

#### `#headerMobileMenuItems`
Add custom items to the mobile dropdown menu.

#### `#modals`
Add custom modal components with access to editor and config.

```vue
<CollabEditorApp>
  <template #modals="{ editor, config }">
    <YourCustomModal :editor="editor" />
  </template>
</CollabEditorApp>
```

## 🔌 Integration Examples

### Built-in Modals (Recommended)

The SDK includes fully-functional modals for common features. Just provide the data and handle the events:

```vue
<template>
  <CollabEditorApp
    v-model="content"
    :config="editorConfig"
    :onlineUsers="onlineUsers"
    :documents="documents"
    :currentDocumentId="currentDocId"
    :isTrashView="isTrashView"
    :documentsLoading="docsLoading"
    @ai-generate="handleAIGenerate"
    @rename-document="handleRenameDocument"
    @delete-document="handleDeleteDocument"
    @restore-document="handleRestoreDocument"
    @select-document="handleSelectDocument"
  />
</template>

<script setup>
import { ref } from 'vue'
import { CollabEditorApp } from '@jitword/collab-editor'

const currentDocId = ref('doc-123')
const documents = ref([
  { id: 'doc-1', name: 'Project Proposal', updatedAt: Date.now() },
  { id: 'doc-2', name: 'Meeting Notes', updatedAt: Date.now() - 86400000 }
])
const isTrashView = ref(false)
const docsLoading = ref(false)

const editorConfig = {
  roomName: 'my-room',
  enableDocumentList: true,
  enableShare: true,
  enableAI: true,
  // Customize modals
  shareConfig: {
    showQRCode: true,
    enableReadLink: true,  // Enable read-only link tab
    editLinkLabel: '✏️ Edit Link',
    readLinkLabel: '👁️ Read-only Link',
    editDescription: 'Share this link with others to collaborate in real-time:',
    readDescription: 'Share this read-only link. Others can only view the document:',
    showStats: true
  },
  aiConfig: {
    placeholder: 'Describe what you want to write...',
    quickPrompts: [
      { icon: '📝', label: 'Write', value: 'Write a professional email' }
    ]
  },
  aiSettingsConfig: {
    title: '⚙️ AI Settings',
    okText: 'Save Settings',
    apiKeyPlaceholder: 'Enter your API key',
    hint: '🔒 Your API keys are stored locally in your browser',
    storageKey: 'my_app_ai_settings',
    autoSave: false
  },
  docsConfig: {
    title: 'My Documents',
    enableRename: true,
    enableDelete: true,
    enableTrash: true
  }
}

// Handle AI generation
const handleAIGenerate = async ({ prompt, editor }) => {
  const response = await fetch('/api/ai/generate', {
    method: 'POST',
    body: JSON.stringify({ prompt })
  })
  const { text } = await response.json()
  editor.commands.insertContent(text)
}

// Handle document rename
const handleRenameDocument = async (docId, newName, onSuccess) => {
  const response = await fetch(`/api/documents/${docId}`, {
    method: 'PATCH',
    body: JSON.stringify({ name: newName })
  })
  
  if (response.ok) {
    // Update local state
    const doc = documents.value.find(d => d.id === docId)
    if (doc) doc.name = newName
    // Notify success (closes edit mode)
    onSuccess()
  }
}

// Handle document delete
const handleDeleteDocument = async (docId) => {
  await fetch(`/api/documents/${docId}`, { method: 'DELETE' })
  // Reload documents
  loadDocuments()
}

// Handle document restore
const handleRestoreDocument = async (docId) => {
  await fetch(`/api/documents/${docId}/restore`, { method: 'POST' })
  loadDocuments()
}

// Handle document select
const handleSelectDocument = (docId) => {
  currentDocId.value = docId
  router.push(`/editor/${docId}`)
}
</script>
```

### Custom Modals (Advanced)

If you need complete control, use custom modals:

```vue
<CollabEditorApp
  v-model="content"
  :config="{ useBuiltinModals: false }"
  @ai-click="customAIModal = true"
  @share-click="customShareModal = true"
>
  <template #modals="{ editor, config }">
    <MyCustomAIModal v-model:visible="customAIModal" :editor="editor" />
    <MyCustomShareModal v-model:visible="customShareModal" />
  </template>
</CollabEditorApp>
```

### Read Mode Integration

The SDK handles read mode automatically:

**Default behavior** (opens `/read/{docId}`):
```vue
<CollabEditorApp
  :currentDocumentId="currentDocId"
  :config="{ enableReadMode: true }"
/>
```

**Using built-in read mode UI** (simple header, no toolbar):
```vue
<CollabEditorApp
  v-model="content"
  :config="{
    readMode: true,
    documentTitle: 'My Document',
    readModeBadgeText: 'Read-only Mode',
    editable: false  // Disable editing
  }"
/>
```

**What happens in read mode:**
- ❌ Toolbar is hidden
- ❌ Header buttons are hidden
- ❌ Table of contents sidebar is hidden
- ❌ Footer is hidden
- ✅ Simple header with document title and "Read-only" badge
- ✅ Document statistics (paragraphs & characters)
- ✅ Clean, distraction-free reading experience

**Custom URL pattern**:
```javascript
const editorConfig = {
  enableReadMode: true,
  getReadModeUrl: (docId) => `/documents/${docId}/preview`
}
```

**Custom handler** (e.g., open modal instead):
```javascript
const editorConfig = {
  enableReadMode: true,
  onReadModeClick: ({ editor, currentDocumentId }) => {
    showReadModeModal.value = true
  }
}
```

### Real-time Collaboration with Yjs

**All collaboration dependencies are bundled!** No need to install `yjs`, `y-websocket`, or Tiptap extensions separately.

```javascript
import { 
  CollabEditorApp,
  Y,                  // Yjs CRDT
  WebsocketProvider,  // WebSocket provider
  Collaboration,      // Tiptap extension
  CollaborationCursor // Collaborative cursors
} from '@jitword/collab-editor'

const ydoc = new Y.Doc()
const provider = new WebsocketProvider(
  'wss://your-websocket-server.com',
  'document-id',
  ydoc
)

// Update online users from provider awareness
provider.awareness.on('change', () => {
  const states = Array.from(provider.awareness.getStates().entries())
  onlineUsers.value = states.map(([clientID, state]) => ({
    id: clientID,
    name: state.user?.name || `User ${clientID}`,
    color: state.user?.color || '3b82f6'
  }))
})

const editorConfig = {
  // ... other config
  extensions: [
    NotionKit,
    Collaboration.configure({ document: ydoc }),
    CollaborationCursor.configure({ provider })
  ]
}
```

### Custom File Upload

```javascript
const editorConfig = {
  // ... other config
  uploadFn: async (file) => {
    const formData = new FormData()
    formData.append('file', file)
    
    const response = await fetch('https://your-api.com/upload', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${yourToken}`
      },
      body: formData
    })
    
    const data = await response.json()
    return data.url // Return the uploaded file URL
  }
}
```

### AI Integration

```javascript
const handleAIClick = async ({ editor }) => {
  // Get selected text
  const { from, to } = editor.state.selection
  const selectedText = editor.state.doc.textBetween(from, to, ' ')
  
  // Call your AI service
  const response = await fetch('https://your-ai-api.com/complete', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${yourAIKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      prompt: selectedText,
      context: 'improve_writing'
    })
  })
  
  const result = await response.json()
  
  // Insert AI response
  editor.chain().focus().insertContent(result.text).run()
}
```

### AI Settings Modal

The built-in AI Settings modal allows users to configure their AI provider, model, and API key:

```javascript
const editorConfig = {
  enableAISettings: true,  // Show AI Settings button
  aiSettingsConfig: {
    title: '⚙️ AI Settings',
    okText: 'Save Settings',
    cancelText: 'Cancel',
    apiKeyPlaceholder: 'Enter your API key (saved locally)',
    hint: '🔒 Your API keys are stored locally in your browser and never sent to our servers',
    storageKey: 'my_app_ai_settings',  // localStorage key for settings
    autoSave: false  // false = manual save, true = auto-save on change
  }
}
```

**Features:**
- Provider selection (DeepSeek, Kimi, OpenAI, Custom)
- Model selection (auto-updates based on provider)
- API key management (stored in localStorage)
- Custom base URL for OpenAI-compatible endpoints
- Temperature control (0-2)
- Secure local storage (never sent to servers)

**Usage:**
1. User clicks "AI Settings" button in header
2. Modal opens with current settings
3. User configures provider, model, and API key
4. Settings are saved to localStorage
5. AI Writer modal uses these settings for generation

### Version History Modal

The built-in Version History modal provides a complete version control system with create, restore, preview, edit, and delete operations:

```vue
<template>
  <CollabEditorApp
    v-model="content"
    :config="editorConfig"
    :currentDocumentId="currentDocId"
    @version-restore="handleVersionRestore"
    @version-create="handleVersionCreate"
    @version-update="handleVersionUpdate"
    @version-delete="handleVersionDelete"
    @version-preview="handleVersionPreview"
  />
</template>

<script setup>
import { ref } from 'vue'
import { CollabEditorApp, Message } from '@jitword/collab-editor'

const currentDocId = ref('doc-123')
const content = ref({ type: 'doc', content: [] })

const editorConfig = {
  enableVersions: true,  // Show Versions button
  versionConfig: {
    title: 'Version History',
    saveButtonText: 'Save Version',
    emptyText: 'No version history yet',
    autoSaveLabel: 'Auto',
    restoreText: 'Restore',
    editText: 'Edit Info',
    deleteText: 'Delete',
    previewText: 'Preview',
    loadMoreText: 'Load More',
    
    // Required: Load versions from your backend
    onLoadVersions: async (documentId, page, pageSize) => {
      const response = await fetch(
        `/api/documents/${documentId}/versions?page=${page}&pageSize=${pageSize}`
      )
      const data = await response.json()
      
      return {
        versions: data.versions.map(v => ({
          id: v.id,
          title: v.title,
          description: v.description,
          createdAt: v.createdAt,  // ISO string
          author: v.author,
          size: v.size,  // bytes
          isAutoSave: v.isAutoSave  // boolean
        })),
        total: data.total
      }
    },
    
    // Required: Create new version
    onCreateVersion: async (versionData) => {
      await fetch(`/api/documents/${versionData.documentId}/versions`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          content: versionData.content,
          title: versionData.title,
          description: versionData.description,
          isAutoSave: versionData.isAutoSave
        })
      })
    },
    
    // Required: Get version content for preview/restore
    onGetVersion: async (documentId, versionId) => {
      const response = await fetch(
        `/api/documents/${documentId}/versions/${versionId}`
      )
      const data = await response.json()
      return data.content  // ProseMirror JSON or HTML
    },
    
    // Required: Restore version
    onRestoreVersion: async (content) => {
      // Update editor with restored content
      // This is typically handled in the event handler below
    },
    
    // Optional: Update version metadata
    onUpdateVersion: async (updateData) => {
      await fetch(
        `/api/documents/${updateData.documentId}/versions/${updateData.versionId}`,
        {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            title: updateData.title,
            description: updateData.description
          })
        }
      )
    },
    
    // Optional: Delete version
    onDeleteVersion: async (documentId, versionId) => {
      await fetch(
        `/api/documents/${documentId}/versions/${versionId}`,
        { method: 'DELETE' }
      )
    }
  }
}

// Handle version restore - update editor content
const handleVersionRestore = (content) => {
  // Update the editor with restored version
  content.value = content
  Message.success('Version restored successfully!')
}

// Handle version create - track in analytics
const handleVersionCreate = (versionData) => {
  console.log('Version created:', versionData)
  // Optional: Send analytics, update UI, etc.
}

// Handle version update - track metadata changes
const handleVersionUpdate = (updateData) => {
  console.log('Version updated:', updateData)
}

// Handle version delete - cleanup
const handleVersionDelete = (deleteData) => {
  console.log('Version deleted:', deleteData)
}

// Handle version preview - track which versions users view
const handleVersionPreview = (previewData) => {
  console.log('Version previewed:', previewData)
}
</script>
```

**Features:**
- ✅ Create new versions with title and description
- ✅ Preview versions in modal
- ✅ Restore to any previous version
- ✅ Edit version metadata (title, description)
- ✅ Delete versions with confirmation
- ✅ Auto-save label for automatic backups
- ✅ Pagination with "Load More"
- ✅ Version statistics (creation time, author, size)

**Version Events:**

| Event | Payload | Description |
|-------|---------|-------------|
| `@version-restore` | `content` (Object) | Emitted when user restores a version. Update your editor content. |
| `@version-create` | `{ documentId, content, title, description, isAutoSave, author }` | Emitted after version is created. Use for analytics. |
| `@version-update` | `{ documentId, versionId, title, description }` | Emitted when version metadata is updated. |
| `@version-delete` | `{ documentId, versionId, version }` | Emitted when version is deleted. Handle cleanup. |
| `@version-preview` | `{ documentId, versionId, version, content }` | Emitted when user previews a version. Track analytics. |

**How it works:**
1. User clicks "Versions" button in header → Opens drawer
2. SDK calls `onLoadVersions()` to fetch version list
3. User clicks "Save Version" → SDK calls `onCreateVersion()` → Emits `@version-create`
4. User clicks "Preview" → SDK calls `onGetVersion()` → Shows modal → Emits `@version-preview`
5. User clicks "Restore" → SDK calls `onGetVersion()` → Emits `@version-restore` → You update editor
6. User clicks "Edit Info" → Updates metadata → SDK calls `onUpdateVersion()` → Emits `@version-update`
7. User clicks "Delete" → Shows confirmation → SDK calls `onDeleteVersion()` → Emits `@version-delete`

**Best Practices:**
- Store versions in your database with documentId as foreign key
- Include metadata: title, description, author, createdAt, size
- Mark auto-saves with `isAutoSave: true` for visual distinction
- Implement pagination for performance (default 10 versions per page)
- Use events for analytics and UI updates
- Handle restore by updating your `v-model` content

### Keyboard Shortcuts

The editor supports keyboard shortcuts out of the box:

#### Save Document (Command+S / Ctrl+S)

The save shortcut prevents the browser's default save dialog and emits a `save` event:

**Option 1: Handle via event listener** (recommended):
```vue
<template>
  <CollabEditorApp
    v-model="content"
    :config="editorConfig"
    :currentDocumentId="currentDocId"
    @save="handleSave"
  />
</template>

<script setup>
const handleSave = async ({ editor, content, currentDocumentId }) => {
  // Save to your backend
  await fetch(`/api/documents/${currentDocumentId}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ content })
  })
  
  // Show success message
  Message.success('Document saved!')
}
</script>
```

**Option 2: Provide custom handler in config**:
```javascript
const editorConfig = {
  // ... other config
  onSave: async ({ editor, content, currentDocumentId }) => {
    // Your custom save logic
    await saveToDatabase(currentDocumentId, content)
    showNotification('Saved!')
  }
}
```

**What happens:**
- User presses Command+S (Mac) or Ctrl+S (Windows/Linux)
- Browser's default save dialog is prevented
- Your save handler is called with editor instance, content, and document ID
- You can save to backend, localStorage, or any storage

## 🎨 Customization

### Custom Theme

The SDK uses CSS variables for theming. You can override them:

```css
:root {
  --color-border-1: #e5e7eb;
  --color-text-2: #6b7280;
  --color-text-3: #9ca3af;
}
```

### Custom Toolbar Category

```javascript
const editorConfig = {
  defaultCategory: 'insert', // 'home' | 'insert' | 'view' | 'export'
  // ... other config
}
```

## 📱 Responsive Design

The SDK automatically adapts to different screen sizes:

```javascript
const isMobile = computed(() => window.innerWidth < 768)
const isTablet = computed(() => window.innerWidth >= 768 && window.innerWidth < 1024)

const editorConfig = {
  isMobile: isMobile.value,
  isTablet: isTablet.value,
  // ... other config
}
```

## 🛠️ Development

```bash
# Install dependencies
pnpm install

# Build SDK
pnpm run build

# Watch mode (for development)
pnpm run dev

# Run example
pnpm run dev:example
```

## 📄 License

MIT © JitWord

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## 🔗 Links

- [GitHub Repository](https://github.com/jitword/collab-editor)
- [Documentation](https://docs.jitword.com)
- [Issue Tracker](https://github.com/jitword/collab-editor/issues)

## 💡 Next Steps

After publishing v1.0.0, we'll focus on:

- 🔐 **Security**: Keep API keys and authentication in user code
- 🔌 **Flexibility**: Support any collaboration backend (Yjs, Socket.io, custom)
- 📦 **Lightweight**: Remove specific network libraries from SDK
- 🎯 **Clear Responsibilities**: SDK handles UI, users handle data
pnpm install

# Build SDK
pnpm run build

# Watch mode (for development)
pnpm run dev

# Run example
pnpm run dev:example
```

## 📄 License

MIT © JitWord

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## 🔗 Links

- [GitHub Repository](https://github.com/jitword/collab-editor)
- [Documentation](https://docs.jitword.com)
- [Issue Tracker](https://github.com/jitword/collab-editor/issues)

## 💡 Next Steps

After publishing v1.0.0, we'll focus on:

- 🔐 **Security**: Keep API keys and authentication in user code
- 🔌 **Flexibility**: Support any collaboration backend (Yjs, Socket.io, custom)
- 📦 **Lightweight**: Remove specific network libraries from SDK
- 🎯 **Clear Responsibilities**: SDK handles UI, users handle data