a tool for shared writing and social publishing

wire up backdating posts from the publish flow. still broken from update button

+36 -97
-86
actions/backdatePost.ts
··· 1 - "use server"; 2 - 3 - import { AtpBaseClient, PubLeafletDocument } from "lexicons/api"; 4 - import { restoreOAuthSession, OAuthSessionError } from "src/atproto-oauth"; 5 - import { getIdentityData } from "actions/getIdentityData"; 6 - import { supabaseServerClient } from "supabase/serverClient"; 7 - import { Json } from "supabase/database.types"; 8 - import { AtUri } from "@atproto/syntax"; 9 - 10 - type BackdateResult = 11 - | { success: true; publishedAt: string } 12 - | { success: false; error?: OAuthSessionError | string }; 13 - 14 - export async function backdatePost({ 15 - uri, 16 - publishedAt, 17 - }: { 18 - uri: string; 19 - publishedAt: string; 20 - }): Promise<BackdateResult> { 21 - let identity = await getIdentityData(); 22 - if (!identity || !identity.atp_did) { 23 - return { 24 - success: false, 25 - error: "Not authenticated", 26 - }; 27 - } 28 - 29 - const sessionResult = await restoreOAuthSession(identity.atp_did); 30 - if (!sessionResult.ok) { 31 - return { success: false, error: sessionResult.error }; 32 - } 33 - let credentialSession = sessionResult.value; 34 - let agent = new AtpBaseClient( 35 - credentialSession.fetchHandler.bind(credentialSession), 36 - ); 37 - 38 - // Get the existing document 39 - let { data: existingDoc } = await supabaseServerClient 40 - .from("documents") 41 - .select("*") 42 - .eq("uri", uri) 43 - .single(); 44 - 45 - if (!existingDoc) { 46 - return { success: false, error: "Document not found" }; 47 - } 48 - 49 - let record = existingDoc.data as PubLeafletDocument.Record; 50 - 51 - // Check if the user is the author 52 - if (record.author !== identity.atp_did) { 53 - return { success: false, error: "Not authorized" }; 54 - } 55 - 56 - let aturi = new AtUri(uri); 57 - 58 - // Update the record with the new publishedAt date 59 - let updatedRecord: PubLeafletDocument.Record = { 60 - ...record, 61 - publishedAt, 62 - }; 63 - 64 - // Update the record on ATP 65 - let result = await agent.com.atproto.repo.putRecord({ 66 - repo: credentialSession.did!, 67 - rkey: aturi.rkey, 68 - record: updatedRecord, 69 - collection: record.$type, 70 - validate: false, 71 - }); 72 - 73 - // Optimistically write to our db 74 - let { error } = await supabaseServerClient 75 - .from("documents") 76 - .update({ 77 - data: updatedRecord as Json, 78 - }) 79 - .eq("uri", uri); 80 - 81 - if (error) { 82 - return { success: false, error: error.message }; 83 - } 84 - 85 - return { success: true, publishedAt }; 86 - }
+1 -1
actions/publishToPublication.ts
··· 201 201 } 202 202 }), 203 203 publishedAt: 204 - existingRecord?.publishedAt || publishedAt || new Date().toISOString(), 204 + publishedAt || existingRecord?.publishedAt || new Date().toISOString(), 205 205 }; 206 206 207 207 // Keep the same rkey if updating an existing document
+7
app/[leaflet_id]/actions/PublishButton.tsx
··· 95 95 tx.get<string | null>("publication_cover_image"), 96 96 ); 97 97 98 + // Get localPublishedAt from Replicache state 99 + let localPublishedAt = useSubscribe(rep, (tx) => 100 + tx.get<string | null>("publication_local_published_at"), 101 + ); 102 + console.log("local: " + localPublishedAt); 103 + 98 104 return ( 99 105 <ActionButton 100 106 primary ··· 111 117 description: currentDescription, 112 118 tags: currentTags, 113 119 cover_image: coverImage, 120 + ...(localPublishedAt && { publishedAt: localPublishedAt }), 114 121 }); 115 122 setIsLoading(false); 116 123 mutate();
+2 -3
app/[leaflet_id]/publish/PublishPost.tsx
··· 92 92 ); 93 93 94 94 // Use Replicache tags only when we have a draft 95 - const hasDraft = props.hasDraft; 96 - const currentTags = hasDraft 95 + const currentTags = props.hasDraft 97 96 ? Array.isArray(replicacheTags) 98 97 ? replicacheTags 99 98 : [] ··· 101 100 102 101 // Update tags via Replicache mutation or local state depending on context 103 102 const handleTagsChange = async (newTags: string[]) => { 104 - if (hasDraft) { 103 + if (props.hasDraft) { 105 104 await rep?.mutate.updatePublicationDraft({ 106 105 tags: newTags, 107 106 });
+22 -5
components/Pages/Backdater.tsx
··· 4 4 import { timeAgo } from "src/utils/timeAgo"; 5 5 import { Popover } from "components/Popover"; 6 6 import { Separator } from "react-aria-components"; 7 + import { useReplicache } from "src/replicache"; 7 8 8 9 export const Backdater = (props: { publishedAt: string }) => { 10 + let { rep } = useReplicache(); 9 11 let [localPublishedAt, setLocalPublishedAt] = useState( 10 12 new Date(props.publishedAt), 11 13 ); ··· 16 18 17 19 let currentTime = `${new Date().getHours().toString().padStart(2, "0")}:${new Date().getMinutes().toString().padStart(2, "0")}`; 18 20 19 - const handleTimeChange = (time: string) => { 21 + const handleTimeChange = async (time: string) => { 20 22 setTimeValue(time); 21 23 const [hours, minutes] = time.split(":").map((str) => parseInt(str, 10)); 22 24 const newDate = new Date(localPublishedAt); ··· 27 29 if (newDate > currentDate) { 28 30 setLocalPublishedAt(currentDate); 29 31 setTimeValue(currentTime); 30 - } else setLocalPublishedAt(newDate); 32 + await rep?.mutate.updatePublicationDraft({ 33 + localPublishedAt: currentDate.toISOString(), 34 + }); 35 + } else { 36 + setLocalPublishedAt(newDate); 37 + await rep?.mutate.updatePublicationDraft({ 38 + localPublishedAt: newDate.toISOString(), 39 + }); 40 + } 31 41 }; 32 42 33 - const handleDateChange = (date: Date | undefined) => { 43 + const handleDateChange = async (date: Date | undefined) => { 34 44 if (!date) return; 35 45 const [hours, minutes] = timeValue 36 46 .split(":") ··· 43 53 if (newDate > currentDate) { 44 54 setLocalPublishedAt(currentDate); 45 55 setTimeValue(currentTime); 46 - } else setLocalPublishedAt(newDate); 56 + await rep?.mutate.updatePublicationDraft({ 57 + localPublishedAt: currentDate.toISOString(), 58 + }); 59 + } else { 60 + setLocalPublishedAt(newDate); 61 + await rep?.mutate.updatePublicationDraft({ 62 + localPublishedAt: newDate.toISOString(), 63 + }); 64 + } 47 65 }; 48 - console.log(localPublishedAt); 49 66 50 67 return ( 51 68 <Popover
+4 -2
src/replicache/mutations.ts
··· 637 637 description?: string; 638 638 tags?: string[]; 639 639 cover_image?: string | null; 640 + localPublishedAt?: string | null; 640 641 }> = async (args, ctx) => { 641 642 await ctx.runOnServer(async (serverCtx) => { 642 643 console.log("updating"); ··· 670 671 } 671 672 }); 672 673 await ctx.runOnClient(async ({ tx }) => { 673 - if (args.title !== undefined) 674 - await tx.set("publication_title", args.title); 674 + if (args.title !== undefined) await tx.set("publication_title", args.title); 675 675 if (args.description !== undefined) 676 676 await tx.set("publication_description", args.description); 677 677 if (args.tags !== undefined) await tx.set("publication_tags", args.tags); 678 678 if (args.cover_image !== undefined) 679 679 await tx.set("publication_cover_image", args.cover_image); 680 + if (args.localPublishedAt !== undefined) 681 + await tx.set("publication_local_published_at", args.localPublishedAt); 680 682 }); 681 683 }; 682 684