A plugin for @samvera/clover-iiif that adds
annotation tooling for image, audio, and video canvases.
It provides:
- OpenSeadragon drawing tools for image canvases
- an information panel for managing CloverMarks
- multilingual supplementing/translating text bodies
- IIIF AnnotationPage export for session annotations
- WEBVTT ingest/edit/export support for timed caption workflows
- optional in-browser streaming speech-to-text for AV workflows using the Parakeet runtime and models
In a Clover host app, install Clover and this plugin:
npm install @samvera/clover-iiif@latest @nulib/clover-mark-plugin@samvera/clover-iiif is a peer dependency (>=3.3.8 <5) so the host app controls Clover versioning.
Image canvas workflow showing drawing tools, annotation targeting, and CloverMark panel editing in one view.

Audiovisual workflow with model loading, microphone controls, and transcription/translation editing in the CloverMark tab.

Word-level timestamp editing for timed transcription output while preserving each token's original time window.

Use the plugin as a named import in your React app:
import React, { useMemo, useState } from "react";
import Viewer from "@samvera/clover-iiif/viewer";
import { initCloverI18n } from "@samvera/clover-iiif/i18n";
import { cloverMarkPlugin } from "@nulib/clover-mark-plugin";
const MOTIVATION_OPTIONS = [
"commenting",
"highlighting",
"tagging",
"supplementing",
] as const;
const TRANSLATION_LANGUAGE_OPTIONS = ["en", "fr", "es", "ht", "ar"] as const;
type MotivationOption = (typeof MOTIVATION_OPTIONS)[number];
type TranslationLanguageOption = (typeof TRANSLATION_LANGUAGE_OPTIONS)[number];
export function ManifestViewer({ manifestUrl }: { manifestUrl: string }) {
const i18n = useMemo(() => initCloverI18n(), []);
const [language, setLanguage] = useState("en");
const defaultMotivation: MotivationOption = "supplementing";
const defaultTranslationLanguage: TranslationLanguageOption = "en";
const plugins = useMemo(
() => [
cloverMarkPlugin({
id: "clover-mark",
defaultMotivation,
motivationOptions: [...MOTIVATION_OPTIONS],
translationLanguageOptions: [...TRANSLATION_LANGUAGE_OPTIONS],
defaultTranslationLanguage,
enableStreamingStt: true,
tabLabelByLanguage: {
en: "CloverMark",
es: "CloverMark",
fr: "CloverMark (francais)",
},
translations: {
es: {
tabLabel: "CloverMark",
motivationCommenting: "Comentario",
motivationHighlighting: "Resaltado",
motivationDescribing: "Descripcion",
motivationTranscribing: "Transcripcion",
motivationTranslating: "Traduccion",
motivationTagging: "Etiquetado",
motivationSupplementing: "Suplemento",
},
fr: {
tabLabel: "CloverMark (francais)",
motivationCommenting: "Commentaire",
motivationHighlighting: "Surlignage",
motivationDescribing: "Description",
motivationTranscribing: "Transcription",
motivationTranslating: "Traduction",
motivationTagging: "Etiquetage",
motivationSupplementing: "Complement",
},
},
}),
],
[],
);
return (
<>
<label htmlFor="viewer-language">Viewer language</label>
<select
id="viewer-language"
value={language}
onChange={(event) => {
const nextLanguage = event.target.value;
setLanguage(nextLanguage);
void i18n.changeLanguage(nextLanguage);
}}
>
<option value="en">English</option>
<option value="fr">French</option>
<option value="es">Spanish</option>
</select>
<Viewer
iiifContent={manifestUrl}
plugins={plugins}
options={{
informationPanel: {
open: true,
defaultTab: "clover-mark",
},
showTitle: true,
}}
/>
</>
);
}cloverMarkPlugin is a named export. Use:
import { cloverMarkPlugin } from "@nulib/clover-mark-plugin";Do not use a default import (import cloverMarkPlugin from ...).
Plugin options:
id(optional): custom plugin id. Default:clover-mark.enableImageDrawing(optional): setfalseto disable OpenSeadragon drawing controls. Default: enabled.defaultMotivation(optional): default annotation motivation. Default:supplementing.motivationOptions(optional): allowed motivations in the panel.translationLanguageOptions(optional): selectable translation languages. Default fallback:["en", "fr", "es"].defaultTranslationLanguage(optional): default translation language for new draft text.enableStreamingStt(optional): setfalseto disable streaming speech-to-text UI. Default: enabled.sttModelVersion(optional): Parakeet model id. Default:parakeet-tdt-0.6b-v3.sttUpdateIntervalMs(optional): streaming update cadence (minimum250ms). Default:500ms.tabLabel(optional): fallback information panel label (nonelocale). Default:CloverMark.tabLabelByLanguage(optional): localized information panel labels by language code.translations(optional): i18n translation overrides/additions by language code.
- Clover controls active language through i18next.
- This plugin registers its own namespace:
CloverMark. - Built-in strings are included for
en,fr, andes. - Provide
translationsto override or extend strings for your locales.
Common translation keys:
tabLabelsessionCloverMarks,noSessionCloverMarksscholiumLabel,scholiumComment,motivationtranslationLanguage,translationText,translationAdd,translationDeletedrawingOn,drawingOff,drawingRectangle,drawingPolygonexportAnnotations,exportWebVtt,exportNoAnnotations,exportNoWebVtt,exportSuccess,exportWebVttSuccesssttLoadModel,sttStartRecording,sttStartViewer,sttStartViewerFast,sttStopRecordingsttStatus,sttModelStateReady,sttStreamingError,sttViewerUnavailable
- Adds an information panel tab for CloverMark session management
- Adds drawing controls (rectangle/polygon) for image canvases
- Supports annotation editing for image and AV canvases
- Supports translation bodies with per-translation language codes
- Supports quick-start viewer and microphone transcription workflows
- Captures timed words from STT and supports timestamp seeking/editing
- Supports WEBVTT cue parsing, timed-segment editing, and WEBVTT export
- Exports current session annotations as a IIIF Presentation 3 AnnotationPage
- Includes built-in English, French, and Spanish UI strings
- Reads WEBVTT from annotation
TextualBodyvalues whenformatistext/vttortext/webvtt. - Reads remote WEBVTT references from
TextualBody.idURLs (whenformatis WEBVTT) and fetches cue text in-browser. - Accepts timed-word JSON payloads (
schema: clover.parakeet.word_timestamps.v1) and segments them into caption-length WEBVTT cues. - Parses cue identifiers and timing settings, ignores
NOTEblocks, strips cue markup tags, and decodes common HTML entities. - Normalizes cue timings to millisecond precision for consistent parse/serialize round-trips.
- Lets users edit timed segments in the panel regardless of whether storage began as JSON timed words or WEBVTT.
- Writes edited timed segments back as WEBVTT cue text when the source body is WEBVTT-backed.
- Exports all session timed segments as a single
clover-mark-annotations.vttfile from the panel. - During native annotation-page sync, converts timed transcript bodies into segmented WEBVTT data-URI bodies and keeps external WEBVTT URLs when present.
npm installRun local dev viewer (Vite, port 3003):
npm run devBuild distributable output (dist/, ESM + CJS + types):
npm run buildWatch library build:
npm run watchType-check:
npm run typecheckRun tests:
npm run testnpm publishThe prepublishOnly script runs typecheck and build before publish.
- Streaming STT is fully client-side and loads the Parakeet runtime/model on demand.
- Default STT model:
parakeet-tdt-0.6b-v3(large download, about2.5GB). - Session annotations are kept in runtime state; export captures the current in-memory session.