bsky-richtext-react 1.1.0 → 2.0.0
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/CHANGELOG.md +44 -0
- package/README.md +30 -3
- package/dist/index.cjs +30 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +30 -20
- package/dist/index.js.map +1 -1
- package/package.json +24 -25
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types/facets.ts","../src/defaults/classNames.ts","../src/utils/classNames.ts","../src/utils/utf8.ts","../src/utils/parser.ts","../src/hooks/useRichText.ts","../src/utils/url.ts","../src/components/RichTextDisplay/RichTextDisplay.tsx","../src/utils/blueskyApi.ts","../src/components/RichTextEditor/MentionSuggestionList.tsx","../src/components/RichTextEditor/createSuggestionRenderer.ts","../src/components/RichTextEditor/extensions/BskyMention.ts","../src/components/RichTextEditor/extensions/BskyLinkDecorator.ts","../src/components/RichTextEditor/RichTextEditor.tsx"],"names":["useMemo","jsx","forwardRef","useState","useEffect","useImperativeHandle","jsxs","ReactRenderer","tippy","Mention","Decoration","DecorationSet","PluginKey","Plugin","Extension","AtpRichText","Document","Paragraph","Text","History","HardBreak","Placeholder","useEditor","EditorContent"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * TypeScript types mirroring the `app.bsky.richtext.facet` lexicon.\n *\n * Spec: /lexicons/app/richtext/facet.json\n * Reference: https://atproto.com/lexicons/app-bsky-richtext\n */\n\n// ─── Byte Slice ─────────────────────────────────────────────────────────────\n\n/**\n * Specifies the sub-string range a facet feature applies to.\n * Indices are ZERO-indexed byte offsets of the UTF-8 encoded text.\n * - byteStart: inclusive\n * - byteEnd: exclusive\n *\n * ⚠️ JavaScript strings are UTF-16. Always convert to a UTF-8 byte array\n * before computing byte offsets.\n */\nexport interface ByteSlice {\n byteStart: number\n byteEnd: number\n}\n\n// ─── Facet Features ─────────────────────────────────────────────────────────\n\n/** Mention of another AT Protocol account. The DID is the canonical identifier. */\nexport interface MentionFeature {\n $type: 'app.bsky.richtext.facet#mention'\n /** DID of the mentioned account */\n did: string\n}\n\n/** A hyperlink facet. The URI is the full, unshortened URL. */\nexport interface LinkFeature {\n $type: 'app.bsky.richtext.facet#link'\n /** The full URL */\n uri: string\n}\n\n/** A hashtag facet. The tag value does NOT include the leading '#'. */\nexport interface TagFeature {\n $type: 'app.bsky.richtext.facet#tag'\n /** The tag text without the '#' prefix (max 64 graphemes / 640 bytes) */\n tag: string\n}\n\n/** Union of all possible facet feature types. */\nexport type FacetFeature = MentionFeature | LinkFeature | TagFeature\n\n// ─── Facet ──────────────────────────────────────────────────────────────────\n\n/**\n * A single richtext annotation — maps a byte-range within the post text\n * to one or more semantic features (mention, link, tag).\n */\nexport interface Facet {\n index: ByteSlice\n features: FacetFeature[]\n}\n\n// ─── RichText Record ────────────────────────────────────────────────────────\n\n/**\n * Represents the `text` + `facets` fields as they appear in an AT Protocol\n * record (e.g. `app.bsky.feed.post`).\n */\nexport interface RichTextRecord {\n text: string\n facets?: Facet[]\n}\n\n// ─── Segment ────────────────────────────────────────────────────────────────\n\n/**\n * A parsed segment of richtext — a slice of text with its associated feature\n * (if any). Produced by the richtext parser.\n */\nexport interface RichTextSegment {\n /** The raw text of this segment */\n text: string\n /** The facet feature associated with this segment, if any */\n feature?: FacetFeature\n}\n\n// ─── Type Guards ────────────────────────────────────────────────────────────\n\nexport function isMentionFeature(feature: FacetFeature): feature is MentionFeature {\n return feature.$type === 'app.bsky.richtext.facet#mention'\n}\n\nexport function isLinkFeature(feature: FacetFeature): feature is LinkFeature {\n return feature.$type === 'app.bsky.richtext.facet#link'\n}\n\nexport function isTagFeature(feature: FacetFeature): feature is TagFeature {\n return feature.$type === 'app.bsky.richtext.facet#tag'\n}\n","/**\n * Default classNames for bsky-richtext-react components.\n *\n * These are Tailwind CSS utility classes applied when no override is provided.\n * Consumers can extend or replace them using `generateClassNames()` and the\n * `classNames` prop on each component.\n *\n * Because these are plain utility classes, no separate stylesheet needs to be\n * imported — as long as Tailwind is configured in the consumer's project, all\n * styles are applied automatically.\n *\n * @example Keep defaults, override one class\n * ```tsx\n * import { generateClassNames, defaultEditorClassNames } from 'bsky-richtext-react'\n *\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'border rounded-lg p-2' },\n * ])}\n * ```\n *\n * @example Completely replace defaults (opt out of all built-in class names)\n * ```tsx\n * classNames={{ root: 'my-editor', mention: 'my-mention' }}\n * ```\n */\n\nimport type { DisplayClassNames, EditorClassNames, SuggestionClassNames } from '../types/classNames'\n\n// ─── Display ─────────────────────────────────────────────────────────────────\n\nexport const defaultDisplayClassNames: DisplayClassNames = {\n root: 'inline break-words',\n mention: 'inline text-blue-500 hover:underline cursor-pointer',\n link: 'inline text-blue-500 hover:underline',\n tag: 'inline text-blue-500 hover:underline cursor-pointer',\n}\n\n// ─── Suggestion ──────────────────────────────────────────────────────────────\n\nexport const defaultSuggestionClassNames: SuggestionClassNames = {\n root: 'flex flex-col max-h-80 overflow-y-auto bg-white rounded-lg shadow-lg border border-gray-200 min-w-60',\n item: 'flex items-center gap-3 w-full px-3 py-2 text-left cursor-pointer border-none bg-transparent hover:bg-gray-100 select-none',\n itemSelected: 'bg-gray-100',\n avatar:\n 'flex-shrink-0 w-10 h-10 rounded-full overflow-hidden bg-gray-200 flex items-center justify-center',\n avatarImg: 'block w-full h-full object-cover',\n avatarPlaceholder:\n 'flex items-center justify-center w-full h-full text-gray-500 font-medium text-sm',\n text: 'flex flex-col flex-1 min-w-0 overflow-hidden',\n name: 'block truncate font-medium text-gray-900 text-sm',\n handle: 'block truncate text-xs text-gray-500',\n empty: 'block px-3 py-2 text-sm text-gray-500',\n}\n\n// ─── Editor ──────────────────────────────────────────────────────────────────\n\nexport const defaultEditorClassNames: EditorClassNames = {\n root: 'block w-full relative',\n content: 'block w-full',\n mention: 'inline text-blue-500',\n link: 'inline text-blue-500',\n suggestion: defaultSuggestionClassNames,\n}\n","/**\n * generateClassNames — deep-merge utility for component classNames objects.\n *\n * Accepts an array of partial classNames objects and merges them left-to-right,\n * so later entries override earlier ones. String values at the same key are\n * combined using the optional `cn` function (e.g. `clsx`, `tailwind-merge`).\n * Nested objects (e.g. `suggestion` inside `EditorClassNames`) are recursively\n * merged using the same rules.\n *\n * Falsy values in the array (`undefined`, `null`, `false`) are silently\n * ignored, which makes conditional spreading ergonomic:\n *\n * @example Basic override\n * ```ts\n * import { generateClassNames, defaultEditorClassNames } from 'bsky-richtext-react'\n *\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'border rounded-lg', mention: 'text-blue-500' },\n * ])}\n * ```\n *\n * @example With a Tailwind-merge / clsx utility\n * ```ts\n * import { cn } from '@/lib/utils'\n *\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'rounded-none' },\n * ], cn)}\n * ```\n *\n * @example Conditional overrides\n * ```ts\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * isCompact && { root: 'text-sm' },\n * isDark && darkThemeClassNames,\n * ], cn)}\n * ```\n *\n * @example Deep nested override (suggestion dropdown)\n * ```ts\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { suggestion: { item: 'hover:bg-gray-100', itemSelected: 'bg-blue-50' } },\n * ])}\n * ```\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * A function that accepts any number of class strings (plus falsy values) and\n * returns a single merged class string. Compatible with `clsx`, `classnames`,\n * and `tailwind-merge`'s `twMerge` / `cn` utilities.\n */\nexport type ClassNameFn = (\n ...inputs: Array<string | undefined | null | false>\n) => string\n\n// ─── Implementation ──────────────────────────────────────────────────────────\n\n/**\n * Deep-merge an array of classNames objects into a single classNames object.\n *\n * @param classNamesArray - Objects to merge, left-to-right. Falsy entries are skipped.\n * @param cn - Optional class utility function. When omitted, strings are\n * joined with a single space (filtering empty strings).\n */\nexport function generateClassNames<T extends object>(\n classNamesArray: Array<Partial<T> | undefined | null | false>,\n cn?: ClassNameFn,\n): T {\n // Default merge: join non-empty strings with a space\n const merge: ClassNameFn =\n cn ?? ((...args) => args.filter(Boolean).join(' '))\n\n // Filter out falsy entries\n const validObjects = classNamesArray.filter(\n (obj): obj is Partial<T> => Boolean(obj),\n )\n\n if (validObjects.length === 0) {\n return {} as T\n }\n\n // Collect the union of all keys across every object\n const allKeys = new Set<string>()\n for (const obj of validObjects) {\n for (const key of Object.keys(obj)) {\n allKeys.add(key)\n }\n }\n\n const result = {} as T\n\n for (const key of allKeys) {\n const typedKey = key as keyof T\n\n // Collect all defined values for this key, in order\n const values: unknown[] = validObjects\n .map((obj) => obj[typedKey])\n .filter((v) => v !== undefined)\n\n if (values.length === 0) continue\n\n const firstValue = values[0]\n\n if (\n typeof firstValue === 'object' &&\n firstValue !== null &&\n !Array.isArray(firstValue)\n ) {\n // Nested classNames object — recurse via type-erased internal helper\n result[typedKey] = generateClassNames(\n values as object[],\n cn,\n ) as T[keyof T]\n } else if (values.every((v) => typeof v === 'string')) {\n // String values — combine with merge function\n result[typedKey] = merge(...(values)) as T[keyof T]\n } else {\n // Fallback: use the last defined value\n result[typedKey] = values[values.length - 1] as T[keyof T]\n }\n }\n\n return result\n}\n","/**\n * UTF-8 byte offset utilities.\n *\n * The AT Protocol richtext spec uses UTF-8 byte offsets for facet indices,\n * but JavaScript strings are UTF-16. These helpers bridge that gap.\n *\n * Reference: https://atproto.com/specs/richtext\n */\n\nconst encoder = new TextEncoder()\n\n/**\n * Encode a string to a UTF-8 byte array.\n */\nexport function toUtf8Bytes(text: string): Uint8Array {\n return encoder.encode(text)\n}\n\n/**\n * Convert a UTF-8 byte offset to a JavaScript (UTF-16) string index.\n * Needed when slicing a JS string using AT Protocol byte offsets.\n */\nexport function utf8ByteOffsetToCharIndex(text: string, byteOffset: number): number {\n const bytes = encoder.encode(text)\n // Decode only up to the byte offset, then measure the resulting string length\n const decoder = new TextDecoder()\n const slice = bytes.slice(0, byteOffset)\n return decoder.decode(slice).length\n}\n\n/**\n * Slice a string using UTF-8 byte offsets (inclusive start, exclusive end).\n */\nexport function sliceByByteOffset(text: string, byteStart: number, byteEnd: number): string {\n const startChar = utf8ByteOffsetToCharIndex(text, byteStart)\n const endChar = utf8ByteOffsetToCharIndex(text, byteEnd)\n return text.slice(startChar, endChar)\n}\n\n/**\n * Get the UTF-8 byte length of a string.\n */\nexport function utf8ByteLength(text: string): number {\n return encoder.encode(text).length\n}\n","/**\n * Richtext parser — converts `{ text, facets }` into an ordered array of\n * `RichTextSegment` objects, each with their text slice and optional feature.\n *\n * Algorithm:\n * 1. Sort facets by byteStart (ascending).\n * 2. Walk through the text byte-by-byte, emitting plain segments between\n * facets and annotated segments for each facet's byte range.\n * 3. Overlapping facets are handled gracefully (skipped).\n */\n\nimport type { Facet, FacetFeature, RichTextRecord, RichTextSegment } from '../types/facets'\nimport { sliceByByteOffset, utf8ByteLength } from './utf8'\n\n// ─── Internal helpers ────────────────────────────────────────────────────────\n\n/**\n * Sort facets by their byte start position. Mutates a copy.\n */\nfunction sortFacets(facets: Facet[]): Facet[] {\n return [...facets].sort((a, b) => a.index.byteStart - b.index.byteStart)\n}\n\n/**\n * Pick the \"primary\" feature from a facet's feature array.\n * The lexicon allows multiple features per facet, but for rendering we\n * pick the first valid one (mention > link > tag precedence is implicit\n * in the order they appear).\n */\nfunction pickFeature(features: FacetFeature[]): FacetFeature | undefined {\n return features[0]\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Parse a `RichTextRecord` into an ordered array of segments, each\n * carrying its text and an optional facet feature.\n *\n * The segments are contiguous — joining all `segment.text` values\n * reconstructs the original `record.text` exactly.\n */\nexport function parseRichText(record: RichTextRecord): RichTextSegment[] {\n const { text, facets } = record\n\n if (!facets || facets.length === 0) {\n return [{ text }]\n }\n\n const textByteLength = utf8ByteLength(text)\n const sorted = sortFacets(facets)\n const segments: RichTextSegment[] = []\n\n let cursor = 0 // current byte position\n\n for (const facet of sorted) {\n const { byteStart, byteEnd } = facet.index\n\n // Skip malformed or out-of-bounds facets\n if (byteStart < cursor || byteEnd > textByteLength || byteStart >= byteEnd) {\n continue\n }\n\n // Emit plain text segment before this facet\n if (byteStart > cursor) {\n segments.push({\n text: sliceByByteOffset(text, cursor, byteStart),\n })\n }\n\n // Emit annotated segment for this facet\n const feature = pickFeature(facet.features)\n const segment: RichTextSegment = { text: sliceByByteOffset(text, byteStart, byteEnd) }\n if (feature !== undefined) segment.feature = feature\n segments.push(segment)\n\n cursor = byteEnd\n }\n\n // Emit any trailing plain text after the last facet\n if (cursor < textByteLength) {\n segments.push({\n text: sliceByByteOffset(text, cursor, textByteLength),\n })\n }\n\n return segments\n}\n","import { useMemo } from 'react'\nimport type { RichTextRecord, RichTextSegment } from '../types/facets'\nimport { parseRichText } from '../utils/parser'\n\n/**\n * Parse a `RichTextRecord` into an array of `RichTextSegment` objects.\n *\n * The result is memoized — re-computation only occurs when `text` or the\n * serialized facets change.\n *\n * @example\n * ```tsx\n * const segments = useRichText({ text: post.text, facets: post.facets })\n * // [{ text: 'Hello ' }, { text: '@alice', feature: { $type: '...mention', did: '...' } }]\n * ```\n */\nexport function useRichText(record: RichTextRecord): RichTextSegment[] {\n // Stable serialization key for the facets array so useMemo can diff it\n const facetsKey = useMemo(\n () => (record.facets ? JSON.stringify(record.facets) : ''),\n [record.facets],\n )\n\n return useMemo(\n () => parseRichText(record),\n // record is intentionally not in deps — we only react to text+facets changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [record.text, facetsKey],\n )\n}\n","/**\n * URL display utilities for richtext rendering.\n */\n\n/**\n * Shorten a URL for display purposes — strips the protocol and truncates\n * the path if it's very long (mirrors Bluesky's `toShortUrl` behaviour).\n *\n * @example\n * toShortUrl('https://example.com/some/very/long/path?q=foo')\n * // => 'example.com/some/very/long/path?q=foo'\n */\nexport function toShortUrl(url: string, maxLength = 30): string {\n try {\n const parsed = new URL(url)\n const host = parsed.hostname.replace(/^www\\./, '')\n const rest = parsed.pathname + parsed.search + parsed.hash\n\n const full = host + (rest === '/' ? '' : rest)\n if (full.length <= maxLength) return full\n\n return full.slice(0, maxLength) + '…'\n } catch {\n // Not a valid URL — return as-is\n return url\n }\n}\n\n/**\n * Validate that a string is a well-formed URL.\n */\nexport function isValidUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch {\n return false\n }\n}\n","import { useMemo, type AnchorHTMLAttributes, type HTMLAttributes, type ReactNode } from 'react'\nimport type { RichTextRecord, MentionFeature, LinkFeature, TagFeature } from '../../types/facets'\nimport { isMentionFeature, isLinkFeature, isTagFeature } from '../../types/facets'\nimport type { DisplayClassNames } from '../../types/classNames'\nimport { defaultDisplayClassNames } from '../../defaults/classNames'\nimport { generateClassNames } from '../../utils/classNames'\nimport { useRichText } from '../../hooks/useRichText'\nimport { toShortUrl } from '../../utils/url'\n\n// ─── Render Prop Types ───────────────────────────────────────────────────────\n\nexport interface MentionProps {\n /** The raw segment text (e.g. \"@alice.bsky.social\") */\n text: string\n /** The resolved DID of the mentioned account */\n did: string\n /** The mention facet feature */\n feature: MentionFeature\n}\n\nexport interface LinkProps {\n /** The display text for the link (may be shortened) */\n text: string\n /** The full URL */\n uri: string\n /** The link facet feature */\n feature: LinkFeature\n}\n\nexport interface TagProps {\n /** The display text including '#' (e.g. \"#atproto\") */\n text: string\n /** The tag value without '#' prefix (e.g. \"atproto\") */\n tag: string\n /** The tag facet feature */\n feature: TagFeature\n}\n\n// ─── Component Props ─────────────────────────────────────────────────────────\n\nexport interface RichTextDisplayProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {\n /**\n * The richtext record to render.\n * Accepts `{ text, facets? }` — i.e. the raw AT Protocol record fields.\n */\n value: RichTextRecord | string\n\n /**\n * Custom renderer for @mention segments.\n * If not provided, renders a plain `<a>` linking to the profile.\n */\n renderMention?: (props: MentionProps) => ReactNode\n\n /**\n * Custom renderer for link segments.\n * If not provided, renders a plain `<a>` with the shortened URL as text.\n */\n renderLink?: (props: LinkProps) => ReactNode\n\n /**\n * Custom renderer for #hashtag segments.\n * If not provided, renders a plain `<a>` linking to the hashtag search.\n */\n renderTag?: (props: TagProps) => ReactNode\n\n /**\n * When true, all interactive facets (mentions, links, tags) are rendered\n * as plain text with no anchor elements.\n * @default false\n */\n disableLinks?: boolean\n\n /**\n * Props forwarded to every `<a>` element rendered by the default renderers.\n * Ignored when custom `renderMention` / `renderLink` / `renderTag` are used.\n */\n linkProps?: AnchorHTMLAttributes<HTMLAnchorElement>\n\n /**\n * CSS class names for each styleable part of the component.\n *\n * Use `generateClassNames()` to cleanly merge with the built-in defaults:\n * @example\n * ```tsx\n * import { generateClassNames, defaultDisplayClassNames } from 'bsky-richtext-react'\n *\n * <RichTextDisplay\n * classNames={generateClassNames([\n * defaultDisplayClassNames,\n * { mention: 'text-blue-500 hover:underline' },\n * ], cn)}\n * />\n * ```\n *\n * Or pass a completely custom object to opt out of the defaults entirely:\n * ```tsx\n * <RichTextDisplay classNames={{ root: 'my-richtext', mention: 'my-mention' }} />\n * ```\n */\n classNames?: Partial<DisplayClassNames>\n\n /**\n * Generate the `href` for @mention anchors.\n * Called with the mention's DID.\n * @default (did) => `https://bsky.app/profile/${did}`\n *\n * @example Route mentions to your own profile pages\n * ```tsx\n * mentionUrl={(did) => `/profile/${did}`}\n * ```\n */\n mentionUrl?: (did: string) => string\n\n /**\n * Generate the `href` for #hashtag anchors.\n * Called with the tag value (without the '#' prefix).\n * @default (tag) => `https://bsky.app/hashtag/${encodeURIComponent(tag)}`\n *\n * @example Route hashtags to your own search page\n * ```tsx\n * tagUrl={(tag) => `/search?tag=${encodeURIComponent(tag)}`}\n * ```\n */\n tagUrl?: (tag: string) => string\n\n /**\n * Transform a link URI before it is used as the anchor's `href`.\n * Useful for proxying external links or adding UTM parameters.\n * @default (uri) => uri (identity — no transformation)\n *\n * @example Add a referral parameter\n * ```tsx\n * linkUrl={(uri) => `${uri}?ref=myapp`}\n * ```\n */\n linkUrl?: (uri: string) => string\n}\n\n// ─── Default Renderers ───────────────────────────────────────────────────────\n\ninterface DefaultMentionRendererProps extends MentionProps {\n mentionUrl?: (did: string) => string\n mentionClass?: string\n linkProps?: AnchorHTMLAttributes<HTMLAnchorElement>\n}\n\nfunction DefaultMentionRenderer({\n text,\n did,\n mentionUrl,\n mentionClass,\n linkProps,\n}: DefaultMentionRendererProps) {\n const href = mentionUrl?.(did) ?? `https://bsky.app/profile/${did}`\n return (\n <a\n href={href}\n className={mentionClass}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-did={did}\n {...linkProps}\n >\n {text}\n </a>\n )\n}\n\ninterface DefaultLinkRendererProps extends LinkProps {\n linkUrl?: (uri: string) => string\n linkClass?: string\n linkProps?: AnchorHTMLAttributes<HTMLAnchorElement>\n}\n\nfunction DefaultLinkRenderer({\n text,\n uri,\n linkUrl,\n linkClass,\n linkProps,\n}: DefaultLinkRendererProps) {\n const href = linkUrl?.(uri) ?? uri\n return (\n <a\n href={href}\n className={linkClass}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n {...linkProps}\n >\n {toShortUrl(text)}\n </a>\n )\n}\n\ninterface DefaultTagRendererProps extends TagProps {\n tagUrl?: (tag: string) => string\n tagClass?: string\n linkProps?: AnchorHTMLAttributes<HTMLAnchorElement>\n}\n\nfunction DefaultTagRenderer({\n text,\n tag,\n tagUrl,\n tagClass,\n linkProps,\n}: DefaultTagRendererProps) {\n const href =\n tagUrl?.(tag) ?? `https://bsky.app/hashtag/${encodeURIComponent(tag)}`\n return (\n <a\n href={href}\n className={tagClass}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-tag={tag}\n {...linkProps}\n >\n {text}\n </a>\n )\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * `RichTextDisplay` renders AT Protocol richtext content — a string with\n * optional `facets` that annotate byte ranges as mentions, links, or hashtags.\n *\n * @example Basic usage\n * ```tsx\n * <RichTextDisplay value={{ text: post.text, facets: post.facets }} />\n * ```\n *\n * @example With custom mention renderer\n * ```tsx\n * <RichTextDisplay\n * value={post}\n * renderMention={({ text, did }) => (\n * <Link to={`/profile/${did}`}>{text}</Link>\n * )}\n * />\n * ```\n *\n * @example With URL resolvers pointing to your own routes\n * ```tsx\n * <RichTextDisplay\n * value={post}\n * mentionUrl={(did) => `/profile/${did}`}\n * tagUrl={(tag) => `/search?tag=${tag}`}\n * />\n * ```\n *\n * @example With classNames (using generateClassNames for clean merging)\n * ```tsx\n * import { generateClassNames, defaultDisplayClassNames } from 'bsky-richtext-react'\n *\n * <RichTextDisplay\n * value={post}\n * classNames={generateClassNames([\n * defaultDisplayClassNames,\n * { mention: 'text-blue-500 font-semibold' },\n * ], cn)}\n * />\n * ```\n */\nexport function RichTextDisplay({\n value,\n renderMention,\n renderLink,\n renderTag,\n disableLinks = false,\n linkProps,\n classNames: classNamesProp,\n mentionUrl,\n tagUrl,\n linkUrl,\n ...spanProps\n}: RichTextDisplayProps) {\n // Normalise plain string input into a RichTextRecord\n const record: RichTextRecord = typeof value === 'string' ? { text: value } : value\n\n // Merge provided classNames with defaults.\n // Memoized via JSON.stringify so inline object literals don't recalculate every render.\n const cn = useMemo(\n () => generateClassNames([defaultDisplayClassNames, classNamesProp]),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(classNamesProp)],\n )\n\n const segments = useRichText(record)\n\n const children: ReactNode[] = segments.map((segment, index) => {\n const { text, feature } = segment\n\n if (!feature || disableLinks) {\n return text\n }\n\n if (isMentionFeature(feature)) {\n if (renderMention) {\n return (\n <span key={index} className={cn.mention}>\n {renderMention({ text, did: feature.did, feature })}\n </span>\n )\n }\n return (\n <DefaultMentionRenderer\n key={index}\n text={text}\n did={feature.did}\n feature={feature}\n {...(mentionUrl !== undefined ? { mentionUrl } : {})}\n {...(cn.mention !== undefined ? { mentionClass: cn.mention } : {})}\n {...(linkProps !== undefined ? { linkProps } : {})}\n />\n )\n }\n\n if (isLinkFeature(feature)) {\n if (renderLink) {\n return (\n <span key={index} className={cn.link}>\n {renderLink({ text, uri: feature.uri, feature })}\n </span>\n )\n }\n return (\n <DefaultLinkRenderer\n key={index}\n text={text}\n uri={feature.uri}\n feature={feature}\n {...(linkUrl !== undefined ? { linkUrl } : {})}\n {...(cn.link !== undefined ? { linkClass: cn.link } : {})}\n {...(linkProps !== undefined ? { linkProps } : {})}\n />\n )\n }\n\n if (isTagFeature(feature)) {\n if (renderTag) {\n return (\n <span key={index} className={cn.tag}>\n {renderTag({ text, tag: feature.tag, feature })}\n </span>\n )\n }\n return (\n <DefaultTagRenderer\n key={index}\n text={text}\n tag={feature.tag}\n feature={feature}\n {...(tagUrl !== undefined ? { tagUrl } : {})}\n {...(cn.tag !== undefined ? { tagClass: cn.tag } : {})}\n {...(linkProps !== undefined ? { linkProps } : {})}\n />\n )\n }\n\n // Unknown feature type — render plain text as fallback\n return text\n })\n\n return (\n <span className={cn.root} {...spanProps}>\n {children}\n </span>\n )\n}\n","/**\n * Bluesky public API helpers for mention search.\n *\n * `searchBskyActors` calls the unauthenticated public Bluesky API to look up\n * actor suggestions. It is used as the default `onMentionQuery` implementation\n * in `RichTextEditor` — no API key or authentication required.\n *\n * `createDebouncedSearch` wraps `searchBskyActors` with a debounce so rapid\n * keystrokes don't fire unnecessary network requests. Only the latest in-flight\n * query resolves; stale promises from earlier keystrokes are silently discarded.\n */\n\nimport type { MentionSuggestion } from '../components/RichTextEditor/RichTextEditor'\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst BSKY_SEARCH_API =\n 'https://public.api.bsky.app/xrpc/app.bsky.actor.searchActors'\n\n// ─── Raw API ─────────────────────────────────────────────────────────────────\n\n/**\n * Shape of a single actor returned by the Bluesky public search API.\n * Only the fields we care about are typed here.\n */\ninterface BskyActor {\n did: string\n handle: string\n displayName?: string\n avatar?: string\n}\n\n/**\n * Search for Bluesky actors using the public, unauthenticated API.\n *\n * Returns up to `limit` matching `MentionSuggestion` objects, or an empty\n * array if the query is blank, the network request fails, or the response is\n * malformed.\n *\n * @param query - Text the user typed after \"@\"\n * @param limit - Max results to return (default: 8)\n */\nexport async function searchBskyActors(\n query: string,\n limit = 8,\n): Promise<MentionSuggestion[]> {\n if (!query.trim()) return []\n\n try {\n const url = new URL(BSKY_SEARCH_API)\n url.searchParams.set('q', query.trim())\n url.searchParams.set('limit', String(limit))\n\n const res = await fetch(url.toString())\n if (!res.ok) return []\n\n const data = (await res.json()) as { actors?: BskyActor[] }\n\n return (data.actors ?? []).map((actor) => ({\n did: actor.did,\n handle: actor.handle,\n ...(actor.displayName !== undefined\n ? { displayName: actor.displayName }\n : {}),\n ...(actor.avatar !== undefined ? { avatarUrl: actor.avatar } : {}),\n }))\n } catch {\n // Network error, JSON parse error, etc. — fail gracefully\n return []\n }\n}\n\n// ─── Debounced search ────────────────────────────────────────────────────────\n\n/**\n * Create a debounced version of `searchBskyActors`.\n *\n * Rapid calls within `delayMs` are coalesced — only the *latest* invocation\n * fires a network request. Earlier pending promises resolve with the same\n * result as the latest call (they are not rejected or left dangling).\n *\n * @param delayMs - Debounce window in milliseconds (default: 300)\n *\n * @example\n * ```ts\n * const debouncedSearch = createDebouncedSearch(400)\n * // Pass to onMentionQuery or use as the internal default\n * ```\n */\nexport function createDebouncedSearch(\n delayMs = 300,\n): (query: string) => Promise<MentionSuggestion[]> {\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n // Track all pending resolvers so every outstanding promise gets the result\n // of the most recent query, preventing stale dangling promises.\n const pendingResolvers: Array<(value: MentionSuggestion[]) => void> = []\n\n return (query: string): Promise<MentionSuggestion[]> => {\n return new Promise((resolve) => {\n // Queue this caller's resolver\n pendingResolvers.push(resolve)\n\n // Cancel the previous scheduled search\n if (timeoutId !== null) {\n clearTimeout(timeoutId)\n }\n\n timeoutId = setTimeout(async () => {\n timeoutId = null\n const results = await searchBskyActors(query)\n\n // Flush all queued resolvers with the same result set.\n // Splice to avoid mutation-during-iteration issues.\n const toResolve = pendingResolvers.splice(0, pendingResolvers.length)\n for (const r of toResolve) {\n r(results)\n }\n }, delayMs)\n })\n }\n}\n","/**\n * MentionSuggestionList\n *\n * Default built-in mention autocomplete dropdown.\n * Heavily inspired by Bluesky's social-app Autocomplete.tsx.\n *\n * Default classNames apply Tailwind utility classes for a ready-to-use\n * appearance. Override specific parts using the `classNames` prop with\n * `generateClassNames()`.\n *\n * TipTap requires the `render` factory to return lifecycle callbacks\n * ({ onStart, onUpdate, onKeyDown, onExit }). The component itself is mounted\n * via `ReactRenderer` and positioned via `tippy.js` — see createSuggestionRenderer.ts.\n */\n\nimport { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react'\nimport type { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion'\nimport type { SuggestionClassNames } from '../../types/classNames'\nimport { defaultSuggestionClassNames } from '../../defaults/classNames'\nimport { generateClassNames } from '../../utils/classNames'\nimport type { MentionSuggestion } from './RichTextEditor'\n\n// ─── Imperative handle ───────────────────────────────────────────────────────\n\n/**\n * Ref handle that TipTap calls into for keyboard events while the popup is open.\n * Mirrors the `MentionListRef` interface from the Bluesky reference implementation.\n */\nexport interface MentionSuggestionListRef {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\n// ─── Props ───────────────────────────────────────────────────────────────────\n\nexport interface MentionSuggestionListProps extends SuggestionProps<MentionSuggestion> {\n /**\n * Whether to render avatars when `avatarUrl` is present on a suggestion.\n * When false, avatar placeholder is hidden entirely.\n * @default true\n */\n showAvatars?: boolean\n\n /**\n * Text to show when the items array is empty.\n * @default \"No results\"\n */\n noResultsText?: string\n\n /**\n * CSS class names for each styleable part of the suggestion dropdown.\n *\n * Use `generateClassNames()` to merge with the built-in defaults:\n * @example\n * ```tsx\n * import { generateClassNames, defaultSuggestionClassNames } from 'bsky-richtext-react'\n *\n * classNames={generateClassNames([\n * defaultSuggestionClassNames,\n * { item: 'px-3 py-2', itemSelected: 'bg-blue-50' },\n * ], cn)}\n * ```\n */\n classNames?: Partial<SuggestionClassNames>\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * Default mention suggestion dropdown rendered by `RichTextEditor`.\n *\n * Consumers can pass `classNames` to style specific parts, or pass a completely\n * custom `renderMentionSuggestion` factory to the editor to replace this\n * component entirely.\n */\nexport const MentionSuggestionList = forwardRef<\n MentionSuggestionListRef,\n MentionSuggestionListProps\n>(function MentionSuggestionListImpl(\n { items, command, showAvatars = true, noResultsText = 'No results', classNames: classNamesProp },\n ref,\n) {\n const [selectedIndex, setSelectedIndex] = useState(0)\n\n // Merge provided classNames with defaults.\n // Memoized via JSON.stringify so inline object literals don't recalculate every render.\n const cn = useMemo(\n () => generateClassNames([defaultSuggestionClassNames, classNamesProp]),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(classNamesProp)],\n )\n\n // Reset selection when items change (new query results arrived)\n useEffect(() => {\n setSelectedIndex(0)\n }, [items])\n\n // ─── Selection helpers ───────────────────────────────────────────────────\n\n const selectItem = (index: number) => {\n const item = items[index]\n if (item) {\n // `id` is the handle — the editor's `renderLabel` uses it to build \"@handle\"\n command({ id: item.handle })\n }\n }\n\n const moveUp = () => {\n setSelectedIndex((prev) => (prev + items.length - 1) % items.length)\n }\n\n const moveDown = () => {\n setSelectedIndex((prev) => (prev + 1) % items.length)\n }\n\n // ─── Keyboard handler (called by TipTap via the ref) ────────────────────\n\n useImperativeHandle(ref, () => ({\n onKeyDown({ event }: SuggestionKeyDownProps): boolean {\n if (event.key === 'ArrowUp') {\n moveUp()\n return true\n }\n if (event.key === 'ArrowDown') {\n moveDown()\n return true\n }\n if (event.key === 'Enter' || event.key === 'Tab') {\n selectItem(selectedIndex)\n return true\n }\n return false\n },\n }))\n\n // ─── Render ──────────────────────────────────────────────────────────────\n\n return (\n <div\n className={cn.root}\n // Prevent the editor from losing focus when clicking a suggestion\n onMouseDown={(e) => e.preventDefault()}\n >\n {items.length === 0 ? (\n <div className={cn.empty}>{noResultsText}</div>\n ) : (\n items.map((item, index) => {\n const isSelected = index === selectedIndex\n const itemClass = isSelected\n ? `${cn.item ?? ''} ${cn.itemSelected ?? ''}`.trim()\n : cn.item\n\n return (\n <button\n key={item.did}\n type=\"button\"\n className={itemClass}\n onMouseEnter={() => setSelectedIndex(index)}\n onClick={() => selectItem(index)}\n >\n {showAvatars && (\n <span className={cn.avatar}>\n {item.avatarUrl ? (\n <img\n src={item.avatarUrl}\n alt={item.displayName ?? item.handle}\n className={cn.avatarImg}\n />\n ) : (\n <span className={cn.avatarPlaceholder} aria-hidden=\"true\">\n {(item.displayName ?? item.handle).charAt(0).toUpperCase()}\n </span>\n )}\n </span>\n )}\n\n <span className={cn.text}>\n {item.displayName && <span className={cn.name}>{item.displayName}</span>}\n <span className={cn.handle}>@{item.handle}</span>\n </span>\n </button>\n )\n })\n )}\n </div>\n )\n})\n","/**\n * createSuggestionRenderer\n *\n * Factory that returns a TipTap `SuggestionOptions['render']` function.\n * It uses `tippy.js` for cursor-anchored positioning and `ReactRenderer`\n * to mount the `MentionSuggestionList` React component into the popup.\n *\n * Heavily inspired by Bluesky's social-app Autocomplete.tsx and the\n * official TipTap mention example:\n * https://tiptap.dev/docs/editor/extensions/nodes/mention#usage\n *\n * This is the default renderer used when the consumer does NOT supply\n * a custom `renderMentionSuggestion` prop to `<RichTextEditor>`.\n */\n\nimport type { Instance as TippyInstance } from 'tippy.js'\nimport tippy from 'tippy.js'\nimport { ReactRenderer } from '@tiptap/react'\nimport type { SuggestionOptions, SuggestionProps } from '@tiptap/suggestion'\nimport type { SuggestionClassNames } from '../../types/classNames'\nimport {\n MentionSuggestionList,\n type MentionSuggestionListRef,\n type MentionSuggestionListProps,\n} from './MentionSuggestionList'\nimport type { MentionSuggestion } from './RichTextEditor'\n\n// ─── Options ─────────────────────────────────────────────────────────────────\n\nexport interface DefaultSuggestionRendererOptions {\n /**\n * Whether to show avatars in the suggestion list.\n * Forwarded to `MentionSuggestionList`.\n * @default true\n */\n showAvatars?: boolean\n\n /**\n * Text shown when the query returns no results.\n * @default \"No results\"\n */\n noResultsText?: string\n\n /**\n * CSS class names for each styleable part of the suggestion dropdown.\n * Forwarded directly to `MentionSuggestionList`.\n */\n classNames?: Partial<SuggestionClassNames>\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Create the default TipTap `suggestion.render` factory.\n *\n * The returned function is called once per \"suggestion session\"\n * (i.e. each time the user types \"@\" and a popup should open/update/close).\n *\n * It follows the same lifecycle pattern as the Bluesky reference:\n * - `onStart` → Mount ReactRenderer, create tippy popup\n * - `onUpdate` → Update props, reposition popup\n * - `onKeyDown`→ Delegate to the MentionSuggestionList imperative ref\n * - `onExit` → Destroy popup and React renderer\n */\nexport function createDefaultSuggestionRenderer(\n options: DefaultSuggestionRendererOptions = {},\n): SuggestionOptions<MentionSuggestion>['render'] {\n return () => {\n let renderer: ReactRenderer<MentionSuggestionListRef, MentionSuggestionListProps> | undefined\n let popup: TippyInstance[] | undefined\n\n const destroy = () => {\n popup?.[0]?.destroy()\n renderer?.destroy()\n renderer = undefined\n popup = undefined\n }\n\n const buildProps = (\n props: SuggestionProps<MentionSuggestion>,\n ): MentionSuggestionListProps => ({\n ...props,\n showAvatars: options.showAvatars ?? true,\n noResultsText: options.noResultsText ?? 'No results',\n ...(options.classNames !== undefined ? { classNames: options.classNames } : {}),\n })\n\n return {\n onStart(props: SuggestionProps<MentionSuggestion>) {\n renderer = new ReactRenderer(MentionSuggestionList, {\n props: buildProps(props),\n editor: props.editor,\n })\n\n if (!props.clientRect) return\n\n // Create the tippy popup anchored to the cursor position.\n // Matches the Bluesky reference: tippy('body', { ... })\n //\n // We call tippy with a CSS selector string ('body') which uses the\n // MultipleTargets overload and returns Instance[]. The tippy type\n // definitions don't expose a string-selector overload directly, so\n // we cast through unknown to keep things clean.\n //\n // tippy's getReferenceClientRect expects `() => DOMRect | ClientRect`,\n // but TipTap's clientRect can return null — we guard that here.\n const clientRect = props.clientRect\n popup = tippy('body', {\n getReferenceClientRect: () => clientRect?.() ?? new DOMRect(),\n appendTo: () => document.body,\n content: renderer.element,\n showOnCreate: true,\n interactive: true,\n trigger: 'manual',\n placement: 'bottom-start',\n }) as unknown as TippyInstance[]\n },\n\n onUpdate(props: SuggestionProps<MentionSuggestion>) {\n renderer?.updateProps(buildProps(props))\n\n if (!props.clientRect) return\n\n const clientRect = props.clientRect\n popup?.[0]?.setProps({\n getReferenceClientRect: () => clientRect?.() ?? new DOMRect(),\n })\n },\n\n onKeyDown(props) {\n // Escape dismisses without selecting\n if (props.event.key === 'Escape') {\n popup?.[0]?.hide()\n return true\n }\n // All other keys delegated to the list component\n return renderer?.ref?.onKeyDown(props) ?? false\n },\n\n onExit() {\n destroy()\n },\n }\n }\n}\n","/**\n * TipTap Mention extension configured for Bluesky @handle autocomplete.\n *\n * Builds on `@tiptap/extension-mention` and wires up:\n * - Consumer-supplied `onMentionQuery` for fetching suggestions\n * - Default popup renderer (tippy.js + MentionSuggestionList) when no\n * custom `renderMentionSuggestion` is provided\n *\n * Heavily inspired by Bluesky's social-app TextInput.web.tsx and Autocomplete.tsx.\n */\nimport { Mention } from '@tiptap/extension-mention'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\nimport type { MentionSuggestion } from '../RichTextEditor'\nimport {\n createDefaultSuggestionRenderer,\n type DefaultSuggestionRendererOptions,\n} from '../createSuggestionRenderer'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface BskyMentionOptions {\n /**\n * Async function that returns suggestions for a given query string.\n * Called every time the user types after \"@\".\n * Return an empty array to show the \"No results\" state.\n */\n onMentionQuery: (query: string) => Promise<MentionSuggestion[]>\n\n /**\n * Custom TipTap `suggestion.render` factory.\n * When provided, replaces the default tippy.js + MentionSuggestionList renderer.\n * When omitted, the built-in renderer is used.\n */\n renderSuggestionList?: SuggestionOptions['render']\n\n /**\n * Options forwarded to the default renderer (ignored when `renderSuggestionList`\n * is provided).\n */\n defaultRendererOptions?: DefaultSuggestionRendererOptions\n\n /**\n * CSS class applied to mention chips rendered inside the editor.\n * Sourced from `EditorClassNames.mention` and defaults to `bsky-editor-mention`.\n */\n mentionClass?: string\n}\n\n// ─── Extension factory ───────────────────────────────────────────────────────\n\n/**\n * Create a configured TipTap Mention extension for Bluesky.\n *\n * The mention node stores the account handle as `id` and surfaces it via\n * `renderLabel` as \"@handle\". When the editor JSON is serialised in\n * `editorJsonToText`, mention nodes are rendered as `@{id}`.\n */\nexport function createBskyMentionExtension({\n onMentionQuery,\n renderSuggestionList,\n defaultRendererOptions,\n mentionClass = 'bsky-editor-mention',\n}: BskyMentionOptions) {\n // Use the consumer-supplied renderer, or fall back to our built-in one.\n const render =\n renderSuggestionList ?? createDefaultSuggestionRenderer(defaultRendererOptions)\n\n return Mention.configure({\n HTMLAttributes: {\n class: mentionClass,\n },\n\n /**\n * Render the mention node's text content inside the editor.\n * The `id` attribute stores the handle (e.g. \"alice.bsky.social\"),\n * so we prefix it with \"@\".\n *\n * Mirrors the Bluesky reference:\n * text += `@${json.attrs?.id || ''}` (in editorJsonToText)\n */\n renderLabel({ options, node }) {\n const handle =\n (node.attrs.label as string | undefined) ??\n (node.attrs.id as string | undefined) ??\n ''\n return `${options.suggestion.char ?? '@'}${handle}`\n },\n\n suggestion: {\n char: '@',\n allowSpaces: false,\n startOfLine: false,\n\n /**\n * Fetch suggestion items from the consumer.\n * Matches Bluesky's pattern: `autocomplete({ query })`.\n * Returns up to 8 items (same limit as the reference implementation).\n */\n items: async ({ query }) => {\n if (!query) return []\n try {\n const results = await onMentionQuery(query)\n return results.slice(0, 8)\n } catch {\n return []\n }\n },\n\n // Spread so the key is only present when defined (exactOptionalPropertyTypes)\n ...(render !== undefined ? { render } : {}),\n },\n })\n}\n","/**\n * BskyLinkDecorator — stateless URL decoration for the RichTextEditor.\n *\n * Unlike TipTap Marks (which store formatting in the document model), this\n * extension uses a ProseMirror Plugin with a DecorationSet. Decorations are\n * purely visual — they are recalculated from scratch on every document change\n * by re-running the URL regex over the plain text. This means:\n *\n * - Typing a space after a URL naturally ends the decoration on the next tick\n * - Editing inside a URL never duplicates characters\n * - No stale mark state can accumulate in the document\n *\n * This is identical in approach to Bluesky's reference LinkDecorator.ts in\n * social-app/src/view/com/composer/text-input/web/LinkDecorator.ts.\n *\n * The visual class applied is `autolink` (matching Bluesky's reference).\n * Style it via `.bsky-editor .autolink { … }` or the `classNames.link` prop.\n */\n\nimport { Extension } from '@tiptap/core'\nimport type { Node as ProsemirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\n// ─── URL Regex ───────────────────────────────────────────────────────────────\n\n/**\n * Matches http/https URLs in plain text.\n * We keep this simple and consistent with @atproto/api's detectFacetsWithoutResolution,\n * so the visual decoration always matches what will become a link facet.\n */\nconst URL_REGEX = /https?:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/g\n\n// ─── Decoration helpers ──────────────────────────────────────────────────────\n\n/**\n * Walk every text node in the document, run URL_REGEX over its content,\n * and emit an inline Decoration for each match.\n */\nfunction getDecorations(doc: ProsemirrorNode, linkClass: string): DecorationSet {\n const decorations: Decoration[] = []\n\n doc.descendants((node, pos) => {\n if (!node.isText || !node.text) return\n\n const text = node.text\n // Reset lastIndex before each new text node\n URL_REGEX.lastIndex = 0\n\n let match: RegExpExecArray | null\n while ((match = URL_REGEX.exec(text)) !== null) {\n let uri = match[0]\n const from = pos + match.index\n let to = from + uri.length\n\n // Strip trailing punctuation (mirrors Bluesky's iterateUris)\n if (/[.,;!?]$/.test(uri)) {\n uri = uri.slice(0, -1)\n to--\n }\n if (/[)]$/.test(uri) && !uri.includes('(')) {\n uri = uri.slice(0, -1)\n to--\n }\n\n decorations.push(\n Decoration.inline(from, to, {\n class: linkClass,\n 'data-autolink': '',\n }),\n )\n }\n })\n\n return DecorationSet.create(doc, decorations)\n}\n\n// ─── Plugin factory ──────────────────────────────────────────────────────────\n\nfunction createLinkDecoratorPlugin(linkClass: string): Plugin {\n const key = new PluginKey<DecorationSet>('bsky-link-decorator')\n\n return new Plugin<DecorationSet>({\n key,\n\n state: {\n init: (_, { doc }) => getDecorations(doc, linkClass),\n apply: (transaction, decorationSet) => {\n if (transaction.docChanged) {\n return getDecorations(transaction.doc, linkClass)\n }\n // If the doc didn't change (e.g. selection change), just map existing\n // decorations to their new positions.\n return decorationSet.map(transaction.mapping, transaction.doc)\n },\n },\n\n props: {\n decorations(state) {\n return key.getState(state)\n },\n },\n })\n}\n\n// ─── TipTap Extension ────────────────────────────────────────────────────────\n\nexport interface BskyLinkDecoratorOptions {\n /**\n * CSS class applied to each decorated URL span.\n * Override via the editor's `classNames.link` prop.\n * @default 'autolink'\n */\n linkClass: string\n}\n\nexport const BskyLinkDecorator = Extension.create<BskyLinkDecoratorOptions>({\n name: 'bskyLinkDecorator',\n\n addOptions() {\n return {\n linkClass: 'autolink',\n }\n },\n\n addProseMirrorPlugins() {\n return [createLinkDecoratorPlugin(this.options.linkClass)]\n },\n})\n","/**\n * RichTextEditor\n *\n * TipTap-based editor for composing AT Protocol richtext content.\n * Heavily inspired by Bluesky's social-app TextInput.web.tsx.\n *\n * Key differences from the reference implementation:\n * - No React Native / Expo dependencies — pure React DOM\n * - No Bluesky-specific theming (ALF) — headless, consumer styles it\n * - No media paste / emoji picker (out of scope for this library)\n * - `onChange` emits a plain `RichTextRecord` instead of an `RichText` class\n */\n\nimport { useEffect, useImperativeHandle, useMemo, type HTMLAttributes, type Ref } from 'react'\nimport { EditorContent, useEditor, type JSONContent } from '@tiptap/react'\nimport { Document } from '@tiptap/extension-document'\nimport { Paragraph } from '@tiptap/extension-paragraph'\nimport { Text } from '@tiptap/extension-text'\nimport { History } from '@tiptap/extension-history'\nimport { HardBreak } from '@tiptap/extension-hard-break'\nimport { Placeholder } from '@tiptap/extension-placeholder'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\nimport { RichText as AtpRichText } from '@atproto/api'\nimport type { RichTextRecord, Facet } from '../../types/facets'\nimport type { EditorClassNames } from '../../types/classNames'\nimport { defaultEditorClassNames } from '../../defaults/classNames'\nimport { generateClassNames } from '../../utils/classNames'\nimport { createDebouncedSearch } from '../../utils/blueskyApi'\nimport { createBskyMentionExtension } from './extensions/BskyMention'\nimport { BskyLinkDecorator } from './extensions/BskyLinkDecorator'\nimport type { DefaultSuggestionRendererOptions } from './createSuggestionRenderer'\n\n// ─── Public Types ────────────────────────────────────────────────────────────\n\n/**\n * A single suggestion item for the @mention autocomplete popup.\n */\nexport interface MentionSuggestion {\n /** DID of the suggested account — used as the facet's `did` value */\n did: string\n /** Display handle (e.g. \"alice.bsky.social\") */\n handle: string\n /** Optional display name */\n displayName?: string\n /** Optional avatar URL */\n avatarUrl?: string\n}\n\n/**\n * Imperative ref API for `RichTextEditor`.\n */\nexport interface RichTextEditorRef {\n /** Focus the editor */\n focus: () => void\n /** Blur the editor */\n blur: () => void\n /** Clear the editor content */\n clear: () => void\n /** Get the current plain-text content */\n getText: () => string\n}\n\n// ─── Component Props ─────────────────────────────────────────────────────────\n\nexport interface RichTextEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /**\n * Initial richtext value. The editor is pre-populated with this content on mount.\n * This is an uncontrolled initial state — use `onChange` to track updates.\n */\n initialValue?: RichTextRecord | string\n\n /**\n * Called on every content change with the latest `RichTextRecord`.\n *\n * The `facets` array is populated via `detectFacetsWithoutResolution()` —\n * facets will contain handles (not DIDs) for mentions until you resolve them\n * server-side using the AT Protocol agent.\n */\n onChange?: (record: RichTextRecord) => void\n\n /**\n * Placeholder text shown when the editor is empty.\n */\n placeholder?: string\n\n /**\n * Called when the editor gains focus.\n */\n onFocus?: () => void\n\n /**\n * Called when the editor loses focus.\n */\n onBlur?: () => void\n\n /**\n * Async function to fetch @mention suggestions.\n * Called with the query string (text after \"@\") as the user types.\n * Return an empty array to show no suggestions / \"No results\".\n *\n * When not provided, the built-in Bluesky public API search is used\n * (debounced by `mentionSearchDebounceMs`). Set `disableDefaultMentionSearch`\n * to true to disable this default behaviour entirely.\n *\n * @example\n * ```tsx\n * onMentionQuery={async (q) => {\n * const res = await agent.searchActors({ term: q, limit: 8 })\n * return res.data.actors.map(a => ({\n * did: a.did,\n * handle: a.handle,\n * displayName: a.displayName,\n * avatarUrl: a.avatar,\n * }))\n * }}\n * ```\n */\n onMentionQuery?: (query: string) => Promise<MentionSuggestion[]>\n\n /**\n * Debounce delay (in milliseconds) applied to the built-in Bluesky mention\n * search. Has no effect when `onMentionQuery` is provided.\n * @default 300\n */\n mentionSearchDebounceMs?: number\n\n /**\n * When true, disables the default Bluesky public API mention search.\n * No suggestions will appear unless you provide `onMentionQuery`.\n * @default false\n */\n disableDefaultMentionSearch?: boolean\n\n /**\n * Custom TipTap `suggestion.render` factory.\n * When provided, replaces the default tippy.js + MentionSuggestionList renderer.\n * The factory must return `{ onStart, onUpdate, onKeyDown, onExit }`.\n *\n * See: https://tiptap.dev/docs/editor/extensions/nodes/mention#usage\n */\n renderMentionSuggestion?: SuggestionOptions['render']\n\n /**\n * Options forwarded to the default suggestion renderer.\n * Only used when `renderMentionSuggestion` is NOT provided.\n */\n mentionSuggestionOptions?: DefaultSuggestionRendererOptions\n\n /**\n * CSS class names for each styleable part of the editor.\n *\n * Use `generateClassNames()` to cleanly merge with the built-in defaults:\n * @example\n * ```tsx\n * import { generateClassNames, defaultEditorClassNames } from 'bsky-richtext-react'\n *\n * <RichTextEditor\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'border rounded-lg p-3', mention: 'text-blue-500' },\n * ], cn)}\n * />\n * ```\n *\n * Or pass a completely custom object to opt out of the defaults:\n * ```tsx\n * <RichTextEditor classNames={{ root: 'my-editor', mention: 'my-mention' }} />\n * ```\n */\n classNames?: Partial<EditorClassNames>\n\n /**\n * Imperative ref for programmatic control.\n */\n editorRef?: Ref<RichTextEditorRef>\n\n /**\n * Whether the editor content is editable.\n * @default true\n */\n editable?: boolean\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Convert a `RichTextRecord` or string to the HTML the editor uses as\n * its initial content.\n *\n * Mention nodes must be expressed as `<span data-type=\"mention\" data-id=\"handle\">`\n * so TipTap's Mention extension can parse them correctly on load.\n *\n * Mirrors `richTextToHTML` in the Bluesky reference implementation.\n */\nfunction toInitialHTML(value: RichTextRecord | string | undefined): string {\n if (!value) return ''\n\n if (typeof value === 'string') {\n return `<p>${escapeHTML(value)}</p>`\n }\n\n const { text, facets } = value\n\n if (!facets?.length) {\n return `<p>${escapeHTML(text)}</p>`\n }\n\n // Use @atproto/api's RichText class to iterate segments — it handles\n // the byte-offset arithmetic for us.\n // Cast via unknown: our Facet type is structurally identical to @atproto/api's\n // internal Main[] but lacks the index signature that atproto adds.\n // We also guard against undefined since exactOptionalPropertyTypes is enabled.\n const atpFacets = facets as unknown as AtpRichText['facets']\n const rt = new AtpRichText(atpFacets ? { text, facets: atpFacets } : { text })\n let html = ''\n\n for (const segment of rt.segments()) {\n if (segment.mention) {\n // Mention: emit a TipTap mention node using the DID as the `data-id`.\n // The mention extension will render it via `renderLabel` as \"@handle\".\n html += `<span data-type=\"mention\" data-id=\"${escapeHTML(segment.mention.did)}\"></span>`\n } else {\n html += escapeHTML(segment.text)\n }\n }\n\n return html\n}\n\nfunction escapeHTML(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\n/**\n * Convert the TipTap editor's JSON document to a plain text string.\n *\n * - `doc` nodes iterate their children\n * - `paragraph` nodes add a newline after themselves (except the last one)\n * - `hardBreak` nodes add a newline\n * - `text` nodes emit their text content\n * - `mention` nodes emit \"@{id}\" (the handle stored in `attrs.id`)\n *\n * Directly mirrors `editorJsonToText` from the Bluesky reference.\n */\nfunction editorJsonToText(json: JSONContent, isLastDocumentChild = false): string {\n let text = ''\n\n if (json.type === 'doc') {\n if (json.content?.length) {\n for (let i = 0; i < json.content.length; i++) {\n const node = json.content[i]\n if (!node) continue\n const isLast = i === json.content.length - 1\n text += editorJsonToText(node, isLast)\n }\n }\n } else if (json.type === 'paragraph') {\n if (json.content?.length) {\n for (const node of json.content) {\n text += editorJsonToText(node)\n }\n }\n if (!isLastDocumentChild) {\n text += '\\n'\n }\n } else if (json.type === 'hardBreak') {\n text += '\\n'\n } else if (json.type === 'text') {\n text += json.text ?? ''\n } else if (json.type === 'mention') {\n // The `id` attribute holds the handle chosen during autocomplete\n text += `@${(json.attrs?.id as string | undefined) ?? ''}`\n }\n\n return text\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * `RichTextEditor` is a TipTap-based editor for composing AT Protocol richtext.\n *\n * Features:\n * - Real-time @mention autocomplete — defaults to the Bluesky public API,\n * override with `onMentionQuery`\n * - Automatic URL decoration (link facets detected on change)\n * - Hard-break (Shift+Enter) for newlines inside a paragraph\n * - Undo/redo history\n * - `onChange` emits a `RichTextRecord` with `text` + `facets` populated via\n * `detectFacetsWithoutResolution()`\n * - Headless by default — Tailwind utility classes are applied via the default classNames; override freely via the `classNames` prop\n *\n * @example Basic usage (built-in Bluesky mention search)\n * ```tsx\n * <RichTextEditor\n * placeholder=\"What's on your mind?\"\n * onChange={(record) => setPost(record)}\n * />\n * ```\n *\n * @example Custom mention search\n * ```tsx\n * <RichTextEditor\n * placeholder=\"What's on your mind?\"\n * onMentionQuery={async (q) => searchProfiles(q)}\n * onChange={(record) => setPost(record)}\n * />\n * ```\n *\n * @example With classNames\n * ```tsx\n * import { generateClassNames, defaultEditorClassNames } from 'bsky-richtext-react'\n *\n * <RichTextEditor\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'border rounded-lg p-3' },\n * ], cn)}\n * />\n * ```\n */\nexport function RichTextEditor({\n initialValue,\n onChange,\n placeholder,\n onFocus,\n onBlur,\n onMentionQuery,\n mentionSearchDebounceMs = 300,\n disableDefaultMentionSearch = false,\n renderMentionSuggestion,\n mentionSuggestionOptions,\n classNames: classNamesProp,\n editorRef,\n editable = true,\n ...divProps\n}: RichTextEditorProps) {\n // Merge provided classNames with defaults.\n // Memoized so that inline object literals passed as `classNames` prop don't\n // produce a new object on every render — which would otherwise cascade into\n // extensions and useEditor recreating infinitely.\n const cn = useMemo(\n () => generateClassNames([defaultEditorClassNames, classNamesProp]),\n // We compare the *serialised* form of classNamesProp so that structurally\n // identical objects (common with inline literals) are treated as equal.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(classNamesProp)],\n )\n\n // Create a stable debounced search function that recreates when delay changes\n const debouncedSearch = useMemo(\n () => createDebouncedSearch(mentionSearchDebounceMs),\n [mentionSearchDebounceMs],\n )\n\n // Resolve the mention query function:\n // 1. Consumer-provided → use as-is\n // 2. Default search disabled → return empty\n // 3. Otherwise → use debounced Bluesky public API search\n const mentionQuery = useMemo<(q: string) => Promise<MentionSuggestion[]>>(() => {\n if (onMentionQuery) return onMentionQuery\n if (disableDefaultMentionSearch) return () => Promise.resolve([])\n return debouncedSearch\n }, [onMentionQuery, disableDefaultMentionSearch, debouncedSearch])\n\n // Stable values extracted from the memoized cn object.\n // Primitives (strings) are compared by value in useMemo deps, so they won't\n // cause spurious extension re-creations even if the cn object reference changes.\n const linkClass = cn.link ?? 'autolink'\n const mentionClass = cn.mention\n const suggestionClassNames = cn.suggestion\n // Serialise the nested suggestion object so it can be used as a stable dep.\n const suggestionClassNamesKey = JSON.stringify(suggestionClassNames)\n\n const extensions = useMemo(\n () => [\n Document,\n Paragraph,\n Text,\n History,\n HardBreak,\n // Configure link decorator with the resolved link class\n BskyLinkDecorator.configure({ linkClass }),\n Placeholder.configure({ placeholder: placeholder ?? '' }),\n createBskyMentionExtension({\n onMentionQuery: mentionQuery,\n ...(mentionClass !== undefined ? { mentionClass } : {}),\n // Only include optional fields when defined (exactOptionalPropertyTypes)\n ...(renderMentionSuggestion !== undefined\n ? { renderSuggestionList: renderMentionSuggestion }\n : {}),\n // Merge suggestion classNames into the default renderer options\n ...(mentionSuggestionOptions !== undefined || suggestionClassNames !== undefined\n ? {\n defaultRendererOptions: {\n ...(mentionSuggestionOptions ?? {}),\n ...(suggestionClassNames !== undefined ? { classNames: suggestionClassNames } : {}),\n },\n }\n : {}),\n }),\n ],\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n mentionQuery,\n placeholder,\n renderMentionSuggestion,\n mentionSuggestionOptions,\n linkClass,\n mentionClass,\n suggestionClassNamesKey,\n ],\n )\n\n const editor = useEditor(\n {\n extensions,\n editable,\n content: toInitialHTML(initialValue),\n\n /**\n * Disable immediate rendering to prevent SSR/hydration issues in Next.js,\n * Remix, and other server-rendering frameworks. The editor defers rendering\n * until after the component mounts on the client.\n * @see https://github.com/ueberdosis/tiptap/issues/5856\n */\n immediatelyRender: false,\n\n /**\n * Clipboard text serialisation: use '\\n' as the block separator so\n * multi-paragraph content is copied as plain newline-delimited text.\n * Matches the Bluesky reference's `coreExtensionOptions.clipboardTextSerializer`.\n */\n coreExtensionOptions: {\n clipboardTextSerializer: {\n blockSeparator: '\\n',\n },\n },\n\n editorProps: {\n /**\n * Paste handler: strip HTML formatting and paste as plain text.\n * Matches `handlePaste` in the Bluesky reference.\n */\n handlePaste(view, event) {\n const clipboardData = event.clipboardData\n if (!clipboardData) return false\n\n if (clipboardData.types.includes('text/html')) {\n const plainText = clipboardData.getData('text/plain')\n view.pasteText(plainText)\n return true\n }\n\n return false\n },\n },\n\n onFocus() {\n onFocus?.()\n },\n onBlur() {\n onBlur?.()\n },\n\n /**\n * On every document change:\n * 1. Extract plain text from the ProseMirror JSON tree (handles mention nodes)\n * 2. Use @atproto/api's `detectFacetsWithoutResolution()` to populate facets\n * 3. Emit the result as a `RichTextRecord`\n *\n * Mirrors the Bluesky reference's `onUpdate` handler.\n */\n onUpdate({ editor: ed }) {\n if (!onChange) return\n\n const json = ed.getJSON()\n const text = editorJsonToText(json)\n\n // Detect facets (mentions as handles, not DIDs — resolve server-side)\n const rt = new AtpRichText({ text })\n rt.detectFacetsWithoutResolution()\n\n // Cast via unknown: atproto's internal facet type has an extra index\n // signature but is structurally identical to our public Facet type.\n const record: RichTextRecord = {\n text: rt.text,\n ...(rt.facets?.length ? { facets: rt.facets as unknown as Facet[] } : {}),\n }\n\n onChange(record)\n },\n },\n // Only recreate the editor when extensions change (e.g. placeholder update)\n [extensions],\n )\n\n // Sync `editable` prop changes reactively after mount\n useEffect(() => {\n if (editor && editor.isEditable !== editable) {\n editor.setEditable(editable)\n }\n }, [editor, editable])\n\n // Expose imperative API\n useImperativeHandle(\n editorRef,\n () => ({\n focus() {\n editor?.commands.focus()\n },\n blur() {\n editor?.commands.blur()\n },\n clear() {\n editor?.commands.clearContent(true)\n },\n getText() {\n if (!editor) return ''\n return editorJsonToText(editor.getJSON())\n },\n }),\n [editor],\n )\n\n return (\n <div className={cn.root} {...divProps}>\n <EditorContent editor={editor} className={cn.content} />\n </div>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types/facets.ts","../src/defaults/classNames.ts","../src/utils/classNames.ts","../src/utils/utf8.ts","../src/utils/parser.ts","../src/hooks/useRichText.ts","../src/utils/url.ts","../src/components/RichTextDisplay/RichTextDisplay.tsx","../src/utils/blueskyApi.ts","../src/components/RichTextEditor/MentionSuggestionList.tsx","../src/components/RichTextEditor/createSuggestionRenderer.ts","../src/components/RichTextEditor/extensions/BskyMention.ts","../src/components/RichTextEditor/extensions/BskyLinkDecorator.ts","../src/components/RichTextEditor/RichTextEditor.tsx"],"names":["useMemo","jsx","forwardRef","useState","useEffect","useImperativeHandle","jsxs","ReactRenderer","computePosition","offset","flip","shift","Mention","Decoration","DecorationSet","PluginKey","Plugin","Extension","AtpRichText","Document","Paragraph","Text","History","HardBreak","Placeholder","useEditor","EditorContent"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * TypeScript types mirroring the `app.bsky.richtext.facet` lexicon.\n *\n * Spec: /lexicons/app/richtext/facet.json\n * Reference: https://atproto.com/lexicons/app-bsky-richtext\n */\n\n// ─── Byte Slice ─────────────────────────────────────────────────────────────\n\n/**\n * Specifies the sub-string range a facet feature applies to.\n * Indices are ZERO-indexed byte offsets of the UTF-8 encoded text.\n * - byteStart: inclusive\n * - byteEnd: exclusive\n *\n * ⚠️ JavaScript strings are UTF-16. Always convert to a UTF-8 byte array\n * before computing byte offsets.\n */\nexport interface ByteSlice {\n byteStart: number\n byteEnd: number\n}\n\n// ─── Facet Features ─────────────────────────────────────────────────────────\n\n/** Mention of another AT Protocol account. The DID is the canonical identifier. */\nexport interface MentionFeature {\n $type: 'app.bsky.richtext.facet#mention'\n /** DID of the mentioned account */\n did: string\n}\n\n/** A hyperlink facet. The URI is the full, unshortened URL. */\nexport interface LinkFeature {\n $type: 'app.bsky.richtext.facet#link'\n /** The full URL */\n uri: string\n}\n\n/** A hashtag facet. The tag value does NOT include the leading '#'. */\nexport interface TagFeature {\n $type: 'app.bsky.richtext.facet#tag'\n /** The tag text without the '#' prefix (max 64 graphemes / 640 bytes) */\n tag: string\n}\n\n/** Union of all possible facet feature types. */\nexport type FacetFeature = MentionFeature | LinkFeature | TagFeature\n\n// ─── Facet ──────────────────────────────────────────────────────────────────\n\n/**\n * A single richtext annotation — maps a byte-range within the post text\n * to one or more semantic features (mention, link, tag).\n */\nexport interface Facet {\n index: ByteSlice\n features: FacetFeature[]\n}\n\n// ─── RichText Record ────────────────────────────────────────────────────────\n\n/**\n * Represents the `text` + `facets` fields as they appear in an AT Protocol\n * record (e.g. `app.bsky.feed.post`).\n */\nexport interface RichTextRecord {\n text: string\n facets?: Facet[]\n}\n\n// ─── Segment ────────────────────────────────────────────────────────────────\n\n/**\n * A parsed segment of richtext — a slice of text with its associated feature\n * (if any). Produced by the richtext parser.\n */\nexport interface RichTextSegment {\n /** The raw text of this segment */\n text: string\n /** The facet feature associated with this segment, if any */\n feature?: FacetFeature\n}\n\n// ─── Type Guards ────────────────────────────────────────────────────────────\n\nexport function isMentionFeature(feature: FacetFeature): feature is MentionFeature {\n return feature.$type === 'app.bsky.richtext.facet#mention'\n}\n\nexport function isLinkFeature(feature: FacetFeature): feature is LinkFeature {\n return feature.$type === 'app.bsky.richtext.facet#link'\n}\n\nexport function isTagFeature(feature: FacetFeature): feature is TagFeature {\n return feature.$type === 'app.bsky.richtext.facet#tag'\n}\n","/**\n * Default classNames for bsky-richtext-react components.\n *\n * These are Tailwind CSS utility classes applied when no override is provided.\n * Consumers can extend or replace them using `generateClassNames()` and the\n * `classNames` prop on each component.\n *\n * Because these are plain utility classes, no separate stylesheet needs to be\n * imported — as long as Tailwind is configured in the consumer's project, all\n * styles are applied automatically.\n *\n * @example Keep defaults, override one class\n * ```tsx\n * import { generateClassNames, defaultEditorClassNames } from 'bsky-richtext-react'\n *\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'border rounded-lg p-2' },\n * ])}\n * ```\n *\n * @example Completely replace defaults (opt out of all built-in class names)\n * ```tsx\n * classNames={{ root: 'my-editor', mention: 'my-mention' }}\n * ```\n */\n\nimport type { DisplayClassNames, EditorClassNames, SuggestionClassNames } from '../types/classNames'\n\n// ─── Display ─────────────────────────────────────────────────────────────────\n\nexport const defaultDisplayClassNames: DisplayClassNames = {\n root: 'inline break-words',\n mention: 'inline text-blue-500 hover:underline cursor-pointer',\n link: 'inline text-blue-500 hover:underline',\n tag: 'inline text-blue-500 hover:underline cursor-pointer',\n}\n\n// ─── Suggestion ──────────────────────────────────────────────────────────────\n\nexport const defaultSuggestionClassNames: SuggestionClassNames = {\n root: 'flex flex-col max-h-80 overflow-y-auto bg-white rounded-lg shadow-lg border border-gray-200 min-w-60',\n item: 'flex items-center gap-3 w-full px-3 py-2 text-left cursor-pointer border-none bg-transparent hover:bg-gray-100 select-none',\n itemSelected: 'bg-gray-100',\n avatar:\n 'flex-shrink-0 w-10 h-10 rounded-full overflow-hidden bg-gray-200 flex items-center justify-center',\n avatarImg: 'block w-full h-full object-cover',\n avatarPlaceholder:\n 'flex items-center justify-center w-full h-full text-gray-500 font-medium text-sm',\n text: 'flex flex-col flex-1 min-w-0 overflow-hidden',\n name: 'block truncate font-medium text-gray-900 text-sm',\n handle: 'block truncate text-xs text-gray-500',\n empty: 'block px-3 py-2 text-sm text-gray-500',\n}\n\n// ─── Editor ──────────────────────────────────────────────────────────────────\n\nexport const defaultEditorClassNames: EditorClassNames = {\n root: 'block w-full relative',\n content: 'block w-full',\n mention: 'inline text-blue-500',\n link: 'inline text-blue-500',\n suggestion: defaultSuggestionClassNames,\n}\n","/**\n * generateClassNames — deep-merge utility for component classNames objects.\n *\n * Accepts an array of partial classNames objects and merges them left-to-right,\n * so later entries override earlier ones. String values at the same key are\n * combined using the optional `cn` function (e.g. `clsx`, `tailwind-merge`).\n * Nested objects (e.g. `suggestion` inside `EditorClassNames`) are recursively\n * merged using the same rules.\n *\n * Falsy values in the array (`undefined`, `null`, `false`) are silently\n * ignored, which makes conditional spreading ergonomic:\n *\n * @example Basic override\n * ```ts\n * import { generateClassNames, defaultEditorClassNames } from 'bsky-richtext-react'\n *\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'border rounded-lg', mention: 'text-blue-500' },\n * ])}\n * ```\n *\n * @example With a Tailwind-merge / clsx utility\n * ```ts\n * import { cn } from '@/lib/utils'\n *\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'rounded-none' },\n * ], cn)}\n * ```\n *\n * @example Conditional overrides\n * ```ts\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * isCompact && { root: 'text-sm' },\n * isDark && darkThemeClassNames,\n * ], cn)}\n * ```\n *\n * @example Deep nested override (suggestion dropdown)\n * ```ts\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { suggestion: { item: 'hover:bg-gray-100', itemSelected: 'bg-blue-50' } },\n * ])}\n * ```\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * A function that accepts any number of class strings (plus falsy values) and\n * returns a single merged class string. Compatible with `clsx`, `classnames`,\n * and `tailwind-merge`'s `twMerge` / `cn` utilities.\n */\nexport type ClassNameFn = (\n ...inputs: Array<string | undefined | null | false>\n) => string\n\n// ─── Implementation ──────────────────────────────────────────────────────────\n\n/**\n * Deep-merge an array of classNames objects into a single classNames object.\n *\n * @param classNamesArray - Objects to merge, left-to-right. Falsy entries are skipped.\n * @param cn - Optional class utility function. When omitted, strings are\n * joined with a single space (filtering empty strings).\n */\nexport function generateClassNames<T extends object>(\n classNamesArray: Array<Partial<T> | undefined | null | false>,\n cn?: ClassNameFn,\n): T {\n // Default merge: join non-empty strings with a space\n const merge: ClassNameFn =\n cn ?? ((...args) => args.filter(Boolean).join(' '))\n\n // Filter out falsy entries\n const validObjects = classNamesArray.filter(\n (obj): obj is Partial<T> => Boolean(obj),\n )\n\n if (validObjects.length === 0) {\n return {} as T\n }\n\n // Collect the union of all keys across every object\n const allKeys = new Set<string>()\n for (const obj of validObjects) {\n for (const key of Object.keys(obj)) {\n allKeys.add(key)\n }\n }\n\n const result = {} as T\n\n for (const key of allKeys) {\n const typedKey = key as keyof T\n\n // Collect all defined values for this key, in order\n const values: unknown[] = validObjects\n .map((obj) => obj[typedKey])\n .filter((v) => v !== undefined)\n\n if (values.length === 0) continue\n\n const firstValue = values[0]\n\n if (\n typeof firstValue === 'object' &&\n firstValue !== null &&\n !Array.isArray(firstValue)\n ) {\n // Nested classNames object — recurse via type-erased internal helper\n result[typedKey] = generateClassNames(\n values as object[],\n cn,\n ) as T[keyof T]\n } else if (values.every((v) => typeof v === 'string')) {\n // String values — combine with merge function\n result[typedKey] = merge(...(values)) as T[keyof T]\n } else {\n // Fallback: use the last defined value\n result[typedKey] = values[values.length - 1] as T[keyof T]\n }\n }\n\n return result\n}\n","/**\n * UTF-8 byte offset utilities.\n *\n * The AT Protocol richtext spec uses UTF-8 byte offsets for facet indices,\n * but JavaScript strings are UTF-16. These helpers bridge that gap.\n *\n * Reference: https://atproto.com/specs/richtext\n */\n\nconst encoder = new TextEncoder()\n\n/**\n * Encode a string to a UTF-8 byte array.\n */\nexport function toUtf8Bytes(text: string): Uint8Array {\n return encoder.encode(text)\n}\n\n/**\n * Convert a UTF-8 byte offset to a JavaScript (UTF-16) string index.\n * Needed when slicing a JS string using AT Protocol byte offsets.\n */\nexport function utf8ByteOffsetToCharIndex(text: string, byteOffset: number): number {\n const bytes = encoder.encode(text)\n // Decode only up to the byte offset, then measure the resulting string length\n const decoder = new TextDecoder()\n const slice = bytes.slice(0, byteOffset)\n return decoder.decode(slice).length\n}\n\n/**\n * Slice a string using UTF-8 byte offsets (inclusive start, exclusive end).\n */\nexport function sliceByByteOffset(text: string, byteStart: number, byteEnd: number): string {\n const startChar = utf8ByteOffsetToCharIndex(text, byteStart)\n const endChar = utf8ByteOffsetToCharIndex(text, byteEnd)\n return text.slice(startChar, endChar)\n}\n\n/**\n * Get the UTF-8 byte length of a string.\n */\nexport function utf8ByteLength(text: string): number {\n return encoder.encode(text).length\n}\n","/**\n * Richtext parser — converts `{ text, facets }` into an ordered array of\n * `RichTextSegment` objects, each with their text slice and optional feature.\n *\n * Algorithm:\n * 1. Sort facets by byteStart (ascending).\n * 2. Walk through the text byte-by-byte, emitting plain segments between\n * facets and annotated segments for each facet's byte range.\n * 3. Overlapping facets are handled gracefully (skipped).\n */\n\nimport type { Facet, FacetFeature, RichTextRecord, RichTextSegment } from '../types/facets'\nimport { sliceByByteOffset, utf8ByteLength } from './utf8'\n\n// ─── Internal helpers ────────────────────────────────────────────────────────\n\n/**\n * Sort facets by their byte start position. Mutates a copy.\n */\nfunction sortFacets(facets: Facet[]): Facet[] {\n return [...facets].sort((a, b) => a.index.byteStart - b.index.byteStart)\n}\n\n/**\n * Pick the \"primary\" feature from a facet's feature array.\n * The lexicon allows multiple features per facet, but for rendering we\n * pick the first valid one (mention > link > tag precedence is implicit\n * in the order they appear).\n */\nfunction pickFeature(features: FacetFeature[]): FacetFeature | undefined {\n return features[0]\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Parse a `RichTextRecord` into an ordered array of segments, each\n * carrying its text and an optional facet feature.\n *\n * The segments are contiguous — joining all `segment.text` values\n * reconstructs the original `record.text` exactly.\n */\nexport function parseRichText(record: RichTextRecord): RichTextSegment[] {\n const { text, facets } = record\n\n if (!facets || facets.length === 0) {\n return [{ text }]\n }\n\n const textByteLength = utf8ByteLength(text)\n const sorted = sortFacets(facets)\n const segments: RichTextSegment[] = []\n\n let cursor = 0 // current byte position\n\n for (const facet of sorted) {\n const { byteStart, byteEnd } = facet.index\n\n // Skip malformed or out-of-bounds facets\n if (byteStart < cursor || byteEnd > textByteLength || byteStart >= byteEnd) {\n continue\n }\n\n // Emit plain text segment before this facet\n if (byteStart > cursor) {\n segments.push({\n text: sliceByByteOffset(text, cursor, byteStart),\n })\n }\n\n // Emit annotated segment for this facet\n const feature = pickFeature(facet.features)\n const segment: RichTextSegment = { text: sliceByByteOffset(text, byteStart, byteEnd) }\n if (feature !== undefined) segment.feature = feature\n segments.push(segment)\n\n cursor = byteEnd\n }\n\n // Emit any trailing plain text after the last facet\n if (cursor < textByteLength) {\n segments.push({\n text: sliceByByteOffset(text, cursor, textByteLength),\n })\n }\n\n return segments\n}\n","import { useMemo } from 'react'\nimport type { RichTextRecord, RichTextSegment } from '../types/facets'\nimport { parseRichText } from '../utils/parser'\n\n/**\n * Parse a `RichTextRecord` into an array of `RichTextSegment` objects.\n *\n * The result is memoized — re-computation only occurs when `text` or the\n * serialized facets change.\n *\n * @example\n * ```tsx\n * const segments = useRichText({ text: post.text, facets: post.facets })\n * // [{ text: 'Hello ' }, { text: '@alice', feature: { $type: '...mention', did: '...' } }]\n * ```\n */\nexport function useRichText(record: RichTextRecord): RichTextSegment[] {\n // Stable serialization key for the facets array so useMemo can diff it\n const facetsKey = useMemo(\n () => (record.facets ? JSON.stringify(record.facets) : ''),\n [record.facets],\n )\n\n return useMemo(\n () => parseRichText(record),\n // record is intentionally not in deps — we only react to text+facets changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [record.text, facetsKey],\n )\n}\n","/**\n * URL display utilities for richtext rendering.\n */\n\n/**\n * Shorten a URL for display purposes — strips the protocol and truncates\n * the path if it's very long (mirrors Bluesky's `toShortUrl` behaviour).\n *\n * @example\n * toShortUrl('https://example.com/some/very/long/path?q=foo')\n * // => 'example.com/some/very/long/path?q=foo'\n */\nexport function toShortUrl(url: string, maxLength = 30): string {\n try {\n const parsed = new URL(url)\n const host = parsed.hostname.replace(/^www\\./, '')\n const rest = parsed.pathname + parsed.search + parsed.hash\n\n const full = host + (rest === '/' ? '' : rest)\n if (full.length <= maxLength) return full\n\n return full.slice(0, maxLength) + '…'\n } catch {\n // Not a valid URL — return as-is\n return url\n }\n}\n\n/**\n * Validate that a string is a well-formed URL.\n */\nexport function isValidUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch {\n return false\n }\n}\n","import { useMemo, type AnchorHTMLAttributes, type HTMLAttributes, type ReactNode } from 'react'\nimport type { RichTextRecord, MentionFeature, LinkFeature, TagFeature } from '../../types/facets'\nimport { isMentionFeature, isLinkFeature, isTagFeature } from '../../types/facets'\nimport type { DisplayClassNames } from '../../types/classNames'\nimport { defaultDisplayClassNames } from '../../defaults/classNames'\nimport { generateClassNames } from '../../utils/classNames'\nimport { useRichText } from '../../hooks/useRichText'\nimport { toShortUrl } from '../../utils/url'\n\n// ─── Render Prop Types ───────────────────────────────────────────────────────\n\nexport interface MentionProps {\n /** The raw segment text (e.g. \"@alice.bsky.social\") */\n text: string\n /** The resolved DID of the mentioned account */\n did: string\n /** The mention facet feature */\n feature: MentionFeature\n}\n\nexport interface LinkProps {\n /** The display text for the link (may be shortened) */\n text: string\n /** The full URL */\n uri: string\n /** The link facet feature */\n feature: LinkFeature\n}\n\nexport interface TagProps {\n /** The display text including '#' (e.g. \"#atproto\") */\n text: string\n /** The tag value without '#' prefix (e.g. \"atproto\") */\n tag: string\n /** The tag facet feature */\n feature: TagFeature\n}\n\n// ─── Component Props ─────────────────────────────────────────────────────────\n\nexport interface RichTextDisplayProps\n extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {\n /**\n * The richtext record to render.\n * Accepts `{ text, facets? }` — i.e. the raw AT Protocol record fields.\n */\n value: RichTextRecord | string\n\n /**\n * Custom renderer for @mention segments.\n * If not provided, renders a plain `<a>` linking to the profile.\n */\n renderMention?: (props: MentionProps) => ReactNode\n\n /**\n * Custom renderer for link segments.\n * If not provided, renders a plain `<a>` with the shortened URL as text.\n */\n renderLink?: (props: LinkProps) => ReactNode\n\n /**\n * Custom renderer for #hashtag segments.\n * If not provided, renders a plain `<a>` linking to the hashtag search.\n */\n renderTag?: (props: TagProps) => ReactNode\n\n /**\n * When true, all interactive facets (mentions, links, tags) are rendered\n * as plain text with no anchor elements.\n * @default false\n */\n disableLinks?: boolean\n\n /**\n * Props forwarded to every `<a>` element rendered by the default renderers.\n * Ignored when custom `renderMention` / `renderLink` / `renderTag` are used.\n */\n linkProps?: AnchorHTMLAttributes<HTMLAnchorElement>\n\n /**\n * CSS class names for each styleable part of the component.\n *\n * Use `generateClassNames()` to cleanly merge with the built-in defaults:\n * @example\n * ```tsx\n * import { generateClassNames, defaultDisplayClassNames } from 'bsky-richtext-react'\n *\n * <RichTextDisplay\n * classNames={generateClassNames([\n * defaultDisplayClassNames,\n * { mention: 'text-blue-500 hover:underline' },\n * ], cn)}\n * />\n * ```\n *\n * Or pass a completely custom object to opt out of the defaults entirely:\n * ```tsx\n * <RichTextDisplay classNames={{ root: 'my-richtext', mention: 'my-mention' }} />\n * ```\n */\n classNames?: Partial<DisplayClassNames>\n\n /**\n * Generate the `href` for @mention anchors.\n * Called with the mention's DID.\n * @default (did) => `https://bsky.app/profile/${did}`\n *\n * @example Route mentions to your own profile pages\n * ```tsx\n * mentionUrl={(did) => `/profile/${did}`}\n * ```\n */\n mentionUrl?: (did: string) => string\n\n /**\n * Generate the `href` for #hashtag anchors.\n * Called with the tag value (without the '#' prefix).\n * @default (tag) => `https://bsky.app/hashtag/${encodeURIComponent(tag)}`\n *\n * @example Route hashtags to your own search page\n * ```tsx\n * tagUrl={(tag) => `/search?tag=${encodeURIComponent(tag)}`}\n * ```\n */\n tagUrl?: (tag: string) => string\n\n /**\n * Transform a link URI before it is used as the anchor's `href`.\n * Useful for proxying external links or adding UTM parameters.\n * @default (uri) => uri (identity — no transformation)\n *\n * @example Add a referral parameter\n * ```tsx\n * linkUrl={(uri) => `${uri}?ref=myapp`}\n * ```\n */\n linkUrl?: (uri: string) => string\n}\n\n// ─── Default Renderers ───────────────────────────────────────────────────────\n\ninterface DefaultMentionRendererProps extends MentionProps {\n mentionUrl?: (did: string) => string\n mentionClass?: string\n linkProps?: AnchorHTMLAttributes<HTMLAnchorElement>\n}\n\nfunction DefaultMentionRenderer({\n text,\n did,\n mentionUrl,\n mentionClass,\n linkProps,\n}: DefaultMentionRendererProps) {\n const href = mentionUrl?.(did) ?? `https://bsky.app/profile/${did}`\n return (\n <a\n href={href}\n className={mentionClass}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-did={did}\n {...linkProps}\n >\n {text}\n </a>\n )\n}\n\ninterface DefaultLinkRendererProps extends LinkProps {\n linkUrl?: (uri: string) => string\n linkClass?: string\n linkProps?: AnchorHTMLAttributes<HTMLAnchorElement>\n}\n\nfunction DefaultLinkRenderer({\n text,\n uri,\n linkUrl,\n linkClass,\n linkProps,\n}: DefaultLinkRendererProps) {\n const href = linkUrl?.(uri) ?? uri\n return (\n <a\n href={href}\n className={linkClass}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n {...linkProps}\n >\n {toShortUrl(text)}\n </a>\n )\n}\n\ninterface DefaultTagRendererProps extends TagProps {\n tagUrl?: (tag: string) => string\n tagClass?: string\n linkProps?: AnchorHTMLAttributes<HTMLAnchorElement>\n}\n\nfunction DefaultTagRenderer({\n text,\n tag,\n tagUrl,\n tagClass,\n linkProps,\n}: DefaultTagRendererProps) {\n const href =\n tagUrl?.(tag) ?? `https://bsky.app/hashtag/${encodeURIComponent(tag)}`\n return (\n <a\n href={href}\n className={tagClass}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-tag={tag}\n {...linkProps}\n >\n {text}\n </a>\n )\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * `RichTextDisplay` renders AT Protocol richtext content — a string with\n * optional `facets` that annotate byte ranges as mentions, links, or hashtags.\n *\n * @example Basic usage\n * ```tsx\n * <RichTextDisplay value={{ text: post.text, facets: post.facets }} />\n * ```\n *\n * @example With custom mention renderer\n * ```tsx\n * <RichTextDisplay\n * value={post}\n * renderMention={({ text, did }) => (\n * <Link to={`/profile/${did}`}>{text}</Link>\n * )}\n * />\n * ```\n *\n * @example With URL resolvers pointing to your own routes\n * ```tsx\n * <RichTextDisplay\n * value={post}\n * mentionUrl={(did) => `/profile/${did}`}\n * tagUrl={(tag) => `/search?tag=${tag}`}\n * />\n * ```\n *\n * @example With classNames (using generateClassNames for clean merging)\n * ```tsx\n * import { generateClassNames, defaultDisplayClassNames } from 'bsky-richtext-react'\n *\n * <RichTextDisplay\n * value={post}\n * classNames={generateClassNames([\n * defaultDisplayClassNames,\n * { mention: 'text-blue-500 font-semibold' },\n * ], cn)}\n * />\n * ```\n */\nexport function RichTextDisplay({\n value,\n renderMention,\n renderLink,\n renderTag,\n disableLinks = false,\n linkProps,\n classNames: classNamesProp,\n mentionUrl,\n tagUrl,\n linkUrl,\n ...spanProps\n}: RichTextDisplayProps) {\n // Normalise plain string input into a RichTextRecord\n const record: RichTextRecord = typeof value === 'string' ? { text: value } : value\n\n // Merge provided classNames with defaults.\n // Memoized via JSON.stringify so inline object literals don't recalculate every render.\n const cn = useMemo(\n () => generateClassNames([defaultDisplayClassNames, classNamesProp]),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(classNamesProp)],\n )\n\n const segments = useRichText(record)\n\n const children: ReactNode[] = segments.map((segment, index) => {\n const { text, feature } = segment\n\n if (!feature || disableLinks) {\n return text\n }\n\n if (isMentionFeature(feature)) {\n if (renderMention) {\n return (\n <span key={index} className={cn.mention}>\n {renderMention({ text, did: feature.did, feature })}\n </span>\n )\n }\n return (\n <DefaultMentionRenderer\n key={index}\n text={text}\n did={feature.did}\n feature={feature}\n {...(mentionUrl !== undefined ? { mentionUrl } : {})}\n {...(cn.mention !== undefined ? { mentionClass: cn.mention } : {})}\n {...(linkProps !== undefined ? { linkProps } : {})}\n />\n )\n }\n\n if (isLinkFeature(feature)) {\n if (renderLink) {\n return (\n <span key={index} className={cn.link}>\n {renderLink({ text, uri: feature.uri, feature })}\n </span>\n )\n }\n return (\n <DefaultLinkRenderer\n key={index}\n text={text}\n uri={feature.uri}\n feature={feature}\n {...(linkUrl !== undefined ? { linkUrl } : {})}\n {...(cn.link !== undefined ? { linkClass: cn.link } : {})}\n {...(linkProps !== undefined ? { linkProps } : {})}\n />\n )\n }\n\n if (isTagFeature(feature)) {\n if (renderTag) {\n return (\n <span key={index} className={cn.tag}>\n {renderTag({ text, tag: feature.tag, feature })}\n </span>\n )\n }\n return (\n <DefaultTagRenderer\n key={index}\n text={text}\n tag={feature.tag}\n feature={feature}\n {...(tagUrl !== undefined ? { tagUrl } : {})}\n {...(cn.tag !== undefined ? { tagClass: cn.tag } : {})}\n {...(linkProps !== undefined ? { linkProps } : {})}\n />\n )\n }\n\n // Unknown feature type — render plain text as fallback\n return text\n })\n\n return (\n <span className={cn.root} {...spanProps}>\n {children}\n </span>\n )\n}\n","/**\n * Bluesky public API helpers for mention search.\n *\n * `searchBskyActors` calls the unauthenticated public Bluesky API to look up\n * actor suggestions. It is used as the default `onMentionQuery` implementation\n * in `RichTextEditor` — no API key or authentication required.\n *\n * `createDebouncedSearch` wraps `searchBskyActors` with a debounce so rapid\n * keystrokes don't fire unnecessary network requests. Only the latest in-flight\n * query resolves; stale promises from earlier keystrokes are silently discarded.\n */\n\nimport type { MentionSuggestion } from '../components/RichTextEditor/RichTextEditor'\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst BSKY_SEARCH_API =\n 'https://public.api.bsky.app/xrpc/app.bsky.actor.searchActors'\n\n// ─── Raw API ─────────────────────────────────────────────────────────────────\n\n/**\n * Shape of a single actor returned by the Bluesky public search API.\n * Only the fields we care about are typed here.\n */\ninterface BskyActor {\n did: string\n handle: string\n displayName?: string\n avatar?: string\n}\n\n/**\n * Search for Bluesky actors using the public, unauthenticated API.\n *\n * Returns up to `limit` matching `MentionSuggestion` objects, or an empty\n * array if the query is blank, the network request fails, or the response is\n * malformed.\n *\n * @param query - Text the user typed after \"@\"\n * @param limit - Max results to return (default: 8)\n */\nexport async function searchBskyActors(\n query: string,\n limit = 8,\n): Promise<MentionSuggestion[]> {\n if (!query.trim()) return []\n\n try {\n const url = new URL(BSKY_SEARCH_API)\n url.searchParams.set('q', query.trim())\n url.searchParams.set('limit', String(limit))\n\n const res = await fetch(url.toString())\n if (!res.ok) return []\n\n const data = (await res.json()) as { actors?: BskyActor[] }\n\n return (data.actors ?? []).map((actor) => ({\n did: actor.did,\n handle: actor.handle,\n ...(actor.displayName !== undefined\n ? { displayName: actor.displayName }\n : {}),\n ...(actor.avatar !== undefined ? { avatarUrl: actor.avatar } : {}),\n }))\n } catch {\n // Network error, JSON parse error, etc. — fail gracefully\n return []\n }\n}\n\n// ─── Debounced search ────────────────────────────────────────────────────────\n\n/**\n * Create a debounced version of `searchBskyActors`.\n *\n * Rapid calls within `delayMs` are coalesced — only the *latest* invocation\n * fires a network request. Earlier pending promises resolve with the same\n * result as the latest call (they are not rejected or left dangling).\n *\n * @param delayMs - Debounce window in milliseconds (default: 300)\n *\n * @example\n * ```ts\n * const debouncedSearch = createDebouncedSearch(400)\n * // Pass to onMentionQuery or use as the internal default\n * ```\n */\nexport function createDebouncedSearch(\n delayMs = 300,\n): (query: string) => Promise<MentionSuggestion[]> {\n let timeoutId: ReturnType<typeof setTimeout> | null = null\n // Track all pending resolvers so every outstanding promise gets the result\n // of the most recent query, preventing stale dangling promises.\n const pendingResolvers: Array<(value: MentionSuggestion[]) => void> = []\n\n return (query: string): Promise<MentionSuggestion[]> => {\n return new Promise((resolve) => {\n // Queue this caller's resolver\n pendingResolvers.push(resolve)\n\n // Cancel the previous scheduled search\n if (timeoutId !== null) {\n clearTimeout(timeoutId)\n }\n\n timeoutId = setTimeout(async () => {\n timeoutId = null\n const results = await searchBskyActors(query)\n\n // Flush all queued resolvers with the same result set.\n // Splice to avoid mutation-during-iteration issues.\n const toResolve = pendingResolvers.splice(0, pendingResolvers.length)\n for (const r of toResolve) {\n r(results)\n }\n }, delayMs)\n })\n }\n}\n","/**\n * MentionSuggestionList\n *\n * Default built-in mention autocomplete dropdown.\n * Heavily inspired by Bluesky's social-app Autocomplete.tsx.\n *\n * Default classNames apply Tailwind utility classes for a ready-to-use\n * appearance. Override specific parts using the `classNames` prop with\n * `generateClassNames()`.\n *\n * TipTap requires the `render` factory to return lifecycle callbacks\n * ({ onStart, onUpdate, onKeyDown, onExit }). The component itself is mounted\n * via `ReactRenderer` and positioned via `@floating-ui/dom` — see createSuggestionRenderer.ts.\n */\n\nimport { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react'\nimport type { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion'\nimport type { SuggestionClassNames } from '../../types/classNames'\nimport { defaultSuggestionClassNames } from '../../defaults/classNames'\nimport { generateClassNames } from '../../utils/classNames'\nimport type { MentionSuggestion } from './RichTextEditor'\n\n// ─── Imperative handle ───────────────────────────────────────────────────────\n\n/**\n * Ref handle that TipTap calls into for keyboard events while the popup is open.\n * Mirrors the `MentionListRef` interface from the Bluesky reference implementation.\n */\nexport interface MentionSuggestionListRef {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\n// ─── Props ───────────────────────────────────────────────────────────────────\n\nexport interface MentionSuggestionListProps extends SuggestionProps<MentionSuggestion> {\n /**\n * Whether to render avatars when `avatarUrl` is present on a suggestion.\n * When false, avatar placeholder is hidden entirely.\n * @default true\n */\n showAvatars?: boolean\n\n /**\n * Text to show when the items array is empty.\n * @default \"No results\"\n */\n noResultsText?: string\n\n /**\n * CSS class names for each styleable part of the suggestion dropdown.\n *\n * Use `generateClassNames()` to merge with the built-in defaults:\n * @example\n * ```tsx\n * import { generateClassNames, defaultSuggestionClassNames } from 'bsky-richtext-react'\n *\n * classNames={generateClassNames([\n * defaultSuggestionClassNames,\n * { item: 'px-3 py-2', itemSelected: 'bg-blue-50' },\n * ], cn)}\n * ```\n */\n classNames?: Partial<SuggestionClassNames>\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * Default mention suggestion dropdown rendered by `RichTextEditor`.\n *\n * Consumers can pass `classNames` to style specific parts, or pass a completely\n * custom `renderMentionSuggestion` factory to the editor to replace this\n * component entirely.\n */\nexport const MentionSuggestionList = forwardRef<\n MentionSuggestionListRef,\n MentionSuggestionListProps\n>(function MentionSuggestionListImpl(\n { items, command, showAvatars = true, noResultsText = 'No results', classNames: classNamesProp },\n ref,\n) {\n const [selectedIndex, setSelectedIndex] = useState(0)\n\n // Merge provided classNames with defaults.\n // Memoized via JSON.stringify so inline object literals don't recalculate every render.\n const cn = useMemo(\n () => generateClassNames([defaultSuggestionClassNames, classNamesProp]),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(classNamesProp)],\n )\n\n // Reset selection when items change (new query results arrived)\n useEffect(() => {\n setSelectedIndex(0)\n }, [items])\n\n // ─── Selection helpers ───────────────────────────────────────────────────\n\n const selectItem = (index: number) => {\n const item = items[index]\n if (item) {\n // `id` is the handle — the editor's `renderLabel` uses it to build \"@handle\"\n command({ id: item.handle })\n }\n }\n\n const moveUp = () => {\n setSelectedIndex((prev) => (prev + items.length - 1) % items.length)\n }\n\n const moveDown = () => {\n setSelectedIndex((prev) => (prev + 1) % items.length)\n }\n\n // ─── Keyboard handler (called by TipTap via the ref) ────────────────────\n\n useImperativeHandle(ref, () => ({\n onKeyDown({ event }: SuggestionKeyDownProps): boolean {\n if (event.key === 'ArrowUp') {\n moveUp()\n return true\n }\n if (event.key === 'ArrowDown') {\n moveDown()\n return true\n }\n if (event.key === 'Enter' || event.key === 'Tab') {\n selectItem(selectedIndex)\n return true\n }\n return false\n },\n }))\n\n // ─── Render ──────────────────────────────────────────────────────────────\n\n return (\n <div\n className={cn.root}\n // Prevent the editor from losing focus when clicking a suggestion\n onMouseDown={(e) => e.preventDefault()}\n >\n {items.length === 0 ? (\n <div className={cn.empty}>{noResultsText}</div>\n ) : (\n items.map((item, index) => {\n const isSelected = index === selectedIndex\n const itemClass = isSelected\n ? `${cn.item ?? ''} ${cn.itemSelected ?? ''}`.trim()\n : cn.item\n\n return (\n <button\n key={item.did}\n type=\"button\"\n className={itemClass}\n onMouseEnter={() => setSelectedIndex(index)}\n onClick={() => selectItem(index)}\n >\n {showAvatars && (\n <span className={cn.avatar}>\n {item.avatarUrl ? (\n <img\n src={item.avatarUrl}\n alt={item.displayName ?? item.handle}\n className={cn.avatarImg}\n />\n ) : (\n <span className={cn.avatarPlaceholder} aria-hidden=\"true\">\n {(item.displayName ?? item.handle).charAt(0).toUpperCase()}\n </span>\n )}\n </span>\n )}\n\n <span className={cn.text}>\n {item.displayName && <span className={cn.name}>{item.displayName}</span>}\n <span className={cn.handle}>@{item.handle}</span>\n </span>\n </button>\n )\n })\n )}\n </div>\n )\n})\n","/**\n * createSuggestionRenderer\n *\n * Factory that returns a TipTap `SuggestionOptions['render']` function.\n * It uses `@floating-ui/dom` for cursor-anchored positioning and `ReactRenderer`\n * to mount the `MentionSuggestionList` React component into the popup.\n *\n * Heavily inspired by Bluesky's social-app Autocomplete.tsx and the\n * official TipTap mention example:\n * https://tiptap.dev/docs/editor/extensions/nodes/mention#usage\n *\n * This is the default renderer used when the consumer does NOT supply\n * a custom `renderMentionSuggestion` prop to `<RichTextEditor>`.\n */\n\nimport { computePosition, flip, offset, shift } from '@floating-ui/dom'\nimport { ReactRenderer } from '@tiptap/react'\nimport type { SuggestionOptions, SuggestionProps } from '@tiptap/suggestion'\nimport type { SuggestionClassNames } from '../../types/classNames'\nimport {\n MentionSuggestionList,\n type MentionSuggestionListRef,\n type MentionSuggestionListProps,\n} from './MentionSuggestionList'\nimport type { MentionSuggestion } from './RichTextEditor'\n\n// ─── Options ─────────────────────────────────────────────────────────────────\n\nexport interface DefaultSuggestionRendererOptions {\n /**\n * Whether to show avatars in the suggestion list.\n * Forwarded to `MentionSuggestionList`.\n * @default true\n */\n showAvatars?: boolean\n\n /**\n * Text shown when the query returns no results.\n * @default \"No results\"\n */\n noResultsText?: string\n\n /**\n * CSS class names for each styleable part of the suggestion dropdown.\n * Forwarded directly to `MentionSuggestionList`.\n */\n classNames?: Partial<SuggestionClassNames>\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Create the default TipTap `suggestion.render` factory.\n *\n * The returned function is called once per \"suggestion session\"\n * (i.e. each time the user types \"@\" and a popup should open/update/close).\n *\n * It follows the same lifecycle pattern as the Bluesky reference:\n * - `onStart` → Mount ReactRenderer, create floating-ui popup\n * - `onUpdate` → Update props, reposition popup\n * - `onKeyDown`→ Delegate to the MentionSuggestionList imperative ref\n * - `onExit` → Destroy popup and React renderer\n */\nexport function createDefaultSuggestionRenderer(\n options: DefaultSuggestionRendererOptions = {},\n): SuggestionOptions<MentionSuggestion>['render'] {\n return () => {\n let renderer: ReactRenderer<MentionSuggestionListRef, MentionSuggestionListProps> | undefined\n let popup: HTMLDivElement | undefined\n\n const buildProps = (props: SuggestionProps<MentionSuggestion>): MentionSuggestionListProps => ({\n ...props,\n showAvatars: options.showAvatars ?? true,\n noResultsText: options.noResultsText ?? 'No results',\n ...(options.classNames !== undefined ? { classNames: options.classNames } : {}),\n })\n\n return {\n onStart(props: SuggestionProps<MentionSuggestion>) {\n renderer = new ReactRenderer(MentionSuggestionList, {\n props: buildProps(props),\n editor: props.editor,\n })\n\n if (!props.clientRect) return\n\n const clientRect = props.clientRect\n\n // Create a wrapper div with fixed positioning (viewport-relative coords\n // from clientRect) and append the ReactRenderer's element into it.\n popup = document.createElement('div')\n popup.style.position = 'fixed'\n popup.style.zIndex = '9999'\n popup.appendChild(renderer.element)\n document.body.appendChild(popup)\n\n // Create a virtual reference element for @floating-ui/dom.\n const virtualEl = { getBoundingClientRect: () => clientRect?.() ?? new DOMRect() }\n\n void computePosition(virtualEl, popup, {\n placement: 'bottom-start',\n middleware: [offset(8), flip(), shift({ padding: 8 })],\n }).then(({ x, y }) => {\n if (popup) {\n popup.style.left = `${x}px`\n popup.style.top = `${y}px`\n }\n })\n },\n\n onUpdate(props: SuggestionProps<MentionSuggestion>) {\n renderer?.updateProps(buildProps(props))\n\n if (!props.clientRect || !popup) return\n\n const clientRect = props.clientRect\n const virtualEl = { getBoundingClientRect: () => clientRect?.() ?? new DOMRect() }\n\n void computePosition(virtualEl, popup, {\n placement: 'bottom-start',\n middleware: [offset(8), flip(), shift({ padding: 8 })],\n }).then(({ x, y }) => {\n if (popup) {\n popup.style.left = `${x}px`\n popup.style.top = `${y}px`\n }\n })\n },\n\n onKeyDown(props) {\n // Escape dismisses without selecting\n if (props.event.key === 'Escape') {\n if (popup) popup.style.display = 'none'\n return true\n }\n // All other keys delegated to the list component\n return renderer?.ref?.onKeyDown(props) ?? false\n },\n\n onExit() {\n popup?.remove()\n renderer?.destroy()\n popup = undefined\n renderer = undefined\n },\n }\n }\n}\n","/**\n * TipTap Mention extension configured for Bluesky @handle autocomplete.\n *\n * Builds on `@tiptap/extension-mention` and wires up:\n * - Consumer-supplied `onMentionQuery` for fetching suggestions\n * - Default popup renderer (@floating-ui/dom + MentionSuggestionList) when no\n * custom `renderMentionSuggestion` is provided\n *\n * Heavily inspired by Bluesky's social-app TextInput.web.tsx and Autocomplete.tsx.\n */\nimport { Mention } from '@tiptap/extension-mention'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\nimport type { MentionSuggestion } from '../RichTextEditor'\nimport {\n createDefaultSuggestionRenderer,\n type DefaultSuggestionRendererOptions,\n} from '../createSuggestionRenderer'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface BskyMentionOptions {\n /**\n * Async function that returns suggestions for a given query string.\n * Called every time the user types after \"@\".\n * Return an empty array to show the \"No results\" state.\n */\n onMentionQuery: (query: string) => Promise<MentionSuggestion[]>\n\n /**\n * Custom TipTap `suggestion.render` factory.\n * When provided, replaces the default @floating-ui/dom + MentionSuggestionList renderer.\n * When omitted, the built-in renderer is used.\n */\n renderSuggestionList?: SuggestionOptions['render']\n\n /**\n * Options forwarded to the default renderer (ignored when `renderSuggestionList`\n * is provided).\n */\n defaultRendererOptions?: DefaultSuggestionRendererOptions\n\n /**\n * CSS class applied to mention chips rendered inside the editor.\n * Sourced from `EditorClassNames.mention` and defaults to `bsky-editor-mention`.\n */\n mentionClass?: string\n}\n\n// ─── Extension factory ───────────────────────────────────────────────────────\n\n/**\n * Create a configured TipTap Mention extension for Bluesky.\n *\n * The mention node stores the account handle as `id` and surfaces it via\n * `renderLabel` as \"@handle\". When the editor JSON is serialised in\n * `editorJsonToText`, mention nodes are rendered as `@{id}`.\n */\nexport function createBskyMentionExtension({\n onMentionQuery,\n renderSuggestionList,\n defaultRendererOptions,\n mentionClass = 'bsky-editor-mention',\n}: BskyMentionOptions) {\n // Use the consumer-supplied renderer, or fall back to our built-in one.\n const render = renderSuggestionList ?? createDefaultSuggestionRenderer(defaultRendererOptions)\n\n return Mention.configure({\n HTMLAttributes: {\n class: mentionClass,\n },\n\n /**\n * Render the mention node's text content inside the editor.\n * The `id` attribute stores the handle (e.g. \"alice.bsky.social\"),\n * so we prefix it with \"@\".\n *\n * Mirrors the Bluesky reference:\n * text += `@${json.attrs?.id || ''}` (in editorJsonToText)\n */\n renderLabel({ options, node }) {\n const handle =\n (node.attrs.label as string | undefined) ?? (node.attrs.id as string | undefined) ?? ''\n return `${options.suggestion.char ?? '@'}${handle}`\n },\n\n suggestion: {\n char: '@',\n allowSpaces: false,\n startOfLine: false,\n\n /**\n * Fetch suggestion items from the consumer.\n * Matches Bluesky's pattern: `autocomplete({ query })`.\n * Returns up to 8 items (same limit as the reference implementation).\n */\n items: async ({ query }) => {\n if (!query) return []\n try {\n const results = await onMentionQuery(query)\n return results.slice(0, 8)\n } catch {\n return []\n }\n },\n\n // Spread so the key is only present when defined (exactOptionalPropertyTypes)\n ...(render !== undefined ? { render } : {}),\n },\n })\n}\n","/**\n * BskyLinkDecorator — stateless URL decoration for the RichTextEditor.\n *\n * Unlike TipTap Marks (which store formatting in the document model), this\n * extension uses a ProseMirror Plugin with a DecorationSet. Decorations are\n * purely visual — they are recalculated from scratch on every document change\n * by re-running the URL regex over the plain text. This means:\n *\n * - Typing a space after a URL naturally ends the decoration on the next tick\n * - Editing inside a URL never duplicates characters\n * - No stale mark state can accumulate in the document\n *\n * This is identical in approach to Bluesky's reference LinkDecorator.ts in\n * social-app/src/view/com/composer/text-input/web/LinkDecorator.ts.\n *\n * The visual class applied is `autolink` (matching Bluesky's reference).\n * Style it via `.bsky-editor .autolink { … }` or the `classNames.link` prop.\n */\n\nimport { Extension } from '@tiptap/core'\nimport type { Node as ProsemirrorNode } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\n\n// ─── URL Regex ───────────────────────────────────────────────────────────────\n\n/**\n * Matches http/https URLs in plain text.\n * We keep this simple and consistent with @atproto/api's detectFacetsWithoutResolution,\n * so the visual decoration always matches what will become a link facet.\n */\nconst URL_REGEX = /https?:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/g\n\n// ─── Decoration helpers ──────────────────────────────────────────────────────\n\n/**\n * Walk every text node in the document, run URL_REGEX over its content,\n * and emit an inline Decoration for each match.\n */\nfunction getDecorations(doc: ProsemirrorNode, linkClass: string): DecorationSet {\n const decorations: Decoration[] = []\n\n doc.descendants((node, pos) => {\n if (!node.isText || !node.text) return\n\n const text = node.text\n // Reset lastIndex before each new text node\n URL_REGEX.lastIndex = 0\n\n let match: RegExpExecArray | null\n while ((match = URL_REGEX.exec(text)) !== null) {\n let uri = match[0]\n const from = pos + match.index\n let to = from + uri.length\n\n // Strip trailing punctuation (mirrors Bluesky's iterateUris)\n if (/[.,;!?]$/.test(uri)) {\n uri = uri.slice(0, -1)\n to--\n }\n if (/[)]$/.test(uri) && !uri.includes('(')) {\n uri = uri.slice(0, -1)\n to--\n }\n\n decorations.push(\n Decoration.inline(from, to, {\n class: linkClass,\n 'data-autolink': '',\n }),\n )\n }\n })\n\n return DecorationSet.create(doc, decorations)\n}\n\n// ─── Plugin factory ──────────────────────────────────────────────────────────\n\nfunction createLinkDecoratorPlugin(linkClass: string): Plugin {\n const key = new PluginKey<DecorationSet>('bsky-link-decorator')\n\n return new Plugin<DecorationSet>({\n key,\n\n state: {\n init: (_, { doc }) => getDecorations(doc, linkClass),\n apply: (transaction, decorationSet) => {\n if (transaction.docChanged) {\n return getDecorations(transaction.doc, linkClass)\n }\n // If the doc didn't change (e.g. selection change), just map existing\n // decorations to their new positions.\n return decorationSet.map(transaction.mapping, transaction.doc)\n },\n },\n\n props: {\n decorations(state) {\n return key.getState(state)\n },\n },\n })\n}\n\n// ─── TipTap Extension ────────────────────────────────────────────────────────\n\nexport interface BskyLinkDecoratorOptions {\n /**\n * CSS class applied to each decorated URL span.\n * Override via the editor's `classNames.link` prop.\n * @default 'autolink'\n */\n linkClass: string\n}\n\nexport const BskyLinkDecorator = Extension.create<BskyLinkDecoratorOptions>({\n name: 'bskyLinkDecorator',\n\n addOptions() {\n return {\n linkClass: 'autolink',\n }\n },\n\n addProseMirrorPlugins() {\n return [createLinkDecoratorPlugin(this.options.linkClass)]\n },\n})\n","/**\n * RichTextEditor\n *\n * TipTap-based editor for composing AT Protocol richtext content.\n * Heavily inspired by Bluesky's social-app TextInput.web.tsx.\n *\n * Key differences from the reference implementation:\n * - No React Native / Expo dependencies — pure React DOM\n * - No Bluesky-specific theming (ALF) — headless, consumer styles it\n * - No media paste / emoji picker (out of scope for this library)\n * - `onChange` emits a plain `RichTextRecord` instead of an `RichText` class\n */\n\nimport { useEffect, useImperativeHandle, useMemo, type HTMLAttributes, type Ref } from 'react'\nimport { EditorContent, useEditor, type JSONContent } from '@tiptap/react'\nimport { Document } from '@tiptap/extension-document'\nimport { Paragraph } from '@tiptap/extension-paragraph'\nimport { Text } from '@tiptap/extension-text'\nimport { History } from '@tiptap/extension-history'\nimport { HardBreak } from '@tiptap/extension-hard-break'\nimport { Placeholder } from '@tiptap/extension-placeholder'\nimport type { SuggestionOptions } from '@tiptap/suggestion'\nimport { RichText as AtpRichText } from '@atproto/api'\nimport type { RichTextRecord, Facet } from '../../types/facets'\nimport type { EditorClassNames } from '../../types/classNames'\nimport { defaultEditorClassNames } from '../../defaults/classNames'\nimport { generateClassNames } from '../../utils/classNames'\nimport { createDebouncedSearch } from '../../utils/blueskyApi'\nimport { createBskyMentionExtension } from './extensions/BskyMention'\nimport { BskyLinkDecorator } from './extensions/BskyLinkDecorator'\nimport type { DefaultSuggestionRendererOptions } from './createSuggestionRenderer'\n\n// ─── Public Types ────────────────────────────────────────────────────────────\n\n/**\n * A single suggestion item for the @mention autocomplete popup.\n */\nexport interface MentionSuggestion {\n /** DID of the suggested account — used as the facet's `did` value */\n did: string\n /** Display handle (e.g. \"alice.bsky.social\") */\n handle: string\n /** Optional display name */\n displayName?: string\n /** Optional avatar URL */\n avatarUrl?: string\n}\n\n/**\n * Imperative ref API for `RichTextEditor`.\n */\nexport interface RichTextEditorRef {\n /** Focus the editor */\n focus: () => void\n /** Blur the editor */\n blur: () => void\n /** Clear the editor content */\n clear: () => void\n /** Get the current plain-text content */\n getText: () => string\n}\n\n// ─── Component Props ─────────────────────────────────────────────────────────\n\nexport interface RichTextEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /**\n * Initial richtext value. The editor is pre-populated with this content on mount.\n * This is an uncontrolled initial state — use `onChange` to track updates.\n */\n initialValue?: RichTextRecord | string\n\n /**\n * Called on every content change with the latest `RichTextRecord`.\n *\n * The `facets` array is populated via `detectFacetsWithoutResolution()` —\n * facets will contain handles (not DIDs) for mentions until you resolve them\n * server-side using the AT Protocol agent.\n */\n onChange?: (record: RichTextRecord) => void\n\n /**\n * Placeholder text shown when the editor is empty.\n */\n placeholder?: string\n\n /**\n * Called when the editor gains focus.\n */\n onFocus?: () => void\n\n /**\n * Called when the editor loses focus.\n */\n onBlur?: () => void\n\n /**\n * Async function to fetch @mention suggestions.\n * Called with the query string (text after \"@\") as the user types.\n * Return an empty array to show no suggestions / \"No results\".\n *\n * When not provided, the built-in Bluesky public API search is used\n * (debounced by `mentionSearchDebounceMs`). Set `disableDefaultMentionSearch`\n * to true to disable this default behaviour entirely.\n *\n * @example\n * ```tsx\n * onMentionQuery={async (q) => {\n * const res = await agent.searchActors({ term: q, limit: 8 })\n * return res.data.actors.map(a => ({\n * did: a.did,\n * handle: a.handle,\n * displayName: a.displayName,\n * avatarUrl: a.avatar,\n * }))\n * }}\n * ```\n */\n onMentionQuery?: (query: string) => Promise<MentionSuggestion[]>\n\n /**\n * Debounce delay (in milliseconds) applied to the built-in Bluesky mention\n * search. Has no effect when `onMentionQuery` is provided.\n * @default 300\n */\n mentionSearchDebounceMs?: number\n\n /**\n * When true, disables the default Bluesky public API mention search.\n * No suggestions will appear unless you provide `onMentionQuery`.\n * @default false\n */\n disableDefaultMentionSearch?: boolean\n\n /**\n * Custom TipTap `suggestion.render` factory.\n * When provided, replaces the default @floating-ui/dom + MentionSuggestionList renderer.\n * The factory must return `{ onStart, onUpdate, onKeyDown, onExit }`.\n *\n * See: https://tiptap.dev/docs/editor/extensions/nodes/mention#usage\n */\n renderMentionSuggestion?: SuggestionOptions['render']\n\n /**\n * Options forwarded to the default suggestion renderer.\n * Only used when `renderMentionSuggestion` is NOT provided.\n */\n mentionSuggestionOptions?: DefaultSuggestionRendererOptions\n\n /**\n * CSS class names for each styleable part of the editor.\n *\n * Use `generateClassNames()` to cleanly merge with the built-in defaults:\n * @example\n * ```tsx\n * import { generateClassNames, defaultEditorClassNames } from 'bsky-richtext-react'\n *\n * <RichTextEditor\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'border rounded-lg p-3', mention: 'text-blue-500' },\n * ], cn)}\n * />\n * ```\n *\n * Or pass a completely custom object to opt out of the defaults:\n * ```tsx\n * <RichTextEditor classNames={{ root: 'my-editor', mention: 'my-mention' }} />\n * ```\n */\n classNames?: Partial<EditorClassNames>\n\n /**\n * Imperative ref for programmatic control.\n */\n editorRef?: Ref<RichTextEditorRef>\n\n /**\n * Whether the editor content is editable.\n * @default true\n */\n editable?: boolean\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Convert a `RichTextRecord` or string to the HTML the editor uses as\n * its initial content.\n *\n * Mention nodes must be expressed as `<span data-type=\"mention\" data-id=\"handle\">`\n * so TipTap's Mention extension can parse them correctly on load.\n *\n * Mirrors `richTextToHTML` in the Bluesky reference implementation.\n */\nfunction toInitialHTML(value: RichTextRecord | string | undefined): string {\n if (!value) return ''\n\n if (typeof value === 'string') {\n return `<p>${escapeHTML(value)}</p>`\n }\n\n const { text, facets } = value\n\n if (!facets?.length) {\n return `<p>${escapeHTML(text)}</p>`\n }\n\n // Use @atproto/api's RichText class to iterate segments — it handles\n // the byte-offset arithmetic for us.\n // Cast via unknown: our Facet type is structurally identical to @atproto/api's\n // internal Main[] but lacks the index signature that atproto adds.\n // We also guard against undefined since exactOptionalPropertyTypes is enabled.\n const atpFacets = facets as unknown as AtpRichText['facets']\n const rt = new AtpRichText(atpFacets ? { text, facets: atpFacets } : { text })\n let html = ''\n\n for (const segment of rt.segments()) {\n if (segment.mention) {\n // Mention: emit a TipTap mention node using the DID as the `data-id`.\n // The mention extension will render it via `renderLabel` as \"@handle\".\n html += `<span data-type=\"mention\" data-id=\"${escapeHTML(segment.mention.did)}\"></span>`\n } else {\n html += escapeHTML(segment.text)\n }\n }\n\n return html\n}\n\nfunction escapeHTML(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\n/**\n * Convert the TipTap editor's JSON document to a plain text string.\n *\n * - `doc` nodes iterate their children\n * - `paragraph` nodes add a newline after themselves (except the last one)\n * - `hardBreak` nodes add a newline\n * - `text` nodes emit their text content\n * - `mention` nodes emit \"@{id}\" (the handle stored in `attrs.id`)\n *\n * Directly mirrors `editorJsonToText` from the Bluesky reference.\n */\nfunction editorJsonToText(json: JSONContent, isLastDocumentChild = false): string {\n let text = ''\n\n if (json.type === 'doc') {\n if (json.content?.length) {\n for (let i = 0; i < json.content.length; i++) {\n const node = json.content[i]\n if (!node) continue\n const isLast = i === json.content.length - 1\n text += editorJsonToText(node, isLast)\n }\n }\n } else if (json.type === 'paragraph') {\n if (json.content?.length) {\n for (const node of json.content) {\n text += editorJsonToText(node)\n }\n }\n if (!isLastDocumentChild) {\n text += '\\n'\n }\n } else if (json.type === 'hardBreak') {\n text += '\\n'\n } else if (json.type === 'text') {\n text += json.text ?? ''\n } else if (json.type === 'mention') {\n // The `id` attribute holds the handle chosen during autocomplete\n text += `@${(json.attrs?.id as string | undefined) ?? ''}`\n }\n\n return text\n}\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * `RichTextEditor` is a TipTap-based editor for composing AT Protocol richtext.\n *\n * Features:\n * - Real-time @mention autocomplete — defaults to the Bluesky public API,\n * override with `onMentionQuery`\n * - Automatic URL decoration (link facets detected on change)\n * - Hard-break (Shift+Enter) for newlines inside a paragraph\n * - Undo/redo history\n * - `onChange` emits a `RichTextRecord` with `text` + `facets` populated via\n * `detectFacetsWithoutResolution()`\n * - Headless by default — Tailwind utility classes are applied via the default classNames; override freely via the `classNames` prop\n *\n * @example Basic usage (built-in Bluesky mention search)\n * ```tsx\n * <RichTextEditor\n * placeholder=\"What's on your mind?\"\n * onChange={(record) => setPost(record)}\n * />\n * ```\n *\n * @example Custom mention search\n * ```tsx\n * <RichTextEditor\n * placeholder=\"What's on your mind?\"\n * onMentionQuery={async (q) => searchProfiles(q)}\n * onChange={(record) => setPost(record)}\n * />\n * ```\n *\n * @example With classNames\n * ```tsx\n * import { generateClassNames, defaultEditorClassNames } from 'bsky-richtext-react'\n *\n * <RichTextEditor\n * classNames={generateClassNames([\n * defaultEditorClassNames,\n * { root: 'border rounded-lg p-3' },\n * ], cn)}\n * />\n * ```\n */\nexport function RichTextEditor({\n initialValue,\n onChange,\n placeholder,\n onFocus,\n onBlur,\n onMentionQuery,\n mentionSearchDebounceMs = 300,\n disableDefaultMentionSearch = false,\n renderMentionSuggestion,\n mentionSuggestionOptions,\n classNames: classNamesProp,\n editorRef,\n editable = true,\n ...divProps\n}: RichTextEditorProps) {\n // Merge provided classNames with defaults.\n // Memoized so that inline object literals passed as `classNames` prop don't\n // produce a new object on every render — which would otherwise cascade into\n // extensions and useEditor recreating infinitely.\n const cn = useMemo(\n () => generateClassNames([defaultEditorClassNames, classNamesProp]),\n // We compare the *serialised* form of classNamesProp so that structurally\n // identical objects (common with inline literals) are treated as equal.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(classNamesProp)],\n )\n\n // Create a stable debounced search function that recreates when delay changes\n const debouncedSearch = useMemo(\n () => createDebouncedSearch(mentionSearchDebounceMs),\n [mentionSearchDebounceMs],\n )\n\n // Resolve the mention query function:\n // 1. Consumer-provided → use as-is\n // 2. Default search disabled → return empty\n // 3. Otherwise → use debounced Bluesky public API search\n const mentionQuery = useMemo<(q: string) => Promise<MentionSuggestion[]>>(() => {\n if (onMentionQuery) return onMentionQuery\n if (disableDefaultMentionSearch) return () => Promise.resolve([])\n return debouncedSearch\n }, [onMentionQuery, disableDefaultMentionSearch, debouncedSearch])\n\n // Stable values extracted from the memoized cn object.\n // Primitives (strings) are compared by value in useMemo deps, so they won't\n // cause spurious extension re-creations even if the cn object reference changes.\n const linkClass = cn.link ?? 'autolink'\n const mentionClass = cn.mention\n const suggestionClassNames = cn.suggestion\n // Serialise the nested suggestion object so it can be used as a stable dep.\n const suggestionClassNamesKey = JSON.stringify(suggestionClassNames)\n\n const extensions = useMemo(\n () => [\n Document,\n Paragraph,\n Text,\n History,\n HardBreak,\n // Configure link decorator with the resolved link class\n BskyLinkDecorator.configure({ linkClass }),\n Placeholder.configure({ placeholder: placeholder ?? '' }),\n createBskyMentionExtension({\n onMentionQuery: mentionQuery,\n ...(mentionClass !== undefined ? { mentionClass } : {}),\n // Only include optional fields when defined (exactOptionalPropertyTypes)\n ...(renderMentionSuggestion !== undefined\n ? { renderSuggestionList: renderMentionSuggestion }\n : {}),\n // Merge suggestion classNames into the default renderer options\n ...(mentionSuggestionOptions !== undefined || suggestionClassNames !== undefined\n ? {\n defaultRendererOptions: {\n ...(mentionSuggestionOptions ?? {}),\n ...(suggestionClassNames !== undefined ? { classNames: suggestionClassNames } : {}),\n },\n }\n : {}),\n }),\n ],\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n mentionQuery,\n placeholder,\n renderMentionSuggestion,\n mentionSuggestionOptions,\n linkClass,\n mentionClass,\n suggestionClassNamesKey,\n ],\n )\n\n const editor = useEditor(\n {\n extensions,\n editable,\n content: toInitialHTML(initialValue),\n\n /**\n * Disable immediate rendering to prevent SSR/hydration issues in Next.js,\n * Remix, and other server-rendering frameworks. The editor defers rendering\n * until after the component mounts on the client.\n * @see https://github.com/ueberdosis/tiptap/issues/5856\n */\n immediatelyRender: false,\n\n /**\n * Clipboard text serialisation: use '\\n' as the block separator so\n * multi-paragraph content is copied as plain newline-delimited text.\n * Matches the Bluesky reference's `coreExtensionOptions.clipboardTextSerializer`.\n */\n coreExtensionOptions: {\n clipboardTextSerializer: {\n blockSeparator: '\\n',\n },\n },\n\n editorProps: {\n /**\n * Paste handler: strip HTML formatting and paste as plain text.\n * Matches `handlePaste` in the Bluesky reference.\n */\n handlePaste(view, event) {\n const clipboardData = event.clipboardData\n if (!clipboardData) return false\n\n if (clipboardData.types.includes('text/html')) {\n const plainText = clipboardData.getData('text/plain')\n view.pasteText(plainText)\n return true\n }\n\n return false\n },\n },\n\n onFocus() {\n onFocus?.()\n },\n onBlur() {\n onBlur?.()\n },\n\n /**\n * On every document change:\n * 1. Extract plain text from the ProseMirror JSON tree (handles mention nodes)\n * 2. Use @atproto/api's `detectFacetsWithoutResolution()` to populate facets\n * 3. Emit the result as a `RichTextRecord`\n *\n * Mirrors the Bluesky reference's `onUpdate` handler.\n */\n onUpdate({ editor: ed }) {\n if (!onChange) return\n\n const json = ed.getJSON()\n const text = editorJsonToText(json)\n\n // Detect facets (mentions as handles, not DIDs — resolve server-side)\n const rt = new AtpRichText({ text })\n rt.detectFacetsWithoutResolution()\n\n // Cast via unknown: atproto's internal facet type has an extra index\n // signature but is structurally identical to our public Facet type.\n const record: RichTextRecord = {\n text: rt.text,\n ...(rt.facets?.length ? { facets: rt.facets as unknown as Facet[] } : {}),\n }\n\n onChange(record)\n },\n },\n // Only recreate the editor when extensions change (e.g. placeholder update)\n [extensions],\n )\n\n // Sync `editable` prop changes reactively after mount\n useEffect(() => {\n if (editor && editor.isEditable !== editable) {\n editor.setEditable(editable)\n }\n }, [editor, editable])\n\n // Expose imperative API\n useImperativeHandle(\n editorRef,\n () => ({\n focus() {\n editor?.commands.focus()\n },\n blur() {\n editor?.commands.blur()\n },\n clear() {\n editor?.commands.clearContent(true)\n },\n getText() {\n if (!editor) return ''\n return editorJsonToText(editor.getJSON())\n },\n }),\n [editor],\n )\n\n return (\n <div className={cn.root} {...divProps}>\n <EditorContent editor={editor} className={cn.content} />\n </div>\n )\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -313,7 +313,7 @@ declare function RichTextDisplay({ value, renderMention, renderLink, renderTag,
|
|
|
313
313
|
* createSuggestionRenderer
|
|
314
314
|
*
|
|
315
315
|
* Factory that returns a TipTap `SuggestionOptions['render']` function.
|
|
316
|
-
* It uses `
|
|
316
|
+
* It uses `@floating-ui/dom` for cursor-anchored positioning and `ReactRenderer`
|
|
317
317
|
* to mount the `MentionSuggestionList` React component into the popup.
|
|
318
318
|
*
|
|
319
319
|
* Heavily inspired by Bluesky's social-app Autocomplete.tsx and the
|
|
@@ -349,7 +349,7 @@ interface DefaultSuggestionRendererOptions {
|
|
|
349
349
|
* (i.e. each time the user types "@" and a popup should open/update/close).
|
|
350
350
|
*
|
|
351
351
|
* It follows the same lifecycle pattern as the Bluesky reference:
|
|
352
|
-
* - `onStart` → Mount ReactRenderer, create
|
|
352
|
+
* - `onStart` → Mount ReactRenderer, create floating-ui popup
|
|
353
353
|
* - `onUpdate` → Update props, reposition popup
|
|
354
354
|
* - `onKeyDown`→ Delegate to the MentionSuggestionList imperative ref
|
|
355
355
|
* - `onExit` → Destroy popup and React renderer
|
|
@@ -445,7 +445,7 @@ interface RichTextEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onCh
|
|
|
445
445
|
disableDefaultMentionSearch?: boolean;
|
|
446
446
|
/**
|
|
447
447
|
* Custom TipTap `suggestion.render` factory.
|
|
448
|
-
* When provided, replaces the default
|
|
448
|
+
* When provided, replaces the default @floating-ui/dom + MentionSuggestionList renderer.
|
|
449
449
|
* The factory must return `{ onStart, onUpdate, onKeyDown, onExit }`.
|
|
450
450
|
*
|
|
451
451
|
* See: https://tiptap.dev/docs/editor/extensions/nodes/mention#usage
|
package/dist/index.d.ts
CHANGED
|
@@ -313,7 +313,7 @@ declare function RichTextDisplay({ value, renderMention, renderLink, renderTag,
|
|
|
313
313
|
* createSuggestionRenderer
|
|
314
314
|
*
|
|
315
315
|
* Factory that returns a TipTap `SuggestionOptions['render']` function.
|
|
316
|
-
* It uses `
|
|
316
|
+
* It uses `@floating-ui/dom` for cursor-anchored positioning and `ReactRenderer`
|
|
317
317
|
* to mount the `MentionSuggestionList` React component into the popup.
|
|
318
318
|
*
|
|
319
319
|
* Heavily inspired by Bluesky's social-app Autocomplete.tsx and the
|
|
@@ -349,7 +349,7 @@ interface DefaultSuggestionRendererOptions {
|
|
|
349
349
|
* (i.e. each time the user types "@" and a popup should open/update/close).
|
|
350
350
|
*
|
|
351
351
|
* It follows the same lifecycle pattern as the Bluesky reference:
|
|
352
|
-
* - `onStart` → Mount ReactRenderer, create
|
|
352
|
+
* - `onStart` → Mount ReactRenderer, create floating-ui popup
|
|
353
353
|
* - `onUpdate` → Update props, reposition popup
|
|
354
354
|
* - `onKeyDown`→ Delegate to the MentionSuggestionList imperative ref
|
|
355
355
|
* - `onExit` → Destroy popup and React renderer
|
|
@@ -445,7 +445,7 @@ interface RichTextEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onCh
|
|
|
445
445
|
disableDefaultMentionSearch?: boolean;
|
|
446
446
|
/**
|
|
447
447
|
* Custom TipTap `suggestion.render` factory.
|
|
448
|
-
* When provided, replaces the default
|
|
448
|
+
* When provided, replaces the default @floating-ui/dom + MentionSuggestionList renderer.
|
|
449
449
|
* The factory must return `{ onStart, onUpdate, onKeyDown, onExit }`.
|
|
450
450
|
*
|
|
451
451
|
* See: https://tiptap.dev/docs/editor/extensions/nodes/mention#usage
|