+31
-14
app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx
+31
-14
app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx
···
170
171
const handleMentionSelect = useCallback(
172
(mention: Mention) => {
173
-
if (mention.type !== "did") return;
174
if (!viewRef.current || mentionInsertPos === null) return;
175
const view = viewRef.current;
176
const from = mentionInsertPos - 1;
···
180
// Delete the @ symbol
181
tr.delete(from, to);
182
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);
196
197
view.dispatch(tr);
198
view.focus();
···
170
171
const handleMentionSelect = useCallback(
172
(mention: Mention) => {
173
if (!viewRef.current || mentionInsertPos === null) return;
174
const view = viewRef.current;
175
const from = mentionInsertPos - 1;
···
179
// Delete the @ symbol
180
tr.delete(from, to);
181
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
+
}
213
214
view.dispatch(tr);
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 { z } from "zod";
2
import { makeRoute } from "../lib";
3
import type { Env } from "./route";
4
5
export type SearchPublicationDocumentsReturnType = Awaited<
6
ReturnType<(typeof search_publication_documents)["handler"]>
···
18
{ supabase }: Pick<Env, "supabase">,
19
) => {
20
// Get documents in the publication, filtering by title using JSON operator
21
const { data: documents, error } = await supabase
22
.from("documents_in_publications")
23
-
.select("document, documents!inner(uri, data)")
24
.eq("publication", publication_uri)
25
.ilike("documents.data->>title", `%${query}%`)
26
.limit(limit);
···
31
);
32
}
33
34
-
const result = documents.map((d) => ({
35
-
uri: d.documents.uri,
36
-
title: (d.documents.data as { title?: string })?.title || "Untitled",
37
-
}));
38
39
return { result: { documents: result } };
40
},
···
1
+
import { AtUri } from "@atproto/api";
2
import { z } from "zod";
3
import { makeRoute } from "../lib";
4
import type { Env } from "./route";
5
+
import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
6
7
export type SearchPublicationDocumentsReturnType = Awaited<
8
ReturnType<(typeof search_publication_documents)["handler"]>
···
20
{ supabase }: Pick<Env, "supabase">,
21
) => {
22
// Get documents in the publication, filtering by title using JSON operator
23
+
// Also join with publications to get the record for URL construction
24
const { data: documents, error } = await supabase
25
.from("documents_in_publications")
26
+
.select(
27
+
"document, documents!inner(uri, data), publications!inner(uri, record)",
28
+
)
29
.eq("publication", publication_uri)
30
.ilike("documents.data->>title", `%${query}%`)
31
.limit(limit);
···
36
);
37
}
38
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
+
});
49
50
return { result: { documents: result } };
51
},
+10
-8
app/api/rpc/[command]/search_publication_names.ts
+10
-8
app/api/rpc/[command]/search_publication_names.ts
···
1
import { z } from "zod";
2
import { makeRoute } from "../lib";
3
import type { Env } from "./route";
4
5
export type SearchPublicationNamesReturnType = Awaited<
6
ReturnType<(typeof search_publication_names)["handler"]>
···
12
query: z.string(),
13
limit: z.number().optional().default(10),
14
}),
15
-
handler: async (
16
-
{ query, limit },
17
-
{ supabase }: Pick<Env, "supabase">,
18
-
) => {
19
// Search publications by name in record (case-insensitive partial match)
20
const { data: publications, error } = await supabase
21
.from("publications")
···
27
throw new Error(`Failed to search publications: ${error.message}`);
28
}
29
30
-
const result = publications.map((p) => ({
31
-
uri: p.uri,
32
-
name: (p.record as { name?: string })?.name || "Untitled",
33
-
}));
34
35
return { result: { publications: result } };
36
},
···
1
import { z } from "zod";
2
import { makeRoute } from "../lib";
3
import type { Env } from "./route";
4
+
import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
5
6
export type SearchPublicationNamesReturnType = Awaited<
7
ReturnType<(typeof search_publication_names)["handler"]>
···
13
query: z.string(),
14
limit: z.number().optional().default(10),
15
}),
16
+
handler: async ({ query, limit }, { supabase }: Pick<Env, "supabase">) => {
17
// Search publications by name in record (case-insensitive partial match)
18
const { data: publications, error } = await supabase
19
.from("publications")
···
25
throw new Error(`Failed to search publications: ${error.message}`);
26
}
27
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
+
});
36
37
return { result: { publications: result } };
38
},
+4
-2
components/Mention.tsx
+4
-2
components/Mention.tsx
···
457
displayName?: string;
458
avatar?: string;
459
}
460
-
| { type: "publication"; uri: string; name: string }
461
-
| { type: "post"; uri: string; title: string };
462
463
export type MentionScope =
464
| { type: "default" }
···
493
type: "post" as const,
494
uri: d.uri,
495
title: d.title,
496
})),
497
);
498
} else {
···
517
type: "publication" as const,
518
uri: p.uri,
519
name: p.name,
520
})),
521
]);
522
}
···
457
displayName?: string;
458
avatar?: string;
459
}
460
+
| { type: "publication"; uri: string; name: string; url: string }
461
+
| { type: "post"; uri: string; title: string; url: string };
462
463
export type MentionScope =
464
| { type: "default" }
···
493
type: "post" as const,
494
uri: d.uri,
495
title: d.title,
496
+
url: d.url,
497
})),
498
);
499
} else {
···
518
type: "publication" as const,
519
uri: p.uri,
520
name: p.name,
521
+
url: p.url,
522
})),
523
]);
524
}