← back to Open Seo

src/client/components/table/url.tsx

86 lines

import { ExternalLink } from "lucide-react";

export function formatUrlForDisplay(value: string): string {
  try {
    const url = new URL(value);
    const hash = url.hash.startsWith("#:~:") ? "" : url.hash;
    const cleaned = `${url.protocol}//${url.host}${url.pathname}${url.search}${hash}`;
    try {
      return decodeURI(cleaned);
    } catch {
      return cleaned;
    }
  } catch {
    return value;
  }
}

export function resolveUrlHref(
  value: string | null | undefined,
  baseDomain?: string,
): string | null {
  if (!value) return null;
  if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value)) {
    return getSafeExternalUrl(value);
  }
  if (!baseDomain) return null;
  return getSafeExternalUrl(
    `https://${baseDomain}${value.startsWith("/") ? value : `/${value}`}`,
  );
}

export function ExternalUrlCell({
  value,
  label,
  baseDomain,
  className = "link link-primary inline-flex items-center gap-1",
  display = "formatted",
  empty = "-",
}: {
  value: string | null | undefined;
  label?: string | null;
  baseDomain?: string;
  className?: string;
  display?: "formatted" | "path" | "raw";
  empty?: string;
}) {
  const href = resolveUrlHref(value, baseDomain);
  if (!value || !href) {
    return <span className="text-base-content/40">{empty}</span>;
  }

  const visibleLabel = label ?? getUrlDisplayLabel(value, display);
  return (
    <a className={className} href={href} target="_blank" rel="noreferrer">
      <span className="truncate">{visibleLabel}</span>
      <ExternalLink className="size-3 shrink-0" />
    </a>
  );
}

function getUrlDisplayLabel(
  value: string,
  display: "formatted" | "path" | "raw",
) {
  if (display === "raw") return value;
  if (display === "path") {
    try {
      return new URL(value).pathname;
    } catch {
      return value;
    }
  }
  return formatUrlForDisplay(value);
}

export function getSafeExternalUrl(value: string) {
  try {
    const parsed = new URL(value);
    return parsed.protocol === "http:" || parsed.protocol === "https:"
      ? parsed.toString()
      : null;
  } catch {
    return null;
  }
}