Skip to content

Commit a795a8d

Browse files
committed
initial port
1 parent 8203cd1 commit a795a8d

File tree

10 files changed

+1462
-117
lines changed

10 files changed

+1462
-117
lines changed

apps/roam/src/components/Export.tsx

Lines changed: 124 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -82,23 +82,26 @@ const ExportProgress = ({ id }: { id: string }) => {
8282
);
8383
};
8484

85+
const EXPORT_DESTINATIONS = [
86+
{ id: "local", label: "Download Locally", active: true },
87+
{ id: "app", label: "Store in Roam", active: false },
88+
{ id: "github", label: "Send to GitHub", active: true },
89+
];
90+
8591
export type ExportDialogProps = {
8692
results?: Result[];
8793
title?: string;
8894
columns?: Column[];
8995
isExportDiscourseGraph?: boolean;
9096
initialPanel?: "sendTo" | "export";
97+
initialExportDestination?: (typeof EXPORT_DESTINATIONS)[number]["id"];
98+
onClose?: () => void;
9199
};
92100

93101
type ExportDialogComponent = (
94102
props: RoamOverlayProps<ExportDialogProps>,
95103
) => JSX.Element;
96104

97-
const EXPORT_DESTINATIONS = [
98-
{ id: "local", label: "Download Locally", active: true },
99-
{ id: "app", label: "Store in Roam", active: false },
100-
{ id: "github", label: "Send to GitHub", active: true },
101-
];
102105
const SEND_TO_DESTINATIONS = ["page", "graph"];
103106

