diff --git a/crates/jirac/src/cmd/view.rs b/crates/jirac/src/cmd/view.rs index 1003b59..0592c07 100644 --- a/crates/jirac/src/cmd/view.rs +++ b/crates/jirac/src/cmd/view.rs @@ -1,13 +1,13 @@ use crate::cli::FormatMode; use crate::jira_config::JiraConfig; -use crate::renderer::hyperlink; +use crate::renderer::{hyperlink, render_doc}; use crossterm::style::{Color, Stylize}; use libjirac::client::commands::IssueGetCommand; use libjirac::client::JiraClient; -use libjirac::entities::issue::JiraIssue; +use libjirac::entities::issue::{Description, JiraIssue}; use std::io::Write; -fn pretty_print(issue: &JiraIssue) -> Result<(), Box> { +fn pretty_print(config: &JiraConfig, issue: &JiraIssue) -> Result<(), Box> { println!("\n== Title {:=<71}", ""); println!( "{}: {}", @@ -65,7 +65,9 @@ fn pretty_print(issue: &JiraIssue) -> Result<(), Box> { print!("{}", written); println!("\n== Description {:=<65}", ""); match issue.fields.description.clone() { - Some(x) => println!("{}", x), + Some(x) => match x { + Description::Doc(doc) => println!("{}", render_doc(doc)?), + }, None => println!("(Issue does not have a description)"), } @@ -76,13 +78,13 @@ fn pretty_print(issue: &JiraIssue) -> Result<(), Box> { comment.author.display_name.red(), comment.created.with_timezone(&chrono::Local) ); - println!("{}", comment.body); + println!("{}", render_doc(comment.body)?); } println!("\n== Actions {:=<69}", ""); println!( "{}", hyperlink( - &issue.href, + &format!("{}/browse/{}", config.url, issue.key), &"Open Issue".green().underline(Color::Green).to_string() ) ); @@ -108,7 +110,7 @@ pub async fn exec(output: FormatMode, issue_key: &str) -> Result<(), Box pretty_print(&fetched_issue)?, + FormatMode::Pretty => pretty_print(&config, &fetched_issue)?, FormatMode::Json => json_print(&fetched_issue)?, FormatMode::Compact => todo!(), } diff --git a/crates/jirac/src/renderer.rs b/crates/jirac/src/renderer.rs index f3fd1d1..1329c8a 100644 --- a/crates/jirac/src/renderer.rs +++ b/crates/jirac/src/renderer.rs @@ -1,3 +1,5 @@ +mod doc; mod hyperlink; +pub use doc::render_doc; pub use hyperlink::hyperlink; diff --git a/crates/jirac/src/renderer/doc.rs b/crates/jirac/src/renderer/doc.rs new file mode 100644 index 0000000..939cfab --- /dev/null +++ b/crates/jirac/src/renderer/doc.rs @@ -0,0 +1,109 @@ +use crossterm::style::Stylize; +use libjirac::entities::doc::{Doc, DocMedia, DocNode, DocTextMarker, DocTextNode}; +use std::fmt::Write; + +fn render_doc_node(f: &mut String, node: &DocNode) -> Result<(), Box> { + match node { + DocNode::Paragraph(x) => { + for node in x { + render_text_node(f, node)?; + } + writeln!(f)?; + } + DocNode::BulletList(x) => { + for node in x { + write!(f, "* ")?; + render_doc_node(f, node)?; + } + } + DocNode::ListItem(x) => { + for node in x { + render_doc_node(f, node)?; + } + } + DocNode::OrderedList(x) => { + let mut i = 1; + for node in x { + write!(f, "{}. ", i)?; + i += 1; + render_doc_node(f, node)?; + } + } + DocNode::Rule => { + writeln!(f, "---")?; + } + DocNode::Heading(x) => { + for node in x { + render_text_node(f, node)?; + } + } + DocNode::MediaSingle(x) => { + for node in x { + render_text_markets(f, node)?; + } + } + } + + Ok(()) +} + +fn render_text_node(f: &mut String, node: &DocTextNode) -> Result<(), Box> { + match node { + DocTextNode::Text(x) => { + write!(f, "{}", x.text)?; + } + DocTextNode::HardBreak => { + writeln!(f)?; + } + DocTextNode::InlineCard(x) => { + writeln!(f, "{}", x.attrs.url)?; + } + DocTextNode::Mention(x) => { + write!(f, "{}", x.attrs.text.clone().green())?; + } + } + + Ok(()) +} + +fn render_text_markets( + f: &mut String, + node: &DocTextMarker, +) -> Result<(), Box> { + match node { + DocTextMarker::Link(x) => { + write!(f, "{}", x.href)?; + } + DocTextMarker::Strong => { + // no-op + } + DocTextMarker::Code => { + // no-op + } + DocTextMarker::Media(x) => { + render_media(f, x)?; + } + } + + Ok(()) +} + +fn render_media(f: &mut String, node: &DocMedia) -> Result<(), Box> { + match node { + DocMedia::File(x) => { + writeln!(f, "[file:{}:{}]", x.id, x.alt)?; + } + } + + Ok(()) +} + +pub fn render_doc(doc: Doc) -> Result> { + let mut f = String::new(); + + for node in doc.content { + render_doc_node(&mut f, &node)?; + } + + Ok(f) +} diff --git a/crates/libjirac/src/client/commands/issue_create_command.rs b/crates/libjirac/src/client/commands/issue_create_command.rs index ce19ae3..390ed0c 100644 --- a/crates/libjirac/src/client/commands/issue_create_command.rs +++ b/crates/libjirac/src/client/commands/issue_create_command.rs @@ -18,7 +18,7 @@ impl JiraCommand for IssueCreateCommand { const REQUEST_TYPE: JiraRequestType = JiraRequestType::Create; fn endpoint(&self) -> String { - "/rest/api/2/issue".to_string() + "/rest/api/3/issue".to_string() } fn request_body(&self) -> Option<&Self::TPayload> { diff --git a/crates/libjirac/src/client/commands/issue_get_command.rs b/crates/libjirac/src/client/commands/issue_get_command.rs index e52a5f7..2b04f93 100644 --- a/crates/libjirac/src/client/commands/issue_get_command.rs +++ b/crates/libjirac/src/client/commands/issue_get_command.rs @@ -20,7 +20,7 @@ impl JiraCommand for IssueGetCommand { const REQUEST_TYPE: JiraRequestType = JiraRequestType::Read; fn endpoint(&self) -> String { - format!("/rest/api/2/issue/{}", self.key) + format!("/rest/api/3/issue/{}", self.key) } fn request_body(&self) -> Option<&Self::TPayload> { diff --git a/crates/libjirac/src/client/commands/search_command.rs b/crates/libjirac/src/client/commands/search_command.rs index b44e4b8..6318fe2 100644 --- a/crates/libjirac/src/client/commands/search_command.rs +++ b/crates/libjirac/src/client/commands/search_command.rs @@ -20,7 +20,7 @@ impl JiraCommand for SearchCommand { const REQUEST_TYPE: JiraRequestType = JiraRequestType::Read; fn endpoint(&self) -> String { - "/rest/api/2/search".to_string() + "/rest/api/3/search".to_string() } fn request_body(&self) -> Option<&Self::TPayload> { diff --git a/crates/libjirac/src/entities.rs b/crates/libjirac/src/entities.rs index 1f415ef..76b0a32 100644 --- a/crates/libjirac/src/entities.rs +++ b/crates/libjirac/src/entities.rs @@ -1,3 +1,4 @@ +pub mod doc; pub mod issue; pub mod issue_request; pub mod search; diff --git a/crates/libjirac/src/entities/doc.rs b/crates/libjirac/src/entities/doc.rs new file mode 100644 index 0000000..ea8a53e --- /dev/null +++ b/crates/libjirac/src/entities/doc.rs @@ -0,0 +1,90 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Doc { + pub version: i32, + pub content: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "type", content = "content")] +pub enum DocNode { + Paragraph(Vec), + BulletList(Vec), + ListItem(Vec), + OrderedList(Vec), + Rule, + Heading(Vec), + MediaSingle(Vec), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "type")] +pub enum DocTextNode { + Text(DocTextNodeContent), + HardBreak, + InlineCard(DocInlineCard), + Mention(DocMention), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DocTextNodeContent { + pub text: String, + pub marks: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "type", content = "attrs")] +pub enum DocTextMarker { + Link(DocTextMarkerLink), + Strong, + Code, + Media(DocMedia), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DocTextMarkerLink { + pub href: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "type")] +pub enum DocMedia { + File(DocMediaFile), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DocMediaFile { + pub id: String, + pub alt: String, + pub collection: String, + pub height: i32, + pub width: i32, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DocInlineCard { + pub attrs: DocInlineCardAttrs, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DocInlineCardAttrs { + pub url: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DocMention { + pub attrs: DocMentionAttrs, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DocMentionAttrs { + pub id: String, + pub text: String, + pub local_id: String, +} diff --git a/crates/libjirac/src/entities/issue.rs b/crates/libjirac/src/entities/issue.rs index a576a7b..45fa31f 100644 --- a/crates/libjirac/src/entities/issue.rs +++ b/crates/libjirac/src/entities/issue.rs @@ -1,3 +1,4 @@ +use crate::entities::doc::Doc; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Deserialize, Serialize)] @@ -11,7 +12,7 @@ pub struct JiraIssue { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct JiraIssueResponseFields { pub summary: String, - pub description: Option, + pub description: Option, pub status: Status, pub created: chrono::DateTime, pub priority: Priority, @@ -24,6 +25,13 @@ pub struct JiraIssueResponseFields { pub votes: Votes, } +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "type")] +pub enum Description { + Doc(Doc), +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Status { pub name: String, @@ -63,7 +71,7 @@ pub struct Comment { pub href: String, pub id: String, pub author: Person, - pub body: String, + pub body: Doc, #[serde(rename = "updateAuthor")] pub update_author: Person, pub created: chrono::DateTime,