Skip to content

Commit d5cfbbd

Browse files
feat(details): add links to internal refs by hovering over hashes
1 parent 2044729 commit d5cfbbd

File tree

5 files changed

+102
-5
lines changed

5 files changed

+102
-5
lines changed

src/lib/components/renderers/LinkRenderer.svelte

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<script lang="ts">
2+
import type { Snippet } from "svelte";
23
import type { HTMLAnchorAttributes } from "svelte/elements";
34
import { dev } from "$app/environment";
45
56
type Props = {
67
attributes: HTMLAnchorAttributes;
8+
linkChildren?: Snippet<[Snippet]>;
79
};
810
9-
let { attributes }: Props = $props();
11+
let { attributes, linkChildren }: Props = $props();
1012
let { children, ...rest } = $derived(attributes);
1113
let { href } = $derived(rest);
1214
@@ -44,9 +46,16 @@
4446

4547
<!-- Renders -->
4648
{#snippet link()}
47-
<a {...rest}>
48-
{@render children?.()}
49-
</a>
49+
{#snippet original()}
50+
<a {...rest}>
51+
{@render children?.()}
52+
</a>
53+
{/snippet}
54+
{#if linkChildren}
55+
{@render linkChildren(original)}
56+
{:else}
57+
{@render original()}
58+
{/if}
5059
{/snippet}
5160

5261
{#snippet image(alt?: string)}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script lang="ts">
2+
import { LinkPreview as HoverCardPrimitive } from "bits-ui";
3+
import { cn } from "$lib/utils.js";
4+
5+
let {
6+
ref = $bindable(null),
7+
class: className,
8+
align = "center",
9+
sideOffset = 4,
10+
portalProps,
11+
...restProps
12+
}: HoverCardPrimitive.ContentProps & {
13+
portalProps?: HoverCardPrimitive.PortalProps;
14+
} = $props();
15+
</script>
16+
17+
<HoverCardPrimitive.Portal {...portalProps}>
18+
<HoverCardPrimitive.Content
19+
bind:ref
20+
data-slot="hover-card-content"
21+
{align}
22+
{sideOffset}
23+
class={cn(
24+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-hidden z-50 mt-3 w-64 rounded-md border p-4 shadow-md outline-none",
25+
className
26+
)}
27+
{...restProps}
28+
/>
29+
</HoverCardPrimitive.Portal>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script lang="ts">
2+
import { LinkPreview as HoverCardPrimitive } from "bits-ui";
3+
4+
let { ref = $bindable(null), ...restProps }: HoverCardPrimitive.TriggerProps = $props();
5+
</script>
6+
7+
<HoverCardPrimitive.Trigger bind:ref data-slot="hover-card-trigger" {...restProps} />
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { LinkPreview as HoverCardPrimitive } from "bits-ui";
2+
import Content from "./hover-card-content.svelte";
3+
import Trigger from "./hover-card-trigger.svelte";
4+
5+
const Root = HoverCardPrimitive.Root;
6+
7+
export {
8+
Root,
9+
Content,
10+
Trigger,
11+
Root as HoverCard,
12+
Content as HoverCardContent,
13+
Trigger as HoverCardTrigger,
14+
};

src/routes/[pid=pid]/[org]/[repo]/[id=number]/PageRenderer.svelte

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import {
4242
ArrowUpRight,
4343
ChevronLeft,
44+
ChevronRight,
4445
FileDiff,
4546
GitCommitVertical,
4647
GitMerge,
@@ -66,6 +67,7 @@
6667
import * as Avatar from "$lib/components/ui/avatar";
6768
import { Badge } from "$lib/components/ui/badge";
6869
import { Button } from "$lib/components/ui/button";
70+
import * as HoverCard from "$lib/components/ui/hover-card";
6971
import { Separator } from "$lib/components/ui/separator";
7072
import * as Tooltip from "$lib/components/ui/tooltip";
7173
import AnimatedButton from "$lib/components/AnimatedButton.svelte";
@@ -490,7 +492,43 @@
490492
{@render headingRenderer("h6", props)}
491493
{/snippet}
492494
{#snippet a(props)}
493-
<LinkRenderer attributes={props} />
495+
{@const { href, children, ...rest } = props}
496+
<LinkRenderer attributes={props}>
497+
{#snippet linkChildren(original)}
498+
{@const match = (href ?? "").match(
499+
/^https:\/\/github.com\/(\S+)\/(\S+)\/(\S+)\/(\d+)(#[a-z]+-\d+)?$/
500+
)}
501+
{#if href && match}
502+
{@const [, org, repo, pid, id] = [...match]}
503+
<HoverCard.Root openDelay={300}>
504+
<HoverCard.Content class="flex w-fit items-center justify-center px-6">
505+
<Button
506+
variant="link"
507+
href={resolve("/[pid=pid]/[org]/[repo]/[id=number]", {
508+
pid: pid ?? "",
509+
org: org ?? "",
510+
repo: repo ?? "",
511+
id: id ?? ""
512+
})}
513+
class="group h-auto p-0! text-base"
514+
>
515+
Open in Svelte Changelog
516+
<ChevronRight class="transition-transform group-hover:translate-x-1" />
517+
</Button>
518+
</HoverCard.Content>
519+
<HoverCard.Trigger>
520+
{#snippet child({ props: cardProps })}
521+
<a {...cardProps} {...rest} {href}>
522+
{@render children?.()}
523+
</a>
524+
{/snippet}
525+
</HoverCard.Trigger>
526+
</HoverCard.Root>
527+
{:else}
528+
{@render original()}
529+
{/if}
530+
{/snippet}
531+
</LinkRenderer>
494532
{/snippet}
495533
</MarkdownRenderer>
496534
{#if "reactions" in info}

0 commit comments

Comments
 (0)