···2323import { useHandlePaste } from "./useHandlePaste";
2424import { BlockProps } from "../Block";
2525import { useEntitySetContext } from "components/EntitySetProvider";
2626+import { didToBlueskyUrl, atUriToUrl } from "src/utils/mentionUtils";
26272728export function useMountProsemirror({
2829 props,
···8081 handleClickOn: (_view, _pos, node, _nodePos, _event, direct) => {
8182 if (!direct) return;
8283 if (node.nodeSize - 2 <= _pos) return;
8383- let mark =
8484- node
8585- .nodeAt(_pos - 1)
8686- ?.marks.find((f) => f.type === schema.marks.link) ||
8787- node
8888- .nodeAt(Math.max(_pos - 2, 0))
8989- ?.marks.find((f) => f.type === schema.marks.link);
9090- if (mark) {
9191- window.open(mark.attrs.href, "_blank");
8484+8585+ // Check for marks at the clicked position
8686+ const nodeAt1 = node.nodeAt(_pos - 1);
8787+ const nodeAt2 = node.nodeAt(Math.max(_pos - 2, 0));
8888+8989+ // Check for link marks
9090+ let linkMark = nodeAt1?.marks.find((f) => f.type === schema.marks.link) ||
9191+ nodeAt2?.marks.find((f) => f.type === schema.marks.link);
9292+ if (linkMark) {
9393+ window.open(linkMark.attrs.href, "_blank");
9494+ return;
9595+ }
9696+9797+ // Check for didMention marks
9898+ let didMentionMark = nodeAt1?.marks.find((f) => f.type === schema.marks.didMention) ||
9999+ nodeAt2?.marks.find((f) => f.type === schema.marks.didMention);
100100+ if (didMentionMark) {
101101+ window.open(didToBlueskyUrl(didMentionMark.attrs.did), "_blank", "noopener,noreferrer");
102102+ return;
103103+ }
104104+105105+ // Check for atMention marks
106106+ let atMentionMark = nodeAt1?.marks.find((f) => f.type === schema.marks.atMention) ||
107107+ nodeAt2?.marks.find((f) => f.type === schema.marks.atMention);
108108+ if (atMentionMark) {
109109+ const url = atUriToUrl(atMentionMark.attrs.atURI);
110110+ window.open(url, "_blank", "noopener,noreferrer");
111111+ return;
92112 }
93113 },
94114 dispatchTransaction,
+3
components/Blocks/TextBlock/schema.ts
···120120 },
121121 ],
122122 toDOM(node) {
123123+ // NOTE: This rendering should match the AtMentionLink component in
124124+ // components/AtMentionLink.tsx. If you update one, update the other.
125125+ // We can't use the React component here because ProseMirror expects DOM specs.
123126 let className = "atMention text-accent-contrast";
124127 let aturi = new AtUri(node.attrs.atURI);
125128 if (aturi.collection === "pub.leaflet.publication")
+59
src/utils/mentionUtils.ts
···11+import { AtUri } from "@atproto/api";
22+33+/**
44+ * Converts a DID to a Bluesky profile URL
55+ */
66+export function didToBlueskyUrl(did: string): string {
77+ return `https://bsky.app/profile/${did}`;
88+}
99+1010+/**
1111+ * Converts an AT URI (publication or document) to the appropriate URL
1212+ */
1313+export function atUriToUrl(atUri: string): string {
1414+ try {
1515+ const uri = new AtUri(atUri);
1616+1717+ if (uri.collection === "pub.leaflet.publication") {
1818+ // Publication URL: /lish/{did}/{rkey}
1919+ return `/lish/${uri.host}/${uri.rkey}`;
2020+ } else if (uri.collection === "pub.leaflet.document") {
2121+ // Document URL - we need to resolve this via the API
2222+ // For now, create a redirect route that will handle it
2323+ return `/lish/uri/${encodeURIComponent(atUri)}`;
2424+ }
2525+2626+ return "#";
2727+ } catch (e) {
2828+ console.error("Failed to parse AT URI:", atUri, e);
2929+ return "#";
3030+ }
3131+}
3232+3333+/**
3434+ * Opens a mention link in the appropriate way
3535+ * - DID mentions open in a new tab (external Bluesky)
3636+ * - Publication/document mentions navigate in the same tab
3737+ */
3838+export function handleMentionClick(
3939+ e: MouseEvent | React.MouseEvent,
4040+ type: "did" | "at-uri",
4141+ value: string
4242+) {
4343+ e.preventDefault();
4444+ e.stopPropagation();
4545+4646+ if (type === "did") {
4747+ // Open Bluesky profile in new tab
4848+ window.open(didToBlueskyUrl(value), "_blank", "noopener,noreferrer");
4949+ } else {
5050+ // Navigate to publication/document in same tab
5151+ const url = atUriToUrl(value);
5252+ if (url.startsWith("/lish/uri/")) {
5353+ // Redirect route - navigate to it
5454+ window.location.href = url;
5555+ } else {
5656+ window.location.href = url;
5757+ }
5858+ }
5959+}