+31
-14
app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx
+31
-14
app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx
···
170
170
171
171
const handleMentionSelect = useCallback(
172
172
(mention: Mention) => {
173
-
if (mention.type !== "did") return;
174
173
if (!viewRef.current || mentionInsertPos === null) return;
175
174
const view = viewRef.current;
176
175
const from = mentionInsertPos - 1;
···
180
179
// Delete the @ symbol
181
180
tr.delete(from, to);
182
181
183
-
// Insert @handle
184
-
const mentionText = "@" + mention.handle;
185
-
tr.insertText(mentionText, from);
186
-
187
-
// Apply mention mark
188
-
tr.addMark(
189
-
from,
190
-
from + mentionText.length,
191
-
bskyPostSchema.marks.mention.create({ did: mention.did }),
192
-
);
193
-
194
-
// Add a space after the mention
195
-
tr.insertText(" ", from + mentionText.length);
182
+
if (mention.type === "did") {
183
+
// Insert @handle with mention mark
184
+
const mentionText = "@" + mention.handle;
185
+
tr.insertText(mentionText, from);
186
+
tr.addMark(
187
+
from,
188
+
from + mentionText.length,
189
+
bskyPostSchema.marks.mention.create({ did: mention.did }),
190
+
);
191
+
tr.insertText(" ", from + mentionText.length);
192
+
} else if (mention.type === "publication") {
193
+
// Insert publication name as a link
194
+
const linkText = mention.name;
195
+
tr.insertText(linkText, from);
196
+
tr.addMark(
197
+
from,
198
+
from + linkText.length,
199
+
bskyPostSchema.marks.link.create({ href: mention.url }),
200
+
);
201
+
tr.insertText(" ", from + linkText.length);
202
+
} else if (mention.type === "post") {
203
+
// Insert post title as a link
204
+
const linkText = mention.title;
205
+
tr.insertText(linkText, from);
206
+
tr.addMark(
207
+
from,
208
+
from + linkText.length,
209
+
bskyPostSchema.marks.link.create({ href: mention.url }),
210
+
);
211
+
tr.insertText(" ", from + linkText.length);
212
+
}
196
213
197
214
view.dispatch(tr);
198
215
view.focus();
+16
-5
app/api/rpc/[command]/search_publication_documents.ts
+16
-5
app/api/rpc/[command]/search_publication_documents.ts
···
1
+
import { AtUri } from "@atproto/api";
1
2
import { z } from "zod";
2
3
import { makeRoute } from "../lib";
3
4
import type { Env } from "./route";
5
+
import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
4
6
5
7
export type SearchPublicationDocumentsReturnType = Awaited<
6
8
ReturnType<(typeof search_publication_documents)["handler"]>
···
18
20
{ supabase }: Pick<Env, "supabase">,
19
21
) => {
20
22
// Get documents in the publication, filtering by title using JSON operator
23
+
// Also join with publications to get the record for URL construction
21
24
const { data: documents, error } = await supabase
22
25
.from("documents_in_publications")
23
-
.select("document, documents!inner(uri, data)")
26
+
.select(
27
+
"document, documents!inner(uri, data), publications!inner(uri, record)",
28
+
)
24
29
.eq("publication", publication_uri)
25
30
.ilike("documents.data->>title", `%${query}%`)
26
31
.limit(limit);
···
31
36
);
32
37
}
33
38
34
-
const result = documents.map((d) => ({
35
-
uri: d.documents.uri,
36
-
title: (d.documents.data as { title?: string })?.title || "Untitled",
37
-
}));
39
+
const result = documents.map((d) => {
40
+
const docUri = new AtUri(d.documents.uri);
41
+
const pubUrl = getPublicationURL(d.publications);
42
+
43
+
return {
44
+
uri: d.documents.uri,
45
+
title: (d.documents.data as { title?: string })?.title || "Untitled",
46
+
url: `${pubUrl}/${docUri.rkey}`,
47
+
};
48
+
});
38
49
39
50
return { result: { documents: result } };
40
51
},
+10
-8
app/api/rpc/[command]/search_publication_names.ts
+10
-8
app/api/rpc/[command]/search_publication_names.ts
···
1
1
import { z } from "zod";
2
2
import { makeRoute } from "../lib";
3
3
import type { Env } from "./route";
4
+
import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
4
5
5
6
export type SearchPublicationNamesReturnType = Awaited<
6
7
ReturnType<(typeof search_publication_names)["handler"]>
···
12
13
query: z.string(),
13
14
limit: z.number().optional().default(10),
14
15
}),
15
-
handler: async (
16
-
{ query, limit },
17
-
{ supabase }: Pick<Env, "supabase">,
18
-
) => {
16
+
handler: async ({ query, limit }, { supabase }: Pick<Env, "supabase">) => {
19
17
// Search publications by name in record (case-insensitive partial match)
20
18
const { data: publications, error } = await supabase
21
19
.from("publications")
···
27
25
throw new Error(`Failed to search publications: ${error.message}`);
28
26
}
29
27
30
-
const result = publications.map((p) => ({
31
-
uri: p.uri,
32
-
name: (p.record as { name?: string })?.name || "Untitled",
33
-
}));
28
+
const result = publications.map((p) => {
29
+
const record = p.record as { name?: string };
30
+
return {
31
+
uri: p.uri,
32
+
name: record.name || "Untitled",
33
+
url: getPublicationURL(p),
34
+
};
35
+
});
34
36
35
37
return { result: { publications: result } };
36
38
},
+4
-2
components/Mention.tsx
+4
-2
components/Mention.tsx
···
457
457
displayName?: string;
458
458
avatar?: string;
459
459
}
460
-
| { type: "publication"; uri: string; name: string }
461
-
| { type: "post"; uri: string; title: string };
460
+
| { type: "publication"; uri: string; name: string; url: string }
461
+
| { type: "post"; uri: string; title: string; url: string };
462
462
463
463
export type MentionScope =
464
464
| { type: "default" }
···
493
493
type: "post" as const,
494
494
uri: d.uri,
495
495
title: d.title,
496
+
url: d.url,
496
497
})),
497
498
);
498
499
} else {
···
517
518
type: "publication" as const,
518
519
uri: p.uri,
519
520
name: p.name,
521
+
url: p.url,
520
522
})),
521
523
]);
522
524
}