Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

highlightUrl.ts7.65 kB
/** * Highlight URL Utilities * * Handles encoding/decoding of text highlight data in URLs * for Substack-style shareable text selections. * * URL format: /bills/{session}/{number}?hl={section}:{start}-{end} * Example: /bills/45-1/c-234?hl=s2.1.a:15-47 */ /** * Parsed highlight data */ export interface HighlightParams { /** Section anchor ID (e.g., "s2.1.a") */ section: string; /** Start character offset within section */ start: number; /** End character offset within section */ end: number; } /** * Multiple highlights (for future extension) */ export interface MultiHighlightParams { highlights: HighlightParams[]; } /** * Parse highlight parameter from URL * * @param hlParam - The ?hl= query parameter value * @returns Parsed highlight data or null if invalid * * @example * parseHighlightParam("s2.1.a:15-47") * // { section: "s2.1.a", start: 15, end: 47 } */ export function parseHighlightParam( hlParam: string | null | undefined ): HighlightParams | null { if (!hlParam) return null; // Decode URI component const decoded = decodeURIComponent(hlParam); // Parse format: section:start-end const match = decoded.match(/^([a-z0-9.-]+):(\d+)-(\d+)$/i); if (!match) return null; const [, section, startStr, endStr] = match; const start = parseInt(startStr, 10); const end = parseInt(endStr, 10); // Validate if (isNaN(start) || isNaN(end) || start < 0 || end <= start) { return null; } return { section, start, end }; } /** * Parse multiple highlights from URL (comma-separated) * * @param hlParam - The ?hl= query parameter value * @returns Array of highlight params * * @example * parseMultipleHighlights("s2.1.a:15-47,s2.2:0-30") * // [{ section: "s2.1.a", start: 15, end: 47 }, { section: "s2.2", start: 0, end: 30 }] */ export function parseMultipleHighlights( hlParam: string | null | undefined ): HighlightParams[] { if (!hlParam) return []; const decoded = decodeURIComponent(hlParam); const parts = decoded.split(','); return parts .map((part) => parseHighlightParam(part)) .filter((hl): hl is HighlightParams => hl !== null); } /** * Encode highlight data for URL * * @param highlight - Highlight data to encode * @returns URL-safe encoded string * * @example * encodeHighlightParam({ section: "s2.1.a", start: 15, end: 47 }) * // "s2.1.a:15-47" */ export function encodeHighlightParam(highlight: HighlightParams): string { const param = `${highlight.section}:${highlight.start}-${highlight.end}`; return encodeURIComponent(param); } /** * Encode multiple highlights for URL * * @param highlights - Array of highlights * @returns URL-safe encoded string */ export function encodeMultipleHighlights(highlights: HighlightParams[]): string { if (highlights.length === 0) return ''; const params = highlights .map((hl) => `${hl.section}:${hl.start}-${hl.end}`) .join(','); return encodeURIComponent(params); } /** * Build a full URL with highlight parameter * * @param baseUrl - Base URL without query params * @param highlight - Highlight data * @returns Full URL with highlight parameter * * @example * buildHighlightUrl("/en/bills/45-1/c-234", { section: "s2.1.a", start: 15, end: 47 }) * // "/en/bills/45-1/c-234?hl=s2.1.a%3A15-47" */ export function buildHighlightUrl( baseUrl: string, highlight: HighlightParams ): string { const encoded = encodeHighlightParam(highlight); const separator = baseUrl.includes('?') ? '&' : '?'; return `${baseUrl}${separator}hl=${encoded}`; } /** * Build URL with section anchor and optional highlight * * @param baseUrl - Base URL * @param section - Section to anchor to * @param highlight - Optional highlight within section * @returns Full URL with anchor and optional highlight */ export function buildSectionUrl( baseUrl: string, section: string, highlight?: Omit<HighlightParams, 'section'> ): string { let url = `${baseUrl}#${section}`; if (highlight) { const hlParam = encodeHighlightParam({ section, start: highlight.start, end: highlight.end, }); url += `?hl=${hlParam}`; } return url; } /** * Extract highlight from current URL search params * * @param searchParams - URLSearchParams object * @returns Parsed highlight or null */ export function getHighlightFromSearchParams( searchParams: URLSearchParams ): HighlightParams | null { return parseHighlightParam(searchParams.get('hl')); } /** * Extract highlight from URL string * * @param url - Full URL or just query string * @returns Parsed highlight or null */ export function getHighlightFromUrl(url: string): HighlightParams | null { try { const urlObj = new URL(url, window.location.origin); return parseHighlightParam(urlObj.searchParams.get('hl')); } catch { // Try parsing as just query string if (url.includes('hl=')) { const match = url.match(/[?&]hl=([^&]+)/); if (match) { return parseHighlightParam(match[1]); } } return null; } } /** * Remove highlight parameter from URL * * @param url - URL with highlight param * @returns URL without highlight param */ export function removeHighlightFromUrl(url: string): string { try { const urlObj = new URL(url, window.location.origin); urlObj.searchParams.delete('hl'); return urlObj.pathname + urlObj.search + urlObj.hash; } catch { return url.replace(/[?&]hl=[^&]+/, ''); } } /** * Validate highlight params against section content * * @param highlight - Highlight params to validate * @param sectionLength - Total length of section text * @returns True if highlight is valid for the section */ export function isValidHighlight( highlight: HighlightParams, sectionLength: number ): boolean { return ( highlight.start >= 0 && highlight.end > highlight.start && highlight.end <= sectionLength ); } /** * Normalize highlight params to valid ranges * * @param highlight - Highlight params to normalize * @param sectionLength - Total length of section text * @returns Normalized highlight or null if invalid */ export function normalizeHighlight( highlight: HighlightParams, sectionLength: number ): HighlightParams | null { const start = Math.max(0, highlight.start); const end = Math.min(sectionLength, highlight.end); if (end <= start) return null; return { section: highlight.section, start, end, }; } /** * Generate social share URL with highlight * * @param platform - Social platform * @param url - URL with highlight * @param text - Text to share (optional, for quote) * @returns Platform-specific share URL */ export function getSocialShareUrl( platform: 'twitter' | 'facebook' | 'linkedin', url: string, text?: string ): string { const encodedUrl = encodeURIComponent(url); switch (platform) { case 'twitter': const twitterText = text ? `"${text.slice(0, 200)}${text.length > 200 ? '...' : ''}"` : ''; return `https://twitter.com/intent/tweet?text=${encodeURIComponent(twitterText)}&url=${encodedUrl}`; case 'facebook': return `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`; case 'linkedin': return `https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`; default: return url; } } export default { parseHighlightParam, parseMultipleHighlights, encodeHighlightParam, encodeMultipleHighlights, buildHighlightUrl, buildSectionUrl, getHighlightFromSearchParams, getHighlightFromUrl, removeHighlightFromUrl, isValidHighlight, normalizeHighlight, getSocialShareUrl, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/northernvariables/FedMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server