TipTap Integration
WriteTrack includes a TipTap extension, compatible with @tiptap/core v2.0+ and tested against v3.19. See React and Vue examples below.
Installation
Section titled “Installation”Install WriteTrack alongside your TipTap packages:
npm i writetrack @tiptap/core @tiptap/starter-kitpnpm add writetrack @tiptap/core @tiptap/starter-kityarn add writetrack @tiptap/core @tiptap/starter-kitbun add writetrack @tiptap/core @tiptap/starter-kitBasic Usage
Section titled “Basic Usage”Add a container element for the editor, then initialize with the extension:
<div id="editor"></div>import { Editor } from '@tiptap/core';import StarterKit from '@tiptap/starter-kit';import { WriteTrackExtension } from 'writetrack/tiptap';
const editor = new Editor({ element: document.querySelector('#editor'), extensions: [ StarterKit, WriteTrackExtension.configure({ license: 'your-license-key', }), ],});
// Access WriteTrack data when neededconst data = editor.storage.writetrack.tracker.getData();Configuration
Section titled “Configuration”Pass options via .configure():
WriteTrackExtension.configure({ license: 'your-license-key', userId: 'user-123', contentId: 'document-456', metadata: { formName: 'signup' }, autoStart: true, // default});| Option | Type | Default | Description |
|---|---|---|---|
license | string | undefined | WriteTrack license key |
userId | string | undefined | User identifier included in metadata |
contentId | string | undefined | Content identifier included in metadata |
metadata | Record<string, unknown> | undefined | Additional metadata |
autoStart | boolean | true | Start tracking when editor is created |
wasmUrl | string | undefined | URL to WASM binary for analysis |
persist | boolean | false | Enable IndexedDB session persistence (requires contentId) |
onTick | (data: { activeTime: number; totalTime: number; tracker: WriteTrack }) => void | undefined | Callback fired every ~1s with active session time and tracker instance |
onReady | (tracker: WriteTrack) => void | undefined | Callback fired when tracker is ready (persistence loaded if applicable) |
Accessing Data
Section titled “Accessing Data”The extension stores the WriteTrack instance in TipTap’s storage system:
// Get the tracker instanceconst tracker = editor.storage.writetrack.tracker;
// Get typing dataconst data = tracker.getData();
// Check readiness and tracking statusconst isReady = editor.storage.writetrack.isReady;const isTracking = editor.storage.writetrack.isTracking;Storage
Section titled “Storage”| Property | Type | Description |
|---|---|---|
tracker | WriteTrack | null | The underlying WriteTrack instance |
isReady | boolean | Whether the tracker is ready (persistence loaded if applicable) |
isTracking | boolean | Whether tracking is currently active |
Editor Event
Section titled “Editor Event”The extension emits a writetrack:ready event on the TipTap editor when the tracker becomes ready. This is useful for sibling components that don’t configure the extension directly:
editor.on('writetrack:ready', ({ tracker }) => { // Tracker is ready — subscribe to events, update UI, etc. tracker.on('analysis', (analysis) => { console.log('Analysis:', analysis); });});import { useEditor, EditorContent } from '@tiptap/react';import StarterKit from '@tiptap/starter-kit';import { WriteTrackExtension } from 'writetrack/tiptap';
const writetrack = WriteTrackExtension.configure({ license: 'your-license-key',});
function FormEditor() { const editor = useEditor({ extensions: [StarterKit, writetrack], });
const handleSubmit = () => { const data = editor?.storage.writetrack.tracker?.getData(); console.log('Typing data:', data); };
return ( <div> <EditorContent editor={editor} /> <button onClick={handleSubmit}>Submit</button> </div> );}<script setup>import { useEditor, EditorContent } from '@tiptap/vue-3';import StarterKit from '@tiptap/starter-kit';import { WriteTrackExtension } from 'writetrack/tiptap';
const writetrack = WriteTrackExtension.configure({ license: 'your-license-key',});
const editor = useEditor({ extensions: [StarterKit, writetrack],});
function handleSubmit() { const data = editor.value?.storage.writetrack.tracker?.getData(); console.log('Typing data:', data);}</script>
<template> <div> <EditorContent :editor="editor" /> <button @click="handleSubmit">Submit</button> </div></template>Full HTML Example
Section titled “Full HTML Example”A complete, self-contained page using TipTap + WriteTrack with no framework:
<!DOCTYPE html><html> <head> <style> .ProseMirror { border: 1px solid #ccc; padding: 12px; min-height: 200px; } .ProseMirror:focus { outline: 2px solid #3b82f6; } </style> </head> <body> <div id="editor"></div> <button id="submit">Get Data</button> <pre id="output"></pre>
<script type="module"> import { Editor } from 'https://esm.sh/@tiptap/core'; import StarterKit from 'https://esm.sh/@tiptap/starter-kit'; import { WriteTrackExtension } from 'https://esm.sh/writetrack/tiptap';
const editor = new Editor({ element: document.querySelector('#editor'), extensions: [ StarterKit, WriteTrackExtension.configure({ onReady(tracker) { document .querySelector('#submit') .addEventListener('click', () => { const data = tracker.getData(); document.querySelector('#output').textContent = JSON.stringify(data.quality, null, 2); }); }, }), ], }); </script> </body></html>Manual DOM Attachment
Section titled “Manual DOM Attachment”If you prefer not to use the extension, you can attach WriteTrack directly to TipTap’s editable element:
import WriteTrack from 'writetrack';import { Editor } from '@tiptap/core';import StarterKit from '@tiptap/starter-kit';
const editor = new Editor({ element: document.querySelector('#editor'), extensions: [StarterKit], onCreate({ editor }) { const tracker = new WriteTrack({ target: editor.view.dom, license: 'your-license-key', }); tracker.start(); },});TypeScript
Section titled “TypeScript”The extension augments TipTap’s Storage interface so editor.storage.writetrack is fully typed automatically.
import type { WriteTrackExtensionOptions, WriteTrackExtensionStorage,} from 'writetrack/tiptap';