diff options
Diffstat (limited to 'src/bin/ngit')
| -rw-r--r-- | src/bin/ngit/cli.rs | 10 | ||||
| -rw-r--r-- | src/bin/ngit/main.rs | 22 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/issue_list.rs | 130 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/list.rs | 138 |
4 files changed, 230 insertions, 70 deletions
diff --git a/src/bin/ngit/cli.rs b/src/bin/ngit/cli.rs index f18759b..0599b51 100644 --- a/src/bin/ngit/cli.rs +++ b/src/bin/ngit/cli.rs | |||
| @@ -187,7 +187,7 @@ pub enum PrCommands { | |||
| 187 | #[arg(long)] | 187 | #[arg(long)] |
| 188 | offline: bool, | 188 | offline: bool, |
| 189 | }, | 189 | }, |
| 190 | /// view a PR and its comments | 190 | /// view a PR; use --comments to include comment thread |
| 191 | View { | 191 | View { |
| 192 | /// Proposal event-id (hex) or nevent (bech32) | 192 | /// Proposal event-id (hex) or nevent (bech32) |
| 193 | #[arg(value_name = "ID|nevent")] | 193 | #[arg(value_name = "ID|nevent")] |
| @@ -195,6 +195,9 @@ pub enum PrCommands { | |||
| 195 | /// Output as JSON | 195 | /// Output as JSON |
| 196 | #[arg(long)] | 196 | #[arg(long)] |
| 197 | json: bool, | 197 | json: bool, |
| 198 | /// Include full comment thread (default: show count only) | ||
| 199 | #[arg(long)] | ||
| 200 | comments: bool, | ||
| 198 | /// Use local cache only, skip network fetch | 201 | /// Use local cache only, skip network fetch |
| 199 | #[arg(long)] | 202 | #[arg(long)] |
| 200 | offline: bool, | 203 | offline: bool, |
| @@ -321,7 +324,7 @@ pub enum IssueCommands { | |||
| 321 | #[arg(long)] | 324 | #[arg(long)] |
| 322 | offline: bool, | 325 | offline: bool, |
| 323 | }, | 326 | }, |
| 324 | /// view an issue and its comments | 327 | /// view an issue; use --comments to include comment thread |
| 325 | View { | 328 | View { |
| 326 | /// Issue event-id (hex) or nevent (bech32) | 329 | /// Issue event-id (hex) or nevent (bech32) |
| 327 | #[arg(value_name = "ID|nevent")] | 330 | #[arg(value_name = "ID|nevent")] |
| @@ -329,6 +332,9 @@ pub enum IssueCommands { | |||
| 329 | /// Output as JSON | 332 | /// Output as JSON |
| 330 | #[arg(long)] | 333 | #[arg(long)] |
| 331 | json: bool, | 334 | json: bool, |
| 335 | /// Include full comment thread (default: show count only) | ||
| 336 | #[arg(long)] | ||
| 337 | comments: bool, | ||
| 332 | /// Use local cache only, skip network fetch | 338 | /// Use local cache only, skip network fetch |
| 333 | #[arg(long)] | 339 | #[arg(long)] |
| 334 | offline: bool, | 340 | offline: bool, |
diff --git a/src/bin/ngit/main.rs b/src/bin/ngit/main.rs index 03a5ce9..4c1aa75 100644 --- a/src/bin/ngit/main.rs +++ b/src/bin/ngit/main.rs | |||
| @@ -60,11 +60,20 @@ async fn main() { | |||
| 60 | json, | 60 | json, |
| 61 | id, | 61 | id, |
| 62 | offline, | 62 | offline, |
| 63 | } => sub_commands::list::launch(status.clone(), *json, id.clone(), *offline).await, | 63 | } => { |
| 64 | PrCommands::View { id, json, offline } => { | 64 | sub_commands::list::launch(status.clone(), *json, false, id.clone(), *offline) |
| 65 | .await | ||
| 66 | } | ||
| 67 | PrCommands::View { | ||
| 68 | id, | ||
| 69 | json, | ||
| 70 | comments, | ||
| 71 | offline, | ||
| 72 | } => { | ||
| 65 | sub_commands::list::launch( | 73 | sub_commands::list::launch( |
| 66 | "open,draft,closed,applied".to_string(), | 74 | "open,draft,closed,applied".to_string(), |
| 67 | *json, | 75 | *json, |
| 76 | *comments, | ||
| 68 | Some(id.clone()), | 77 | Some(id.clone()), |
| 69 | *offline, | 78 | *offline, |
| 70 | ) | 79 | ) |
| @@ -122,16 +131,23 @@ async fn main() { | |||
| 122 | status.clone(), | 131 | status.clone(), |
| 123 | hashtag.clone(), | 132 | hashtag.clone(), |
| 124 | *json, | 133 | *json, |
| 134 | false, | ||
| 125 | id.clone(), | 135 | id.clone(), |
| 126 | *offline, | 136 | *offline, |
| 127 | ) | 137 | ) |
| 128 | .await | 138 | .await |
| 129 | } | 139 | } |
| 130 | IssueCommands::View { id, json, offline } => { | 140 | IssueCommands::View { |
| 141 | id, | ||
| 142 | json, | ||
| 143 | comments, | ||
| 144 | offline, | ||
| 145 | } => { | ||
| 131 | sub_commands::issue_list::launch( | 146 | sub_commands::issue_list::launch( |
| 132 | "open,draft,closed,applied".to_string(), | 147 | "open,draft,closed,applied".to_string(), |
| 133 | None, | 148 | None, |
| 134 | *json, | 149 | *json, |
| 150 | *comments, | ||
| 135 | Some(id.clone()), | 151 | Some(id.clone()), |
| 136 | *offline, | 152 | *offline, |
| 137 | ) | 153 | ) |
diff --git a/src/bin/ngit/sub_commands/issue_list.rs b/src/bin/ngit/sub_commands/issue_list.rs index b7abf8d..864cd76 100644 --- a/src/bin/ngit/sub_commands/issue_list.rs +++ b/src/bin/ngit/sub_commands/issue_list.rs | |||
| @@ -138,6 +138,7 @@ pub async fn launch( | |||
| 138 | status: String, | 138 | status: String, |
| 139 | hashtag: Option<String>, | 139 | hashtag: Option<String>, |
| 140 | json: bool, | 140 | json: bool, |
| 141 | show_comments: bool, | ||
| 141 | id: Option<String>, | 142 | id: Option<String>, |
| 142 | offline: bool, | 143 | offline: bool, |
| 143 | ) -> Result<()> { | 144 | ) -> Result<()> { |
| @@ -236,8 +237,18 @@ pub async fn launch( | |||
| 236 | } else { | 237 | } else { |
| 237 | nostr::EventId::from_hex(event_id_or_nevent).context("failed to parse event id")? | 238 | nostr::EventId::from_hex(event_id_or_nevent).context("failed to parse event id")? |
| 238 | }; | 239 | }; |
| 239 | let comments = get_comments_for_issue(git_repo_path, &target_id).await?; | 240 | let comments = if show_comments { |
| 240 | return show_issue_details(&filtered, event_id_or_nevent, json, &comments); | 241 | get_comments_for_issue(git_repo_path, &target_id).await? |
| 242 | } else { | ||
| 243 | vec![] | ||
| 244 | }; | ||
| 245 | return show_issue_details( | ||
| 246 | &filtered, | ||
| 247 | event_id_or_nevent, | ||
| 248 | json, | ||
| 249 | show_comments, | ||
| 250 | &comments, | ||
| 251 | ); | ||
| 241 | } | 252 | } |
| 242 | 253 | ||
| 243 | if json { | 254 | if json { |
| @@ -249,10 +260,38 @@ pub async fn launch( | |||
| 249 | Ok(()) | 260 | Ok(()) |
| 250 | } | 261 | } |
| 251 | 262 | ||
| 263 | /// Extract the parent comment ID from a NIP-22 comment event. | ||
| 264 | /// Returns `Some(id)` when the lowercase `e` tag differs from the root `E` tag | ||
| 265 | /// (i.e. the comment is a reply to another comment, not a top-level comment). | ||
| 266 | fn comment_reply_to(comment: &nostr::Event) -> Option<nostr::EventId> { | ||
| 267 | let root_id = comment.tags.iter().find_map(|t| { | ||
| 268 | let s = t.as_slice(); | ||
| 269 | if s.len() >= 2 && s[0].eq("E") { | ||
| 270 | nostr::EventId::parse(&s[1]).ok() | ||
| 271 | } else { | ||
| 272 | None | ||
| 273 | } | ||
| 274 | })?; | ||
| 275 | comment.tags.iter().find_map(|t| { | ||
| 276 | let s = t.as_slice(); | ||
| 277 | if s.len() >= 2 && s[0].eq("e") { | ||
| 278 | let parent_id = nostr::EventId::parse(&s[1]).ok()?; | ||
| 279 | if parent_id == root_id { | ||
| 280 | None | ||
| 281 | } else { | ||
| 282 | Some(parent_id) | ||
| 283 | } | ||
| 284 | } else { | ||
| 285 | None | ||
| 286 | } | ||
| 287 | }) | ||
| 288 | } | ||
| 289 | |||
| 252 | fn show_issue_details( | 290 | fn show_issue_details( |
| 253 | issues: &[(&nostr::Event, Kind, Vec<String>, usize)], | 291 | issues: &[(&nostr::Event, Kind, Vec<String>, usize)], |
| 254 | event_id_or_nevent: &str, | 292 | event_id_or_nevent: &str, |
| 255 | json: bool, | 293 | json: bool, |
| 294 | show_comments: bool, | ||
| 256 | comments: &[nostr::Event], | 295 | comments: &[nostr::Event], |
| 257 | ) -> Result<()> { | 296 | ) -> Result<()> { |
| 258 | let target_id = if event_id_or_nevent.starts_with("nevent") { | 297 | let target_id = if event_id_or_nevent.starts_with("nevent") { |
| @@ -266,7 +305,7 @@ fn show_issue_details( | |||
| 266 | nostr::EventId::from_hex(event_id_or_nevent).context("failed to parse event id")? | 305 | nostr::EventId::from_hex(event_id_or_nevent).context("failed to parse event id")? |
| 267 | }; | 306 | }; |
| 268 | 307 | ||
| 269 | let (issue, status_kind, tags, _comment_count) = issues | 308 | let (issue, status_kind, tags, comment_count) = issues |
| 270 | .iter() | 309 | .iter() |
| 271 | .find(|(e, _, _, _)| e.id == target_id) | 310 | .find(|(e, _, _, _)| e.id == target_id) |
| 272 | .context("issue not found")?; | 311 | .context("issue not found")?; |
| @@ -275,26 +314,41 @@ fn show_issue_details( | |||
| 275 | let status = status_kind_to_str(*status_kind); | 314 | let status = status_kind_to_str(*status_kind); |
| 276 | 315 | ||
| 277 | if json { | 316 | if json { |
| 278 | let comments_json: Vec<serde_json::Value> = comments | 317 | let json_output = if show_comments { |
| 279 | .iter() | 318 | let comments_json: Vec<serde_json::Value> = comments |
| 280 | .map(|c| { | 319 | .iter() |
| 281 | serde_json::json!({ | 320 | .map(|c| { |
| 282 | "id": c.id.to_string(), | 321 | let reply_to = comment_reply_to(c).map(|id| id.to_string()); |
| 283 | "author": c.pubkey.to_bech32().unwrap_or_default(), | 322 | serde_json::json!({ |
| 284 | "created_at": c.created_at.as_secs(), | 323 | "id": c.id.to_string(), |
| 285 | "body": c.content, | 324 | "author": c.pubkey.to_bech32().unwrap_or_default(), |
| 325 | "created_at": c.created_at.as_secs(), | ||
| 326 | "reply_to": reply_to, | ||
| 327 | "body": c.content, | ||
| 328 | }) | ||
| 286 | }) | 329 | }) |
| 330 | .collect(); | ||
| 331 | serde_json::json!({ | ||
| 332 | "id": issue.id.to_string(), | ||
| 333 | "status": status, | ||
| 334 | "title": title, | ||
| 335 | "author": issue.pubkey.to_bech32().unwrap_or_default(), | ||
| 336 | "hashtags": tags, | ||
| 337 | "comment_count": comment_count, | ||
| 338 | "comments": comments_json, | ||
| 339 | "description": issue.content, | ||
| 340 | }) | ||
| 341 | } else { | ||
| 342 | serde_json::json!({ | ||
| 343 | "id": issue.id.to_string(), | ||
| 344 | "status": status, | ||
| 345 | "title": title, | ||
| 346 | "author": issue.pubkey.to_bech32().unwrap_or_default(), | ||
| 347 | "hashtags": tags, | ||
| 348 | "comment_count": comment_count, | ||
| 349 | "description": issue.content, | ||
| 287 | }) | 350 | }) |
| 288 | .collect(); | 351 | }; |
| 289 | let json_output = serde_json::json!({ | ||
| 290 | "id": issue.id.to_string(), | ||
| 291 | "status": status, | ||
| 292 | "title": title, | ||
| 293 | "author": issue.pubkey.to_bech32().unwrap_or_default(), | ||
| 294 | "hashtags": tags, | ||
| 295 | "comments": comments_json, | ||
| 296 | "description": issue.content, | ||
| 297 | }); | ||
| 298 | println!("{}", serde_json::to_string_pretty(&json_output)?); | 352 | println!("{}", serde_json::to_string_pretty(&json_output)?); |
| 299 | return Ok(()); | 353 | return Ok(()); |
| 300 | } | 354 | } |
| @@ -318,21 +372,31 @@ fn show_issue_details( | |||
| 318 | } | 372 | } |
| 319 | } | 373 | } |
| 320 | 374 | ||
| 321 | if comments.is_empty() { | 375 | if show_comments { |
| 322 | println!("Comments: 0"); | 376 | if comments.is_empty() { |
| 323 | } else { | 377 | println!("Comments: 0"); |
| 324 | println!(); | 378 | } else { |
| 325 | println!("Comments ({}):", comments.len()); | ||
| 326 | let dim = console::Style::new().color256(247); | ||
| 327 | for comment in comments { | ||
| 328 | let author = comment.pubkey.to_bech32().unwrap_or_default(); | ||
| 329 | let ts = chrono_timestamp(comment.created_at.as_secs()); | ||
| 330 | println!(); | 379 | println!(); |
| 331 | println!("{}", dim.apply_to(format!(" {author} {ts}"))); | 380 | println!("Comments ({}):", comments.len()); |
| 332 | for line in comment.content.lines() { | 381 | let dim = console::Style::new().color256(247); |
| 333 | println!(" {line}"); | 382 | for comment in comments { |
| 383 | let author = comment.pubkey.to_bech32().unwrap_or_default(); | ||
| 384 | let ts = chrono_timestamp(comment.created_at.as_secs()); | ||
| 385 | println!(); | ||
| 386 | if let Some(parent_id) = comment_reply_to(comment) { | ||
| 387 | println!( | ||
| 388 | "{}", | ||
| 389 | dim.apply_to(format!(" ↳ reply to {}", &parent_id.to_hex()[..8])) | ||
| 390 | ); | ||
| 391 | } | ||
| 392 | println!("{}", dim.apply_to(format!(" {author} {ts}"))); | ||
| 393 | for line in comment.content.lines() { | ||
| 394 | println!(" {line}"); | ||
| 395 | } | ||
| 334 | } | 396 | } |
| 335 | } | 397 | } |
| 398 | } else { | ||
| 399 | println!("Comments: {comment_count} (use --comments to view)"); | ||
| 336 | } | 400 | } |
| 337 | 401 | ||
| 338 | Ok(()) | 402 | Ok(()) |
diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs index a583ca5..60e129f 100644 --- a/src/bin/ngit/sub_commands/list.rs +++ b/src/bin/ngit/sub_commands/list.rs | |||
| @@ -95,7 +95,13 @@ fn run_git_fetch(remote_name: &str) -> Result<()> { | |||
| 95 | } | 95 | } |
| 96 | 96 | ||
| 97 | #[allow(clippy::too_many_lines)] | 97 | #[allow(clippy::too_many_lines)] |
| 98 | pub async fn launch(status: String, json: bool, id: Option<String>, offline: bool) -> Result<()> { | 98 | pub async fn launch( |
| 99 | status: String, | ||
| 100 | json: bool, | ||
| 101 | show_comments: bool, | ||
| 102 | id: Option<String>, | ||
| 103 | offline: bool, | ||
| 104 | ) -> Result<()> { | ||
| 99 | if std::env::var("NGIT_INTERACTIVE_MODE").is_ok() { | 105 | if std::env::var("NGIT_INTERACTIVE_MODE").is_ok() { |
| 100 | return launch_interactive().await; | 106 | return launch_interactive().await; |
| 101 | } | 107 | } |
| @@ -203,12 +209,26 @@ pub async fn launch(status: String, json: bool, id: Option<String>, offline: boo | |||
| 203 | if let Some(ref event_id_or_nevent) = id { | 209 | if let Some(ref event_id_or_nevent) = id { |
| 204 | // Resolve the target proposal ID so we can fetch its comments. | 210 | // Resolve the target proposal ID so we can fetch its comments. |
| 205 | let target_id = resolve_event_id(event_id_or_nevent)?; | 211 | let target_id = resolve_event_id(event_id_or_nevent)?; |
| 206 | let comments = get_comments_for_proposal(git_repo_path, &target_id).await?; | 212 | let comments = if show_comments { |
| 213 | get_comments_for_proposal(git_repo_path, &target_id).await? | ||
| 214 | } else { | ||
| 215 | vec![] | ||
| 216 | }; | ||
| 217 | // Always fetch the count so we can display it even without --comments. | ||
| 218 | let comment_count = if show_comments { | ||
| 219 | comments.len() | ||
| 220 | } else { | ||
| 221 | get_comments_for_proposal(git_repo_path, &target_id) | ||
| 222 | .await? | ||
| 223 | .len() | ||
| 224 | }; | ||
| 207 | return show_proposal_details( | 225 | return show_proposal_details( |
| 208 | &filtered_proposals, | 226 | &filtered_proposals, |
| 209 | &repo_ref, | 227 | &repo_ref, |
| 210 | event_id_or_nevent, | 228 | event_id_or_nevent, |
| 211 | json, | 229 | json, |
| 230 | show_comments, | ||
| 231 | comment_count, | ||
| 212 | &comments, | 232 | &comments, |
| 213 | ); | 233 | ); |
| 214 | } | 234 | } |
| @@ -353,11 +373,40 @@ fn output_json(proposals: &[(&nostr::Event, Kind)], _repo_ref: &RepoRef) -> Resu | |||
| 353 | Ok(()) | 373 | Ok(()) |
| 354 | } | 374 | } |
| 355 | 375 | ||
| 376 | /// Extract the parent comment ID from a NIP-22 comment event. | ||
| 377 | /// Returns `Some(id)` when the lowercase `e` tag differs from the root `E` tag | ||
| 378 | /// (i.e. the comment is a reply to another comment, not a top-level comment). | ||
| 379 | fn comment_reply_to(comment: &nostr::Event) -> Option<nostr::EventId> { | ||
| 380 | let root_id = comment.tags.iter().find_map(|t| { | ||
| 381 | let s = t.as_slice(); | ||
| 382 | if s.len() >= 2 && s[0].eq("E") { | ||
| 383 | nostr::EventId::parse(&s[1]).ok() | ||
| 384 | } else { | ||
| 385 | None | ||
| 386 | } | ||
| 387 | })?; | ||
| 388 | comment.tags.iter().find_map(|t| { | ||
| 389 | let s = t.as_slice(); | ||
| 390 | if s.len() >= 2 && s[0].eq("e") { | ||
| 391 | let parent_id = nostr::EventId::parse(&s[1]).ok()?; | ||
| 392 | if parent_id == root_id { | ||
| 393 | None | ||
| 394 | } else { | ||
| 395 | Some(parent_id) | ||
| 396 | } | ||
| 397 | } else { | ||
| 398 | None | ||
| 399 | } | ||
| 400 | }) | ||
| 401 | } | ||
| 402 | |||
| 356 | fn show_proposal_details( | 403 | fn show_proposal_details( |
| 357 | proposals: &[(&nostr::Event, Kind)], | 404 | proposals: &[(&nostr::Event, Kind)], |
| 358 | _repo_ref: &RepoRef, | 405 | _repo_ref: &RepoRef, |
| 359 | event_id_or_nevent: &str, | 406 | event_id_or_nevent: &str, |
| 360 | json: bool, | 407 | json: bool, |
| 408 | show_comments: bool, | ||
| 409 | comment_count: usize, | ||
| 361 | comments: &[nostr::Event], | 410 | comments: &[nostr::Event], |
| 362 | ) -> Result<()> { | 411 | ) -> Result<()> { |
| 363 | use nostr::ToBech32; | 412 | use nostr::ToBech32; |
| @@ -373,26 +422,41 @@ fn show_proposal_details( | |||
| 373 | .context("failed to extract proposal details from proposal root event")?; | 422 | .context("failed to extract proposal details from proposal root event")?; |
| 374 | 423 | ||
| 375 | if json { | 424 | if json { |
| 376 | let comments_json: Vec<serde_json::Value> = comments | 425 | let json_output = if show_comments { |
| 377 | .iter() | 426 | let comments_json: Vec<serde_json::Value> = comments |
| 378 | .map(|c| { | 427 | .iter() |
| 379 | serde_json::json!({ | 428 | .map(|c| { |
| 380 | "id": c.id.to_string(), | 429 | let reply_to = comment_reply_to(c).map(|id| id.to_string()); |
| 381 | "author": c.pubkey.to_bech32().unwrap_or_default(), | 430 | serde_json::json!({ |
| 382 | "created_at": c.created_at.as_secs(), | 431 | "id": c.id.to_string(), |
| 383 | "body": c.content, | 432 | "author": c.pubkey.to_bech32().unwrap_or_default(), |
| 433 | "created_at": c.created_at.as_secs(), | ||
| 434 | "reply_to": reply_to, | ||
| 435 | "body": c.content, | ||
| 436 | }) | ||
| 384 | }) | 437 | }) |
| 438 | .collect(); | ||
| 439 | serde_json::json!({ | ||
| 440 | "id": proposal.id.to_string(), | ||
| 441 | "status": status_kind_to_str(*status_kind), | ||
| 442 | "title": cover_letter.title, | ||
| 443 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), | ||
| 444 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | ||
| 445 | "comment_count": comment_count, | ||
| 446 | "comments": comments_json, | ||
| 447 | "description": cover_letter.description, | ||
| 385 | }) | 448 | }) |
| 386 | .collect(); | 449 | } else { |
| 387 | let json_output = serde_json::json!({ | 450 | serde_json::json!({ |
| 388 | "id": proposal.id.to_string(), | 451 | "id": proposal.id.to_string(), |
| 389 | "status": status_kind_to_str(*status_kind), | 452 | "status": status_kind_to_str(*status_kind), |
| 390 | "title": cover_letter.title, | 453 | "title": cover_letter.title, |
| 391 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), | 454 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), |
| 392 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 455 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 393 | "comments": comments_json, | 456 | "comment_count": comment_count, |
| 394 | "description": cover_letter.description, | 457 | "description": cover_letter.description, |
| 395 | }); | 458 | }) |
| 459 | }; | ||
| 396 | println!("{}", serde_json::to_string_pretty(&json_output)?); | 460 | println!("{}", serde_json::to_string_pretty(&json_output)?); |
| 397 | return Ok(()); | 461 | return Ok(()); |
| 398 | } | 462 | } |
| @@ -416,21 +480,31 @@ fn show_proposal_details( | |||
| 416 | } | 480 | } |
| 417 | } | 481 | } |
| 418 | 482 | ||
| 419 | if comments.is_empty() { | 483 | if show_comments { |
| 420 | println!("Comments: 0"); | 484 | if comments.is_empty() { |
| 421 | } else { | 485 | println!("Comments: 0"); |
| 422 | println!(); | 486 | } else { |
| 423 | println!("Comments ({}):", comments.len()); | ||
| 424 | let dim = console::Style::new().color256(247); | ||
| 425 | for comment in comments { | ||
| 426 | let author = comment.pubkey.to_bech32().unwrap_or_default(); | ||
| 427 | let ts = chrono_timestamp(comment.created_at.as_secs()); | ||
| 428 | println!(); | 487 | println!(); |
| 429 | println!("{}", dim.apply_to(format!(" {author} {ts}"))); | 488 | println!("Comments ({comment_count}):"); |
| 430 | for line in comment.content.lines() { | 489 | let dim = console::Style::new().color256(247); |
| 431 | println!(" {line}"); | 490 | for comment in comments { |
| 491 | let author = comment.pubkey.to_bech32().unwrap_or_default(); | ||
| 492 | let ts = chrono_timestamp(comment.created_at.as_secs()); | ||
| 493 | println!(); | ||
| 494 | if let Some(parent_id) = comment_reply_to(comment) { | ||
| 495 | println!( | ||
| 496 | "{}", | ||
| 497 | dim.apply_to(format!(" ↳ reply to {}", &parent_id.to_hex()[..8])) | ||
| 498 | ); | ||
| 499 | } | ||
| 500 | println!("{}", dim.apply_to(format!(" {author} {ts}"))); | ||
| 501 | for line in comment.content.lines() { | ||
| 502 | println!(" {line}"); | ||
| 503 | } | ||
| 432 | } | 504 | } |
| 433 | } | 505 | } |
| 506 | } else { | ||
| 507 | println!("Comments: {comment_count} (use --comments to view)"); | ||
| 434 | } | 508 | } |
| 435 | 509 | ||
| 436 | println!(); | 510 | println!(); |