a tool for shared writing and social publishing
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};