@@ -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+
8591export 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
93101type 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- ] ;
102105const SEND_TO_DESTINATIONS = [ "page" , "graph" ] ;
103106
104107const 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