import * as TID from "@atcute/tid";
import { A, useLocation } from "@solidjs/router";
import { createResource, createSignal, For, onMount, Show } from "solid-js";
import { getAllBacklinks, getRecordBacklinks, LinksWithRecords } from "../lib/api.js";
import { useRepo } from "../lib/repo-context.jsx";
import { localDateFromTimestamp } from "../utils/date.js";
import { Button } from "./button.jsx";
import { Favicon } from "./favicon.jsx";
import DidHoverCard from "./hover-card/did.jsx";
import RecordHoverCard from "./hover-card/record.jsx";
type BacklinksProps = {
target: string;
collection: string;
path: string;
repoDid: string;
};
type BacklinkEntry = {
collection: string;
path: string;
counts: { distinct_dids: number; records: number };
};
type CollectionGroup = {
collection: string;
entries: BacklinkEntry[];
totalRecords: number;
};
const flattenLinks = (links: Record): BacklinkEntry[] => {
const entries: BacklinkEntry[] = [];
Object.keys(links)
.toSorted()
.forEach((collection) => {
const paths = links[collection];
Object.keys(paths)
.toSorted()
.forEach((path) => {
if (paths[path].records > 0) {
entries.push({ collection, path, counts: paths[path] });
}
});
});
return entries;
};
const groupByCollection = (entries: BacklinkEntry[]): CollectionGroup[] => {
const map = new Map();
for (const entry of entries) {
const existing = map.get(entry.collection);
if (existing) {
existing.push(entry);
} else {
map.set(entry.collection, [entry]);
}
}
return Array.from(map.entries()).map(([collection, entries]) => ({
collection,
entries,
totalRecords: entries.reduce((sum, e) => sum + e.counts.records, 0),
}));
};
const BacklinkRecords = (props: BacklinksProps & { cursor?: string }) => {
const [links, setLinks] = createSignal();
const [more, setMore] = createSignal(false);
onMount(async () => {
const res = await getRecordBacklinks(props.target, props.collection, props.path, props.cursor);
setLinks(res);
});
return (
Loading…
}>
{({ did, collection, rkey }) => {
const uri = `at://${did}/${collection}/${rkey}`;
const rkeyIsRepo = rkey === props.repoDid;
const timestamp =
!rkeyIsRepo && TID.validate(rkey)
? localDateFromTimestamp(TID.parse(rkey).timestamp / 1000)
: null;
return (
{rkey}
e.stopPropagation()}
>
{did}
}
/>
{timestamp ?? ""}
}
/>
);
}}
}
>
);
};
const BacklinkDirectory = (props: { groups: CollectionGroup[]; pathname: string }) => {
return (
);
};
const BacklinkCollectionDetail = (props: {
target: string;
collection: string;
entries: BacklinkEntry[];
pathname: string;
repoDid: string;
}) => {
const authority = () => props.collection.split(".").slice(0, 2).join(".");
return (
{(entry) => (
{entry.path.slice(1)}
{entry.counts.records} from {entry.counts.distinct_dids} repo
{entry.counts.distinct_dids > 1 ? "s" : ""}
)}
);
};
const Backlinks = (props: { target: string }) => {
const repo = useRepo();
const location = useLocation();
const [response] = createResource(async () => {
const res = await getAllBacklinks(props.target);
return flattenLinks(res.links);
});
const groups = () => (response() ? groupByCollection(response()!) : []);
const selectedCollection = () => {
const hash = location.hash;
if (hash.startsWith("#backlinks:")) {
return hash.slice("#backlinks:".length);
}
return null;
};
const selectedEntries = () => {
const col = selectedCollection();
if (!col || !response()) return [];
return response()!.filter((e) => e.collection === col);
};
return (
Failed to load backlinks.}>
Loading…}>
}
>
data from{" "}
microcosm
);
};
export { Backlinks };