Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/roam/scripts/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ export const compile = ({
minify: process.env.NODE_ENV === "production",
entryNames: out,
external: externalModules.map(([e]) => e).concat(["crypto"]),
define: {
"process.env.NODE_ENV": `"${process.env.NODE_ENV}"`,
},
plugins: [
importAsGlobals(
Object.fromEntries(
Expand Down
151 changes: 126 additions & 25 deletions apps/roam/src/components/Export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,23 +82,26 @@ const ExportProgress = ({ id }: { id: string }) => {
);
};

const EXPORT_DESTINATIONS = [
{ id: "local", label: "Download Locally", active: true },
{ id: "app", label: "Store in Roam", active: false },
{ id: "github", label: "Send to GitHub", active: true },
];

export type ExportDialogProps = {
results?: Result[];
title?: string;
columns?: Column[];
isExportDiscourseGraph?: boolean;
initialPanel?: "sendTo" | "export";
initialExportDestination?: (typeof EXPORT_DESTINATIONS)[number]["id"];
onClose?: () => void;
};

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

const EXPORT_DESTINATIONS = [
{ id: "local", label: "Download Locally", active: true },
{ id: "app", label: "Store in Roam", active: false },
{ id: "github", label: "Send to GitHub", active: true },
];
const SEND_TO_DESTINATIONS = ["page", "graph"];

const exportDestinationById = Object.fromEntries(
Expand All @@ -113,9 +116,10 @@ const ExportDialog: ExportDialogComponent = ({
title = "Share Data",
isExportDiscourseGraph = false,
initialPanel,
initialExportDestination,
}) => {
const [selectedRepo, setSelectedRepo] = useState<string>(
getSetting<string>("selected-repo", ""),
getSetting("selected-repo"),
);
const exportId = useMemo(() => nanoid(), []);
useEffect(() => {
Expand Down Expand Up @@ -143,7 +147,11 @@ const ExportDialog: ExportDialogComponent = ({
exportTypes[0].name,
);
const [activeExportDestination, setActiveExportDestination] =
useState<string>(EXPORT_DESTINATIONS[0].id);
useState<string>(
initialExportDestination
? exportDestinationById[initialExportDestination].id
: EXPORT_DESTINATIONS[0].id,
);

const firstColumnKey = columns?.[0]?.key || "text";
const currentPageUid = getCurrentPageUid();
Expand All @@ -163,7 +171,7 @@ const ExportDialog: ExportDialogComponent = ({
}, [initialPanel]);
const [includeDiscourseContext, setIncludeDiscourseContext] = useState(false);
const [gitHubAccessToken, setGitHubAccessToken] = useState<string | null>(
getSetting<string | null>("oauth-github", null),
getSetting<string>("oauth-github"),
);

const [canSendToGitHub, setCanSendToGitHub] = useState(false);
Expand All @@ -177,9 +185,15 @@ const ExportDialog: ExportDialogComponent = ({
content: string;
setError: (error: string) => void;
}): Promise<{ status: number }> => {
const base64Content = btoa(content);
const gitHubAccessToken = getSetting("github-oauth");
const selectedRepo = getSetting("github-repo");

const encoder = new TextEncoder();
const uint8Array = encoder.encode(content);
const base64Content = btoa(String.fromCharCode(...uint8Array));

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

const writeFileToIssue = async ({
title,
body,
setError,
pageUid,
}: {
title: string;
body: string;
setError: (error: string) => void;
pageUid: string;
}): Promise<{ status: number }> => {
const gitHubAccessToken = getSetting("github-oauth");
const selectedRepo = getSetting("github-repo");
try {
// https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#create-an-issue
const response = await apiPost({
domain: "https://api.github.com",
path: `repos/${selectedRepo}/issues`,
headers: {
Authorization: `token ${gitHubAccessToken}`,
},
data: {
title,
body,
// milestone,
// labels,
// assignees
},
});
if (response.status === 401) {
setError("Authentication failed. Please log in again.");
setSetting("github-oauth", "");
return { status: 401 };
}

if (response.status === 201) {
const props = getBlockProps(pageUid);
const newProps = {
...props,
["github-sync"]: {
issue: {
id: response.id,
number: response.number,
html_url: response.html_url,
state: response.state,
labels: response.labels,
createdAt: response.created_at,
updatedAt: response.updated_at,
repo: selectedRepo,
},
},
};
window.roamAlphaAPI.updateBlock({
block: {
uid: pageUid,
props: newProps,
},
});
}

return { status: response.status };
} catch (error) {
const e = error as Error;
setError("Failed to create issue");
return { status: 500 };
}
};

const handleSetSelectedPage = (title: string) => {
setSelectedPageTitle(title);
setSelectedPageUid(getPageUidByPageTitle(title));
Expand Down Expand Up @@ -521,15 +602,12 @@ const ExportDialog: ExportDialogComponent = ({
onItemSelect={(et) => setActiveExportDestination(et)}
/>
</Label>
<ExportGithub
isVisible={activeExportDestination === "github"}
selectedRepo={selectedRepo}
setSelectedRepo={setSelectedRepo}
setError={setError}
gitHubAccessToken={gitHubAccessToken}
setGitHubAccessToken={setGitHubAccessToken}
setCanSendToGitHub={setCanSendToGitHub}
/>
{activeExportDestination === "github" && (
<ExportGithub
setError={setError}
setCanSendToGitHub={setCanSendToGitHub}
/>
)}
</div>
</div>

Expand Down Expand Up @@ -620,17 +698,40 @@ const ExportDialog: ExportDialogComponent = ({

if (activeExportDestination === "github") {
const { title, content } = files[0];
const githubDestination =
getSetting("github-destination");
try {
const { status } = await writeFileToRepo({
filename: title,
content,
setError,
});
let status;
if (githubDestination === "File") {
status = (
await writeFileToRepo({
filename: title,
content,
setError,
})
).status;
}
if (githubDestination === "Issue") {
const pageUid =
typeof results === "function" ? "" : results[0].uid; // TODO handle multiple results
if (!pageUid) {
setError("No page UID found.");
return;
}
status = (
await writeFileToIssue({
title: title.replace(/\.[^/.]+$/, ""), // remove extension
body: content,
setError,
pageUid,
})
).status;
}
if (status === 201) {
// TODO: remove toast by prolonging ExportProgress
renderToast({
id: "export-success",
content: "Upload Success",
content: `Upload Success to ${githubDestination}`,
intent: "success",
});
onClose();
Expand Down
Loading