diff --git a/actions/publishToPublication.ts b/actions/publishToPublication.ts
index eb62eb97..8d21c053 100644
--- a/actions/publishToPublication.ts
+++ b/actions/publishToPublication.ts
@@ -53,7 +53,7 @@ export async function publishToPublication({
description,
}: {
root_entity: string;
- publication_uri: string;
+ publication_uri?: string;
leaflet_id: string;
title?: string;
description?: string;
@@ -66,14 +66,34 @@ export async function publishToPublication({
let agent = new AtpBaseClient(
credentialSession.fetchHandler.bind(credentialSession),
);
- let { data: draft } = await supabaseServerClient
- .from("leaflets_in_publications")
- .select("*, publications(*), documents(*)")
- .eq("publication", publication_uri)
- .eq("leaflet", leaflet_id)
- .single();
- if (!draft || identity.atp_did !== draft?.publications?.identity_did)
- throw new Error("No draft or not publisher");
+
+ // Check if we're publishing to a publication or standalone
+ let draft: any = null;
+ let existingDocUri: string | null = null;
+
+ if (publication_uri) {
+ // Publishing to a publication - use leaflets_in_publications
+ let { data } = await supabaseServerClient
+ .from("leaflets_in_publications")
+ .select("*, publications(*), documents(*)")
+ .eq("publication", publication_uri)
+ .eq("leaflet", leaflet_id)
+ .single();
+ if (!data || identity.atp_did !== data?.publications?.identity_did)
+ throw new Error("No draft or not publisher");
+ draft = data;
+ existingDocUri = draft?.doc;
+ } else {
+ // Publishing standalone - use leaflets_to_documents
+ let { data } = await supabaseServerClient
+ .from("leaflets_to_documents")
+ .select("*, documents(*)")
+ .eq("leaflet", leaflet_id)
+ .single();
+ draft = data;
+ existingDocUri = draft?.document;
+ }
+
let { data } = await supabaseServerClient.rpc("get_facts", {
root: root_entity,
});
@@ -91,7 +111,7 @@ export async function publishToPublication({
let record: PubLeafletDocument.Record = {
$type: "pub.leaflet.document",
author: credentialSession.did!,
- publication: publication_uri,
+ ...(publication_uri && { publication: publication_uri }),
publishedAt: new Date().toISOString(),
...existingRecord,
title: title || "Untitled",
@@ -118,7 +138,9 @@ export async function publishToPublication({
}),
],
};
- let rkey = draft?.doc ? new AtUri(draft.doc).rkey : TID.nextStr();
+
+ // Keep the same rkey if updating an existing document
+ let rkey = existingDocUri ? new AtUri(existingDocUri).rkey : TID.nextStr();
let { data: result } = await agent.com.atproto.repo.putRecord({
rkey,
repo: credentialSession.did!,
@@ -127,24 +149,36 @@ export async function publishToPublication({
validate: false, //TODO publish the lexicon so we can validate!
});
+ // Optimistically create database entries
await supabaseServerClient.from("documents").upsert({
uri: result.uri,
data: record as Json,
});
- await Promise.all([
- //Optimistically put these in!
- supabaseServerClient.from("documents_in_publications").upsert({
- publication: record.publication,
+
+ if (publication_uri) {
+ // Publishing to a publication - update both tables
+ await Promise.all([
+ supabaseServerClient.from("documents_in_publications").upsert({
+ publication: publication_uri,
+ document: result.uri,
+ }),
+ supabaseServerClient
+ .from("leaflets_in_publications")
+ .update({
+ doc: result.uri,
+ })
+ .eq("leaflet", leaflet_id)
+ .eq("publication", publication_uri),
+ ]);
+ } else {
+ // Publishing standalone - update leaflets_to_documents
+ await supabaseServerClient.from("leaflets_to_documents").upsert({
+ leaflet: leaflet_id,
document: result.uri,
- }),
- supabaseServerClient
- .from("leaflets_in_publications")
- .update({
- doc: result.uri,
- })
- .eq("leaflet", leaflet_id)
- .eq("publication", publication_uri),
- ]);
+ title: title || "Untitled",
+ description: description || "",
+ });
+ }
return { rkey, record: JSON.parse(JSON.stringify(record)) };
}
diff --git a/app/(home-pages)/home/HomeLayout.tsx b/app/(home-pages)/home/HomeLayout.tsx
index e714df2e..d7d55166 100644
--- a/app/(home-pages)/home/HomeLayout.tsx
+++ b/app/(home-pages)/home/HomeLayout.tsx
@@ -38,6 +38,10 @@ type Leaflet = {
GetLeafletDataReturnType["result"]["data"],
null
>["leaflets_in_publications"];
+ leaflets_to_documents?: Exclude<
+ GetLeafletDataReturnType["result"]["data"],
+ null
+ >["leaflets_to_documents"];
};
};
@@ -218,6 +222,7 @@ export function LeafletList(props: {
value={{
...leaflet,
leaflets_in_publications: leaflet.leaflets_in_publications || [],
+ leaflets_to_documents: leaflet.leaflets_to_documents || [],
blocked_by_admin: null,
custom_domain_routes: [],
}}
diff --git a/app/[leaflet_id]/actions/PublishButton.tsx b/app/[leaflet_id]/actions/PublishButton.tsx
index 295a2bc5..73044af0 100644
--- a/app/[leaflet_id]/actions/PublishButton.tsx
+++ b/app/[leaflet_id]/actions/PublishButton.tsx
@@ -60,6 +60,7 @@ const UpdateButton = () => {
let [isLoading, setIsLoading] = useState(false);
let { data: pub, mutate } = useLeafletPublicationData();
let { permission_token, rootEntity } = useReplicache();
+ let { identity } = useIdentityData();
let toaster = useToaster();
return (
@@ -68,26 +69,28 @@ const UpdateButton = () => {
icon={}
label={isLoading ? : "Update!"}
onClick={async () => {
- if (!pub || !pub.publications) return;
+ if (!pub) return;
setIsLoading(true);
let doc = await publishToPublication({
root_entity: rootEntity,
- publication_uri: pub.publications.uri,
+ publication_uri: pub.publications?.uri,
leaflet_id: permission_token.id,
title: pub.title,
description: pub.description,
});
setIsLoading(false);
mutate();
+
+ // Generate URL based on whether it's in a publication or standalone
+ let docUrl = pub.publications
+ ? `${getPublicationURL(pub.publications)}/${doc?.rkey}`
+ : `https://leaflet.pub/p/${identity?.atp_did}/${doc?.rkey}`;
+
toaster({
content: (
{pub.doc ? "Updated! " : "Published! "}
-
- link
-
+ link
),
type: "success",
@@ -169,21 +172,31 @@ const PublishToPublicationButton = (props: { entityID: string }) => {
-
+ {selectedPub !== "looseleaf" && (
+
+ )}
{
if (!selectedPub) return;
e.preventDefault();
if (selectedPub === "create") return;
- router.push(
- `${permission_token.id}/publish?publication_uri=${encodeURIComponent(selectedPub)}&title=${encodeURIComponent(title)}&description=${encodeURIComponent(description)}`,
- );
+
+ // For looseleaf, navigate without publication_uri
+ if (selectedPub === "looseleaf") {
+ router.push(
+ `${permission_token.id}/publish?title=${encodeURIComponent(title)}&description=${encodeURIComponent(description)}`,
+ );
+ } else {
+ router.push(
+ `${permission_token.id}/publish?publication_uri=${encodeURIComponent(selectedPub)}&title=${encodeURIComponent(title)}&description=${encodeURIComponent(description)}`,
+ );
+ }
}}
>
Next{selectedPub === "create" && ": Create Pub!"}
@@ -305,6 +318,7 @@ const PubSelector = (props: {
let pubRecord = p.record as PubLeafletPublication.Record;
return (
props.setSelectedPub(p.uri)}
>
diff --git a/app/[leaflet_id]/publish/PublishPost.tsx b/app/[leaflet_id]/publish/PublishPost.tsx
index 570dfd4a..a7e46399 100644
--- a/app/[leaflet_id]/publish/PublishPost.tsx
+++ b/app/[leaflet_id]/publish/PublishPost.tsx
@@ -18,6 +18,8 @@ import {
editorStateToFacetedText,
} from "./BskyPostEditorProsemirror";
import { EditorState } from "prosemirror-state";
+import { LooseLeafSmall } from "components/Icons/LooseleafSmall";
+import { PubIcon } from "components/ActionBar/Publications";
type Props = {
title: string;
@@ -25,7 +27,7 @@ type Props = {
root_entity: string;
profile: ProfileViewDetailed;
description: string;
- publication_uri: string;
+ publication_uri?: string;
record?: PubLeafletPublication.Record;
posts_in_pub?: number;
};
@@ -75,7 +77,11 @@ const PublishPostForm = (
});
if (!doc) return;
- let post_url = `https://${props.record?.base_path}/${doc.rkey}`;
+ // Generate post URL based on whether it's in a publication or standalone
+ let post_url = props.record?.base_path
+ ? `https://${props.record.base_path}/${doc.rkey}`
+ : `https://leaflet.pub/p/${props.profile.did}/${doc.rkey}`;
+
let [text, facets] = editorStateRef.current
? editorStateToFacetedText(editorStateRef.current)
: [];
@@ -103,6 +109,11 @@ const PublishPostForm = (
}}
>
+
+
{
@@ -199,23 +210,59 @@ const PublishPostForm = (
);
};
+const PublishingTo = (props: {
+ publication_uri?: string;
+ record?: PubLeafletPublication.Record;
+}) => {
+ if (props.publication_uri && props.record) {
+ return (
+
+
Publishing to…
+
+
+
{props.record.name}
+
+
+ );
+ }
+
+ return (
+
+ );
+};
+
const PublishPostSuccess = (props: {
post_url: string;
- publication_uri: string;
+ publication_uri?: string;
record: Props["record"];
posts_in_pub: number;
}) => {
- let uri = new AtUri(props.publication_uri);
+ let uri = props.publication_uri ? new AtUri(props.publication_uri) : null;
return (
Published!
-
-
Back to Dashboard
-
+ {uri && props.record ? (
+
+
Back to Dashboard
+
+ ) : (
+
+
Back to Home
+
+ )}
See published post
);
diff --git a/app/[leaflet_id]/publish/page.tsx b/app/[leaflet_id]/publish/page.tsx
index 592990ff..68629120 100644
--- a/app/[leaflet_id]/publish/page.tsx
+++ b/app/[leaflet_id]/publish/page.tsx
@@ -32,25 +32,36 @@ export default async function PublishLeafletPage(props: Props) {
*,
documents_in_publications(count)
),
- documents(*))`,
+ documents(*)),
+ leaflets_to_documents(
+ *,
+ documents(*)
+ )`,
)
.eq("id", leaflet_id)
.single();
let rootEntity = data?.root_entity;
+
+ // Try to find publication from leaflets_in_publications first
let publication = data?.leaflets_in_publications[0]?.publications;
+
+ // If not found, check if publication_uri is in searchParams
if (!publication) {
let pub_uri = (await props.searchParams).publication_uri;
- if (!pub_uri) return;
- console.log(decodeURIComponent(pub_uri));
- let { data, error } = await supabaseServerClient
- .from("publications")
- .select("*, documents_in_publications(count)")
- .eq("uri", decodeURIComponent(pub_uri))
- .single();
- console.log(error);
- publication = data;
+ if (pub_uri) {
+ console.log(decodeURIComponent(pub_uri));
+ let { data: pubData, error } = await supabaseServerClient
+ .from("publications")
+ .select("*, documents_in_publications(count)")
+ .eq("uri", decodeURIComponent(pub_uri))
+ .single();
+ console.log(error);
+ publication = pubData;
+ }
}
- if (!data || !rootEntity || !publication)
+
+ // Check basic data requirements
+ if (!data || !rootEntity)
return (
missin something
@@ -60,15 +71,20 @@ export default async function PublishLeafletPage(props: Props) {
let identity = await getIdentityData();
if (!identity || !identity.atp_did) return null;
+
+ // Get title and description from either source
let title =
data.leaflets_in_publications[0]?.title ||
- decodeURIComponent((await props.searchParams).title);
+ data.leaflets_to_documents[0]?.title ||
+ decodeURIComponent((await props.searchParams).title || "");
let description =
data.leaflets_in_publications[0]?.description ||
- decodeURIComponent((await props.searchParams).description);
- let agent = new AtpAgent({ service: "https://public.api.bsky.app" });
+ data.leaflets_to_documents[0]?.description ||
+ decodeURIComponent((await props.searchParams).description || "");
+ let agent = new AtpAgent({ service: "https://public.api.bsky.app" });
let profile = await agent.getProfile({ actor: identity.atp_did });
+
return (
);
diff --git a/app/api/rpc/[command]/get_leaflet_data.ts b/app/api/rpc/[command]/get_leaflet_data.ts
index e8390b9e..654a0f74 100644
--- a/app/api/rpc/[command]/get_leaflet_data.ts
+++ b/app/api/rpc/[command]/get_leaflet_data.ts
@@ -7,6 +7,7 @@ export type GetLeafletDataReturnType = Awaited<
>;
const leaflets_in_publications_query = `leaflets_in_publications(*, publications(*), documents(*))`;
+const leaflets_to_documents_query = `leaflets_to_documents(*, documents(*))`;
export const get_leaflet_data = makeRoute({
route: "get_leaflet_data",
input: z.object({
@@ -20,7 +21,8 @@ export const get_leaflet_data = makeRoute({
`*,
permission_token_rights(*, entity_sets(permission_tokens(${leaflets_in_publications_query}))),
custom_domain_routes!custom_domain_routes_edit_permission_token_fkey(*),
- ${leaflets_in_publications_query}`,
+ ${leaflets_in_publications_query},
+ ${leaflets_to_documents_query}`,
)
.eq("id", token_id)
.single();
diff --git a/components/PageSWRDataProvider.tsx b/components/PageSWRDataProvider.tsx
index cc03de90..e9d3abe5 100644
--- a/components/PageSWRDataProvider.tsx
+++ b/components/PageSWRDataProvider.tsx
@@ -66,13 +66,27 @@ let useLeafletData = () => {
};
export function useLeafletPublicationData() {
let { data, mutate } = useLeafletData();
+
+ // First check for leaflets in publications
+ let pubData =
+ data?.leaflets_in_publications?.[0] ||
+ data?.permission_token_rights[0].entity_sets?.permission_tokens?.find(
+ (p) => p.leaflets_in_publications.length,
+ )?.leaflets_in_publications?.[0];
+
+ // If not found, check for standalone documents
+ if (!pubData && data?.leaflets_to_documents?.[0]) {
+ // Transform standalone document data to match the expected format
+ let standaloneDoc = data.leaflets_to_documents[0];
+ pubData = {
+ ...standaloneDoc,
+ publications: null, // No publication for standalone docs
+ doc: standaloneDoc.document,
+ } as any;
+ }
+
return {
- data:
- data?.leaflets_in_publications?.[0] ||
- data?.permission_token_rights[0].entity_sets?.permission_tokens?.find(
- (p) => p.leaflets_in_publications.length,
- )?.leaflets_in_publications?.[0] ||
- null,
+ data: pubData || null,
mutate,
};
}