import * as vscode from "vscode" ; import axios from "axios" ; import { writeFile , unlink } from "fs" ; import { UserDataFolder } from "../common/UserDataFolder" ; import { RestyaBoard , RestyaboardList , RestyaboardCard , RestyaboardChecklist , RestyaboardActionComment , CheckItem , RestyaboardMember , RestyaboardLabel } from "./restyaboardComponents" ; import { RestyaboardItem } from "./RestyaboardItem" ; import { VSCODE_VIEW_COLUMN , TEMP_RESTYABOARD_FILE_NAME , SETTING_PREFIX , SETTING_CONFIG , GLOBALSTATE_CONFIG , } from "./constants" ; export class RestyaboardUtils { private globalState : any ; private API_TOKEN : string | undefined ; private SITE_URL : string | undefined ; private tempRestyaboardFile : string ; constructor ( context ? ) { this . globalState = context ? context . globalState : { } ; this . tempRestyaboardFile = new UserDataFolder ( ) . getPathCodeSettings ( ) + TEMP_RESTYABOARD_FILE_NAME || "" ; this . getCredentials ( ) ; this . setMarkdownPreviewBreaks ( ) ; } setMarkdownPreviewBreaks ( ) { try { const config = vscode . workspace . getConfiguration ( "markdown.preview" , null ) ; const showPreviewBreaks = config . get < boolean > ( "breaks" ) ; if ( ! showPreviewBreaks ) { config . update ( "breaks" , true , true ) ; } } catch ( error ) { console . error ( error ) ; } } isCredentialsProvided ( ) { return ( ! this . API_TOKEN && ! this . SITE_URL ) ; } getCredentials ( ) { try { this . API_TOKEN = this . globalState . get ( GLOBALSTATE_CONFIG . API_TOKEN ) ; this . SITE_URL = this . globalState . get ( GLOBALSTATE_CONFIG . SITE_URL ) ; } catch ( error ) { console . error ( error ) ; vscode . window . showErrorMessage ( "Error getting credentials" ) ; } } setRestyaboardCredential ( isPassword , placeHolderText ) { return vscode . window . showInputBox ( { ignoreFocusOut : true , password : isPassword , placeHolder : placeHolderText } ) ; } async setCredentials ( ) { try { const siteURL = await this . setRestyaboardCredential ( false , "Your RestyaBoard URL" ) ; const apiToken = await this . setRestyaboardCredential ( true , "Your Restyaboard API token" ) ; if ( siteURL !== undefined ) this . globalState . update ( GLOBALSTATE_CONFIG . SITE_URL , siteURL ) ; if ( apiToken !== undefined ) this . globalState . update ( GLOBALSTATE_CONFIG . API_TOKEN , apiToken ) ; this . getCredentials ( ) ; } catch ( error ) { console . error ( error ) ; vscode . window . showErrorMessage ( "Error while setting credentials" ) ; } } async fetchApiToken ( siteURL ) { const apiTokenUrl = `${ siteURL }/oauth/authorize?response_type=code&client_id=9335847554774492&scope=read%20write&state=1562312999016&redirect_uri=${ siteURL }/apps/r_visualstudio/login.html` ; try { vscode . commands . executeCommand ( "vscode.open" , vscode . Uri . parse ( apiTokenUrl ) ) ; const apiToken = await this . setRestyaboardCredential ( true , "Your Restya Access token" ) ; if ( apiToken !== undefined ) this . globalState . update ( GLOBALSTATE_CONFIG . API_TOKEN , apiToken ) ; if ( siteURL !== undefined ) this . globalState . update ( GLOBALSTATE_CONFIG . SITE_URL , siteURL ) ; } catch ( error ) { console . error ( error ) ; vscode . window . showErrorMessage ( "Error fetching API token" ) ; } } async authenticate ( ) { try { const siteURL = await this . setRestyaboardCredential ( false , "Your RestyaBoard URL" ) ; if ( siteURL !== undefined ) { this . globalState . update ( GLOBALSTATE_CONFIG . SITE_URL , siteURL ) ; await this . fetchApiToken ( siteURL ) ; this . getCredentials ( ) ; } else { await vscode . window . showInformationMessage ( "Get your Restyaboard Access Token by entering your restyaboard URL" ) ; } vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; } catch ( error ) { console . error ( error ) ; vscode . window . showErrorMessage ( "Error during authentication" ) ; } } resetCredentials ( ) { Object . keys ( GLOBALSTATE_CONFIG ) . forEach ( key => { const value = GLOBALSTATE_CONFIG [ key ] ; this . globalState . update ( value , undefined ) ; } ) ; vscode . window . showInformationMessage ( "Credentials have been reset" ) ; this . getCredentials ( ) ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; } showRestyaboardInfo ( ) { this . getCredentials ( ) ; const info = `
    API_TOKEN = ${ this . API_TOKEN },
      SITE_URL = ${ this . SITE_URL }
    ` ; vscode . window . showInformationMessage ( info ) ; } async showSuccessMessage ( msg , url ? ) { let cardUrl ; if ( url ) { cardUrl = await vscode . window . showInformationMessage ( msg , url ) ; if ( cardUrl ) { vscode . commands . executeCommand ( "vscode.open" , vscode . Uri . parse ( cardUrl ) ) ; } } else { vscode . window . showInformationMessage ( msg ) ; } } async restyaboardApiGetRequest ( url , params ) { try { axios . defaults . baseURL = this . SITE_URL ; const res = await axios . get ( url , { params } ) ; return ( res . data . data ) ? res . data . data : [ ] ; } catch ( error ) { if ( error . response ) { console . error ( "GET error" , error . response ) ; vscode . window . showErrorMessage ( `HTTP error: ${ error . response . status } - ${ error . response . data }` ) ; } } return null ; } async restyaboardApiSingleGetRequest ( url , params ) { try { axios . defaults . baseURL = this . SITE_URL ; const res = await axios . get ( url , { params } ) ; return res . data ; } catch ( error ) { if ( error . response ) { console . error ( "GET error" , error . response ) ; vscode . window . showErrorMessage ( `HTTP error: ${ error . response . status } - ${ error . response . data }` ) ; } } return null ; } async restyaboardApiPostRequest ( url , data ) { try { axios . defaults . baseURL = this . SITE_URL ; const res = await axios . post ( url , data ) ; return res ; } catch ( error ) { if ( error . response ) { console . error ( "POST error" , error . response ) ; vscode . window . showErrorMessage ( `HTTP error: ${ error . response . status } - ${ error . response . data }` ) ; } } return null ; } async restyaboardApiPutRequest ( url , data ) { try { axios . defaults . baseURL = this . SITE_URL ; const res = await axios . put ( url , data ) ; return res . data ; } catch ( error ) { if ( error . response ) { console . error ( "PUT error" , error . response ) ; vscode . window . showErrorMessage ( `HTTP error: ${ error . response . status } - ${ error . response . data }` ) ; } } return null ; } async restyaboardApiDeleteRequest ( url , params ) { try { axios . defaults . baseURL = this . SITE_URL ; const res = await axios . delete ( url , { params } ) ; return res . data ; } catch ( error ) { if ( error . response ) { console . error ( "DELETE error" , error . response ) ; vscode . window . showErrorMessage ( `HTTP error: ${ error . response . status } - ${ error . response . data }` ) ; } } return null ; } async getBoardById ( boardId ) { const res = await this . restyaboardApiSingleGetRequest ( `/api/v1/boards/${ boardId }.json` , { token : this . API_TOKEN , } ) ; return res ; } async getListById ( listId ) { const list = await this . restyaboardApiGetRequest ( `/api/v1/lists/${ listId }` , { token : this . API_TOKEN , } ) ; return list ; } getBoards ( starredBoards ? ) { const res = this . restyaboardApiGetRequest ( "/api/v1/boards.json" , { filter : starredBoards ? "starred" : "all" , type : 'simple' , token : this . API_TOKEN , } ) ; console . log ( JSON . stringify ( res ) ) ; return res ; } getListsFromBoard ( boardId ) { const res = this . restyaboardApiGetRequest ( `/api/v1/boards/${ boardId }/lists.json` , { token : this . API_TOKEN , } ) ; return res ; } getCardsFromList ( listId , boardId ) { const res = this . restyaboardApiGetRequest ( `/api/v1/boards/${ boardId }/lists/${ listId }/cards.json` , { token : this . API_TOKEN , } ) ; return res ; } getCardById ( cardId , boardId , listId ) { const res = this . restyaboardApiSingleGetRequest ( `/api/v1/boards/${ boardId }/lists/${ listId }/cards/${ cardId }.json` , { token : this . API_TOKEN , } ) ; return res ; } getcardComments ( cardId , boardId , listId ) { const res = this . restyaboardApiGetRequest ( `/api/v1/boards/${ boardId }/lists/${ listId }/cards/${ cardId }/activities.json` , { token : this . API_TOKEN , view : 'modal_card' , mode : 'comment' } ) ; return res ; } async addCardToList ( list ) { if ( ! list ) { vscode . window . showErrorMessage ( "Could not get valid list" ) ; return 1 ; } const cardName = await vscode . window . showInputBox ( { ignoreFocusOut : true , placeHolder : "Enter name of card" } ) ; if ( cardName === undefined ) return 2 ; const resData = await this . restyaboardApiPostRequest ( `/api/v1/boards/${ list . boardId }/lists/${ list . id }/cards.json?token=${ this . API_TOKEN }` , { list_id : list . id , name : cardName , is_offline : true , board_id : list . boardId , } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; let card_url = this . SITE_URL + '/#/board/' + list . boardId + '/card/' + resData . data . activity . card_id ; this . showSuccessMessage ( `Created Card: ${ resData . data . activity . card_id }-${ resData . data . activity . card_name }` , card_url ) ; return 0 ; } async editTitle ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "Could not get valid card" ) ; return 1 ; } const restyaboardCard = await this . getCardById ( card . id , card . boardId , card . listId ) ; const name = await vscode . window . showInputBox ( { ignoreFocusOut : true , value : restyaboardCard . title } ) ; if ( name === undefined ) return 2 ; const resData = await this . restyaboardApiPutRequest ( `/api/v1/boards/${ card . boardId }/lists/${ card . listId }/cards/${ card . id }.json?token=${ this . API_TOKEN }` , { name , } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; this . showSuccessMessage ( `Updated title for card: ${ name }` ) ; return 0 ; } async editDescription ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "Could not get valid card" ) ; return 1 ; } const restyaboardCard = await this . getCardById ( card . id , card . boardId , card . listId ) ; let descRaw = JSON . stringify ( restyaboardCard . description ) ; descRaw = descRaw . slice ( 1 , descRaw . length - 1 ) ; const descUpdated = await vscode . window . showInputBox ( { ignoreFocusOut : true , value : descRaw , placeHolder : "Enter description for card" , } ) ; if ( descUpdated === undefined ) return 2 ; const description = descUpdated . replace ( /\\n/g , "\x0A" ) ; const resData = await this . restyaboardApiPutRequest ( `/api/v1/boards/${ card . boardId }/lists/${ card . listId }/cards/${ card . id }.json?token=${ this . API_TOKEN }` , { description , } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; this . showSuccessMessage ( `Updated description for card: ${ resData . activity . description }` ) ; return 0 ; } async addComment ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "Could not get valid card" ) ; return 1 ; } const user = await this . getSelf ( ) ; const comment = await vscode . window . showInputBox ( { ignoreFocusOut : true , placeHolder : "Add comment" , } ) ; if ( comment === undefined ) return 2 ; const resData = await this . restyaboardApiPostRequest ( `/api/v1/boards/${ card . boardId }/lists/${ card . listId }/cards/${ card . id }/comments.json?token=${ this . API_TOKEN }` , { board_id : card . boardId , card_id : card . id , is_offline : true , list_id : card . listId , user_id : user . id , comment : comment , } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; this . showSuccessMessage ( `Added comment to card: ${ card . label }` ) ; return 0 ; } private getSelf ( ) { return this . restyaboardApiSingleGetRequest ( `/api/v1/users/me.json` , { token : this . API_TOKEN , } ) ; } async addSelfToCard ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "Could not get valid Card" ) ; return 1 ; } const user = await this . getSelf ( ) ; const resData = await this . restyaboardApiPostRequest ( `/api/v1/boards/${ card . boardId }/lists/${ card . listId }/cards/${ card . id }/users/${ user . id }.json?token=${ this . API_TOKEN }` , { card_id : card . id , user_id : user . id , } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; this . showSuccessMessage ( `Added user ${ user . user . initials } to card` ) ; return 0 ; } async removeSelfFromCard ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "Could not get valid Card" ) ; return 1 ; } const user = await this . getSelf ( ) ; const usersOnCard = await this . getCardById ( card . id , card . boardId , card . listId ) ; if ( ! usersOnCard . cards_users && ! usersOnCard . cards_users . length ) { vscode . window . showErrorMessage ( "Card not assigned to you!." ) ; return 3 ; } let removeUser = usersOnCard . cards_users . find ( ( useroncard ) => useroncard . user_id == user . id ) ; if ( ! removeUser ) { vscode . window . showErrorMessage ( "Card not assigned to you!." ) ; return 3 ; } const resData = await this . restyaboardApiDeleteRequest ( `/api/v1/boards/${ card . boardId }/lists/${ card . listId }/cards/${ card . id }/cards_users/${ removeUser . id }.json` , { token : this . API_TOKEN , } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; this . showSuccessMessage ( `Removed user ${ user . user . initials } from card` ) ; return 0 ; } async addUserToCard ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "Could not get valid Card" ) ; return 1 ; } const board_details = await this . getBoardById ( card . boardId || "-1" ) ; const usersOnBoard = board_details . boards_users ; if ( ! usersOnBoard ) return 3 ; const quickPickUsers = usersOnBoard . map ( ( user ) => { return { label : user . full_name , userId : user . user_id , } ; } ) ; const addUser = await vscode . window . showQuickPick ( quickPickUsers , { placeHolder : "Add user from board:" } ) ; if ( addUser === undefined ) return 2 ; const resData = await this . restyaboardApiPostRequest ( `/api/v1/boards/${ card . boardId }/lists/${ card . listId }/cards/${ card . id }/users/${ addUser . userId }.json?token=${ this . API_TOKEN }` , { card_id : card . id , user_id : addUser . userId , } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; this . showSuccessMessage ( `Added user ${ addUser . label } to card` ) ; return 0 ; } async removeUserFromCard ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "Could not get valid Card" ) ; return 1 ; } const usersOnCard = await this . getCardById ( card . id , card . boardId , card . listId ) ; if ( ! usersOnCard ) return 3 ; const quickPickUsers = usersOnCard . cards_users . map ( ( user ) => { return { label : user . full_name , userId : user . id , } ; } ) ; const removeUser = await vscode . window . showQuickPick ( quickPickUsers , { placeHolder : "Remove user from board:" } ) ; if ( removeUser === undefined ) return 2 ; const resData = await this . restyaboardApiDeleteRequest ( `/api/v1/boards/${ card . boardId }/lists/${ card . listId }/cards/${ card . id }/cards_users/${ removeUser . userId }.json` , { token : this . API_TOKEN , } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; this . showSuccessMessage ( `Removed user ${ removeUser . label } from card` ) ; return 0 ; } async moveCardToList ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "Could not get valid card" ) ; return 1 ; } const listsForBoard = await this . getListsFromBoard ( card . boardId || "-1" ) ; if ( ! listsForBoard ) return 3 ; const quickPickLists = listsForBoard . map ( list => { return { label : list . name , listId : list . id , } ; } ) ; const restyaboardCard = await this . getCardById ( card . id , card . boardId , card . listId ) ; const toList = await vscode . window . showQuickPick ( quickPickLists , { placeHolder : "Move card to list:" } ) ; if ( toList === undefined ) return 2 ; const resData = await this . restyaboardApiPutRequest ( `/api/v1/boards/${ card . boardId }/lists/${ card . listId }/cards/${ card . id }.json?token=${ this . API_TOKEN }` , { list_id : toList . listId , board_id : card . boardId , position : restyaboardCard . position } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; this . showSuccessMessage ( `Moved card to list: ${ toList . label }` ) ; return 0 ; } async archiveCard ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "Could not get valid card" ) ; return 1 ; } const resData = await this . restyaboardApiPutRequest ( `/api/v1/boards/${ card . boardId }/lists/${ card . listId }/cards/${ card . id }.json?token=${ this . API_TOKEN }` , { is_archived : 1 , } ) ; if ( ! resData ) return 3 ; vscode . commands . executeCommand ( "restyaboardViewer.refresh" ) ; let card_url = this . SITE_URL + '/#/board/' + card . boardId + '/card/' + resData . activity . card_id ; this . showSuccessMessage ( `Archived Card: ${ resData . activity . card_id }-${ resData . activity . card_name }` , card_url ) ; return 0 ; } showCardMembersAsString ( members ) { if ( ! members || members . length == 0 ) { return "" ; } return members . map ( member => member . initials ) . join ( ", " ) ; } showCardLabelsAsString ( labels ) { if ( ! labels || labels . length == 0 ) { return "" ; } return labels . map ( label => label . name ) . join ( ", " ) ; } showChecklistsAsMarkdown ( checklists ) { if ( ! checklists || checklists . length == 0 ) { return "" ; } let checklistMarkdown = "" ; checklists . map ( checklist => { checklistMarkdown += `\n> ${ checklist . name }\n\n` ; checklist . checklists_items . sort ( ( checkItem1 , checkItem2 ) => checkItem1 . position - checkItem2 . position ) . map ( ( checkItem ) => { checklistMarkdown += checkItem . is_completed == "1" ? `✅ ~~${ checkItem . name }~~  \n` : `🔳 ${ checkItem . name }  \n` ; } ) ; } ) ; return checklistMarkdown ; } showCommentsAsMarkdown ( comments ) { if ( ! comments || comments . length == 0 ) { return "" ; } let commentsMarkdown = "" ; comments . map ( ( comment ) => { const date = new Date ( comment . created ) ; const dateString = `${ date . toLocaleString ( "en-GB" , { day : "2-digit" , month : "short" , year : "numeric" } ) }` ; const timeString = `${ date . toLocaleString ( "en-GB" , { hour : "2-digit" , minute : "2-digit" , hour12 : true , } ) }` ; let depth_string = "" ; for ( let i = 0 ; i <= comment . depth ; i ++ ) { depth_string += '>' ; } commentsMarkdown += `\n${ depth_string } ${ comment . full_name } - ${ dateString } at ${ timeString } ${ comment . revisions ? "(edited)" : "" } \n` ; commentsMarkdown += `\n\n${ comment . comment }\n\n` ; } ) ; return commentsMarkdown ; } showMarkdownDecorated ( header , content ) { return content ? `## **\`${ header }\`** \n${ content }\n\n--- \n` : "" ; } async showCard ( card ) { if ( ! card ) { vscode . window . showErrorMessage ( "No card selected or invalid card." ) ; return ; } let Cardcomments = await this . getcardComments ( card . id , card . board_id , card . list_id ) ; const cardMembers = this . showCardMembersAsString ( card . cards_users ) ; const cardLabels = this . showCardLabelsAsString ( card . cards_labels ) ; const checklistItems = this . showChecklistsAsMarkdown ( card . cards_checklists ) ; const commentItems = this . showCommentsAsMarkdown ( Cardcomments ) ; const cardCoverImageUrl = ! ! card . attachments && card . attachments . length > 0 ? card . attachments [ 0 ] . url : "" ; const cardContentAndHeaders = [ { header : "URL" , content : card . url } , { header : "Title" , content : card . name } , { header : "Board Name" , content : card . board_name } , { header : "List Name" , content : card . list_name } , { header : "Members" , content : cardMembers } , { header : "Labels" , content : cardLabels } , { header : "Description" , content : card . description } , { header : "Checklists" , content : checklistItems } , { header : "Comments" , content : commentItems } , ] ; if ( card . due_date ) { cardContentAndHeaders . push ( { header : "Due Date" , content : card . due_date } ) ; } let cardContent = "" ; cardContentAndHeaders . map ( ( { header , content } ) => { cardContent += this . showMarkdownDecorated ( header , content ) ; } ) ; cardContent += cardCoverImageUrl ? `<img src="${ cardCoverImageUrl }" alt="Image not found" />` : "" ; writeFile ( this . tempRestyaboardFile , cardContent , err => { if ( err ) { vscode . window . showErrorMessage ( `Error writing to temp file: ${ err }` ) ; } console . info ( `✍ Writing to file: ${ this . tempRestyaboardFile }` ) ; } ) ; let viewColumn = vscode . workspace . getConfiguration ( SETTING_PREFIX , null ) . get ( SETTING_CONFIG . VIEW_COLUMN ) || SETTING_CONFIG . DEFAULT_VIEW_COLUMN ; if ( ! ( VSCODE_VIEW_COLUMN . indexOf ( viewColumn ) > - 1 ) ) { vscode . window . showInformationMessage ( `Invalid ${ SETTING_PREFIX }.viewColumn ${ viewColumn } specified` ) ; viewColumn = SETTING_CONFIG . DEFAULT_VIEW_COLUMN ; } const doc = await vscode . workspace . openTextDocument ( this . tempRestyaboardFile ) ; await vscode . window . showTextDocument ( doc , viewColumn , false ) ; await vscode . commands . executeCommand ( "markdown.showPreview" ) ; vscode . commands . executeCommand ( "markdown.preview.toggleLock" ) ; } } export function removeTempRestyaboardFile ( ) { const userDataFolder = new UserDataFolder ( ) ; const tempRestyaboardFile = userDataFolder . getPathCodeSettings ( ) + TEMP_RESTYABOARD_FILE_NAME ; unlink ( tempRestyaboardFile , err => { if ( err ) throw err ; console . info ( `❌ Deleted file: ${ tempRestyaboardFile }` ) ; } ) ; }