{"version":3,"sources":["../../src/utils/social-platforms.ts"],"names":[],"mappings":";;;AA+CO,IAAM,uBAAA,GAAkD;AAAA,EAC7D,CAAA,EAAG,SAAA;AAAA,EACH,IAAA,EAAM,OAAA;AAAA,EACN,GAAA,EAAK,SAAA;AAAA,EACL,GAAA,EAAK,SAAA;AAAA,EACL,EAAA,EAAI,SAAA;AAAA,EACJ,EAAA,EAAI,WAAA;AAAA,EACJ,EAAA,EAAI,UAAA;AAAA,EACJ,OAAA,EAAS;AACX;AAOO,SAAS,wBAAwB,QAAA,EAAoD;AAC1F,EAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,EAAA,MAAM,GAAA,GAAM,QAAA,CAAS,WAAA,EAAY,CAAE,IAAA,EAAK;AACxC,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,OAAO,uBAAA,CAAwB,GAAG,CAAA,IAAK,GAAA;AACzC;AAMO,SAAS,WAAA,CAAY,UAAqC,MAAA,EAAyB;AACxF,EAAA,IAAI,CAAC,UAAU,OAAO,KAAA;AACtB,EAAA,MAAM,OAAO,QAAA,CAAS,WAAA,EAAY,CAAE,OAAA,CAAQ,UAAU,EAAE,CAAA;AACxD,EAAA,MAAM,OAAO,MAAA,CAAO,WAAA,EAAY,CAAE,OAAA,CAAQ,UAAU,EAAE,CAAA;AACtD,EAAA,OAAO,SAAS,IAAA,IAAQ,IAAA,CAAK,QAAA,CAAS,CAAA,CAAA,EAAI,IAAI,CAAA,CAAE,CAAA;AAClD;AAWO,IAAM,yBAAA,GAA+D;AAAA,EAC1E,OAAA,EAAS,CAAC,OAAO,CAAA;AAAA,EACjB,OAAA,EAAS,CAAC,UAAU,CAAA;AAAA,EACpB,OAAA,EAAS,CAAC,YAAY,CAAA;AAAA,EACtB,QAAA,EAAU,CAAC,cAAc,CAAA;AAAA,EACzB,QAAA,EAAU,CAAC,QAAQ;AACrB;AAGA,SAAS,OAAO,KAAA,EAAiD;AAC/D,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,GAAA,CAAI,KAAA,CAAM,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,CAAA,QAAA,EAAW,KAAK,CAAA,CAAE,CAAA,CAAE,QAAA;AAAA,EACrE,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAGO,SAAS,oBAAoB,QAAA,EAA8C;AAChF,EAAA,MAAM,UAAU,MAAA,CAAO,QAAA,CAAS,UAAU,CAAA,IAAK,MAAA,CAAO,SAAS,OAAO,CAAA;AACtE,EAAA,OAAO,CAAC,GAAI,OAAA,GAAU,CAAC,OAAO,CAAA,GAAI,EAAC,EAAI,GAAI,yBAAA,CAA0B,QAAA,CAAS,IAAI,CAAA,IAAK,EAAG,CAAA;AAC5F;AAUO,SAAS,kBAAA,CACd,KACA,SAAA,EACe;AACf,EAAA,MAAM,QAAA,GAAW,OAAO,GAAG,CAAA;AAC3B,EAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,EAAA,KAAA,MAAW,YAAY,SAAA,EAAW;AAChC,IAAA,IAAI,mBAAA,CAAoB,QAAQ,CAAA,CAAE,IAAA,CAAK,YAAU,WAAA,CAAY,QAAA,EAAU,MAAM,CAAC,CAAA,EAAG;AAC/E,MAAA,OAAO,QAAA,CAAS,IAAA;AAAA,IAClB;AAAA,EACF;AACA,EAAA,OAAO,SAAA;AACT;AAoBO,SAAS,cAAA,CACd,OACA,QAAA,EACe;AACf,EAAA,MAAM,MAAA,GAAS,wBAAwB,QAAQ,CAAA;AAC/C,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,KAAA,EAAO,OAAO,MAAA;AAC9B,EAAA,OAAO,MAAM,IAAA,CAAK,CAAA,IAAA,KAAQ,wBAAwB,IAAA,CAAK,QAAQ,MAAM,MAAM,CAAA;AAC7E","file":"social-platforms.cjs","sourcesContent":["/**\n * Social platform helpers — normalization, host classification, the shared link\n * type, and the \"find the link for a platform\" picker.\n *\n * **This module does NOT own the list of platforms.** That list lives in the\n * `social_platforms` table and only there: it is admin-editable, it carries\n * `display_name`, `icon_name`, `base_url`, `url_pattern`, `category` and\n * `sort_order`, and the hub already reads it everywhere\n * (`lib/data/social-platforms-utils.ts`, `useSocialPlatforms`). A hardcoded\n * tuple here would be a second vocabulary that drifts the first time somebody\n * adds a row — and, being a compile-time union, would reject that row outright.\n *\n * So a platform is a `string` at every boundary. What code owns is the two\n * things a table cannot hold:\n *\n *   - the ICON COMPONENTS (`SOCIAL_ICON_COMPONENTS` in `social-icon-row`),\n *     which the DB names through `icon_name`;\n *   - the incoming SPELLINGS below, which are not platforms but ways external\n *     data refers to them (`x` for twitter, `fb` for facebook).\n *\n * JSX-free leaf with its own `exports` subpath, so `server-only` DALs and hub\n * scripts can import the type and the classifier without pulling in React.\n */\n\n/**\n * One row of `social_platforms`, in the shape callers pass around.\n *\n * Deliberately a narrow READ view: the columns that decide presentation and\n * URL-building, not the OAuth or media-config columns, which have their own\n * consumers and no business here.\n */\nexport interface SocialPlatformDefinition {\n  /** `social_platforms.name` — the canonical key, lowercase. */\n  name: string;\n  /** `social_platforms.base_url`, e.g. `https://twitter.com/`. */\n  baseUrl?: string | null;\n  /** `social_platforms.url_pattern`, e.g. `https://twitter.com/{username}`. */\n  urlPattern?: string | null;\n}\n\n/**\n * Accepted SPELLINGS that resolve onto a platform name.\n *\n * Not a vocabulary — every value here must be a `social_platforms.name`. These\n * exist because external data says `x`, `fb` or `generic` where the table says\n * `twitter`, `facebook` and `website`.\n */\nexport const SOCIAL_PLATFORM_ALIASES: Record<string, string> = {\n  x: 'twitter',\n  mail: 'email',\n  web: 'website',\n  url: 'website',\n  yt: 'youtube',\n  ig: 'instagram',\n  fb: 'facebook',\n  generic: 'website',\n};\n\n/**\n * Lower-case, trim, and resolve a known alias. Returns `null` only for a blank\n * input — an unrecognised name is passed THROUGH, because this module is not\n * the authority on which platforms exist.\n */\nexport function normalizeSocialPlatform(platform: string | null | undefined): string | null {\n  if (!platform) return null;\n  const key = platform.toLowerCase().trim();\n  if (!key) return null;\n  return SOCIAL_PLATFORM_ALIASES[key] ?? key;\n}\n\n/**\n * THE hostname test: an exact host match or a subdomain of it.\n * (`hostMatches('www.github.com', 'github.com')` is true; `notgithub.com` is not.)\n */\nexport function hostMatches(hostname: string | null | undefined, domain: string): boolean {\n  if (!hostname) return false;\n  const host = hostname.toLowerCase().replace(/^www\\./, '');\n  const bare = domain.toLowerCase().replace(/^www\\./, '');\n  return host === bare || host.endsWith(`.${bare}`);\n}\n\n/**\n * Alternate domains a platform's own `url_pattern` cannot express — a rename\n * (`x.com`), a share domain (`youtu.be`), an invite domain (`discord.gg`).\n *\n * The ONLY hardcoded host knowledge left, and it is additive: every primary\n * host is derived from the row. Keyed by `social_platforms.name`, so a row that\n * does not exist contributes nothing. If this grows, the answer is an\n * `alt_hosts` column on the table, not a longer map here.\n */\nexport const SOCIAL_PLATFORM_ALT_HOSTS: Record<string, readonly string[]> = {\n  twitter: ['x.com'],\n  youtube: ['youtu.be'],\n  discord: ['discord.gg'],\n  telegram: ['telegram.org'],\n  facebook: ['fb.com'],\n};\n\n/** The host a `base_url`/`url_pattern` points at, or null when it names none. */\nfunction hostOf(value: string | null | undefined): string | null {\n  if (!value) return null;\n  try {\n    return new URL(value.includes('://') ? value : `https://${value}`).hostname;\n  } catch {\n    return null;\n  }\n}\n\n/** Every domain that identifies a platform: its row's host plus any alternates. */\nexport function socialPlatformHosts(platform: SocialPlatformDefinition): string[] {\n  const primary = hostOf(platform.urlPattern) ?? hostOf(platform.baseUrl);\n  return [...(primary ? [primary] : []), ...(SOCIAL_PLATFORM_ALT_HOSTS[platform.name] ?? [])];\n}\n\n/**\n * Classify a URL by its host against the platforms the DATABASE defines.\n *\n * `platforms` is the caller's loaded `social_platforms` rows — passing them in\n * is what keeps this function pure and this module free of a platform list.\n * Returns `'website'` for a URL that parses but matches no row (the table's own\n * catch-all row), and `null` for something that is not a URL at all.\n */\nexport function classifySocialHost(\n  url: string | null | undefined,\n  platforms: readonly SocialPlatformDefinition[],\n): string | null {\n  const hostname = hostOf(url);\n  if (!hostname) return null;\n  for (const platform of platforms) {\n    if (socialPlatformHosts(platform).some(domain => hostMatches(hostname, domain))) {\n      return platform.name;\n    }\n  }\n  return 'website';\n}\n\n/**\n * A rendered social link. Exactly ONE of `href` (anchor, target _blank) or\n * `onClick` (share popup / copy-to-clipboard) — the discriminated union makes a\n * dead no-action entry unrepresentable. `SocialIconRow` re-exports this type;\n * it lives here so `server-only` and script graphs can name it without React.\n */\nexport type SocialIconLink = {\n  platform: string;\n  label?: string;\n  /** `internal` links only render on surfaces that opt in (and must be gated server-side). */\n  visibility?: 'external' | 'internal';\n} & ({ href: string; onClick?: never } | { onClick: () => void; href?: never });\n\n/**\n * THE \"find the link for a platform\" predicate, case-insensitive and\n * alias-aware, over ANY shape that carries a `platform` field (the mapped\n * profile rows and the rendered icon links both qualify).\n */\nexport function pickSocialLink<T extends { platform: string }>(\n  links: readonly T[] | null | undefined,\n  platform: string,\n): T | undefined {\n  const target = normalizeSocialPlatform(platform);\n  if (!target || !links) return undefined;\n  return links.find(link => normalizeSocialPlatform(link.platform) === target);\n}\n"]}