From b760ed727c4e121e57bd85776a7467381cc0e162 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 02:59:07 +0000 Subject: [PATCH] Hide in-explorer-redundant CTAs, add copy-link chip everywhere in /explore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes: * RecordPreview gets a hideExplorerCtas prop that suppresses the \"View Full Record\" JSON-modal button and the \"Open in Explorer — backlinks, identity, raw JSON →\" cross-link. RichRecordPreview passes it: inside the record explorer the linkified raw JSON is already rendered inline immediately below, and the page itself IS the Explorer destination, so both CTAs were noise. Universal link pages keep them. * New ShareLinkChip — small \"Copy link\" button rendered at the end of every explore page. Repos and records copy their universal link (aturi.to/ and aturi.to///), so the pasted URL drops into any compatible Atmosphere client via the WaypointPicker. Collection pages copy the explore URL (no universal route exists for them). PDS pages copy aturi.to/explore/pds/. The chip lives in the Breadcrumb's right-side gap for repo/collection/record pages and inside the PDS card header for the PDS page. --- src/components/RecordPreview.tsx | 85 +++++++++++------- src/components/explore/Breadcrumb.tsx | 21 ++++- src/components/explore/CollectionExplorer.tsx | 2 + src/components/explore/PdsExplorer.tsx | 30 +++++-- src/components/explore/RecordExplorer.tsx | 3 + src/components/explore/RepoExplorer.tsx | 9 +- src/components/explore/RichRecordPreview.tsx | 10 ++- src/components/explore/ShareLinkChip.tsx | 89 +++++++++++++++++++ 8 files changed, 204 insertions(+), 45 deletions(-) create mode 100644 src/components/explore/ShareLinkChip.tsx diff --git a/src/components/RecordPreview.tsx b/src/components/RecordPreview.tsx index 729425c..9858d6c 100644 --- a/src/components/RecordPreview.tsx +++ b/src/components/RecordPreview.tsx @@ -18,9 +18,22 @@ type RecordPreviewProps = { collection: string; handle: string; rkey: string; + /** + * When true, suppress the "View Full Record" JSON-modal button and the + * "Open in Explorer" cross-link. Used inside the explorer's record view, + * which already shows the raw (linkified) JSON inline and is itself the + * Explorer destination. + */ + hideExplorerCtas?: boolean; }; -export default function RecordPreview({ record, collection, handle, rkey }: RecordPreviewProps) { +export default function RecordPreview({ + record, + collection, + handle, + rkey, + hideExplorerCtas, +}: RecordPreviewProps) { const { value, cid } = record; const [showJsonModal, setShowJsonModal] = useState(false); @@ -183,39 +196,44 @@ export default function RecordPreview({ record, collection, handle, rkey }: Reco ))} - {/* View Full Record Button */} - + {/* View Full Record Button — hidden when used inside the explorer, + which renders the linkified raw JSON inline immediately below. */} + {!hideExplorerCtas && ( + + )} - {/* Cross-product: jump into the same record inside the explorer */} + {/* Cross-product: jump into the same record inside the explorer — + suppressed when we're already on an explorer page. */} + {!hideExplorerCtas && ( Open in Explorer — backlinks, identity, raw JSON → + )} {/* Footer: CID */} diff --git a/src/components/explore/Breadcrumb.tsx b/src/components/explore/Breadcrumb.tsx index bfc912d..6767633 100644 --- a/src/components/explore/Breadcrumb.tsx +++ b/src/components/explore/Breadcrumb.tsx @@ -4,6 +4,7 @@ import Link from 'next/link'; import { ChevronRight, Server } from 'lucide-react'; import { encodeRepo } from '@/utils/atproto/urls'; import { pdsHostname } from '@/utils/atproto/pdsServer'; +import ShareLinkChip from './ShareLinkChip'; type Props = { handle: string | null; @@ -12,6 +13,11 @@ type Props = { pds?: string; collection?: string; rkey?: string; + /** + * When provided, renders a "Copy link" chip at the end of the breadcrumb + * row. Path or full URL; bare paths get aturi.to prepended. + */ + shareUrl?: string; }; /** @@ -22,7 +28,14 @@ type Props = { * Each upstream segment is clickable. The PDS segment is optional so older * call sites without a resolved PDS still render correctly. */ -export default function Breadcrumb({ handle, did, pds, collection, rkey }: Props) { +export default function Breadcrumb({ + handle, + did, + pds, + collection, + rkey, + shareUrl, +}: Props) { const repoSegment = encodeRepo(handle || did); const repoLabel = handle ? `@${handle}` : did; const pdsHost = pds ? pdsHostname(pds) : null; @@ -112,6 +125,12 @@ export default function Breadcrumb({ handle, did, pds, collection, rkey }: Props {rkey} )} + + {shareUrl && ( + + + + )} ); } diff --git a/src/components/explore/CollectionExplorer.tsx b/src/components/explore/CollectionExplorer.tsx index 25986a2..7502ee2 100644 --- a/src/components/explore/CollectionExplorer.tsx +++ b/src/components/explore/CollectionExplorer.tsx @@ -132,6 +132,8 @@ function CollectionList({ did={identity.did} pds={identity.pds} collection={collection} + // No universal link route for collections — share the explorer URL. + shareUrl={`/explore/${repoSeg}/${collection}`} /> diff --git a/src/components/explore/PdsExplorer.tsx b/src/components/explore/PdsExplorer.tsx index 4018c9e..bbe5734 100644 --- a/src/components/explore/PdsExplorer.tsx +++ b/src/components/explore/PdsExplorer.tsx @@ -14,6 +14,7 @@ import { describeRepo } from '@/utils/atproto/pdsClient'; import { encodeRepo, shortDid } from '@/utils/atproto/urls'; import AppearIn from './AppearIn'; import CopyButton from './CopyButton'; +import ShareLinkChip from './ShareLinkChip'; type Props = { host: string; @@ -190,18 +191,29 @@ function PdsHeader({ >
- - Personal Data Server +
+ + Personal Data Server +
+

