A diff algorithm and viewer for comparing documents, with special support for UN resolutions.
npm install github:un-eosg-analytics/undifferentThe core module provides a pure TypeScript diff algorithm with no React dependency:
import { diff, similarity, highlight } from 'undifferent/core'
// Compare two arrays of lines
const result = diff(linesA, linesB, { threshold: 0.8 })
console.log(result.score) // Overall similarity (0-1)
console.log(result.items) // Array of diff items with highlighting
// Calculate similarity between two strings
const score = similarity('hello world', 'hello there')
// Get highlighted diff markup
const { left, right } = highlight('old text', 'new text')
// left: "~~old~~ text"
// right: "**new** text"The react module provides components for displaying diffs:
import { DiffViewer, Comparison, DiffItem } from 'undifferent/react'
import type { DiffResult } from 'undifferent/core'
import type { UNDocumentMetadata } from 'undifferent/un-fetcher'
// Full viewer with document headers (symbol, date, PDF link, vote)
<DiffViewer
data={diffResult}
left={{
symbol: 'A/RES/77/16',
metadata: leftMetadata, // UNDocumentMetadata from fetchDocumentMetadata
format: 'doc', // 'doc' | 'pdf'
}}
right={{
symbol: 'A/RES/79/326',
metadata: rightMetadata,
format: 'doc',
}}
/>
// Or build your own UI with individual components
{diffResult.items.map((item, i) => (
<Comparison key={i} item={item} />
))}The un-fetcher module provides utilities for fetching UN documents (server-side only):
import { fetchUNDocument, fetchDocumentMetadata } from 'undifferent/un-fetcher'
// Fetch a UN document by symbol
const doc = await fetchUNDocument('A/RES/77/16')
console.log(doc.lines) // Array of text lines
console.log(doc.format) // 'doc' or 'pdf'
// Fetch document metadata from UN Digital Library
const meta = await fetchDocumentMetadata('A/HRC/RES/50/13')
console.log(meta.title) // "Access to medicines, vaccines..."
console.log(meta.date) // "2022-07-14"
console.log(meta.year) // 2022
console.log(meta.subjects) // ["RIGHT TO HEALTH", "VACCINES", ...]
console.log(meta.vote) // { inFavour: 29, against: 15, abstaining: 3 }The React components use CSS variables for theming:
:root {
--diff-item-bg: #ffffff;
--diff-added-bg: #bbf7d0;
--diff-removed-bg: #fef2f2;
--diff-moved-bg: #fefce8;
--diff-aligned-bg: #eff6ff;
}diff(linesA, linesB, options?)- Compute structured diffsimilarity(a, b)- Calculate Levenshtein similarity ratiohighlight(a, b)- Generate diff markup
<DiffViewer>- Full diff viewer with document headers and diff items<DocumentHeader>- Document metadata display (symbol, date, PDF link, vote)<Comparison>- Single diff row (left + right)<DiffItem>- Single side contentparseHighlightedText(text)- Parse diff markup to React elements
fetchUNDocument(symbol)- Fetch UN document content by symbol from ODSfetchDocumentMetadata(symbol)- Fetch metadata (title, date, year) from UN Digital Library
undifferent/
├── src/ # LIBRARY CODE - edit here for features
│ ├── core/ # Pure TypeScript diff algorithm (no React)
│ │ ├── diff.ts # Main diff algorithm
│ │ ├── similarity.ts # Levenshtein similarity
│ │ └── highlight.ts # Diff markup generation
│ ├── react/ # React components for displaying diffs
│ │ ├── DiffViewer.tsx # Main viewer component
│ │ ├── DocumentHeader.tsx # Document metadata display
│ │ ├── Comparison.tsx # Single diff row (left + right)
│ │ └── DiffItem.tsx # Single side content
│ └── un-fetcher/ # UN document fetching (server-side)
│ ├── fetcher.ts # Fetch documents from ODS
│ └── parser.ts # Parse UN symbols
├── app/ # DEMO APP ONLY - just consumes the library
│ ├── page.tsx # Demo page with example comparisons
│ └── api/diff/route.ts # API endpoint using un-fetcher
└── dist/ # Built library output
IMPORTANT: The app/ directory is only a demo. All features, UI components, and logic should be implemented in src/ (the library). The app should only:
- Provide example comparisons
- Call the API and pass data to library components
- Handle routing/navigation
MIT