From 2774ce9ed22c2dfcc2ed63ad5454f54129d38117 Mon Sep 17 00:00:00 2001 From: Urgau Date: Fri, 16 Jan 2026 21:44:12 +0100 Subject: [PATCH 01/16] Split GraphQl query in two for issues and pull-requests --- src/gh_comments.rs | 2 +- src/github.rs | 84 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index 4433b653..8523635b 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -74,7 +74,7 @@ pub async fn gh_comments( .github .issue_with_comments(&owner, &repo, issue_id) .await - .context("unable to fetch the issue and it's comments (PRs are not yet supported)")?; + .context("unable to fetch the issue/pull-request and it's comments")?; let duration = start.elapsed(); let duration_secs = duration.as_secs_f64(); diff --git a/src/github.rs b/src/github.rs index d0f7f94d..7a09ab53 100644 --- a/src/github.rs +++ b/src/github.rs @@ -2931,33 +2931,65 @@ impl GithubClient { " query ($owner: String!, $repo: String!, $issueNumber: Int!, $cursor: String) { repository(owner: $owner, name: $repo) { - issue(number: $issueNumber) { - url - title - titleHTML - bodyHTML - createdAt - updatedAt - author { - login - avatarUrl - } - comments(first: 100, after: $cursor) { - nodes { - author { - login - avatarUrl + issueOrPullRequest(number: $issueNumber) { + ... on Issue { + url + title + titleHTML + bodyHTML + createdAt + updatedAt + author { + login + avatarUrl + } + comments(first: 100, after: $cursor) { + nodes { + author { + login + avatarUrl + } + createdAt + updatedAt + isMinimized + minimizedReason + bodyHTML + url + } + pageInfo { + hasNextPage + endCursor } - createdAt - updatedAt - isMinimized - minimizedReason - bodyHTML - url } - pageInfo { - hasNextPage - endCursor + } + ... on PullRequest { + url + title + titleHTML + bodyHTML + createdAt + updatedAt + author { + login + avatarUrl + } + comments(first: 100, after: $cursor) { + nodes { + author { + login + avatarUrl + } + createdAt + updatedAt + isMinimized + minimizedReason + bodyHTML + url + } + pageInfo { + hasNextPage + endCursor + } } } } @@ -2974,7 +3006,7 @@ query ($owner: String!, $repo: String!, $issueNumber: Int!, $cursor: String) { .await .context("failed to fetch the issue with comments")?; - issue_json = data["data"]["repository"]["issue"].take(); + issue_json = data["data"]["repository"]["issueOrPullRequest"].take(); let has_next_page = issue_json["comments"]["pageInfo"]["hasNextPage"] .as_bool() From 773910825a3a93bfd4b2b40fb80624a0aaef0925 Mon Sep 17 00:00:00 2001 From: Urgau Date: Fri, 16 Jan 2026 21:45:52 +0100 Subject: [PATCH 02/16] Retrieve reviews and review threads from GitHub GraphQl API --- src/github.rs | 241 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 222 insertions(+), 19 deletions(-) diff --git a/src/github.rs b/src/github.rs index 7a09ab53..e482039c 100644 --- a/src/github.rs +++ b/src/github.rs @@ -2921,15 +2921,22 @@ impl GithubClient { repo: &str, issue: u64, ) -> anyhow::Result { - let mut cursor: Option = None; + let mut comments_cursor: Option = None; + let mut review_threads_cursor: Option = None; + let mut reviews_cursor: Option = None; let mut all_comments = Vec::new(); + let mut all_review_threads = Vec::new(); + let mut all_reviews = Vec::new(); + let mut prev_comments_cursor: Option = None; + let mut prev_review_threads_cursor: Option = None; + let mut prev_reviews_cursor: Option = None; let mut issue_json; loop { let mut data = self - .graphql_query( - " -query ($owner: String!, $repo: String!, $issueNumber: Int!, $cursor: String) { + .graphql_query( + " +query ($owner: String!, $repo: String!, $issueNumber: Int!, $commentsCursor: String, $reviewThreadsCursor: String, $reviewsCursor: String) { repository(owner: $owner, name: $repo) { issueOrPullRequest(number: $issueNumber) { ... on Issue { @@ -2943,7 +2950,7 @@ query ($owner: String!, $repo: String!, $issueNumber: Int!, $cursor: String) { login avatarUrl } - comments(first: 100, after: $cursor) { + comments(first: 100, after: $commentsCursor) { nodes { author { login @@ -2973,7 +2980,7 @@ query ($owner: String!, $repo: String!, $issueNumber: Int!, $cursor: String) { login avatarUrl } - comments(first: 100, after: $cursor) { + comments(first: 100, after: $commentsCursor) { nodes { author { login @@ -2991,16 +2998,65 @@ query ($owner: String!, $repo: String!, $issueNumber: Int!, $cursor: String) { endCursor } } + reviewThreads(first: 100, after: $reviewThreadsCursor) { + nodes { + isCollapsed + isOutdated + isResolved + path + comments(first: 100) { + nodes { + author { + login + avatarUrl + } + createdAt + updatedAt + bodyHTML + url + pullRequestReview { + id + } + } + } + } + pageInfo { + hasNextPage + endCursor + } + } + reviews(first: 100, after: $reviewsCursor) { + nodes { + author { + login + avatarUrl + } + id + state + submittedAt + updatedAt + isMinimized + minimizedReason + bodyHTML + url + } + pageInfo { + hasNextPage + endCursor + } + } } } } } - ", + ", serde_json::json!({ "owner": owner, "repo": repo, "issueNumber": issue, - "cursor": cursor.as_deref(), + "commentsCursor": comments_cursor.as_deref(), + "reviewThreadsCursor": review_threads_cursor.as_deref(), + "reviewsCursor": reviews_cursor.as_deref(), }), ) .await @@ -3008,35 +3064,106 @@ query ($owner: String!, $repo: String!, $issueNumber: Int!, $cursor: String) { issue_json = data["data"]["repository"]["issueOrPullRequest"].take(); - let has_next_page = issue_json["comments"]["pageInfo"]["hasNextPage"] + // Update all cursors from pageInfo + let comments_has_next = issue_json["comments"]["pageInfo"]["hasNextPage"] .as_bool() .unwrap_or(false); - let end_cursor = issue_json["comments"]["pageInfo"]["endCursor"] + let comments_end_cursor = issue_json["comments"]["pageInfo"]["endCursor"] .as_str() .map(|s| s.to_string()); + let comments_cursor_changed = comments_end_cursor != prev_comments_cursor; - // Early return if first page has no more pages (1 API call) - if all_comments.is_empty() && !has_next_page { + let review_threads_has_next = if issue_json["reviewThreads"].is_object() { + issue_json["reviewThreads"]["pageInfo"]["hasNextPage"] + .as_bool() + .unwrap_or(false) + } else { + false + }; + let review_threads_end_cursor = if issue_json["reviewThreads"].is_object() { + issue_json["reviewThreads"]["pageInfo"]["endCursor"] + .as_str() + .map(|s| s.to_string()) + } else { + None + }; + let review_threads_cursor_changed = + review_threads_end_cursor != prev_review_threads_cursor; + + let reviews_has_next = if issue_json["reviews"].is_object() { + issue_json["reviews"]["pageInfo"]["hasNextPage"] + .as_bool() + .unwrap_or(false) + } else { + false + }; + let reviews_end_cursor = if issue_json["reviews"].is_object() { + issue_json["reviews"]["pageInfo"]["endCursor"] + .as_str() + .map(|s| s.to_string()) + } else { + None + }; + let reviews_cursor_changed = reviews_end_cursor != prev_reviews_cursor; + + // Update cursors for next iteration + comments_cursor = comments_end_cursor; + review_threads_cursor = review_threads_end_cursor; + reviews_cursor = reviews_end_cursor; + + // Early return if first page has no more pages for any field (1 API call) + if all_comments.is_empty() + && all_review_threads.is_empty() + && all_reviews.is_empty() + && !comments_has_next + && !review_threads_has_next + && !reviews_has_next + { return serde_json::from_value(issue_json) .context("fail to deserialize the GraphQl json response"); } - // Store cursor for next iteration - cursor = end_cursor; + // Only accumulate if cursor actually advanced (new page of data) + if comments_cursor_changed { + if let Some(comments_array) = issue_json["comments"]["nodes"].as_array_mut() { + all_comments.append(comments_array); + } + } + + // Only accumulate review threads if cursor advanced (only for PullRequest) + if review_threads_cursor_changed { + if let Some(threads_array) = issue_json["reviewThreads"]["nodes"].as_array_mut() { + all_review_threads.append(threads_array); + } + } - // Extract and accumulate comments (avoid full deserialization) - if let Some(comments_array) = issue_json["comments"]["nodes"].as_array_mut() { - all_comments.append(comments_array); + // Only accumulate reviews if cursor advanced (only for PullRequest) + if reviews_cursor_changed { + if let Some(reviews_array) = issue_json["reviews"]["nodes"].as_array_mut() { + all_reviews.append(reviews_array); + } } - if !has_next_page { + // Update previous cursors for next iteration comparison + prev_comments_cursor = comments_cursor.clone(); + prev_review_threads_cursor = review_threads_cursor.clone(); + prev_reviews_cursor = reviews_cursor.clone(); + + // Continue if any field has more pages + if !comments_has_next && !review_threads_has_next && !reviews_has_next { break; } } - // Reconstruct final result with all comments + // Reconstruct final result with all accumulated data let mut final_issue = issue_json; final_issue["comments"]["nodes"] = serde_json::Value::Array(all_comments); + if let Some(threads) = final_issue.get_mut("reviewThreads") { + threads["nodes"] = serde_json::Value::Array(all_review_threads); + } + if let Some(reviews) = final_issue.get_mut("reviews") { + reviews["nodes"] = serde_json::Value::Array(all_reviews); + } serde_json::from_value(final_issue).context("fail to deserialize final response") } @@ -3056,6 +3183,9 @@ pub struct GitHubIssueWithComments { #[serde(rename = "updatedAt")] pub updated_at: chrono::DateTime, pub comments: GitHubGraphQlComments, + #[serde(rename = "reviewThreads")] + pub review_threads: Option, + pub reviews: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize)] @@ -3086,6 +3216,79 @@ pub struct GitHubGraphQlComment { pub url: String, } +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct GitHubGraphQlReviewThreads { + pub nodes: Vec, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct GitHubGraphQlReviewThread { + #[serde(rename = "isCollapsed")] + pub is_collapsed: bool, + #[serde(rename = "isOutdated")] + pub is_outdated: bool, + #[serde(rename = "isResolved")] + pub is_resolved: bool, + pub path: String, + pub comments: GitHubGraphQlReviewThreadComments, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct GitHubGraphQlReviewThreadComments { + pub nodes: Vec, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct GitHubGraphQlReviewThreadComment { + pub author: GitHubSimplifiedAuthor, + #[serde(rename = "createdAt")] + pub created_at: chrono::DateTime, + #[serde(rename = "updatedAt")] + pub updated_at: chrono::DateTime, + #[serde(rename = "bodyHTML")] + pub body_html: String, + pub url: String, + #[serde(rename = "pullRequestReview")] + pub pull_request_review: GitHubGraphQlPullRequestReview, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct GitHubGraphQlPullRequestReview { + pub id: String, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct GitHubGraphQlReviews { + pub nodes: Vec, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct GitHubGraphQlReview { + pub author: GitHubSimplifiedAuthor, + pub id: String, + pub state: GitHubReviewState, + #[serde(rename = "submittedAt")] + pub submitted_at: chrono::DateTime, + #[serde(rename = "updatedAt")] + pub updated_at: chrono::DateTime, + #[serde(rename = "bodyHTML")] + pub body_html: String, + #[serde(rename = "isMinimized")] + pub is_minimized: bool, + #[serde(rename = "minimizedReason")] + pub minimized_reason: Option, + pub url: String, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum GitHubReviewState { + Commented, + Approved, + RequestChanges, + Dismissed, +} + #[derive(Debug, serde::Deserialize)] pub struct GithubCommit { pub sha: String, From c5b9df76ef224f3cbeb7eb8c85cbbb37c7755741 Mon Sep 17 00:00:00 2001 From: Urgau Date: Fri, 16 Jan 2026 22:37:29 +0100 Subject: [PATCH 03/16] Simplify a bit the handling of pageInfo and cursors --- src/github.rs | 65 ++++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/src/github.rs b/src/github.rs index e482039c..1733ce67 100644 --- a/src/github.rs +++ b/src/github.rs @@ -2921,15 +2921,22 @@ impl GithubClient { repo: &str, issue: u64, ) -> anyhow::Result { + fn page_info(data: &serde_json::Value, key: &str) -> (bool, Option) { + if let Some(obj) = data.get(key) { + let has_next = obj["pageInfo"]["hasNextPage"].as_bool().unwrap_or(false); + let end_cursor = obj["pageInfo"]["endCursor"].as_str().map(|s| s.to_string()); + (has_next, end_cursor) + } else { + (false, None) + } + } + let mut comments_cursor: Option = None; let mut review_threads_cursor: Option = None; let mut reviews_cursor: Option = None; let mut all_comments = Vec::new(); let mut all_review_threads = Vec::new(); let mut all_reviews = Vec::new(); - let mut prev_comments_cursor: Option = None; - let mut prev_review_threads_cursor: Option = None; - let mut prev_reviews_cursor: Option = None; let mut issue_json; loop { @@ -3065,46 +3072,15 @@ query ($owner: String!, $repo: String!, $issueNumber: Int!, $commentsCursor: Str issue_json = data["data"]["repository"]["issueOrPullRequest"].take(); // Update all cursors from pageInfo - let comments_has_next = issue_json["comments"]["pageInfo"]["hasNextPage"] - .as_bool() - .unwrap_or(false); - let comments_end_cursor = issue_json["comments"]["pageInfo"]["endCursor"] - .as_str() - .map(|s| s.to_string()); - let comments_cursor_changed = comments_end_cursor != prev_comments_cursor; - - let review_threads_has_next = if issue_json["reviewThreads"].is_object() { - issue_json["reviewThreads"]["pageInfo"]["hasNextPage"] - .as_bool() - .unwrap_or(false) - } else { - false - }; - let review_threads_end_cursor = if issue_json["reviewThreads"].is_object() { - issue_json["reviewThreads"]["pageInfo"]["endCursor"] - .as_str() - .map(|s| s.to_string()) - } else { - None - }; - let review_threads_cursor_changed = - review_threads_end_cursor != prev_review_threads_cursor; + let (comments_has_next, comments_end_cursor) = page_info(&issue_json, "comments"); + let comments_cursor_changed = comments_end_cursor != comments_cursor; - let reviews_has_next = if issue_json["reviews"].is_object() { - issue_json["reviews"]["pageInfo"]["hasNextPage"] - .as_bool() - .unwrap_or(false) - } else { - false - }; - let reviews_end_cursor = if issue_json["reviews"].is_object() { - issue_json["reviews"]["pageInfo"]["endCursor"] - .as_str() - .map(|s| s.to_string()) - } else { - None - }; - let reviews_cursor_changed = reviews_end_cursor != prev_reviews_cursor; + let (review_threads_has_next, review_threads_end_cursor) = + page_info(&issue_json, "reviewThreads"); + let review_threads_cursor_changed = review_threads_end_cursor != review_threads_cursor; + + let (reviews_has_next, reviews_end_cursor) = page_info(&issue_json, "reviews"); + let reviews_cursor_changed = reviews_end_cursor != reviews_cursor; // Update cursors for next iteration comments_cursor = comments_end_cursor; @@ -3144,11 +3120,6 @@ query ($owner: String!, $repo: String!, $issueNumber: Int!, $commentsCursor: Str } } - // Update previous cursors for next iteration comparison - prev_comments_cursor = comments_cursor.clone(); - prev_review_threads_cursor = review_threads_cursor.clone(); - prev_reviews_cursor = reviews_cursor.clone(); - // Continue if any field has more pages if !comments_has_next && !review_threads_has_next && !reviews_has_next { break; From 987008d78251c0a83def024cd82c94bdff801367 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 00:17:07 +0100 Subject: [PATCH 04/16] Add review threads to the end --- src/gh_comments.rs | 96 ++++++++++++++++++++++++++++++++++++++- src/gh_comments/style.css | 55 ++++++++++++++++++---- 2 files changed, 141 insertions(+), 10 deletions(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index 8523635b..25882b61 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -16,7 +16,7 @@ use hyper::{ use crate::{ cache, - github::{GitHubGraphQlComment, GitHubIssueWithComments}, + github::{GitHubGraphQlComment, GitHubGraphQlReviewThreadComment, GitHubIssueWithComments}, }; use crate::{ errors::AppError, @@ -171,6 +171,20 @@ pub async fn gh_comments( )?; } + // FIXME: Don't print review threads at the end, they should be mixed + if let Some(review_threads) = &issue_with_comments.review_threads { + for review_thread in &review_threads.nodes { + write_review_thread_as_html( + &mut html, + &review_thread.path, + review_thread.is_collapsed, + review_thread.is_resolved, + review_thread.is_outdated, + &review_thread.comments.nodes, + )?; + } + } + writeln!(html, r###""###).unwrap(); let mut headers = HeaderMap::new(); @@ -300,3 +314,83 @@ fn write_comment_as_html( Ok(()) } + +fn write_review_thread_as_html( + buffer: &mut String, + path: &str, + is_collapsed: bool, + is_resolved: bool, + is_outdated: bool, + comments: &[GitHubGraphQlReviewThreadComment], +) -> anyhow::Result<()> { + let mut path_html = String::new(); + pulldown_cmark_escape::escape_html(&mut path_html, &path)?; + + let open = if is_collapsed { "" } else { "open" }; + let status = if is_outdated { + " · outdated" + } else if is_resolved { + " · resolved" + } else { + "" + }; + + writeln!( + buffer, + r###" +
+ + {path_html}{status} + + +
+"### + )?; + + for comment in comments { + let author_login = &comment.author.login; + let author_avatar_url = &comment.author.avatar_url; + let created_at = &comment.created_at; + let created_at_rfc3339 = comment.created_at.to_rfc3339(); + let body_html = &comment.body_html; + let comment_url = &comment.url; + + let edited = if comment.created_at != comment.updated_at { + " · edited" + } else { + "" + }; + + writeln!( + buffer, + r###" +
+
+
+ + {author_login} Avatar + + {author_login} + on {created_at}{edited} +
+ View on GitHub +
+ +
+ {body_html} +
+
+"### + )?; + } + + writeln!( + buffer, + r###" +
+
+"### + )?; + + Ok(()) +} diff --git a/src/gh_comments/style.css b/src/gh_comments/style.css index 1626564e..abc63cad 100644 --- a/src/gh_comments/style.css +++ b/src/gh_comments/style.css @@ -47,7 +47,7 @@ body { margin-bottom: 24px; } -.comment { +.comment, .review-thread { background: var(--bg-default); border: 1px solid var(--border-default); border-radius: 6px; @@ -55,7 +55,12 @@ body { overflow: hidden; } -.comment-header { +.review-thread { + margin-left: 80px; + margin-bottom: 1rem; +} + +.comment-header, .review-thread-header { display: flex; justify-content: space-between; align-items: center; @@ -66,7 +71,8 @@ body { color: var(--fg-muted); } -details:not([open]) > .comment-header { +details:not([open]) > .comment-header, +details:not([open]) > .review-thread-header { border-bottom: 0px; } @@ -74,6 +80,28 @@ details:not([open]) > .comment-header { padding: 16px; } +.review-thread-comment:first-child { + padding-top: 1rem; +} + +.review-thread-comment { + position: relative; + padding: 0.5rem 1rem; +} + +.review-thread-comment-header { + font-size: 0.9em; + color: var(--fg-muted); + display: flex; + justify-content: space-between; +} + +.review-thread-comment-body { + margin-left: 2rem; + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + /* === Avatars and author info === */ .avatar-link { display: block; @@ -89,6 +117,11 @@ details:not([open]) > .comment-header { display: block; } +.avatar-small { + width: 24px; + height: 24px; +} + .author-info { display: flex; flex-wrap: wrap; @@ -125,21 +158,25 @@ details:not([open]) > .comment-header { /* === Responsive === */ @media (max-width: 640px) { /* mobile layout */ - .author-info { - display: flex; - flex-direction: column; - line-height: 1.3; - } - .author-mobile { display: flex; align-items: flex-start; gap: 8px; } + .author-mobile .author-info { + display: flex; + flex-direction: column; + line-height: 1.3; + } + .desktop { display: none; } + + .review-thread { + margin-left: 40px; + } } @media (min-width: 641px) { From 2014fc118b5b483f489c4dfcd921a99ed02fb08b Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 00:50:55 +0100 Subject: [PATCH 05/16] Add reviews at the end --- src/gh_comments.rs | 112 +++++++++++++++++++++++++++++++++++++- src/gh_comments/style.css | 9 ++- 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index 25882b61..bed1943e 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -16,7 +16,10 @@ use hyper::{ use crate::{ cache, - github::{GitHubGraphQlComment, GitHubGraphQlReviewThreadComment, GitHubIssueWithComments}, + github::{ + GitHubGraphQlComment, GitHubGraphQlReviewThreadComment, GitHubIssueWithComments, + GitHubReviewState, + }, }; use crate::{ errors::AppError, @@ -185,6 +188,23 @@ pub async fn gh_comments( } } + // FIXME: Don't print reviews at the end, they should be mixed + if let Some(reviews) = &issue_with_comments.reviews { + for review in &reviews.nodes { + write_review_as_html( + &mut html, + &review.body_html, + &review.url, + &review.author, + review.state, + &review.submitted_at, + &review.updated_at, + review.is_minimized, + review.minimized_reason.as_deref(), + )?; + } + } + writeln!(html, r###""###).unwrap(); let mut headers = HeaderMap::new(); @@ -315,6 +335,96 @@ fn write_comment_as_html( Ok(()) } +fn write_review_as_html( + buffer: &mut String, + body_html: &str, + review_url: &str, + author: &GitHubSimplifiedAuthor, + state: GitHubReviewState, + submitted_at: &chrono::DateTime, + updated_at: &chrono::DateTime, + minimized: bool, + minimized_reason: Option<&str>, +) -> anyhow::Result<()> { + let author_login = &author.login; + let author_avatar_url = &author.avatar_url; + let submitted_at_rfc3339 = submitted_at.to_rfc3339(); + + writeln!( + buffer, + r###" +
+ + {author_login} Avatar + + +
+
+ {author_login} + {state:?} on {submitted_at} +
+
+
+"### + )?; + + if !body_html.is_empty() { + if minimized && let Some(minimized_reason) = minimized_reason { + writeln!( + buffer, + r###" +
+
+ +
+ {author_login} + left a comment · hidden as {minimized_reason} +
+ + View on GitHub +
+ +
+ {body_html} +
+
+
+"### + )?; + } else { + let edited = if submitted_at != updated_at { + " · edited" + } else { + "" + }; + + writeln!( + buffer, + r###" +
+
+
+
+ {author_login} + left a comment{edited} +
+ + View on GitHub +
+ +
+ {body_html} +
+
+
+"### + )?; + } + } + + Ok(()) +} + fn write_review_thread_as_html( buffer: &mut String, path: &str, diff --git a/src/gh_comments/style.css b/src/gh_comments/style.css index abc63cad..956ac1a7 100644 --- a/src/gh_comments/style.css +++ b/src/gh_comments/style.css @@ -40,7 +40,7 @@ body { } /* === Comment box === */ -.comment-wrapper { +.comment-wrapper, .review { display: flex; align-items: flex-start; gap: 12px; @@ -89,13 +89,18 @@ details:not([open]) > .review-thread-header { padding: 0.5rem 1rem; } -.review-thread-comment-header { +.review-header, .review-thread-comment-header { font-size: 0.9em; color: var(--fg-muted); display: flex; justify-content: space-between; } +.review-header { + margin-top: auto; + margin-bottom: auto; +} + .review-thread-comment-body { margin-left: 2rem; margin-top: 0.5rem; From fab3eef20f3cde340109b5524329980461ec3640 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 01:18:18 +0100 Subject: [PATCH 06/16] Add `/pull` alias for gh-comments --- src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.rs b/src/main.rs index 05cee624..a9129b6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -212,6 +212,10 @@ async fn run_server(addr: SocketAddr) -> anyhow::Result<()> { "/gh-comments/{owner}/{repo}/issues/{issue}", get(triagebot::gh_comments::gh_comments), ) + .route( + "/gh-comments/{owner}/{repo}/pull/{pr}", + get(triagebot::gh_comments::gh_comments), + ) .layer(GovernorLayer::new(ratelimit_config)); let app = Router::new() From 81d543ed62715fbc5c8547d6edb4872216378018 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 01:30:52 +0100 Subject: [PATCH 07/16] Filter-out intermediate reviews --- src/gh_comments.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index bed1943e..aa6cb5f5 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -73,7 +73,7 @@ pub async fn gh_comments( let start = Instant::now(); - let issue_with_comments = ctx + let mut issue_with_comments = ctx .github .issue_with_comments(&owner, &repo, issue_id) .await @@ -82,6 +82,24 @@ pub async fn gh_comments( let duration = start.elapsed(); let duration_secs = duration.as_secs_f64(); + // Filter-out reviews that either don't have a body or aren't linked by the first + // comment of a review comments. + // + // We need to do that otherwise we will end up showing "intermediate" review when + // someone reply in a review thread. + if let (Some(reviews), Some(review_threads)) = ( + issue_with_comments.reviews.as_mut(), + issue_with_comments.review_threads.as_ref(), + ) { + reviews.nodes.retain(|r| { + !r.body_html.is_empty() + || review_threads + .nodes + .iter() + .any(|rt| rt.comments.nodes[0].pull_request_review.id == r.id) + }); + } + // Rough estimation of the byte size of the issue with comments let estimated_size: usize = std::mem::size_of::() + issue_with_comments.url.len() From 02bf5b67e66f1da82f338de715e7744b0397bbc1 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 11:55:43 +0100 Subject: [PATCH 08/16] Print comments, reviews and review thread in order --- src/gh_comments.rs | 126 ++++++++++++++++++++++++++++++++------------- 1 file changed, 89 insertions(+), 37 deletions(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index aa6cb5f5..84ef73b0 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -179,46 +179,98 @@ pub async fn gh_comments( None, )?; - for comment in &issue_with_comments.comments.nodes { - write_comment_as_html( - &mut html, - &comment.body_html, - &comment.url, - &comment.author, - &comment.created_at, - &comment.updated_at, - comment.is_minimized, - comment.minimized_reason.as_deref(), - )?; - } - - // FIXME: Don't print review threads at the end, they should be mixed - if let Some(review_threads) = &issue_with_comments.review_threads { - for review_thread in &review_threads.nodes { - write_review_thread_as_html( - &mut html, - &review_thread.path, - review_thread.is_collapsed, - review_thread.is_resolved, - review_thread.is_outdated, - &review_thread.comments.nodes, - )?; + if let (Some(reviews), Some(review_threads)) = ( + issue_with_comments.reviews.as_ref(), + issue_with_comments.review_threads.as_ref(), + ) { + // A pull-request + enum Item { + Comment(usize, chrono::DateTime), + Review(usize, chrono::DateTime), } - } - // FIXME: Don't print reviews at the end, they should be mixed - if let Some(reviews) = &issue_with_comments.reviews { - for review in &reviews.nodes { - write_review_as_html( + // Create the timeline + let mut timeline: Vec<_> = issue_with_comments + .comments + .nodes + .iter() + .enumerate() + .map(|(i, c)| Item::Comment(i, c.created_at)) + .collect(); + timeline.extend( + reviews + .nodes + .iter() + .enumerate() + .map(|(i, r)| Item::Review(i, r.submitted_at)), + ); + timeline.sort_unstable_by_key(|i| match i { + Item::Comment(_i, created_at) => *created_at, + Item::Review(_i, submitted_at) => *submitted_at, + }); + + // Print the items + for item in timeline { + match item { + Item::Comment(pos, _) => { + let comment = &issue_with_comments.comments.nodes[pos]; + + write_comment_as_html( + &mut html, + &comment.body_html, + &comment.url, + &comment.author, + &comment.created_at, + &comment.updated_at, + comment.is_minimized, + comment.minimized_reason.as_deref(), + )?; + } + Item::Review(pos, _) => { + let review = &reviews.nodes[pos]; + + write_review_as_html( + &mut html, + &review.body_html, + &review.url, + &review.author, + review.state, + &review.submitted_at, + &review.updated_at, + review.is_minimized, + review.minimized_reason.as_deref(), + )?; + + // Try to print the associated review threads + for review_thread in review_threads + .nodes + .iter() + .filter(|rt| rt.comments.nodes[0].pull_request_review.id == review.id) + { + write_review_thread_as_html( + &mut html, + &review_thread.path, + review_thread.is_collapsed, + review_thread.is_resolved, + review_thread.is_outdated, + &review_thread.comments.nodes, + )?; + } + } + } + } + } else { + // An issue + for comment in &issue_with_comments.comments.nodes { + write_comment_as_html( &mut html, - &review.body_html, - &review.url, - &review.author, - review.state, - &review.submitted_at, - &review.updated_at, - review.is_minimized, - review.minimized_reason.as_deref(), + &comment.body_html, + &comment.url, + &comment.author, + &comment.created_at, + &comment.updated_at, + comment.is_minimized, + comment.minimized_reason.as_deref(), )?; } } From 31b79ca175cdb3deb540a0490be28fde9d4cfcac Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 12:26:07 +0100 Subject: [PATCH 09/16] Adjust a bit the layout for desktop and css update version number --- src/gh_comments.rs | 4 +++- src/gh_comments/style.css | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index 84ef73b0..5e479320 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -28,7 +28,7 @@ use crate::{ utils::{immutable_headers, is_repo_autorized}, }; -pub const STYLE_URL: &str = "/gh-comments/style@0.0.1.css"; +pub const STYLE_URL: &str = "/gh-comments/style@0.0.2.css"; pub const MARKDOWN_URL: &str = "/gh-comments/github-markdown@20260115.css"; pub const GH_COMMENTS_CACHE_CAPACITY_BYTES: usize = 35 * 1024 * 1024; // 35 Mb @@ -444,6 +444,7 @@ fn write_review_as_html( buffer, r###"
+
@@ -472,6 +473,7 @@ fn write_review_as_html( buffer, r###"
+
diff --git a/src/gh_comments/style.css b/src/gh_comments/style.css index 956ac1a7..1f904496 100644 --- a/src/gh_comments/style.css +++ b/src/gh_comments/style.css @@ -39,7 +39,7 @@ body { margin-bottom: 15px; } -/* === Comment box === */ +/* === Comment, review and review thread box === */ .comment-wrapper, .review { display: flex; align-items: flex-start; @@ -47,6 +47,10 @@ body { margin-bottom: 24px; } +.review { + margin-bottom: 0.5rem; +} + .comment, .review-thread { background: var(--bg-default); border: 1px solid var(--border-default); From 3e9e93a8e0085130c9a82afb1decacfb26f79101 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 13:12:42 +0100 Subject: [PATCH 10/16] Add review badges --- src/gh_comments.rs | 25 +++++++++++++++++++++++++ src/gh_comments/style.css | 31 ++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index 5e479320..a4805a5f 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -420,6 +420,30 @@ fn write_review_as_html( let author_avatar_url = &author.avatar_url; let submitted_at_rfc3339 = submitted_at.to_rfc3339(); + let (badge_color, badge_svg) = match state { + GitHubReviewState::Approved => { + // https://primer.github.io/octicons/check-16 + ( + "badge-success", + r##""##, + ) + } + GitHubReviewState::RequestChanges => { + // https://primer.github.io/octicons/file-diff-16 + ( + "badge-danger", + r##""##, + ) + } + GitHubReviewState::Dismissed | GitHubReviewState::Commented => { + // https://primer.github.io/octicons/eye-16 + ( + "", + r##""##, + ) + } + }; + writeln!( buffer, r###" @@ -429,6 +453,7 @@ fn write_review_as_html(
+
{badge_svg}
{author_login} {state:?} on {submitted_at} diff --git a/src/gh_comments/style.css b/src/gh_comments/style.css index 1f904496..02bea6ba 100644 --- a/src/gh_comments/style.css +++ b/src/gh_comments/style.css @@ -2,6 +2,8 @@ :root { --bg-default: #ffffff; --bg-muted: #f6f8fa; + --bg-success: #1f883d; + --bg-danger: #cf222e; --fg-default: #24292f; --fg-muted: #57606a; --fg-accent: #0969da; @@ -100,7 +102,7 @@ details:not([open]) > .review-thread-header { justify-content: space-between; } -.review-header { +.review-header, .review-header .author-info { margin-top: auto; margin-bottom: auto; } @@ -157,6 +159,33 @@ details:not([open]) > .review-thread-header { text-decoration: underline; } +/* === Badge === */ +.review-badge { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: 50%; + background-color: var(--bg-muted); + color: var(--fg-muted); + margin-right: 0.5rem; +} + +.badge-success { + color: white; + background-color: var(--bg-success); +} + +.badge-danger { + color: white; + background-color: var(--bg-danger); +} + +.octicon { + fill: currentColor; +} + /* === Markdown overrides === */ .markdown-body .user-mention { color: var(--fg-default); From d4a5fc2f45a7fb41fde477cd058b70d50fccc776 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 13:34:45 +0100 Subject: [PATCH 11/16] Adjust loaded comments/review comment --- src/gh_comments.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index a4805a5f..07e556a9 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -130,8 +130,6 @@ pub async fn gh_comments( ) }; - let comment_count = issue_with_comments.comments.nodes.len(); - let mut title = String::new(); pulldown_cmark_escape::escape_html(&mut title, &issue_with_comments.title)?; @@ -163,11 +161,26 @@ pub async fn gh_comments(

{title_html} #{issue_id}

-

{comment_count} comments loaded in {duration_secs:.2}s

"###, - ) - .unwrap(); + )?; + + // Print time and number of comments (+ reviews) loaded + if let Some(reviews) = issue_with_comments.reviews.as_ref() { + let count = issue_with_comments.comments.nodes.len() + reviews.nodes.len(); + writeln!( + html, + r###"

{count} comments and reviews loaded in {duration_secs:.2}s

"###, + )?; + } else { + let comment_count = issue_with_comments.comments.nodes.len(); + + writeln!( + html, + r###"

{comment_count} comments loaded in {duration_secs:.2}s

"###, + )?; + } + // Print issue/PR body write_comment_as_html( &mut html, &issue_with_comments.body_html, From 1f07196a85b9955aa771ef13f9e35e271ad70bf7 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 13:52:00 +0100 Subject: [PATCH 12/16] Make the issue link an anchor in the title --- src/gh_comments.rs | 2 +- src/gh_comments/style.css | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index 07e556a9..ba0e8d56 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -160,7 +160,7 @@ pub async fn gh_comments(
-

{title_html} #{issue_id}

+

{title_html} {owner}/{repo}#{issue_id}

"###, )?; diff --git a/src/gh_comments/style.css b/src/gh_comments/style.css index 02bea6ba..54325390 100644 --- a/src/gh_comments/style.css +++ b/src/gh_comments/style.css @@ -37,8 +37,13 @@ body { } .title { + margin-bottom: 0; +} + +.title bdi { font-size: 28px; - margin-bottom: 15px; + font-weight: 600; + display: inline-block; } /* === Comment, review and review thread box === */ From 886b9cea2015e310ed4a579dc6c5058e9d269583 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 14:48:58 +0100 Subject: [PATCH 13/16] Show the state of the issue/pull-request --- src/gh_comments.rs | 21 +++++++++++++++++++-- src/gh_comments/style.css | 21 ++++++++++++++++++++- src/github.rs | 11 +++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index ba0e8d56..dd2bd5cf 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -17,8 +17,8 @@ use hyper::{ use crate::{ cache, github::{ - GitHubGraphQlComment, GitHubGraphQlReviewThreadComment, GitHubIssueWithComments, - GitHubReviewState, + GitHubGraphQlComment, GitHubGraphQlReviewThreadComment, GitHubIssueState, + GitHubIssueWithComments, GitHubReviewState, }, }; use crate::{ @@ -164,6 +164,22 @@ pub async fn gh_comments( "###, )?; + // Print the state + writeln!(html, r##"
"##)?; + + { + let state = issue_with_comments.state; + let badge_color = match state { + GitHubIssueState::Open => "badge-success", + GitHubIssueState::Closed => "badge-danger", + GitHubIssueState::Merged => "badge-done", + }; + writeln!( + html, + r##"
{state:?}
"## + )?; + } + // Print time and number of comments (+ reviews) loaded if let Some(reviews) = issue_with_comments.reviews.as_ref() { let count = issue_with_comments.comments.nodes.len() + reviews.nodes.len(); @@ -179,6 +195,7 @@ pub async fn gh_comments( r###"

{comment_count} comments loaded in {duration_secs:.2}s

"###, )?; } + writeln!(html, "
")?; // Print issue/PR body write_comment_as_html( diff --git a/src/gh_comments/style.css b/src/gh_comments/style.css index 54325390..55fa6adb 100644 --- a/src/gh_comments/style.css +++ b/src/gh_comments/style.css @@ -4,6 +4,7 @@ --bg-muted: #f6f8fa; --bg-success: #1f883d; --bg-danger: #cf222e; + --bg-done: #8250df; --fg-default: #24292f; --fg-muted: #57606a; --fg-accent: #0969da; @@ -36,6 +37,13 @@ body { max-width: 980px; } +.meta-header { + display: flex; + align-items: center; + margin-bottom: 1rem; + gap: 9px; +} + .title { margin-bottom: 0; } @@ -164,7 +172,7 @@ details:not([open]) > .review-thread-header { text-decoration: underline; } -/* === Badge === */ +/* === Review and state badge === */ .review-badge { display: flex; align-items: center; @@ -177,11 +185,22 @@ details:not([open]) > .review-thread-header { margin-right: 0.5rem; } +.state-badge { + border-radius: 2em; + display: inline-block; + padding: 0.25rem 0.75rem; +} + .badge-success { color: white; background-color: var(--bg-success); } +.badge-done { + color: white; + background-color: var(--bg-done); +} + .badge-danger { color: white; background-color: var(--bg-danger); diff --git a/src/github.rs b/src/github.rs index 1733ce67..7a930e9f 100644 --- a/src/github.rs +++ b/src/github.rs @@ -2948,6 +2948,7 @@ query ($owner: String!, $repo: String!, $issueNumber: Int!, $commentsCursor: Str issueOrPullRequest(number: $issueNumber) { ... on Issue { url + state title titleHTML bodyHTML @@ -2978,6 +2979,7 @@ query ($owner: String!, $repo: String!, $issueNumber: Int!, $commentsCursor: Str } ... on PullRequest { url + state title titleHTML bodyHTML @@ -3147,6 +3149,7 @@ pub struct GitHubIssueWithComments { pub title_html: String, #[serde(rename = "bodyHTML")] pub body_html: String, + pub state: GitHubIssueState, pub url: String, pub author: GitHubSimplifiedAuthor, #[serde(rename = "createdAt")] @@ -3260,6 +3263,14 @@ pub enum GitHubReviewState { Dismissed, } +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum GitHubIssueState { + Open, + Closed, + Merged, +} + #[derive(Debug, serde::Deserialize)] pub struct GithubCommit { pub sha: String, From 2c7c3643ad23d007e22ff7fb5ff95d97c78d72f0 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 15:40:01 +0100 Subject: [PATCH 14/16] Add ids to simplify navigation from GitHub --- src/gh_comments.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index dd2bd5cf..dbca0cd0 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -351,6 +351,7 @@ fn write_comment_as_html( let author_login = &author.login; let author_avatar_url = &author.avatar_url; let created_at_rfc3339 = created_at.to_rfc3339(); + let id = extract_id_from_github_link(comment_url); if minimized && let Some(minimized_reason) = minimized_reason { writeln!( @@ -361,7 +362,7 @@ fn write_comment_as_html( {author_login} Avatar -
+
{author_login} @@ -403,7 +404,7 @@ fn write_comment_as_html( {author_login} Avatar -
+
{author_login} @@ -449,6 +450,7 @@ fn write_review_as_html( let author_login = &author.login; let author_avatar_url = &author.avatar_url; let submitted_at_rfc3339 = submitted_at.to_rfc3339(); + let id = extract_id_from_github_link(review_url); let (badge_color, badge_svg) = match state { GitHubReviewState::Approved => { @@ -477,7 +479,7 @@ fn write_review_as_html( writeln!( buffer, r###" -
+
{author_login} Avatar @@ -591,6 +593,7 @@ fn write_review_thread_as_html( let created_at_rfc3339 = comment.created_at.to_rfc3339(); let body_html = &comment.body_html; let comment_url = &comment.url; + let id = extract_id_from_github_link(comment_url); let edited = if comment.created_at != comment.updated_at { " · edited" @@ -601,7 +604,7 @@ fn write_review_thread_as_html( writeln!( buffer, r###" -
+
")?; + // Print shortcut links for PRs + if issue_with_comments.reviews.is_some() { + let base = format!("https://github.com/{owner}/{repo}/pull/{issue_id}"); + writeln!(html, r##"")?; + } + // Print issue/PR body write_comment_as_html( &mut html, diff --git a/src/gh_comments/style.css b/src/gh_comments/style.css index 55fa6adb..24ba7cd7 100644 --- a/src/gh_comments/style.css +++ b/src/gh_comments/style.css @@ -37,7 +37,7 @@ body { max-width: 980px; } -.meta-header { +.meta-header, .meta-links { display: flex; align-items: center; margin-bottom: 1rem; @@ -210,6 +210,16 @@ details:not([open]) > .review-thread-header { fill: currentColor; } +/* === Tab links === */ +.meta-links .selected { + border-radius: 0.375rem; + border: 0.00625rem solid var(--border-default); +} + +.meta-links .github-link { + padding: 0.375rem; +} + /* === Markdown overrides === */ .markdown-body .user-mention { color: var(--fg-default); From e6b4ad4b5dd1e521d56fdc6a93e84d45382b80f2 Mon Sep 17 00:00:00 2001 From: Urgau Date: Sat, 17 Jan 2026 23:02:27 +0100 Subject: [PATCH 16/16] Update `github-markdown` CSS with basic support for suggested changes --- src/gh_comments.rs | 4 +- ...60115.css => github-markdown@20260117.css} | 665 +++++++++++++++++- src/gh_comments/style.css | 4 + 3 files changed, 670 insertions(+), 3 deletions(-) rename src/gh_comments/{github-markdown@20260115.css => github-markdown@20260117.css} (68%) diff --git a/src/gh_comments.rs b/src/gh_comments.rs index 7ec20838..dc58aabd 100644 --- a/src/gh_comments.rs +++ b/src/gh_comments.rs @@ -29,7 +29,7 @@ use crate::{ }; pub const STYLE_URL: &str = "/gh-comments/style@0.0.2.css"; -pub const MARKDOWN_URL: &str = "/gh-comments/github-markdown@20260115.css"; +pub const MARKDOWN_URL: &str = "/gh-comments/github-markdown@20260117.css"; pub const GH_COMMENTS_CACHE_CAPACITY_BYTES: usize = 35 * 1024 * 1024; // 35 Mb @@ -356,7 +356,7 @@ pub async fn style_css() -> impl IntoResponse { } pub async fn markdown_css() -> impl IntoResponse { - const MARKDOWN_CSS: &str = include_str!("gh_comments/github-markdown@20260115.css"); + const MARKDOWN_CSS: &str = include_str!("gh_comments/github-markdown@20260117.css"); (immutable_headers("text/css; charset=utf-8"), MARKDOWN_CSS) } diff --git a/src/gh_comments/github-markdown@20260115.css b/src/gh_comments/github-markdown@20260117.css similarity index 68% rename from src/gh_comments/github-markdown@20260115.css rename to src/gh_comments/github-markdown@20260117.css index 99e4e877..b3013f02 100644 --- a/src/gh_comments/github-markdown@20260115.css +++ b/src/gh_comments/github-markdown@20260117.css @@ -16,6 +16,7 @@ .markdown-body { --base-size-16: 1rem; --base-size-24: 1.5rem; + --base-size-32: 2rem; --base-size-4: 0.25rem; --base-size-40: 2.5rem; --base-size-8: 0.5rem; @@ -25,11 +26,16 @@ --borderRadius-full: 624.9375rem; --borderRadius-medium: 0.375rem; --borderWidth-thin: 0.0625rem; + --overlay-width-small: 20rem; + --overlay-padding-condensed: 0.5rem; + --overlay-paddingBlock-condensed: 0.25rem; --stack-padding-condensed: 0.5rem; --stack-padding-normal: 1rem; --fontStack-monospace: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; --fontStack-sansSerif: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + --text-body-shorthand-small: var(--text-body-weight) var(--text-body-size-small) / var(--text-body-lineHeight-small) var(--fontStack-sansSerif); --h6-size: 0.75rem; + --fgColor-onEmphasis: LinkText; --fgColor-accent: Highlight; } @media (prefers-color-scheme: dark) { @@ -39,6 +45,7 @@ --fgColor-accent: #4493f8; --bgColor-attention-muted: #bb800926; --bgColor-default: #0d1117; + --bgColor-emphasis: #3d444d; --bgColor-muted: #151b23; --bgColor-neutral-muted: #656c7633; --borderColor-accent-emphasis: #1f6feb; @@ -48,6 +55,8 @@ --borderColor-default: #3d444d; --borderColor-done-emphasis: #8957e5; --borderColor-success-emphasis: #238636; + --button-default-shadow-resting: 0px 0px 0px 0px #000000; + --buttonCounter-default-bgColor-rest: #2f3742; --color-prettylights-syntax-brackethighlighter-angle: #9198a1; --color-prettylights-syntax-brackethighlighter-unmatched: #f85149; --color-prettylights-syntax-carriage-return-bg: #b62324; @@ -76,18 +85,41 @@ --color-prettylights-syntax-string-regexp: #7ee787; --color-prettylights-syntax-sublimelinter-gutter-mark: #3d444d; --color-prettylights-syntax-variable: #ffa657; + --diffBlob-additionNum-bgColor: #3fb9504d; + --diffBlob-additionWord-bgColor: #2ea04366; + --diffBlob-deletionNum-bgColor: #f851494d; + --diffBlob-deletionWord-bgColor: #f8514966; --fgColor-attention: #d29922; --fgColor-danger: #f85149; --fgColor-default: #f0f6fc; + --fgColor-disabled: #656c76; --fgColor-done: #ab7df8; --fgColor-muted: #9198a1; --fgColor-success: #3fb950; --borderColor-muted: #3d444db3; + --button-default-bgColor-active: var(--control-bgColor-active); + --button-default-bgColor-hover: var(--control-bgColor-hover); + --button-default-bgColor-rest: var(--control-bgColor-rest); + --button-default-bgColor-selected: var(--control-bgColor-active); --color-prettylights-syntax-invalid-illegal-bg: var(--bgColor-danger-muted); --color-prettylights-syntax-invalid-illegal-text: var(--fgColor-danger); + --diffBlob-additionLine-bgColor: var(--bgColor-success-muted); + --diffBlob-additionNum-fgColor: var(--fgColor-default); + --diffBlob-additionWord-fgColor: var(--fgColor-default); + --diffBlob-deletionLine-bgColor: var(--bgColor-danger-muted); + --diffBlob-deletionNum-fgColor: var(--fgColor-default); + --diffBlob-deletionWord-fgColor: var(--fgColor-default); + --fgColor-onEmphasis: #ffffff; --focus-outlineColor: var(--borderColor-accent-emphasis); --selection-bgColor: #1f6febb3; + --shadow-inset: inset 0px 1px 0px 0px #0104093d; --borderColor-neutral-muted: var(--borderColor-muted); + --button-default-bgColor-disabled: var(--control-bgColor-disabled); + --button-default-borderColor-disabled: var(--control-borderColor-disabled); + --button-default-borderColor-rest: var(--control-borderColor-rest); + --button-default-fgColor-rest: var(--control-fgColor-rest); + --button-default-borderColor-active: var(--button-default-borderColor-rest); + --button-default-borderColor-hover: var(--button-default-borderColor-rest); } } @media (prefers-color-scheme: light) { @@ -96,6 +128,7 @@ color-scheme: light; --fgColor-danger: #d1242f; --bgColor-attention-muted: #fff8c5; + --bgColor-emphasis: #25292e; --bgColor-muted: #f6f8fa; --bgColor-neutral-muted: #818b981f; --borderColor-accent-emphasis: #0969da; @@ -131,21 +164,46 @@ --color-prettylights-syntax-string-regexp: #116329; --color-prettylights-syntax-sublimelinter-gutter-mark: #818b98; --color-prettylights-syntax-variable: #953800; + --diffBlob-additionNum-bgColor: #aceebb; + --diffBlob-additionWord-bgColor: #aceebb; + --diffBlob-deletionNum-bgColor: #ffcecb; + --diffBlob-deletionWord-bgColor: #ffcecb; --fgColor-accent: #0969da; --fgColor-attention: #9a6700; + --fgColor-disabled: #818b98; --fgColor-done: #8250df; --fgColor-muted: #59636e; --fgColor-success: #1a7f37; --bgColor-default: #ffffff; --borderColor-muted: #d1d9e0b3; + --button-default-bgColor-active: var(--control-bgColor-active); + --button-default-bgColor-hover: var(--control-bgColor-hover); + --button-default-bgColor-rest: var(--control-bgColor-rest); + --button-default-bgColor-selected: var(--control-bgColor-active); + --button-default-fgColor-rest: var(--control-fgColor-rest); + --button-default-shadow-resting: 0px 1px 0px 0px #1f23280a; + --buttonCounter-default-bgColor-rest: var(--bgColor-neutral-muted); --color-prettylights-syntax-invalid-illegal-bg: var(--bgColor-danger-muted); --color-prettylights-syntax-markup-bold: #1f2328; --color-prettylights-syntax-markup-italic: #1f2328; --color-prettylights-syntax-storage-modifier-import: #1f2328; + --diffBlob-additionLine-bgColor: var(--bgColor-success-muted); + --diffBlob-deletionLine-bgColor: var(--bgColor-danger-muted); --fgColor-default: #1f2328; + --fgColor-onEmphasis: #ffffff; --focus-outlineColor: var(--borderColor-accent-emphasis); --selection-bgColor: #0969da33; + --shadow-inset: inset 0px 1px 0px 0px #1f23280a; --borderColor-neutral-muted: var(--borderColor-muted); + --button-default-bgColor-disabled: var(--control-bgColor-disabled); + --button-default-borderColor-active: var(--control-borderColor-rest); + --button-default-borderColor-disabled: var(--control-borderColor-disabled); + --button-default-borderColor-rest: var(--control-borderColor-rest); + --diffBlob-additionNum-fgColor: var(--fgColor-default); + --diffBlob-additionWord-fgColor: var(--fgColor-default); + --diffBlob-deletionNum-fgColor: var(--fgColor-default); + --diffBlob-deletionWord-fgColor: var(--fgColor-default); + --button-default-borderColor-hover: var(--button-default-borderColor-rest); } } @@ -161,7 +219,7 @@ color: var(--fgColor-default); background-color: var(--bgColor-default); font-family: var(--fontStack-sansSerif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"); - font-size: 14px; + font-size: 16px; line-height: 1.5; word-wrap: break-word; } @@ -525,12 +583,194 @@ text-align: center; } +.markdown-body .btn { + position: relative; + display: inline-block; + padding: 5px var(--base-size-16); + font-size: 14px; + font-weight: var(--base-text-weight-medium, 500); + line-height: 20px; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + user-select: none; + border: 1px solid; + border-radius: 6px; + appearance: none; + color: var(--button-default-fgColor-rest); + background-color: var(--button-default-bgColor-rest); + border-color: var(--button-default-borderColor-rest); + box-shadow: var(--button-default-shadow-resting, var(--color-btn-shadow)),var(--button-default-shadow-inset, var(--color-btn-inset-shadow)); + transition: 80ms cubic-bezier(0.33, 1, 0.68, 1); + transition-property: color,background-color,box-shadow,border-color; +} + +.markdown-body .btn:hover { + text-decoration: none; +} + +.markdown-body .btn:disabled, +.markdown-body .btn.disabled, +.markdown-body .btn[aria-disabled=true] { + cursor: default; + color: var(--fgColor-disabled); + background-color: var(--button-default-bgColor-disabled); + border-color: var(--button-default-borderColor-disabled); +} + +.markdown-body .btn i { + font-style: normal; + font-weight: var(--base-text-weight-medium, 500); + opacity: .75; +} + +.markdown-body .btn .octicon { + margin-right: var(--base-size-4); + color: var(--fgColor-muted); + vertical-align: text-bottom; +} + +.markdown-body .btn .octicon:only-child { + margin-right: 0; +} + +.markdown-body .btn .Counter { + margin-left: 2px; + color: inherit; + text-shadow: none; + vertical-align: top; + background-color: var(--buttonCounter-default-bgColor-rest); +} + +.markdown-body .btn .dropdown-caret { + margin-left: var(--base-size-4); + opacity: .8; +} + +.markdown-body .btn:hover, +.markdown-body .btn.hover, +.markdown-body [open]>.btn { + background-color: var(--button-default-bgColor-hover); + border-color: var(--button-default-borderColor-hover); + transition-duration: .1s; +} + +.markdown-body .btn:active { + background-color: var(--button-default-bgColor-active); + border-color: var(--button-default-borderColor-active); + transition: none; +} + +.markdown-body .btn.selected, +.markdown-body .btn[aria-selected=true] { + background-color: var(--button-default-bgColor-selected); + box-shadow: var(--shadow-inset, var(--color-primer-shadow-inset)); +} + +.markdown-body .btn:disabled .octicon, +.markdown-body .btn.disabled .octicon, +.markdown-body .btn[aria-disabled=true] .octicon { + color: var(--fgColor-disabled); +} + +.markdown-body .btn-sm { + padding: 3px 12px; + font-size: 12px; + line-height: 20px; +} + +.markdown-body .btn-sm .octicon { + vertical-align: text-top; +} + .markdown-body input::-webkit-outer-spin-button, .markdown-body input::-webkit-inner-spin-button { margin: 0; appearance: none; } +.markdown-body .tooltipped { + position: relative; +} + +.markdown-body .tooltipped::after { + position: absolute; + z-index: 1000000; + display: none; + padding: var(--overlay-paddingBlock-condensed, 0.25rem) var(--overlay-padding-condensed, 0.5rem); + font: var(--text-body-shorthand-small, normal normal 11px/1.5 var(--fontStack-sansSerif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji")); + -webkit-font-smoothing: subpixel-antialiased; + color: var(--fgColor-onEmphasis); + text-align: center; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-wrap: break-word; + white-space: pre; + pointer-events: none; + content: attr(aria-label); + background: var(--bgColor-emphasis); + border-radius: var(--borderRadius-medium); + opacity: 0; +} + +.markdown-body .tooltipped:hover::before, +.markdown-body .tooltipped:hover::after, +.markdown-body .tooltipped:active::before, +.markdown-body .tooltipped:active::after, +.markdown-body .tooltipped:focus::before, +.markdown-body .tooltipped:focus::after { + display: inline-block; + text-decoration: none; + animation-name: tooltip-appear; + animation-duration: .1s; + animation-fill-mode: forwards; + animation-timing-function: ease-in; +} + +.markdown-body .tooltipped-multiline:hover::after, +.markdown-body .tooltipped-multiline:active::after, +.markdown-body .tooltipped-multiline:focus::after { + display: table-cell; +} + +.markdown-body .tooltipped-se::after { + top: 100%; + margin-top: 6px; + right: auto; + left: 50%; + margin-left: calc(var(--base-size-16)*-1); +} + +.markdown-body .tooltipped-n::after { + right: 50%; + bottom: 100%; + margin-bottom: 6px; + transform: translateX(50%); +} + +.markdown-body .tooltipped-multiline::after { + width: max-content; + max-width: var(--overlay-width-small, 20rem); + word-wrap: break-word; + white-space: pre-line; + border-collapse: separate; +} + +.markdown-body .tooltipped-multiline.tooltipped-s::after, +.markdown-body .tooltipped-multiline.tooltipped-n::after { + right: auto; + left: 50%; + transform: translateX(-50%); +} + +.markdown-body .tooltipped-multiline.tooltipped-w::after, +.markdown-body .tooltipped-multiline.tooltipped-e::after { + right: 100%; +} + .markdown-body .border { border: var(--borderWidth-thin, 1px) solid var(--borderColor-default) !important; } @@ -539,6 +779,22 @@ border: 0 !important; } +.markdown-body .border-top { + border-top: var(--borderWidth-thin, 1px) solid var(--borderColor-default) !important; +} + +.markdown-body .border-bottom { + border-bottom: var(--borderWidth-thin, 1px) solid var(--borderColor-default) !important; +} + +.markdown-body .rounded-0 { + border-radius: 0 !important; +} + +.markdown-body .rounded-2 { + border-radius: var(--borderRadius-medium, 6px) !important; +} + .markdown-body .circle { border-radius: var(--borderRadius-full, 50%) !important; } @@ -555,18 +811,58 @@ border-color: var(--borderColor-muted) !important; } +.markdown-body .flex-column { + flex-direction: column !important; +} + +.markdown-body .flex-wrap { + flex-wrap: wrap !important; +} + +.markdown-body .flex-justify-end { + justify-content: flex-end !important; +} + +.markdown-body .flex-items-center { + align-items: center !important; +} + +.markdown-body .flex-auto { + flex: auto !important; +} + +.markdown-body .width-full { + width: 100% !important; +} + .markdown-body .mb-0 { margin-bottom: 0 !important; } +.markdown-body .mb-1 { + margin-bottom: var(--base-size-4, 4px) !important; +} + .markdown-body .ml-1 { margin-left: var(--base-size-4, 4px) !important; } +.markdown-body .mr-n1 { + margin-right: calc(-1*var(--base-size-4, 4px)) !important; +} + +.markdown-body .mb-2 { + margin-bottom: var(--base-size-8, 8px) !important; +} + .markdown-body .mr-2 { margin-right: var(--base-size-8, 8px) !important; } +.markdown-body .ml-2 { + margin-left: var(--base-size-8, 8px) !important; +} + .markdown-body .my-2 { margin-top: var(--base-size-8, 8px) !important; margin-bottom: var(--base-size-8, 8px) !important; @@ -581,6 +877,36 @@ padding-bottom: 0 !important; } +.markdown-body .pb-1 { + padding-bottom: var(--base-size-4, 4px) !important; +} + +.markdown-body .py-1 { + padding-top: var(--base-size-4, 4px) !important; + padding-bottom: var(--base-size-4, 4px) !important; +} + +.markdown-body .p-2 { + padding: var(--base-size-8, 8px) !important; +} + +.markdown-body .pt-2 { + padding-top: var(--base-size-8, 8px) !important; +} + +.markdown-body .pr-2 { + padding-right: var(--base-size-8, 8px) !important; +} + +.markdown-body .pl-2 { + padding-left: var(--base-size-8, 8px) !important; +} + +.markdown-body .px-2 { + padding-right: var(--base-size-8, 8px) !important; + padding-left: var(--base-size-8, 8px) !important; +} + .markdown-body .px-3 { padding-right: var(--base-size-16, 16px) !important; padding-left: var(--base-size-16, 16px) !important; @@ -590,14 +916,42 @@ font-size: var(--h6-size, 12px) !important; } +.markdown-body .lh-condensed { + line-height: 1.25 !important; +} + +.markdown-body .lh-default { + line-height: 1.5 !important; +} + +.markdown-body .text-right { + text-align: right !important; +} + .markdown-body .text-bold { font-weight: var(--base-text-weight-semibold, 600) !important; } +.markdown-body .d-block { + display: block !important; +} + +.markdown-body .d-flex { + display: flex !important; +} + .markdown-body .d-inline-block { display: inline-block !important; } +.markdown-body .d-none { + display: none !important; +} + +.markdown-body .d-table { + display: table !important; +} + .markdown-body .sr-only { position: absolute; width: 1px; @@ -1236,6 +1590,11 @@ color: var(--color-prettylights-syntax-constant-other-reference-link); } +.markdown-body .user-select-contain { + -webkit-user-select: contain; + user-select: contain; +} + .markdown-body [role=button]:focus:not(:focus-visible), .markdown-body [role=tabpanel][tabindex="0"]:focus:not(:focus-visible), .markdown-body button:focus:not(:focus-visible), @@ -1266,6 +1625,23 @@ height: 1em; } +.markdown-body .comment-body { + width: 100%; + padding: var(--base-size-16); + overflow: visible; + font-size: 14px; + color: var(--fgColor-default); +} + +.markdown-body .comment-body .highlight { + overflow: visible !important; + background-color: rgba(0,0,0,0); +} + +.markdown-body .css-overflow-wrap-anywhere { + overflow-wrap: anywhere; +} + .markdown-body .commit-tease-sha { display: inline-block; font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace); @@ -1287,6 +1663,28 @@ overflow-y: auto; } +.markdown-body .diff-view .blob-code-marker-context::before, +.markdown-body .diff-view .blob-code-marker-injected_context::before, +.markdown-body .diff-view .blob-code-marker-addition::before, +.markdown-body .diff-view .blob-code-marker-deletion::before { + top: var(--base-size-4); +} + +.markdown-body .diff-view .line-alert { + position: absolute; + top: 0; + left: -40px; + margin: 2px; +} + +.markdown-body .diff-view .line-alert.codeowners-error { + left: 0; +} + +.markdown-body .comment-body .diff-view .line-alert { + left: 0; +} + .markdown-body .blob-num { position: relative; width: 1%; @@ -1321,6 +1719,10 @@ color: var(--fgColor-muted); } +.markdown-body .blob-num-hidden::before { + visibility: hidden; +} + .markdown-body .blob-code { position: relative; padding-right: 10px; @@ -1366,6 +1768,106 @@ padding-left: 22px !important; } +.markdown-body .blob-code-addition, +.markdown-body .blob-code-deletion { + padding-left: 22px; +} + +.markdown-body .blob-code-marker-addition::before { + position: absolute; + top: 1px; + left: var(--base-size-8); + content: "+ "; +} + +.markdown-body .blob-code-marker-deletion::before { + position: absolute; + top: 1px; + left: var(--base-size-8); + content: "- "; +} + +.markdown-body .soft-wrap .diff-table { + table-layout: fixed; +} + +.markdown-body .soft-wrap .blob-code { + padding-left: 18px; + text-indent: 0; +} + +.markdown-body .soft-wrap .blob-code-inner { + white-space: pre-wrap; +} + +.markdown-body .soft-wrap .no-nl-marker { + display: none; +} + +.markdown-body .soft-wrap .add-line-comment { + margin-top: 0; + margin-left: -24px; +} + +.markdown-body .soft-wrap .blob-code-context, +.markdown-body .soft-wrap .blob-code-addition, +.markdown-body .soft-wrap .blob-code-deletion { + padding-left: 22px; + text-indent: 0; +} + +.markdown-body .blob-code-addition { + background-color: var(--diffBlob-additionLine-bgColor, var(--diffBlob-addition-bgColor-line)); + outline: 1px dotted rgba(0,0,0,0); +} + +.markdown-body .blob-code-addition .x { + color: var(--diffBlob-additionWord-fgColor, var(--diffBlob-addition-fgColor-text)); + background-color: var(--diffBlob-additionWord-bgColor, var(--diffBlob-addition-bgColor-word)); +} + +.markdown-body .blob-num-addition { + color: var(--diffBlob-additionNum-fgColor, var(--diffBlob-addition-fgColor-num)); + background-color: var(--diffBlob-additionNum-bgColor, var(--diffBlob-addition-bgColor-num)); + border-color: var(--borderColor-success-emphasis); +} + +.markdown-body .blob-num-addition:hover { + color: var(--fgColor-default); +} + +.markdown-body .blob-code-deletion { + background-color: var(--diffBlob-deletionLine-bgColor, var(--diffBlob-deletion-bgColor-line)); + outline: 1px dashed rgba(0,0,0,0); +} + +.markdown-body .blob-code-deletion .x { + color: var(--diffBlob-deletionWord-fgColor, var(--diffBlob-deletion-fgColor-text)); + background-color: var(--diffBlob-deletionWord-bgColor, var(--diffBlob-deletion-bgColor-word)); +} + +.markdown-body .blob-num-deletion { + color: var(--diffBlob-deletionNum-fgColor, var(--diffBlob-deletion-fgColor-num)); + background-color: var(--diffBlob-deletionNum-bgColor, var(--diffBlob-deletion-bgColor-num)); + border-color: var(--borderColor-danger-emphasis); +} + +.markdown-body .blob-num-deletion:hover { + color: var(--fgColor-default); +} + +.markdown-body .diff-view .file-type-prose .rich-diff { + display: none; +} + +.markdown-body .diff-view .display-rich-diff .rich-diff { + display: block; +} + +.markdown-body .diff-view .display-rich-diff .file-diff { + display: none; +} + .markdown-body a:has(>p,>div,>pre,>blockquote) { display: block; } @@ -1379,6 +1881,12 @@ outline-offset: 2px; } +.markdown-body .btn .manual-file-chooser { + top: 0; + padding: 0; + line-height: 34px; +} + .markdown-body .task-list-item { list-style-type: none; } @@ -1412,6 +1920,25 @@ margin: 0 -1.6em .25em .2em; } +.markdown-body .comment-body .reference { + font-weight: var(--base-text-weight-semibold, 600); + white-space: nowrap; +} + +.markdown-body .comment-body .issue-link { + white-space: normal; +} + +.markdown-body .comment-body .issue-link .issue-shorthand { + font-weight: var(--base-text-weight-normal, 400); + color: var(--fgColor-muted); +} + +.markdown-body .comment-body .issue-link:hover .issue-shorthand, +.markdown-body .comment-body .issue-link:focus .issue-shorthand { + color: var(--fgColor-accent); +} + .markdown-body .contains-task-list:hover .task-list-item-convert-container, .markdown-body .contains-task-list:focus-within .task-list-item-convert-container { display: block; @@ -1421,10 +1948,25 @@ clip: auto; } +.markdown-body summary[type=button].btn { + appearance: none; +} + .markdown-body ::-webkit-calendar-picker-indicator { filter: invert(50%); } +.markdown-body .btn:hover .icon-sponsor, +.markdown-body .btn:focus .icon-sponsor, +.markdown-body .btn:hover .icon-sponsoring, +.markdown-body .btn:focus .icon-sponsoring { + transform: scale(1.1); +} + +.markdown-body .btn .octicon-triangle-down { + margin-right: 0; +} + .markdown-body .markdown-alert { padding: var(--base-size-8) var(--base-size-16); margin-bottom: var(--base-size-16); @@ -1491,6 +2033,127 @@ margin-top: 0 !important; } +.markdown-body .file .check-annotation { + border-bottom: solid var(--borderWidth-thin) var(--borderColor-default); +} + +.markdown-body .file .check-annotation:last-child { + border-bottom: 0; +} + +.markdown-body .file { + position: relative; + margin-top: var(--base-size-16); + margin-bottom: var(--base-size-16); + border: var(--borderWidth-thin) solid var(--borderColor-default); + border-radius: var(--borderRadius-medium); +} + +.markdown-body .file .drag-and-drop { + border: 0; + border-top: var(--borderWidth-thin) dashed var(--borderColor-default); +} + +.markdown-body .file:target { + outline: none !important; + box-shadow: 0 0 0 2px var(--focus-outlineColor) !important; +} + +.markdown-body .file .data.empty { + padding: var(--base-size-4) var(--base-size-8); + color: var(--fgColor-muted); +} + +.markdown-body .file:not(.open) .file-header.file-header--expandable { + border-bottom: 0; + border-radius: var(--borderRadius-medium); +} + +.markdown-body .file .data.suppressed, +.markdown-body .file.open .image { + display: none; +} + +.markdown-body .file.open .data.suppressed { + display: block; +} + +.markdown-body .file .image { + position: relative; + padding: var(--base-size-32); + text-align: center; + background-color: #ddd; +} + +.markdown-body .file .image table { + margin: 0 auto; +} + +.markdown-body .file .image td { + padding: 0 var(--base-size-4); + color: var(--fgColor-muted); + text-align: center; + vertical-align: top; +} + +.markdown-body .file .image td img { + max-width: 100%; +} + +.markdown-body .file .image .border-wrap { + position: relative; + display: inline-block; + line-height: 0; + background-color: var(--bgColor-default); + border: var(--borderWidth-thin) solid var(--borderColor-default); +} + +.markdown-body .file .image a { + display: inline-block; + line-height: 0; +} + +.markdown-body .file .image img, +.markdown-body .file .image canvas { + max-width: 600px; + background: url("/images/modules/commit/trans_bg.gif") right bottom #eee; + border: var(--borderWidth-thin) solid #fff; +} + +.markdown-body .file .image .view img, +.markdown-body .file .image .view canvas { + position: relative; + top: 0; + right: 0; + max-width: inherit; + background: url("/images/modules/commit/trans_bg.gif") right bottom #eee; +} + +.markdown-body .file .image .view>span { + vertical-align: middle; +} + +.markdown-body .file .empty { + background: none; +} + +.markdown-body .issue-keyword { + border-bottom: var(--borderWidth-thin) dotted var(--borderColor-default); +} + +.markdown-body .issue-keyword:hover { + border-bottom: 0; +} + +.markdown-body .file .readme table[data-table-type=yaml-metadata] { + font-size: 12px; + line-height: 1; +} + +.markdown-body .file .readme table[data-table-type=yaml-metadata] table { + margin: 0; +} + .markdown-body .tab-size[data-tab-size="1"] { tab-size: 1; } diff --git a/src/gh_comments/style.css b/src/gh_comments/style.css index 24ba7cd7..b44d6d1f 100644 --- a/src/gh_comments/style.css +++ b/src/gh_comments/style.css @@ -221,6 +221,10 @@ details:not([open]) > .review-thread-header { } /* === Markdown overrides === */ +.markdown-body { + font-size: 14px; +} + .markdown-body .user-mention { color: var(--fg-default); font-weight: 600;