a tool for shared writing and social publishing
at update/delete-leaflets 256 lines 7.2 kB view raw
1import { useReplicache } from "src/replicache"; 2import React, { useEffect, useState } from "react"; 3import { getShareLink } from "./getShareLink"; 4import { useEntitySetContext } from "components/EntitySetProvider"; 5import { useSmoker } from "components/Toast"; 6import { Menu, MenuItem } from "components/Layout"; 7import { ActionButton } from "components/ActionBar/ActionButton"; 8import useSWR from "swr"; 9import LoginForm from "app/login/LoginForm"; 10import { CustomDomainMenu } from "./DomainOptions"; 11import { useIdentityData } from "components/IdentityProvider"; 12import { 13 useLeafletDomains, 14 useLeafletPublicationData, 15} from "components/PageSWRDataProvider"; 16import { ShareSmall } from "components/Icons/ShareSmall"; 17import { PubLeafletDocument } from "lexicons/api"; 18import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; 19import { AtUri } from "@atproto/syntax"; 20import { useIsMobile } from "src/hooks/isMobile"; 21 22export type ShareMenuStates = "default" | "login" | "domain"; 23 24export let usePublishLink = () => { 25 let { permission_token, rootEntity } = useReplicache(); 26 let entity_set = useEntitySetContext(); 27 let { data: publishLink } = useSWR( 28 "publishLink-" + permission_token.id, 29 async () => { 30 if ( 31 !permission_token.permission_token_rights.find( 32 (s) => s.entity_set === entity_set.set && s.create_token, 33 ) 34 ) 35 return; 36 let shareLink = await getShareLink( 37 { id: permission_token.id, entity_set: entity_set.set }, 38 rootEntity, 39 ); 40 return shareLink?.id; 41 }, 42 ); 43 return publishLink; 44}; 45 46export function ShareOptions() { 47 let [menuState, setMenuState] = useState<ShareMenuStates>("default"); 48 let { data: pub } = useLeafletPublicationData(); 49 let isMobile = useIsMobile(); 50 51 return ( 52 <Menu 53 asChild 54 side={isMobile ? "top" : "right"} 55 align={isMobile ? "center" : "start"} 56 className="max-w-xs" 57 onOpenChange={() => { 58 setMenuState("default"); 59 }} 60 trigger={ 61 <ActionButton 62 icon=<ShareSmall /> 63 primary={!!!pub} 64 secondary={!!pub} 65 label={`Share ${pub ? "Draft" : ""}`} 66 /> 67 } 68 > 69 {menuState === "login" ? ( 70 <div className="px-3 py-1"> 71 <LoginForm text="Save your Leaflets and access them on multiple devices!" /> 72 </div> 73 ) : menuState === "domain" ? ( 74 <CustomDomainMenu setShareMenuState={setMenuState} /> 75 ) : ( 76 <ShareMenu 77 setMenuState={setMenuState} 78 domainConnected={false} 79 isPub={!!pub} 80 /> 81 )} 82 </Menu> 83 ); 84} 85 86const ShareMenu = (props: { 87 setMenuState: (state: ShareMenuStates) => void; 88 domainConnected: boolean; 89 isPub?: boolean; 90}) => { 91 let { permission_token } = useReplicache(); 92 let { data: pub } = useLeafletPublicationData(); 93 94 let record = pub?.documents?.data as PubLeafletDocument.Record | null; 95 96 let postLink = 97 pub?.publications && pub.documents 98 ? `${getPublicationURL(pub.publications)}/${new AtUri(pub?.documents.uri).rkey}` 99 : null; 100 let publishLink = usePublishLink(); 101 let [collabLink, setCollabLink] = useState<null | string>(null); 102 useEffect(() => { 103 // strip leading '/' character from pathname 104 setCollabLink(window.location.pathname.slice(1)); 105 }, []); 106 let { data: domains } = useLeafletDomains(); 107 108 return ( 109 <> 110 <ShareButton 111 text={`Share ${postLink ? "Draft" : ""} Edit Link`} 112 subtext="" 113 smokerText="Edit link copied!" 114 id="get-edit-link" 115 link={collabLink} 116 /> 117 <ShareButton 118 text={`Share ${postLink ? "Draft" : ""} View Link`} 119 subtext=<> 120 {domains?.[0] ? ( 121 <> 122 This Leaflet is published on{" "} 123 <span className="italic underline"> 124 {domains[0].domain} 125 {domains[0].route} 126 </span> 127 </> 128 ) : ( 129 "" 130 )} 131 </> 132 smokerText="View link copied!" 133 id="get-view-link" 134 fullLink={ 135 domains?.[0] 136 ? `https://${domains[0].domain}${domains[0].route}` 137 : undefined 138 } 139 link={publishLink || ""} 140 /> 141 {postLink && ( 142 <> 143 <hr className="border-border-light" /> 144 145 <ShareButton 146 text="Share Published Link" 147 subtext="" 148 smokerText="Post link copied!" 149 id="get-post-link" 150 fullLink={postLink.includes("http") ? postLink : undefined} 151 link={postLink} 152 /> 153 </> 154 )} 155 {!props.isPub && ( 156 <> 157 <hr className="border-border mt-1" /> 158 <DomainMenuItem setMenuState={props.setMenuState} /> 159 </> 160 )} 161 </> 162 ); 163}; 164 165export const ShareButton = (props: { 166 text: React.ReactNode; 167 subtext?: React.ReactNode; 168 smokerText: string; 169 id: string; 170 link: null | string; 171 fullLink?: string; 172 className?: string; 173}) => { 174 let smoker = useSmoker(); 175 176 return ( 177 <MenuItem 178 id={props.id} 179 onSelect={(e) => { 180 e.preventDefault(); 181 let rect = document.getElementById(props.id)?.getBoundingClientRect(); 182 if (props.link || props.fullLink) { 183 navigator.clipboard.writeText( 184 props.fullLink 185 ? props.fullLink 186 : `${location.protocol}//${location.host}/${props.link}`, 187 ); 188 smoker({ 189 position: { 190 x: rect ? rect.left + (rect.right - rect.left) / 2 : 0, 191 y: rect ? rect.top + 26 : 0, 192 }, 193 text: props.smokerText, 194 }); 195 } 196 }} 197 > 198 <div className={`group/${props.id} ${props.className} leading-snug`}> 199 {props.text} 200 201 {props.subtext && ( 202 <div className={`text-sm font-normal text-tertiary`}> 203 {props.subtext} 204 </div> 205 )} 206 </div> 207 </MenuItem> 208 ); 209}; 210 211const DomainMenuItem = (props: { 212 setMenuState: (state: ShareMenuStates) => void; 213}) => { 214 let { identity } = useIdentityData(); 215 let { data: domains } = useLeafletDomains(); 216 217 if (identity === null) 218 return ( 219 <div className="text-tertiary font-normal text-sm px-3 py-1"> 220 <button 221 className="text-accent-contrast hover:font-bold" 222 onClick={() => { 223 props.setMenuState("login"); 224 }} 225 > 226 Log In 227 </button>{" "} 228 to publish on a custom domain! 229 </div> 230 ); 231 else 232 return ( 233 <> 234 {domains?.[0] ? ( 235 <button 236 className="px-3 py-1 text-accent-contrast text-sm hover:font-bold w-fit text-left" 237 onMouseDown={() => { 238 props.setMenuState("domain"); 239 }} 240 > 241 Edit custom domain 242 </button> 243 ) : ( 244 <MenuItem 245 className="font-normal text-tertiary text-sm" 246 onSelect={(e) => { 247 e.preventDefault(); 248 props.setMenuState("domain"); 249 }} 250 > 251 Publish on a custom domain 252 </MenuItem> 253 )} 254 </> 255 ); 256};