From d32c80d0e8c7e4a1a6e0e8ce2d154a291d9936e6 Mon Sep 17 00:00:00 2001 From: VikramVaratharajan-SF Date: Mon, 27 Jul 2026 19:19:07 +0530 Subject: [PATCH] Revise getting started guide for real-time collaboration Updated the documentation for the Vue Block Editor component to focus on real-time collaboration features, including setup instructions and collaboration settings. --- .../vue/block-editor/getting-started.md | 615 ++++++++++++++++-- 1 file changed, 558 insertions(+), 57 deletions(-) diff --git a/rich-text-editor-sdk/vue/block-editor/getting-started.md b/rich-text-editor-sdk/vue/block-editor/getting-started.md index afa32538e6..41992edce9 100644 --- a/rich-text-editor-sdk/vue/block-editor/getting-started.md +++ b/rich-text-editor-sdk/vue/block-editor/getting-started.md @@ -1,110 +1,611 @@ --- layout: post -title: Getting started in Vue Block Editor component | Syncfusion -description: Learn here all about Getting started in Syncfusion Vue Block Editor component of Syncfusion Essential JS 2 and more. -canonical_url: "https://www.syncfusion.com/rich-text-editor-sdk/vue-block-editor" -control: Getting started -platform: ej2-vue +title: Real-Time Collaboration in Vue Block Editor control | Syncfusion +description: Enable real-time collaborative editing in the Vue Block Editor component of Syncfusion Essential JS 2 with user presence and version history. +platform: rich-text-editor-sdk +control: Block Editor +publishingplatform: rich-text-editor-sdk documentation: ug domainurl: https://help.syncfusion.com/rich-text-editor-sdk --- -# Getting Started with the Vue Block Editor component in Vue 2 +# Real-Time Collaboration in Vue Block Editor component -This section explains how to create a simple Block Editor and configure its available functionalities in the Vue environment. +The Block Editor supports real-time collaborative editing, enabling multiple users to work on the same document simultaneously. Collaboration is powered by [**Yjs**](https://yjs.dev/), an open-source Conflict-free Replicated Data Type (CRDT) framework that synchronizes document changes across all connected users and automatically resolves conflicts. -## Prerequisites +With collaboration enabled, users can: -[System requirements for Syncfusion® Vue UI components](https://ej2.syncfusion.com/vue/documentation/system-requirements) +* Edit the same document in real time. +* View remote user cursors and selections. +* Track active collaborators. +* Perform collaboration-aware undo and redo operations. +* Create, restore, compare, export, and import document versions. -## Create a Vue Application +{% doccards %} +{% doccard text="Live Demo" link="https://ej2.syncfusion.com/showcase/vue/blockeditor-collaborative-editing/" %} +{% enddoccards %} -To generate a Vue 2 project using Vue-CLI, use the [vue create](https://cli.vuejs.org/#getting-started) command. If Vue CLI is not installed yet, run the first command below. +## Quick Start -```bash -npm install -g @vue/cli -vue create quickstart +Get real-time collaboration working in just a few minutes using `y-websocket` and a simple WebSocket server in our Block Editor component. + +### Step 1: Set up a basic Vite Vue Block Editor component + +Follow the [Getting Started guide](./vue-3-getting-started.md) to create a Vite-based Vue project with the Block Editor component. This ensures you have all required dependencies and the correct project structure before adding collaboration. + +### Step 2: Install Yjs and the WebSocket provider + +A Yjs provider handles the transport of document updates between connected users. Choose a provider based on your deployment requirements. + +See [Yjs Providers](https://docs.yjs.dev/ecosystem/connection-provider) to choose the right provider for your use case. + +| Provider | Type | Use Case | +| -------- | ---- | -------- | +| `y-websocket` | Self-hosted | Production deployments with your own WebSocket server. | +| `y-webrtc` | Peer-to-peer | Quick local testing and development; no server required. | +| `y-indexeddb` | Local storage | Offline persistence within a single browser. | +| [Hocuspocus](https://tiptap.dev/docs/hocuspocus/getting-started/overview) | Open-source server | Scalable Node.js server with pluggable storage and Redis support. | +| [Liveblocks](https://liveblocks.io/) | Fully managed | Hosted WebSocket infrastructure with REST API and DevTools. | +| [PartyKit](https://www.partykit.io/) | Serverless | Serverless provider on Cloudflare; ideal for prototyping. | + +Install the required libraries using npm: + +```powershell +npm install yjs y-websocket ``` -or +### Step 3: Create a simple WebSocket server -```bash -yarn global add @vue/cli -vue create quickstart +Install the WebSocket server package: + +```powershell +npm install @y/websocket-server ``` -When creating a new project, choose the option `Default ([Vue 2] babel, eslint)` from the menu. +#### Run the WebSocket Server + +{% tabs %} +{% highlight bash tabtitle="CMD" %} -Selecting the Vue 2 preset during project creation +set HOST=localhost&& set PORT=1234&& npx y-websocket -Navigate to the project directory: +{% endhighlight %} +{% highlight bash tabtitle="Powershell" %} -```bash -cd quickstart +$env:HOST="localhost"; $env:PORT="1234"; npx y-websocket + +{% endhighlight %} +{% endtabs %} + +You should see the message: + +``` +running at 'localhost' on port 1234 ``` -## Adding Syncfusion® Vue Block Editor packages +### Step 4: Create a collaboration configuration file + +- Create a shared Yjs document and XML fragment. +- Create an adapter that provides the Yjs runtime and the shared fragment to the Block Editor. +- Create a provider that connects users to the same shared document. + +Create a `collaboration.ts` file in the src folder and add the following code to configure the Yjs document, provider, collaboration adapter and room allocation logic. + +```typescript +import * as Y from 'yjs'; +import { type YjsAdapter } from '@syncfusion/ej2-blockeditor'; +import { WebsocketProvider } from 'y-websocket'; + +// Create a shared Yjs document for collaborative editing. +// Each URL hash gets its own room name (e.g., #wb3lu, #x2p4k) +const roomName = getRoomName(); +const yDoc = new Y.Doc(); +const yFragment = yDoc.getXmlFragment('blockeditor'); + +// Create adapter that provides Yjs runtime and shared fragment +const adapter: YjsAdapter = { + yRuntime: Y, + yXmlFragment: yFragment +}; + +// Connects to local WebSocket server on port 1234 and joins the room based on URL hash. +// Example: https://yourapp.com/#wb3lu joins room "wb3lu" +const provider = new WebsocketProvider( + 'ws://localhost:1234', + roomName, + yDoc +); + +/** + * Get or create room ID and store in URL hash + */ +function getRoomName(): string { + if (typeof window === 'undefined') { + return 'default'; + } + // Check if room ID exists in URL hash + let roomId = getRoomIdFromHash(); + // If no room ID in hash, generate a new one + if (!roomId) { + roomId = generateRoomId(); + setRoomIdInHash(roomId); + } + return roomId; +} + +/** + * Get room ID from URL hash + */ +function getRoomIdFromHash(): string | null { + const hash = window.location.hash.substring(1); + return hash || null; +} + +/** + * Generate a unique 5-character room ID + */ +function generateRoomId(): string { + const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + let roomId = ''; + for (let i = 0; i < 5; i++) { + roomId += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return roomId; +} + +/** + * Set room ID in URL hash + */ +function setRoomIdInHash(roomId: string): void { + window.location.hash = roomId; +} + +export { yDoc, yFragment, adapter, provider, roomName }; +``` -All Syncfusion® Vue packages are published on [npmjs.com](https://www.npmjs.com/search?q=ej2-vue). Install the Vue Block Editor package by running the following command: +### Step 5: Set up the Block Editor with collaboration -```bash -npm install @syncfusion/ej2-vue-blockeditor --save +- Enable collaboration by importing the `Collaboration` module from `@syncfusion/ej2-vue-blockeditor` and injecting it into the blockeditor. +- Use the `collaborationSettings` property of type `CollaborationSettingsModel` to configure collaboration settings for your Block Editor. +- It provides properties such as `provider`, `enableAwareness`, `adapter` and `versionHistory` which allows to customize the collaboration behavior. +- Pass the adapter and provider to the Block Editor through the `collaborationSettings` property. +- Set `enableAwareness` to `true` in `collaborationSettings` property to display remote cursors, text selection overlays, and user details on hover. + +In your Vue component file, add the following code. Replace your existing Block Editor setup with this: + +```ts + + + + + ``` -or + +### Step 6: Test the collaboration + +1. **Start your Vite development server** — In your project terminal, run: ```bash -yarn add @syncfusion/ej2-vue-blockeditor +npm run dev ``` + +> **Important:** Make sure your WebSocket server is still running in another terminal window. + +2. **Open a tab and duplicate it** with your Vue application +3. **Type in one window** — you should see the text appear in the other window instantly -## Adding CSS reference +If the text appears in both tabs, **real-time collaboration is achieved.** -Syncfusion provides multiple themes for the Block Editor component. For a complete list of available themes, refer to the [themes topic](https://ej2.syncfusion.com/vue/documentation/appearance/theme#theme-packages). +> **Note:** The BroadcastChannel mechanism only handles synchronization locally across tabs of the same browser. To synchronize data across entirely different browsers (e.g., Chrome to Firefox), you must utilize the WebSocket provider layer and connect both environments to a properly configured, centralized backend WebSocket server. -To apply the [Tailwind 3](https://www.npmjs.com/package/@syncfusion/ej2-tailwind3-theme) theme, install the corresponding theme package by using the following command: +## Configure the current user -```bash -npm install @syncfusion/ej2-tailwind3-theme --save +Set the current user's display name and cursor highlight color using the `users` and `currentUserId` properties. The `avatarBgColor` value is used for that user's remote cursor and text selection overlay. The users property includes `id`, `user` and `avatarBgColor`. + +```ts + + + ``` -Then add the following CSS reference to the **src/App.vue** file: +### Get active users -{% tabs %} -{% highlight html tabtitle="~/src/App.vue" %} +Retrieve all currently connected users using the `users` property in the block editor. - +```typescript +const blockEditorRef = ref(null); +const users = blockEditorRef.value?.users; +``` -{% endhighlight %} -{% endtabs %} +## Version history + +`Version History` allows you to capture document snapshots and restore earlier versions. This is a built-in capability of the Block Editor and does not require a third-party service. + +### Enable version history + +- Inject the `VersionHistory` module and configure the `versionHistory` property under `collaborationSettings` property. -## Adding Block Editor component +- Version snapshots need to be persisted to enable version history across browser sessions. -The Block Editor code should be added in the **src/App.vue** file, using either the Composition API or the Options API. +- Implement the `IVersionStorage` interface to provide a custom storage backend for managing snapshots. You can use IndexedDB, a backend database, or any other storage solution suitable for your deployment. + +- The `IVersionStorage` interface defines the following methods: + +| Method | Signature | Description | +| -------- | -------- | ----------- | +| `saveSnapshot` | `(snapshot: VersionSnapshot): Promise` | Persist a snapshot. | +| `loadAllSnapshots` | `(): Promise` | Load all persisted snapshots, ordered by timestamp ascending. | +| `loadSnapshot` | `(id: string): Promise` | Load a single snapshot by id. | +| `deleteSnapshot` | `(id: string): Promise` | Permanently remove a snapshot by id. | +| `clearAll` | `(): Promise` | Remove all snapshots from storage. | + +- After the Block Editor initializes, retrieve the version history instance and wait for snapshot data to load before calling any version history methods. + +Before that need to create a storage service for snapshots. +- Create versionHistoryService.ts with IndexedDBVersionStorage class +- This class implements IVersionStorage interface (required by Syncfusion) + +Make Storage Room-Specific by importing roomName from collaboration.ts to make each room gets its own isolated snapshot database. + +Create a `versionHistoryService.ts` file in the src folder, replace the `App.vue` file to configure the BlockEditorComponent, and replace the `App.css` file with the styles required for the version history panel. {% tabs %} -{% highlight html tabtitle="Composition API (~/src/App.vue)" %} -{% include code-snippet/rich-text-editor-sdk/vue/block-editor/getting-started/app-composition.vue %} +{% highlight vue tabtitle="App.vue" %} + + + {% endhighlight %} -{% highlight html tabtitle="Options API (~/src/App.vue)" %} -{% include code-snippet/rich-text-editor-sdk/vue/block-editor/getting-started/app.vue %} + +{% highlight css tabtitle="App.css" %} +.app-container { + display: flex; + gap: 20px; + padding: 20px; +} + +.editor-section { + flex: 1; +} + +.version-history-panel { + width: 350px; + padding: 15px; + border: 1px solid #ddd; +} + +.snapshots-container { + max-height: 600px; + overflow-y: auto; +} + +.snapshot-item { + padding: 10px; + margin-bottom: 10px; + border: 1px solid #eee; +} + +.snapshot-timestamp { + font-size: 12px; + color: #666; + margin-bottom: 8px; +} + +.snapshot-actions { + display: flex; + gap: 5px; +} + +.snapshot-actions button { + padding: 5px 10px; + border: none; + cursor: pointer; + color: white; +} + +.restore-btn { + background-color: #007bff; +} + +.delete-btn { + background-color: #dc3545; +} +{% endhighlight %} + +{% highlight ts tabtitle="versionHistoryService.ts" %} +import type { IVersionStorage, VersionSnapshot } from '@syncfusion/ej2-vue-blockeditor'; + +export class IndexedDBVersionStorage implements IVersionStorage { + private db: IDBDatabase | null = null; + private initPromise: Promise; + + constructor(dbName: string) { + this.initPromise = new Promise((resolve) => { + const req = indexedDB.open(dbName, 1); + req.onsuccess = () => { this.db = req.result; resolve(); }; + req.onupgradeneeded = (e) => { + const db = (e.target as IDBOpenDBRequest).result; + if (!db.objectStoreNames.contains('snapshots')) { + db.createObjectStore('snapshots', { keyPath: 'id' }); + } + }; + }); + } + + private exec(mode: 'readonly' | 'readwrite', fn: (store: IDBObjectStore) => IDBRequest): Promise { + return this.initPromise.then(() => new Promise((resolve, reject) => { + const tx = this.db!.transaction('snapshots', mode); + const req = fn(tx.objectStore('snapshots')); + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + })); + } + + async saveSnapshot(snapshot: VersionSnapshot): Promise { + await this.exec('readwrite', (store) => store.put(snapshot)); + } + + async loadAllSnapshots(): Promise { + return await this.exec('readonly', (store) => store.getAll()); + } + + async loadSnapshot(id: string): Promise { + return await this.exec('readonly', (store) => store.get(id)); + } + + async deleteSnapshot(id: string): Promise { + await this.exec('readwrite', (store) => store.delete(id)); + } + + async clearAll(): Promise { + await this.exec('readwrite', (store) => store.clear()); + } +} {% endhighlight %} {% endtabs %} -{% previewsample "https://help.syncfusion.com/code-snippet/rich-text-editor-sdk/vue/block-editor/getting-started/index" %} +Once done, run the app to see versionHistory panel for individual rooms. -## Run the application +### Methods -Use the following command to run the application in the browser. +The following are the methods available in the `IVersionHistory`: -```bash -npm run serve +#### Create a snapshot + +Creates a new snapshot of the current document state with an optional label and metadata. + +```typescript +const versionHistory = blockEditorRef.value?.getVersionHistory(); +const snapshot = await versionHistory.createSnapshot({ + label: 'Before major update', + modifiedBy: currentUserId +}); ``` -or +#### List snapshots -```bash -yarn run serve +Retrieves all saved snapshots or a paginated subset. Snapshots are returned in chronological order. + +```typescript +const versionHistory = blockEditorRef.value?.getVersionHistory(); +// Retrieve all snapshots +const snapshots = versionHistory.getSnapshots(); + +// Retrieve a paginated subset — getSnapshots(skip, take) +const snapshots = versionHistory.getSnapshots(20, 40); +``` + +#### Rename a snapshot + +Updates the label or metadata of an existing snapshot without modifying its content. + +```typescript +const versionHistory = blockEditorRef.value?.getVersionHistory(); +await versionHistory.renameSnapshot(snapshotId, 'Release Candidate'); +``` + +#### Restore a snapshot + +Reverts the document to a previously saved snapshot state. The current document state is automatically backed up before restoration. + +```typescript +const versionHistory = blockEditorRef.value?.getVersionHistory(); +await versionHistory.restoreSnapshot(snapshotId); ``` -For migrating from Vue 2 to Vue 3, refer to the [`migration`](https://ej2.syncfusion.com/vue/documentation/getting-started/vue-3-vue-cli#migration-from-vue-2-to-vue-3) documentation. +> **Note:** When a snapshot is restored, the current document state is automatically +> backed up before the restore operation is applied. + +#### Compare versions + +Compares two snapshots to identify differences such as added, removed, or modified content. + +```typescript +const versionHistory = blockEditorRef.value?.getVersionHistory(); +const diff = versionHistory.compareVersions(snapshotIdA, snapshotIdB); +``` + +The returned `VersionDiff` object provides a summary of the differences between the two selected versions. + +#### Export a snapshot + +Serializes a snapshot into a portable format that can be stored externally or transferred between systems. + +```typescript +const versionHistory = blockEditorRef.value?.getVersionHistory(); +const exported = await versionHistory.exportSnapshot(snapshotId); +``` + +Exported snapshots can be stored externally or transferred between systems. + +#### Import a snapshot + +Imports a previously exported snapshot back into the version history storage. + +```typescript +const versionHistory = blockEditorRef.value?.getVersionHistory(); +const imported = await versionHistory.importSnapshot(exported); +``` + +## Events + +Use the following event callbacks in `versionHistory` settings to respond to snapshot life cycle events. + +### snapshotCreated + +Triggered when a new snapshot is created. + +```ts + + + +``` + +### snapshotRestored + +Triggered when a snapshot is restored. + +```ts + + + +```