From 46a7d8adb91969c1bd2c25e6431387cdfe8d0df8 Mon Sep 17 00:00:00 2001
From: Jared Pereira
Date: Thu, 4 Dec 2025 18:21:46 -0500
Subject: [PATCH] wire up tags
---
actions/publishToPublication.ts | 3 +
actions/searchTags.ts | 25 +++++++
.../tag/[tag]/getDocumentsByTag.ts | 71 ++++++++++++++++++
app/(home-pages)/tag/[tag]/page.tsx | 75 +++++++++++++++++++
app/[leaflet_id]/actions/PublishButton.tsx | 8 +-
app/[leaflet_id]/publish/PublishPost.tsx | 22 ++++++
app/api/rpc/[command]/pull.ts | 6 ++
.../[rkey]/Interactions/Interactions.tsx | 25 ++++---
.../dashboard/PublishedPostsLists.tsx | 3 +-
app/lish/[did]/[publication]/page.tsx | 25 ++-----
components/InteractionsPreview.tsx | 28 +++----
components/Pages/PublicationMetadata.tsx | 39 ++++++++--
components/PostListing.tsx | 5 +-
components/Tags.tsx | 58 ++++++++------
lexicons/api/lexicons.ts | 7 ++
lexicons/api/types/pub/leaflet/document.ts | 1 +
lexicons/pub/leaflet/document.json | 7 ++
lexicons/src/document.ts | 1 +
src/replicache/mutations.ts | 42 +++++++++--
19 files changed, 363 insertions(+), 88 deletions(-)
create mode 100644 actions/searchTags.ts
create mode 100644 app/(home-pages)/tag/[tag]/getDocumentsByTag.ts
create mode 100644 app/(home-pages)/tag/[tag]/page.tsx
diff --git a/actions/publishToPublication.ts b/actions/publishToPublication.ts
index a03b4e17..dcf5392d 100644
--- a/actions/publishToPublication.ts
+++ b/actions/publishToPublication.ts
@@ -57,6 +57,7 @@ export async function publishToPublication({
leaflet_id,
title,
description,
+ tags,
entitiesToDelete,
}: {
root_entity: string;
@@ -64,6 +65,7 @@ export async function publishToPublication({
leaflet_id: string;
title?: string;
description?: string;
+ tags?: string[];
entitiesToDelete?: string[];
}) {
const oauthClient = await createOauthClient();
@@ -143,6 +145,7 @@ export async function publishToPublication({
...(theme && { theme }),
title: title || "Untitled",
description: description || "",
+ ...(tags !== undefined && { tags }), // Include tags if provided (even if empty array to clear tags)
pages: pages.map((p) => {
if (p.type === "canvas") {
return {
diff --git a/actions/searchTags.ts b/actions/searchTags.ts
new file mode 100644
index 00000000..1c2e7916
--- /dev/null
+++ b/actions/searchTags.ts
@@ -0,0 +1,25 @@
+"use server";
+import { supabaseServerClient } from "supabase/serverClient";
+
+export type TagSearchResult = {
+ name: string;
+ document_count: number;
+};
+
+export async function searchTags(
+ query: string,
+): Promise {
+ const searchQuery = query.trim().toLowerCase();
+
+ // Use raw SQL query to extract and aggregate tags
+ const { data, error } = await supabaseServerClient.rpc("search_tags", {
+ search_query: searchQuery,
+ });
+
+ if (error) {
+ console.error("Error searching tags:", error);
+ return null;
+ }
+
+ return data;
+}
diff --git a/app/(home-pages)/tag/[tag]/getDocumentsByTag.ts b/app/(home-pages)/tag/[tag]/getDocumentsByTag.ts
new file mode 100644
index 00000000..cdd9d7c2
--- /dev/null
+++ b/app/(home-pages)/tag/[tag]/getDocumentsByTag.ts
@@ -0,0 +1,71 @@
+"use server";
+
+import { getPublicationURL } from "app/lish/createPub/getPublicationURL";
+import { supabaseServerClient } from "supabase/serverClient";
+import { AtUri } from "@atproto/api";
+import { Json } from "supabase/database.types";
+import { idResolver } from "app/(home-pages)/reader/idResolver";
+import type { Post } from "app/(home-pages)/reader/getReaderFeed";
+
+export async function getDocumentsByTag(
+ tag: string,
+): Promise<{ posts: Post[] }> {
+ // Normalize tag to lowercase for case-insensitive search
+ const normalizedTag = tag.toLowerCase();
+
+ // Query documents that have this tag
+ const { data: documents, error } = await supabaseServerClient
+ .from("documents")
+ .select(
+ `*,
+ comments_on_documents(count),
+ document_mentions_in_bsky(count),
+ documents_in_publications(publications(*))`,
+ )
+ .contains("data->tags", `["${normalizedTag}"]`)
+ .order("indexed_at", { ascending: false })
+ .limit(50);
+
+ if (error) {
+ console.error("Error fetching documents by tag:", error);
+ return { posts: [] };
+ }
+
+ const posts = await Promise.all(
+ documents.map(async (doc) => {
+ const pub = doc.documents_in_publications[0]?.publications;
+
+ // Skip if document doesn't have a publication
+ if (!pub) {
+ return null;
+ }
+
+ const uri = new AtUri(doc.uri);
+ const handle = await idResolver.did.resolve(uri.host);
+
+ const post: Post = {
+ publication: {
+ href: getPublicationURL(pub),
+ pubRecord: pub?.record || null,
+ uri: pub?.uri || "",
+ },
+ author: handle?.alsoKnownAs?.[0]
+ ? `@${handle.alsoKnownAs[0].slice(5)}`
+ : null,
+ documents: {
+ comments_on_documents: doc.comments_on_documents,
+ document_mentions_in_bsky: doc.document_mentions_in_bsky,
+ data: doc.data,
+ uri: doc.uri,
+ indexed_at: doc.indexed_at,
+ },
+ };
+ return post;
+ }),
+ );
+
+ // Filter out null entries (documents without publications)
+ return {
+ posts: posts.filter((p): p is Post => p !== null),
+ };
+}
diff --git a/app/(home-pages)/tag/[tag]/page.tsx b/app/(home-pages)/tag/[tag]/page.tsx
new file mode 100644
index 00000000..53f10da4
--- /dev/null
+++ b/app/(home-pages)/tag/[tag]/page.tsx
@@ -0,0 +1,75 @@
+import { DashboardLayout } from "components/PageLayouts/DashboardLayout";
+import { Tag } from "components/Tags";
+import { PostListing } from "components/PostListing";
+import { getDocumentsByTag } from "./getDocumentsByTag";
+import { TagTiny } from "components/Icons/TagTiny";
+
+export default async function TagPage(props: {
+ params: Promise<{ tag: string }>;
+}) {
+ const params = await props.params;
+ const decodedTag = decodeURIComponent(params.tag);
+ const { posts } = await getDocumentsByTag(decodedTag);
+
+ return (
+ ,
+ },
+ }}
+ />
+ );
+}
+
+const TagContent = (props: {
+ tag: string;
+ posts: Awaited>["posts"];
+}) => {
+ return (
+
+
+
+
+
+ {props.posts.length === 0 ? (
+
+ ) : (
+ props.posts.map((post) => (
+
+ ))
+ )}
+
+
+ );
+};
+
+const TagHeader = (props: { tag: string; postCount: number }) => {
+ return (
+
+
+
+
{props.tag}
+
+
+ {props.postCount} {props.postCount === 1 ? "post" : "posts"}
+
+
+ );
+};
+
+const EmptyState = (props: { tag: string }) => {
+ return (
+
+
+ No posts found with the tag "{props.tag}"
+
+
+ );
+};
diff --git a/app/[leaflet_id]/actions/PublishButton.tsx b/app/[leaflet_id]/actions/PublishButton.tsx
index 4098d1ff..2ee256fc 100644
--- a/app/[leaflet_id]/actions/PublishButton.tsx
+++ b/app/[leaflet_id]/actions/PublishButton.tsx
@@ -27,6 +27,7 @@ import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useState, useMemo } from "react";
import { useIsMobile } from "src/hooks/isMobile";
import { useReplicache, useEntity } from "src/replicache";
+import { useSubscribe } from "src/replicache/useSubscribe";
import { Json } from "supabase/database.types";
import {
useBlocks,
@@ -63,10 +64,14 @@ export const PublishButton = (props: { entityID: string }) => {
const UpdateButton = () => {
let [isLoading, setIsLoading] = useState(false);
let { data: pub, mutate } = useLeafletPublicationData();
- let { permission_token, rootEntity } = useReplicache();
+ let { permission_token, rootEntity, rep } = useReplicache();
let { identity } = useIdentityData();
let toaster = useToaster();
+ // Get tags from Replicache state (same as draft editor)
+ let tags = useSubscribe(rep, (tx) => tx.get("publication_tags"));
+ const currentTags = Array.isArray(tags) ? tags : [];
+
return (
{
leaflet_id: permission_token.id,
title: pub.title,
description: pub.description,
+ tags: currentTags,
});
setIsLoading(false);
mutate();
diff --git a/app/[leaflet_id]/publish/PublishPost.tsx b/app/[leaflet_id]/publish/PublishPost.tsx
index 6c03e305..142d81c7 100644
--- a/app/[leaflet_id]/publish/PublishPost.tsx
+++ b/app/[leaflet_id]/publish/PublishPost.tsx
@@ -13,6 +13,7 @@ import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/act
import { AtUri } from "@atproto/syntax";
import { PublishIllustration } from "./PublishIllustration/PublishIllustration";
import { useReplicache } from "src/replicache";
+import { useSubscribe } from "src/replicache/useSubscribe";
import {
BlueskyPostEditorProsemirror,
editorStateToFacetedText,
@@ -66,6 +67,19 @@ const PublishPostForm = (
let params = useParams();
let { rep } = useReplicache();
+ // Get tags from Replicache state (same as in the draft editor)
+ let tags = useSubscribe(rep, (tx) => tx.get("publication_tags"));
+
+ // Default to empty array if undefined
+ const currentTags = Array.isArray(tags) ? tags : [];
+
+ // Update tags via the same mutation used in the editor
+ const handleTagsChange = async (newTags: string[]) => {
+ await rep?.mutate.updatePublicationDraft({
+ tags: newTags,
+ });
+ };
+
async function submit() {
if (isLoading) return;
setIsLoading(true);
@@ -76,6 +90,7 @@ const PublishPostForm = (
leaflet_id: props.leaflet_id,
title: props.title,
description: props.description,
+ tags: currentTags,
entitiesToDelete: props.entitiesToDelete,
});
if (!doc) return;
@@ -125,6 +140,13 @@ const PublishPostForm = (
{...props}
/>
+
+
Tags
+
+
{
+ const data = useContext(PostPageContext);
+ const tags = (data?.data as any)?.tags as string[] | undefined;
+ const tagCount = tags?.length || 0;
+
+ if (tagCount === 0) return null;
+
return (
- XX
+ {tagCount}
}
>
@@ -190,24 +196,19 @@ const TagPopover = () => {
};
const TagList = (props: { className?: string }) => {
+ const data = useContext(PostPageContext);
+ const tags = (data?.data as any)?.tags as string[] | undefined;
+
+ if (!tags || tags.length === 0) return null;
+
return (
- {Tags.map((tag, index) => (
+ {tags.map((tag, index) => (
))}
);
};
-
-const Tags = [
- "Hello",
- "these are",
- "some tags",
- "and I'm gonna",
- "make",
- "them super",
- "long",
-];
export function getQuoteCount(document: PostPageData, pageId?: string) {
if (!document) return;
return getQuoteCountFromArray(document.quotesAndMentions, pageId);
diff --git a/app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx b/app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx
index 20641a44..83a5b00a 100644
--- a/app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx
+++ b/app/lish/[did]/[publication]/dashboard/PublishedPostsLists.tsx
@@ -61,6 +61,7 @@ export function PublishedPostsList(props: {
let postRecord = doc.documents.data as PubLeafletDocument.Record;
let quotes = doc.documents.document_mentions_in_bsky[0]?.count || 0;
let comments = doc.documents.comments_on_documents[0]?.count || 0;
+ let tags = (postRecord?.tags as string[] | undefined) || [];
let postLink = data?.publication
? `${getPublicationURL(data?.publication)}/${new AtUri(doc.documents.uri).rkey}`
@@ -137,7 +138,7 @@ export function PublishedPostsList(props: {
diff --git a/app/lish/[did]/[publication]/page.tsx b/app/lish/[did]/[publication]/page.tsx
index 76287ba0..6754053a 100644
--- a/app/lish/[did]/[publication]/page.tsx
+++ b/app/lish/[did]/[publication]/page.tsx
@@ -135,6 +135,7 @@ export default async function Publication(props: {
record?.preferences?.showComments === false
? 0
: doc.documents.comments_on_documents[0].count || 0;
+ let tags = (doc_record?.tags as string[] | undefined) || [];
return (
@@ -163,23 +164,13 @@ export default async function Publication(props: {
)}{" "}
{comments > 0 || quotes > 0 ? "| " : ""}
- {quotes > 0 && (
-
- {quotes}
-
- )}
- {comments > 0 &&
- record?.preferences?.showComments !== false && (
-
- {comments}
-
- )}
+
diff --git a/components/InteractionsPreview.tsx b/components/InteractionsPreview.tsx
index 851daac3..60d5b266 100644
--- a/components/InteractionsPreview.tsx
+++ b/components/InteractionsPreview.tsx
@@ -11,7 +11,7 @@ import { SpeedyLink } from "./SpeedyLink";
export const InteractionPreview = (props: {
quotesCount: number;
commentsCount: number;
- tagsCount: number;
+ tags?: string[];
postUrl: string;
showComments: boolean | undefined;
share?: boolean;
@@ -21,13 +21,15 @@ export const InteractionPreview = (props: {
props.quotesCount > 0 ||
(props.showComments !== false && props.commentsCount > 0);
+ const tagsCount = props.tags?.length || 0;
+
return (
- {props.tagsCount === 0 ? null : (
+ {tagsCount === 0 ? null : (
<>
-
+
{interactionsAvailable || props.share ? (
) : null}
@@ -86,7 +88,7 @@ export const InteractionPreview = (props: {
);
};
-const TagPopover = (props: { tagsCount: number }) => {
+const TagPopover = (props: { tags: string[] }) => {
return (
{
aria-label="Post tags"
className="relative flex gap-1 items-center "
>
- {props.tagsCount}
+ {props.tags.length}
}
>
-
+
);
};
-const TagList = (props: { className?: string }) => {
+const TagList = (props: { tags: string[]; className?: string }) => {
return (
- {Tags.map((tag, index) => (
+ {props.tags.map((tag, index) => (
))}
);
};
-
-const Tags = [
- "Hello",
- "these are",
- "some tags",
- "and I'm gonna",
- "make",
- "them super",
- "long",
-];
diff --git a/components/Pages/PublicationMetadata.tsx b/components/Pages/PublicationMetadata.tsx
index a7f51efd..ce4ad29b 100644
--- a/components/Pages/PublicationMetadata.tsx
+++ b/components/Pages/PublicationMetadata.tsx
@@ -28,7 +28,9 @@ export const PublicationMetadata = () => {
tx.get
("publication_description"),
);
let record = pub?.documents?.data as PubLeafletDocument.Record | null;
- let pubRecord = pub?.publications?.record as PubLeafletPublication.Record;
+ let pubRecord = pub?.publications?.record as
+ | PubLeafletPublication.Record
+ | undefined;
let publishedAt = record?.publishedAt;
if (!pub) return null;
@@ -111,7 +113,7 @@ export const PublicationMetadata = () => {
—
- {pubRecord.preferences?.showComments && (
+ {pubRecord?.preferences?.showComments && (
—
@@ -232,17 +234,44 @@ export const PublicationMetadataPreview = () => {
};
const AddTags = () => {
- // Just update the database with tags as the user adds them, no explicit submit button
+ let { data: pub } = useLeafletPublicationData();
+ let { rep } = useReplicache();
+ let record = pub?.documents?.data as PubLeafletDocument.Record | null;
+
+ // Get tags from Replicache local state or published document
+ let replicacheTags = useSubscribe(rep, (tx) =>
+ tx.get("publication_tags"),
+ );
+
+ // Determine which tags to use - prioritize Replicache state
+ let tags: string[] = [];
+ if (Array.isArray(replicacheTags)) {
+ tags = replicacheTags;
+ } else if (record?.tags && Array.isArray(record.tags)) {
+ tags = record.tags as string[];
+ }
+
+ // Update tags in replicache local state
+ const handleTagsChange = async (newTags: string[]) => {
+ // Store tags in replicache for next publish/update
+ await rep?.mutate.updatePublicationDraft({
+ tags: newTags,
+ });
+ };
+
return (
- Add Tags
+ {" "}
+ {tags.length > 0
+ ? `${tags.length} Tag${tags.length === 1 ? "" : "s"}`
+ : "Add Tags"}
}
>
-
+
);
};
diff --git a/components/PostListing.tsx b/components/PostListing.tsx
index c151eb48..897cc82d 100644
--- a/components/PostListing.tsx
+++ b/components/PostListing.tsx
@@ -20,7 +20,7 @@ export const PostListing = (props: Post) => {
let postRecord = props.documents.data as PubLeafletDocument.Record;
let postUri = new AtUri(props.documents.uri);
- let theme = usePubTheme(pubRecord);
+ let theme = usePubTheme(pubRecord.theme);
let backgroundImage = pubRecord?.theme?.backgroundImage?.image?.ref
? blobRefToSrc(
pubRecord?.theme?.backgroundImage?.image?.ref,
@@ -38,6 +38,7 @@ export const PostListing = (props: Post) => {
pubRecord.preferences?.showComments === false
? 0
: props.documents.comments_on_documents?.[0]?.count || 0;
+ let tags = (postRecord?.tags as string[] | undefined) || [];
return (
@@ -86,7 +87,7 @@ export const PostListing = (props: Post) => {
postUrl={`${props.publication.href}/${postUri.rkey}`}
quotesCount={quotes}
commentsCount={comments}
- tagsCount={6}
+ tags={tags}
showComments={pubRecord.preferences?.showComments}
share
/>
diff --git a/components/Tags.tsx b/components/Tags.tsx
index bcaed365..800b4916 100644
--- a/components/Tags.tsx
+++ b/components/Tags.tsx
@@ -4,17 +4,7 @@ import { Input } from "components/Input";
import { useState, useRef, useEffect } from "react";
import { Popover } from "components/Popover";
import Link from "next/link";
-
-const Tags: { name: string; tagged: number }[] = [
- { name: "dogs", tagged: 240 },
- { name: "cats", tagged: 12 },
- { name: "fruit enthusiam", tagged: 21 },
- { name: "at proto", tagged: 56 },
- { name: "events in nyc", tagged: 6 },
- { name: "react devs", tagged: 93 },
- { name: "fanfic", tagged: 1743 },
- { name: "pokemon", tagged: 81 },
-];
+import { searchTags, type TagSearchResult } from "actions/searchTags";
export const Tag = (props: {
name: string;
@@ -27,7 +17,7 @@ export const Tag = (props: {
className={`tag flex items-center text-xs rounded-md border ${props.selected ? "bg-accent-1 text-accent-2 border-accent-1 font-bold" : "bg-bg-page text-tertiary border-border"} ${props.className}`}
>
{props.name}{" "}
@@ -44,22 +34,25 @@ export const Tag = (props: {
);
};
-export const TagSelector = (props: {}) => {
- let [selectedTags, setSelectedTags] = useState([]);
+export const TagSelector = (props: {
+ selectedTags: string[];
+ setSelectedTags: (tags: string[]) => void;
+}) => {
return (
- {selectedTags.length > 0 ? (
+ {props.selectedTags.length > 0 ? (
- {selectedTags.map((tag) => (
+ {props.selectedTags.map((tag) => (
{
- setSelectedTags(selectedTags.filter((t) => t !== tag));
+ props.setSelectedTags(props.selectedTags.filter((t) => t !== tag));
}}
/>
))}
@@ -78,15 +71,32 @@ export const TagSearchInput = (props: {
let [tagInputValue, setTagInputValue] = useState("");
let [isOpen, setIsOpen] = useState(false);
let [highlightedIndex, setHighlightedIndex] = useState(0);
+ let [searchResults, setSearchResults] = useState([]);
+ let [isSearching, setIsSearching] = useState(false);
const placeholderInputRef = useRef(null);
let inputWidth = placeholderInputRef.current?.clientWidth;
- const filteredTags = Tags.filter(
- (tag) =>
- tag.name.toLowerCase().includes(tagInputValue.toLowerCase()) &&
- !props.selectedTags.includes(tag.name),
+ // Fetch tags whenever the input value changes
+ useEffect(() => {
+ let cancelled = false;
+ setIsSearching(true);
+
+ searchTags(tagInputValue).then((results) => {
+ if (!cancelled && results) {
+ setSearchResults(results);
+ setIsSearching(false);
+ }
+ });
+
+ return () => {
+ cancelled = true;
+ };
+ }, [tagInputValue]);
+
+ const filteredTags = searchResults.filter(
+ (tag) => !props.selectedTags.includes(tag.name),
);
function clearTagInput() {
@@ -225,7 +235,7 @@ export const TagSearchInput = (props: {
key={tag.name}
index={userInputResult ? i + 1 : i}
name={tag.name}
- tagged={tag.tagged}
+ tagged={tag.document_count}
highlighted={(userInputResult ? i + 1 : i) === highlightedIndex}
setHighlightedIndex={setHighlightedIndex}
onSelect={() => {
diff --git a/lexicons/api/lexicons.ts b/lexicons/api/lexicons.ts
index 1ebf4d50..c57f4855 100644
--- a/lexicons/api/lexicons.ts
+++ b/lexicons/api/lexicons.ts
@@ -1440,6 +1440,13 @@ export const schemaDict = {
type: 'ref',
ref: 'lex:pub.leaflet.publication#theme',
},
+ tags: {
+ type: 'array',
+ items: {
+ type: 'string',
+ maxLength: 50,
+ },
+ },
pages: {
type: 'array',
items: {
diff --git a/lexicons/api/types/pub/leaflet/document.ts b/lexicons/api/types/pub/leaflet/document.ts
index 08cfbf1e..e49512f8 100644
--- a/lexicons/api/types/pub/leaflet/document.ts
+++ b/lexicons/api/types/pub/leaflet/document.ts
@@ -23,6 +23,7 @@ export interface Record {
publication?: string
author: string
theme?: PubLeafletPublication.Theme
+ tags?: string[]
pages: (
| $Typed
| $Typed
diff --git a/lexicons/pub/leaflet/document.json b/lexicons/pub/leaflet/document.json
index 32e338a4..dd3a5736 100644
--- a/lexicons/pub/leaflet/document.json
+++ b/lexicons/pub/leaflet/document.json
@@ -46,6 +46,13 @@
"type": "ref",
"ref": "pub.leaflet.publication#theme"
},
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 50
+ }
+ },
"pages": {
"type": "array",
"items": {
diff --git a/lexicons/src/document.ts b/lexicons/src/document.ts
index e743ea97..db493e2e 100644
--- a/lexicons/src/document.ts
+++ b/lexicons/src/document.ts
@@ -23,6 +23,7 @@ export const PubLeafletDocument: LexiconDoc = {
publication: { type: "string", format: "at-uri" },
author: { type: "string", format: "at-identifier" },
theme: { type: "ref", ref: "pub.leaflet.publication#theme" },
+ tags: { type: "array", items: { type: "string", maxLength: 50 } },
pages: {
type: "array",
items: {
diff --git a/src/replicache/mutations.ts b/src/replicache/mutations.ts
index 2978adce..44a15a73 100644
--- a/src/replicache/mutations.ts
+++ b/src/replicache/mutations.ts
@@ -609,19 +609,45 @@ const removePollOption: Mutation<{
};
const updatePublicationDraft: Mutation<{
- title: string;
- description: string;
+ title?: string;
+ description?: string;
+ tags?: string[];
}> = async (args, ctx) => {
await ctx.runOnServer(async (serverCtx) => {
console.log("updating");
- await serverCtx.supabase
- .from("leaflets_in_publications")
- .update({ description: args.description, title: args.title })
- .eq("leaflet", ctx.permission_token_id);
+ const updates: {
+ description?: string;
+ title?: string;
+ tags?: string[];
+ } = {};
+ if (args.description !== undefined) updates.description = args.description;
+ if (args.title !== undefined) updates.title = args.title;
+ if (args.tags !== undefined) updates.tags = args.tags;
+
+ if (Object.keys(updates).length > 0) {
+ // First try to update leaflets_in_publications (for publications)
+ const { data: pubResult } = await serverCtx.supabase
+ .from("leaflets_in_publications")
+ .update(updates)
+ .eq("leaflet", ctx.permission_token_id)
+ .select("leaflet");
+
+ // If no rows were updated in leaflets_in_publications,
+ // try leaflets_to_documents (for standalone documents)
+ if (!pubResult || pubResult.length === 0) {
+ await serverCtx.supabase
+ .from("leaflets_to_documents")
+ .update(updates)
+ .eq("leaflet", ctx.permission_token_id);
+ }
+ }
});
await ctx.runOnClient(async ({ tx }) => {
- await tx.set("publication_title", args.title);
- await tx.set("publication_description", args.description);
+ if (args.title !== undefined)
+ await tx.set("publication_title", args.title);
+ if (args.description !== undefined)
+ await tx.set("publication_description", args.description);
+ if (args.tags !== undefined) await tx.set("publication_tags", args.tags);
});
};
--
2.51.2