noteloom 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -440,41 +440,25 @@ A comment thread is `{ id, blockId, anchorRunIds, resolved, messages: [{ id, aut
440
440
 
441
441
  ## Version history
442
442
 
443
- Point-in-time document snapshots, stored in IndexedDB (a third object store, alongside `usePersistedDocument`'s `documents` and Templates' `templates`) periodic, manual, or both. `examples/07-version-history/` is a complete runnable app with a version list and one-click restore.
443
+ Google Docs-style — there's no "type a label and save" step. Point-in-time document snapshots (stored in IndexedDB, a third object store alongside `usePersistedDocument`'s `documents` and Templates' `templates`) are captured automatically after each burst of edits settles down, each one attributed to whoever made the changes. `examples/07-version-history/` is a complete runnable app.
444
444
 
445
445
  ```jsx
446
- import { createPeriodicVersionSnapshotter, useDocumentVersions, saveDocumentVersion, applyDocumentTemplate, exportDocumentJSON } from 'noteloom';
447
-
448
- // Automatic: snapshot every few minutes, keep the most recent 50.
449
- useEffect(() => {
450
- const snapshotter = createPeriodicVersionSnapshotter({ store: editor.store, docId, intervalMs: 5 * 60 * 1000 });
451
- return () => snapshotter.stop();
452
- }, [editor.store, docId]);
453
-
454
- // Manual: "save a version now" button.
455
- async function saveNow(label) {
456
- const doc = JSON.parse(exportDocumentJSON(editor.store)); // exportDocumentJSON returns a JSON *string* — parse it first
457
- await saveDocumentVersion({ id: crypto.randomUUID(), docId, timestamp: Date.now(), label, doc });
458
- }
446
+ import { useEditor, NoteloomEditor, VersionHistory } from 'noteloom';
459
447
 