104107
const exportDestinationById = Object.fromEntries(
@@ -113,6 +116,7 @@ const ExportDialog: ExportDialogComponent = ({
113116
title = "Share Data",
114117
isExportDiscourseGraph = false,
115118
initialPanel,
119+
initialExportDestination,
116120
}) => {
117121
const [selectedRepo, setSelectedRepo] = useState<string>(
118122
getSetting<string>("selected-repo", ""),
@@ -143,7 +147,11 @@ const ExportDialog: ExportDialogComponent = ({
143147
exportTypes[0].name,
144148
);
145149
const [activeExportDestination, setActiveExportDestination] =
146-
useState<string>(EXPORT_DESTINATIONS[0].id);
150+
useState<string>(
151+
initialExportDestination
152+
? exportDestinationById[initialExportDestination].id
153+
: EXPORT_DESTINATIONS[0].id,
154+
);
147155

148156
const firstColumnKey = columns?.[0]?.key || "text";
149157
const currentPageUid = getCurrentPageUid();
@@ -177,9 +185,15 @@ const ExportDialog: ExportDialogComponent = ({
177185
content: string;
178186
setError: (error: string) => void;
179187
}): Promise<{ status: number }> => {
180-
const base64Content = btoa(content);
188+
const gitHubAccessToken = localStorageGet("github-oauth");
189+
const selectedRepo = localStorageGet("github-repo");
190+
191+
const encoder = new TextEncoder();
192+
const uint8Array = encoder.encode(content);
193+
const base64Content = btoa(String.fromCharCode(...uint8Array));
181194

182195
try {
196+
// https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#create-or-update-file-contents
183197
const response = await apiPut({
184198
domain: "https://api.github.com",
185199
path: `repos/${selectedRepo}/contents/${filename}`,
@@ -192,7 +206,6 @@ const ExportDialog: ExportDialogComponent = ({
192206
},
193207
});
194208
if (response.status === 401) {
195-
setGitHubAccessToken(null);
196209
setError("Authentication failed. Please log in again.");
197210
setSetting("oauth-github", "");
198211
return { status: 401 };
@@ -209,6 +222,74 @@ const ExportDialog: ExportDialogComponent = ({
209222
}
210223
};
211224

225+
const writeFileToIssue = async ({
226+
title,
227+
body,
228+
setError,
229+
pageUid,
230+
}: {
231+
title: string;
232+
body: string;
233+
setError: (error: string) => void;
234+
pageUid: string;
235+
}): Promise<{ status: number }> => {
236+
const gitHubAccessToken = localStorageGet("github-oauth");
237+
const selectedRepo = localStorageGet("github-repo");
238+
try {
239+
// https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#create-an-issue
240+
const response = await apiPost({
241+
domain: "https://api.github.com",
242+
path: `repos/${selectedRepo}/issues`,
243+
headers: {
244+
Authorization: `token ${gitHubAccessToken}`,
245+
},
246+
data: {
247+
title,
248+
body,
249+
// milestone,
250+
// labels,
251+
// assignees
252+
},
253+
});
254+
if (response.status === 401) {
255+
setError("Authentication failed. Please log in again.");
256+
localStorageSet("github-oauth", "");
257+
return { status: 401 };
258+
}
259+
260+
if (response.status === 201) {
261+
const props = getBlockProps(pageUid);
262+
const newProps = {
263+
...props,
264+
["github-sync"]: {
265+
issue: {
266+
id: response.id,
267+
number: response.number,
268+
html_url: response.html_url,
269+
state: response.state,
270+
labels: response.labels,
271+
createdAt: response.created_at,
272+
updatedAt: response.updated_at,
273+
repo: selectedRepo,
274+
},
275+
},
276+
};
277+
window.roamAlphaAPI.updateBlock({
278+
block: {
279+
uid: pageUid,
280+
props: newProps,
281+
},
282+
});
283+
}
284+
285+
return { status: response.status };
286+
} catch (error) {
287+
const e = error as Error;
288+
setError("Failed to create issue");
289+
return { status: 500 };
290+
}
291+
};
292+
212293
const handleSetSelectedPage = (title: string) => {
213294
setSelectedPageTitle(title);
214295
setSelectedPageUid(getPageUidByPageTitle(title));
@@ -521,15 +602,12 @@ const ExportDialog: ExportDialogComponent = ({
521602
onItemSelect={(et) => setActiveExportDestination(et)}
522603
/>
523604
</Label>
524-
<ExportGithub
525-
isVisible={activeExportDestination === "github"}
526-
selectedRepo={selectedRepo}
527-
setSelectedRepo={setSelectedRepo}
528-
setError={setError}
529-
gitHubAccessToken={gitHubAccessToken}
530-
setGitHubAccessToken={setGitHubAccessToken}
531-
setCanSendToGitHub={setCanSendToGitHub}
532-
/>
605+
{activeExportDestination === "github" && (
606+
<ExportGithub
607+
setError={setError}
608+
setCanSendToGitHub={setCanSendToGitHub}
609+
/>
610+
)}
533611
</div>
534612
</div>
535613

@@ -620,17 +698,40 @@ const ExportDialog: ExportDialogComponent = ({
620698

621699
if (activeExportDestination === "github") {
622700
const { title, content } = files[0];
701+
const githubDestination =
702+
localStorageGet("github-destination");
623703
try {
624-
const { status } = await writeFileToRepo({
625-
filename: title,
626-
content,
627-
setError,
628-
});
704+
let status;
705+
if (githubDestination === "File") {
706+
status = (
707+
await writeFileToRepo({
708+
filename: title,
709+
content,
710+
setError,
711+
})
712+
).status;
713+
}
714+
if (githubDestination === "Issue") {
715+
const pageUid =
716+
typeof results === "function" ? "" : results[0].uid; // TODO handle multiple results
717+
if (!pageUid) {
718+
setError("No page UID found.");
719+
return;
720+
}
721+
status = (
722+
await writeFileToIssue({
723+
title: title.replace(/\.[^/.]+$/, ""), // remove extension
724+
body: content,
725+
setError,
726+
pageUid,
727+
})
728+
).status;
729+
}
629730
if (status === 201) {
630731
// TODO: remove toast by prolonging ExportProgress
631732
renderToast({
632733
id: "export-success",
633-
content: "Upload Success",
734+
content: `Upload Success to ${githubDestination}`,
634735
intent: "success",
635736
});
636737
onClose();

0 commit comments

Comments
 (0)