diff --git a/src/components/explore/RepoExplorer.tsx b/src/components/explore/RepoExplorer.tsx index 292f864..902202c 100644 --- a/src/components/explore/RepoExplorer.tsx +++ b/src/components/explore/RepoExplorer.tsx @@ -70,7 +70,14 @@ export default function RepoExplorer({ repo }: { repo: string }) { on this view — it's the only \"drill up\" affordance at the top of the explorer hierarchy. */} - + diff --git a/src/components/explore/RichRecordPreview.tsx b/src/components/explore/RichRecordPreview.tsx index d1fd9b7..b056b2c 100644 --- a/src/components/explore/RichRecordPreview.tsx +++ b/src/components/explore/RichRecordPreview.tsx @@ -105,5 +105,13 @@ export default function RichRecordPreview({ return ; } - return ; + return ( + + ); } diff --git a/src/components/explore/ShareLinkChip.tsx b/src/components/explore/ShareLinkChip.tsx new file mode 100644 index 0000000..81ee2de --- /dev/null +++ b/src/components/explore/ShareLinkChip.tsx @@ -0,0 +1,89 @@ +'use client'; + +import { useState } from 'react'; +import { Check, Link2 } from 'lucide-react'; + +type Props = { + /** Path or full URL — `/`-prefixed paths get the production origin prepended. */ + url: string; + /** Override the button text. Defaults to "Copy link". */ + label?: string; + /** Visual hint string for the tooltip / aria-label. Defaults to the resolved URL. */ + title?: string; +}; + +const PRODUCTION_ORIGIN = 'https://aturi.to'; + +async function writeToClipboard(text: string): Promise { + try { + await navigator.clipboard.writeText(text); + return; + } catch { + /* fall through */ + } + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.select(); + try { + document.execCommand('copy'); + } finally { + document.body.removeChild(ta); + } +} + +/** + * Compact "copy this page's link" button used throughout the explorer. + * Accepts a path (gets prefixed with aturi.to) or a full URL. + */ +export default function ShareLinkChip({ url, label = 'Copy link', title }: Props) { + const [copied, setCopied] = useState(false); + + const resolvedUrl = url.startsWith('http') + ? url + : `${PRODUCTION_ORIGIN}${url.startsWith('/') ? url : `/${url}`}`; + + async function onClick() { + await writeToClipboard(resolvedUrl); + setCopied(true); + window.setTimeout(() => setCopied(false), 1500); + } + + return ( + + ); +} -- 2.51.2