460
- // A version list:
461
- function VersionList({ docId }) {
462
- const { versions, isLoaded } = useDocumentVersions(docId); // newest first
463
- if (!isLoaded) return <p>Loading…</p>;
464
- return (
465
- <ul>
466
- {versions.map((v) => (
467
- <li key={v.id}>
468
- {v.label ?? '(untitled)'} — {new Date(v.timestamp).toLocaleString()}
469
- <button onClick={() => applyDocumentTemplate(editor.store, v.doc)}>Restore</button>
470
- </li>
471
- ))}
472
- </ul>
473
- );
474
- }
448
+ const editor = useEditor({ currentUserId: currentUser.id }); // stamps every edit's author, see below
449
+
450
+ <NoteloomEditor editor={editor}>
451
+ <VersionHistory docId={docId} />
452
+ </NoteloomEditor>;
475
453
  ```
476
454
 
477
- Restoring needs no new function it's the exact same `applyDocumentTemplate(store, doc)` Templates already uses to wholesale-replace a live editor's content. `saveDocumentVersion`/`loadDocumentVersion`/`deleteDocumentVersion`/`listDocumentVersions` are the raw storage operations `useDocumentVersions` is built on. `createPeriodicVersionSnapshotter({ store, docId, intervalMs?, label?, maxVersions? })` prunes the oldest version past `maxVersions` (default 50) after each snapshot, so a long-running document doesn't grow the store unbounded.
455
+ That's the whole integration. `<VersionHistory>` is self-contained: it renders the "Version history" button, and for as long as it's mounted it also quietly captures snapshots in the background no separate wiring needed. Clicking the button opens a drawer (matching the built-in Comments UI's own design language) listing every version grouped by day, each showing an avatar, author, relative time, and a lightweight summary ("3 blocks changed"); clicking one opens it on a **Changes** tab — a word-level diff against the version right before it, insertions highlighted green, deletions struck through in red, Google Docs "show changes"-style — with a **Preview** tab alongside for a plain read-only render, and a "Restore this version" button.
456
+
457
+ **Attribution** — `currentUserId` (passed to `useEditor()`, or `history.setDefaultActorId(id)`/`new History(store, { defaultActorId })` for the granular API) is stamped as every edit's `actorId` automatically; `VersionHistory`/`createAutoVersionHistory` read it straight off the history log, no separate identity plumbing required. Omit it and versions still get created, just with `authorId: null` (shown as "Unknown").
458
+
459
+ **Tuning the capture window** — `<VersionHistory docId idleMs={5 * 60 * 1000} maxVersions={200} />`: `idleMs` (default 5 minutes) is how long edits need to pause before a version is closed and saved (a smaller value in the example app, so you don't have to actually wait); `maxVersions` prunes the oldest versions beyond that count. For the granular API, or to save an explicit snapshot right before some risky action, use `createAutoVersionHistory({ store, docId, idleMs?, maxVersions? })` directly — it returns `{ stop, flush }`; `flush()` closes and saves the current window immediately instead of waiting for the idle gap (e.g. right before navigating away).
460
+
461
+ Restoring needs no new function — it's the exact same `applyDocumentTemplate(store, doc)` Templates already uses to wholesale-replace a live editor's content, which is what the drawer's own Restore button calls. `saveDocumentVersion`/`loadDocumentVersion`/`deleteDocumentVersion`/`listDocumentVersions` are the raw storage operations everything above is built on, for anywhere the built-in UI doesn't fit; `useDocumentVersions(docId)` is the reactive hook if you want to build your own list instead of `<VersionHistory>`; `diffDocumentsHTML(prevDoc, nextDoc)` is the diffing function behind the Changes tab (pass `null` as `prevDoc` to mark everything as newly added), for building a custom diff view instead.
478
462
 
479
463
  ## Right-to-left / multi-language text
480
464
 
package/dist/index.d.ts CHANGED
@@ -148,6 +148,8 @@ export interface HistoryOptions {
148
148
  idleMs?: number;
149
149
  trackChanges?: boolean;
150
150
  maxChangeLogSize?: number;
151
+ /** Stamped as every edit's actorId when a perform/performBatch call doesn't pass its own -- see useEditor's `currentUserId`. */
152
+ defaultActorId?: string | null;
151
153
  }
152
154
 
153
155
  export interface HistoryLogEntry {
@@ -171,6 +173,8 @@ export interface OperationMeta {
171
173
  export class History {
172
174
  constructor(store: EditorStore, options?: HistoryOptions);
173
175
  store: EditorStore;
176
+ defaultActorId: string | null;
177
+ setDefaultActorId(actorId: string | null): void;
174
178
 
175
179
  getBlock(id: string): Block | undefined;
176
180
  getRun(id: string): Run | undefined;
@@ -384,6 +388,13 @@ export interface DocumentVersion {
384
388
  id: string;
385
389
  docId: string;
386
390
  timestamp: number;
391
+ /** Whoever made the most recent edit in this version's window -- read from History's defaultActorId, null if never configured. */
392
+ authorId?: string | null;
393
+ /** Every distinct actorId that contributed to this version's window. */
394
+ authorIds?: string[];
395
+ /** Lightweight auto-generated description (e.g. "3 blocks changed") -- not a full diff. */
396
+ summary?: string;
397
+ /** Only ever set by renaming a version yourself -- nothing in this package's automatic capture sets it. */
387
398
  label?: string;
388
399
  doc: DocumentJSON;
389
400
  }
@@ -394,16 +405,21 @@ export function deleteDocumentVersion(id: string): Promise<void>;
394
405
  /** All versions saved for docId, newest first. */
395
406
  export function listDocumentVersions(docId: string): Promise<DocumentVersion[]>;
396
407
 
397
- /** Periodically snapshots a live document into the versions store; prunes oldest beyond maxVersions for this docId. Restore with applyDocumentTemplate(store, version.doc). */
398
- export function createPeriodicVersionSnapshotter(options: {
399
- store: History | EditorStore;
408
+ /**
409
+ * Automatic, Google Docs-style version history -- no "name it and save"
410
+ * step. `store` must be a History instance (needs getHistoryLog()/
411
+ * subscribeToHistory()). Saves one snapshot after each burst of edits
412
+ * settles (idleMs of inactivity), attributed to whoever made them.
413
+ */
414
+ export function createAutoVersionHistory(options: {
415
+ store: History;
400
416
  docId: string;
401
- intervalMs?: number;
402
- label?: string;
417
+ /** Inactivity gap that closes a version's window. Default 5 minutes. */
418
+ idleMs?: number;
403
419
  maxVersions?: number;
404
420
  onSnapshot?: (version: DocumentVersion) => void;
405
421
  onError?: (error: unknown) => void;
406
- }): { stop: () => void };
422
+ }): { stop: () => void; flush: () => Promise<void> };
407
423
 
408
424
  export function useDocumentVersions(docId: string | null | undefined): {
409
425
  versions: DocumentVersion[];
@@ -411,6 +427,24 @@ export function useDocumentVersions(docId: string | null | undefined): {
411
427
  refresh: () => Promise<void>;
412
428
  };
413
429
 
430
+ /**
431
+ * Google Docs "show changes"-style HTML diff of `nextDoc` against `prevDoc`
432
+ * (pass null/undefined for prevDoc to mark everything as newly added) --
433
+ * word-level insertions/deletions wrapped in `.be-version-diff-added`/
434
+ * `.be-version-diff-removed` spans. Used internally by `<VersionHistory>`'s
435
+ * "Changes" tab; exported for building a custom version-history UI.
436
+ */
437
+ export function diffDocumentsHTML(prevDoc: DocumentJSON | null | undefined, nextDoc: DocumentJSON): string;
438
+
439
+ export interface VersionHistoryProps {
440
+ docId: string;
441
+ idleMs?: number;
442
+ maxVersions?: number;
443
+ }
444
+
445
+ /** Self-contained "Version history" button + drawer (list/preview/restore) -- also owns the automatic capture (createAutoVersionHistory) for as long as it's mounted. `store` (from context) must be a History instance. */
446
+ export const VersionHistory: ComponentType<VersionHistoryProps>;
447
+
414
448
  // ---------------------------------------------------------------------------
415
449
  // comments/
416
450
  // ---------------------------------------------------------------------------
@@ -815,6 +849,8 @@ export interface UseEditorOptions {
815
849
  doc?: DocumentJSON;
816
850
  /** true (default): store is undo/redo-aware (a History instance). false: a plain EditorStore. */
817
851
  history?: boolean;
852
+ /** Stamped as every edit's actorId (History's defaultActorId) -- used by createAutoVersionHistory/VersionHistory for "who changed this", with no separate identity plumbing needed. Ignored when history: false. */
853
+ currentUserId?: string | null;
818
854
  /** Replaces registerBuiltInBlocks for an opt-in subset of block types. */
819
855
  registerBlocks?: (registry: BlockRegistry) => void;
820
856
  /** Replaces registerBuiltInInlineTypes for an opt-in subset of inline types. */