From 0bf2ddaaaee2b013cdc13c42fb4aa027e54313c0 Mon Sep 17 00:00:00 2001 From: Sam <47310940+Vailorium@users.noreply.github.com> Date: Tue, 30 Aug 2022 14:49:38 +1200 Subject: [PATCH] Feature/2389 show reference docs for specific method class (#3941) Reference doc code folding feature - fully implemented --- .../APIView/APIView/CodeFileRenderer.cs | 11 ++-- src/dotnet/APIView/APIView/CodeLine.cs | 4 +- .../APIView/APIViewWeb/Client/css/site.scss | 30 +++++++++- .../APIView/APIViewWeb/Client/src/review.ts | 36 +++++++++++- .../APIViewWeb/Models/CodeLineModel.cs | 4 +- .../APIViewWeb/Models/RenderedCodeFile.cs | 23 ++------ .../APIViewWeb/Pages/Assemblies/Review.cshtml | 12 ++-- .../Pages/Assemblies/Review.cshtml.cs | 56 +++++++++++++------ .../Pages/Assemblies/_CodeLine.cshtml | 20 ++++++- .../Repositories/PullRequestManager.cs | 4 +- .../APIViewWeb/Repositories/ReviewManager.cs | 4 +- .../wwwroot/icons/chevron-right.svg | 3 + .../APIViewWeb/wwwroot/icons/chevron-up.svg | 3 + 13 files changed, 149 insertions(+), 61 deletions(-) create mode 100644 src/dotnet/APIView/APIViewWeb/wwwroot/icons/chevron-right.svg create mode 100644 src/dotnet/APIView/APIViewWeb/wwwroot/icons/chevron-up.svg diff --git a/src/dotnet/APIView/APIView/CodeFileRenderer.cs b/src/dotnet/APIView/APIView/CodeFileRenderer.cs index fa286da99b0..7b35563293a 100644 --- a/src/dotnet/APIView/APIView/CodeFileRenderer.cs +++ b/src/dotnet/APIView/APIView/CodeFileRenderer.cs @@ -14,14 +14,14 @@ public class CodeFileRenderer { public static CodeFileRenderer Instance = new CodeFileRenderer(); - public CodeLine[] Render(CodeFile file, bool showDocumentation = false, bool enableSkipDiff = false) + public CodeLine[] Render(CodeFile file, bool enableSkipDiff = false) { var list = new List(); - Render(list, file.Tokens, showDocumentation, enableSkipDiff); + Render(list, file.Tokens, enableSkipDiff); return list.ToArray(); } - private void Render(List list, IEnumerable node, bool showDocumentation, bool enableSkipDiff) + private void Render(List list, IEnumerable node, bool enableSkipDiff) { var stringBuilder = new StringBuilder(); string currentId = null; @@ -39,9 +39,6 @@ private void Render(List list, IEnumerable node, bool s if (enableSkipDiff && isSkipDiffRange && token.Kind != CodeFileTokenKind.SkipDiffRangeEnd) continue; - if (!showDocumentation && isDocumentationRange && token.Kind != CodeFileTokenKind.DocumentRangeEnd) - continue; - switch(token.Kind) { case CodeFileTokenKind.Newline: @@ -68,7 +65,7 @@ private void Render(List list, IEnumerable node, bool s lineClass = lineClass.Trim(); } - list.Add(new CodeLine(stringBuilder.ToString(), currentId, lineClass, indentSize)); + list.Add(new CodeLine(stringBuilder.ToString(), currentId, lineClass, indentSize, isDocumentationRange)); currentId = null; stringBuilder.Clear(); break; diff --git a/src/dotnet/APIView/APIView/CodeLine.cs b/src/dotnet/APIView/APIView/CodeLine.cs index 9812dd179e7..b4eaec98f60 100644 --- a/src/dotnet/APIView/APIView/CodeLine.cs +++ b/src/dotnet/APIView/APIView/CodeLine.cs @@ -9,13 +9,15 @@ namespace ApiView public string ElementId { get; } public string LineClass { get; } public int IndentSize { get; } + public bool IsDocumentation { get; } - public CodeLine(string html, string id, string lineClass, int indentSize = 0) + public CodeLine(string html, string id, string lineClass, int indentSize = 0, bool isDocumentation = false) { this.DisplayString = html; this.ElementId = id; this.LineClass = lineClass; this.IndentSize = indentSize; + this.IsDocumentation = isDocumentation; } public override string ToString() diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/site.scss b/src/dotnet/APIView/APIViewWeb/Client/css/site.scss index 531404e772a..07fb8347c0b 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/css/site.scss +++ b/src/dotnet/APIView/APIViewWeb/Client/css/site.scss @@ -808,6 +808,14 @@ code { z-index: 1; } +.icon-chevron-right { + background: url(/icons/chevron-right.svg) center center no-repeat; +} + +.icon-chevron-up { + background: url(/icons/chevron-up.svg) center center no-repeat; +} + .icon-language { width: 34px; height: 34px; @@ -864,7 +872,6 @@ code { .icon-swagger { background: url(/icons/swagger-original.svg) center center no-repeat; } - /*---------------------------------------------------------------------*/ .info-text { @@ -911,6 +918,27 @@ code { left: 0px; } +.line-toggle-documentation-button:hover { + transform: scale(1); +} + +.line-toggle-documentation-button { + display: inline-block; + width: 21px; + height: 21px; + line-height: 21px; + color: #fff !important; + text-align: center; + text-indent: 0; + cursor: pointer; + background-color: #0366d6; + border-radius: 3px; + box-shadow: 0 1px 4px rgba(27, 31, 35, .15); + transition: transform .1s ease-in-out; + transform: scale(.8); + user-select: none; +} + .line-details-button-cell { width: 22px; height: 22px; diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/review.ts b/src/dotnet/APIView/APIViewWeb/Client/src/review.ts index 7b20ae2c6ec..104fd495fd3 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/src/review.ts +++ b/src/dotnet/APIView/APIViewWeb/Client/src/review.ts @@ -4,10 +4,11 @@ import { updatePageSettings } from "./helpers"; $(() => { const SEL_DOC_CLASS = ".documentation"; const SHOW_DOC_CHECK_COMPONENT = "#show-documentation-component"; - const SHOW_DOC_CHECKBOX = ".show-doc-checkbox"; + const SHOW_DOC_CHECKBOX = "#show-doc-checkbox"; const SHOW_DOC_HREF = ".show-document"; const SHOW_DIFFONLY_CHECKBOX = ".show-diffonly-checkbox"; const SHOW_DIFFONLY_HREF = ".show-diffonly"; + const TOGGLE_DOCUMENTATION = ".line-toggle-documentation-button"; hideCheckboxIfNoDocs(); @@ -62,7 +63,21 @@ $(() => { /* TOGGLE PAGE OPTIONS --------------------------------------------------------------------------------------------------------------------------------------------------------*/ $(SHOW_DOC_CHECKBOX).on("click", e => { - $(SHOW_DOC_HREF)[0].click(); + if((e.target as HTMLInputElement).checked) { + // show all documentation + $(".code-line-documentation").removeClass('hidden-row'); + $(TOGGLE_DOCUMENTATION).removeClass("icon-chevron-right"); + $(TOGGLE_DOCUMENTATION).addClass("icon-chevron-up"); + } else { + // hide all documentation + $(".code-line-documentation").addClass("hidden-row"); + $(TOGGLE_DOCUMENTATION).removeClass("icon-chevron-up"); + $(TOGGLE_DOCUMENTATION).addClass("icon-chevron-right"); + } + }); + + $(SHOW_DOC_HREF).on("click", e => { + $(SHOW_DOC_CHECKBOX)[0].click(); }); $(SHOW_DIFFONLY_CHECKBOX).on("click", e => { @@ -97,6 +112,23 @@ $(() => { }); }); + /* TOGGLE DOCUMENTATION DROPDOWN + --------------------------------------------------------------------------------------------------------------------------------------------------------*/ + $(TOGGLE_DOCUMENTATION).on("click", function(e){ + const documentedBy = $(this).data('documented-by'); + const codeLines = $(".code-window > tbody > .code-line"); + + for(var i = 0; i < documentedBy.length; i++) { + $(codeLines[documentedBy[i] - 1]).toggleClass("hidden-row"); + } + + $(this).toggleClass('icon-chevron-right'); + $(this).toggleClass('icon-chevron-up'); + + // scroll button to center of screen, so that the line is visible after toggling folding + $(this).get(0).scrollIntoView({ block: "center"}); + }); + /* DROPDOWN FILTER FOR REVIEW, REVISIONS AND DIFF (UPDATES REVIEW PAGE ON CHANGE) --------------------------------------------------------------------------------------------------------------------------------------------------------*/ $('#revisions-bootstraps-select, #review-bootstraps-select, #diff-bootstraps-select').each(function(index, value) { diff --git a/src/dotnet/APIView/APIViewWeb/Models/CodeLineModel.cs b/src/dotnet/APIView/APIViewWeb/Models/CodeLineModel.cs index cb1db3cb471..56971cc1b36 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/CodeLineModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/CodeLineModel.cs @@ -9,13 +9,14 @@ namespace APIViewWeb.Models { public readonly struct CodeLineModel { - public CodeLineModel(DiffLineKind kind, CodeLine codeLine, CommentThreadModel commentThread, CodeDiagnostic[] diagnostics, int lineNumber) + public CodeLineModel(DiffLineKind kind, CodeLine codeLine, CommentThreadModel commentThread, CodeDiagnostic[] diagnostics, int lineNumber, int[] documentedByLines) { CodeLine = codeLine; CommentThread = commentThread; Diagnostics = diagnostics; Kind = kind; LineNumber = lineNumber; + DocumentedByLines = documentedByLines; } public CodeLine CodeLine { get; } @@ -23,5 +24,6 @@ public CodeLineModel(DiffLineKind kind, CodeLine codeLine, CommentThreadModel co public CommentThreadModel CommentThread { get; } public DiffLineKind Kind { get; } public int LineNumber { get; } + public int[] DocumentedByLines { get; } } } diff --git a/src/dotnet/APIView/APIViewWeb/Models/RenderedCodeFile.cs b/src/dotnet/APIView/APIViewWeb/Models/RenderedCodeFile.cs index fde0f8b08f6..49694239e68 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/RenderedCodeFile.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/RenderedCodeFile.cs @@ -19,14 +19,8 @@ public RenderedCodeFile(CodeFile codeFile) public CodeFile CodeFile { get; } - public CodeLine[] Render(bool showDocumentation) + public CodeLine[] Render() { - //Always render when documentation is requested to avoid cach thrashing - if (showDocumentation) - { - return CodeFileHtmlRenderer.Normal.Render(CodeFile, showDocumentation: true); - } - if (_rendered == null) { _rendered = CodeFileHtmlRenderer.Normal.Render(CodeFile); @@ -35,13 +29,8 @@ public CodeLine[] Render(bool showDocumentation) return _rendered; } - public CodeLine[] RenderReadOnly(bool showDocumentation) + public CodeLine[] RenderReadOnly() { - if (showDocumentation) - { - return CodeFileHtmlRenderer.ReadOnly.Render(CodeFile, showDocumentation: true); - } - if (_renderedReadOnly == null) { _renderedReadOnly = CodeFileHtmlRenderer.ReadOnly.Render(CodeFile); @@ -50,11 +39,11 @@ public CodeLine[] RenderReadOnly(bool showDocumentation) return _renderedReadOnly; } - internal CodeLine[] RenderText(bool showDocumentation, bool skipDiff = false) + internal CodeLine[] RenderText(bool skipDiff = false) { - if (showDocumentation || skipDiff) + if (skipDiff) { - return CodeFileRenderer.Instance.Render(CodeFile, showDocumentation: showDocumentation, enableSkipDiff: skipDiff); + return CodeFileRenderer.Instance.Render(CodeFile, enableSkipDiff: skipDiff); } if (_renderedText == null) @@ -65,4 +54,4 @@ internal CodeLine[] RenderText(bool showDocumentation, bool skipDiff = false) return _renderedText; } } -} \ No newline at end of file +} diff --git a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml index 5722855b14e..6fa2efd708d 100644 --- a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml +++ b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml @@ -111,8 +111,8 @@ - - + + @@ -146,7 +146,7 @@ { - + @@ -232,8 +232,7 @@ var optionName = revision.IsApproved ? $"{@revision.DisplayName} {@approvedBadge}" : @revision.DisplayName; var urlValue = @Url.ActionLink("Review", "Assemblies", new { id = @Model.Review.ReviewId, - revisionId = @revision.RevisionId, - doc = @Model.ShowDocumentation + revisionId = @revision.RevisionId }); if (@revision.DisplayName == @Model.Revision.DisplayName) { @@ -256,7 +255,6 @@ id = @Model.Review.ReviewId, revisionId = @Model.Revision.RevisionId, diffRevisionId = @Model.DiffRevisionId, - doc = @Model.ShowDocumentation, diffOnly = @Model.ShowDiffOnly }); @@ -267,7 +265,6 @@ id = @Model.Review.ReviewId, revisionId = @Model.Revision.RevisionId, diffRevisionId = @Model.PreviousRevisions.Last().RevisionId, - doc = @Model.ShowDocumentation, diffOnly = @Model.ShowDiffOnly }); @@ -284,7 +281,6 @@ var urlValue = @Url.ActionLink("Review", "Assemblies", new { id = @Model.Review.ReviewId, diffRevisionId = @revision.RevisionId, - doc = @Model.ShowDocumentation, diffOnly = @Model.ShowDiffOnly, revisionId = @Model.Revision.RevisionId }); diff --git a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml.cs b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml.cs index 44ba6a42436..06c4fedcea3 100644 --- a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml.cs +++ b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Review.cshtml.cs @@ -64,10 +64,6 @@ public ReviewPageModel( [BindProperty(SupportsGet = true)] public string DiffRevisionId { get; set; } - // Flag to decide whether to include documentation - [BindProperty(Name = "doc", SupportsGet = true)] - public bool ShowDocumentation { get; set; } - [BindProperty(Name = "diffOnly", SupportsGet = true)] public bool ShowDiffOnly { get; set; } @@ -94,7 +90,7 @@ public async Task OnGetAsync(string id, string revisionId = null) CodeFile = renderedCodeFile.CodeFile; var fileDiagnostics = CodeFile.Diagnostics ?? Array.Empty(); - var fileHtmlLines = renderedCodeFile.Render(ShowDocumentation); + var fileHtmlLines = renderedCodeFile.Render(); if (DiffRevisionId != null) { @@ -102,9 +98,9 @@ public async Task OnGetAsync(string id, string revisionId = null) var previousRevisionFile = await _codeFileRepository.GetCodeFileAsync(DiffRevision); - var previousHtmlLines = previousRevisionFile.RenderReadOnly(ShowDocumentation); - var previousRevisionTextLines = previousRevisionFile.RenderText(ShowDocumentation); - var fileTextLines = renderedCodeFile.RenderText(ShowDocumentation); + var previousHtmlLines = previousRevisionFile.RenderReadOnly(); + var previousRevisionTextLines = previousRevisionFile.RenderText(); + var fileTextLines = renderedCodeFile.RenderText(); var diffLines = InlineDiff.Compute( previousRevisionTextLines, @@ -186,20 +182,45 @@ private CodeLineModel[] CreateLines(CodeDiagnostic[] diagnostics, InlineDiffLine diffLine.Kind != DiffLineKind.Removed ? diagnostics.Where(d => d.TargetId == diffLine.Line.ElementId).ToArray() : Array.Empty(), - ++index + ++index, + new int[] { } )).ToArray(); } private CodeLineModel[] CreateLines(CodeDiagnostic[] diagnostics, CodeLine[] lines, ReviewCommentsModel comments) { + List documentedByLines = new List(); + int lineNumberExcludingDocumentation = 0; return lines.Select( - (line, index) => new CodeLineModel( - DiffLineKind.Unchanged, - line, - comments.TryGetThreadForLine(line.ElementId, out var thread) ? thread : null, - diagnostics.Where(d => d.TargetId == line.ElementId).ToArray(), - ++index - )).ToArray(); + (line, index) => + { + if (line.IsDocumentation) + { + // documentedByLines must include the index of a line, assuming that documentation lines are counted + documentedByLines.Add(++index); + return new CodeLineModel( + DiffLineKind.Unchanged, + line, + comments.TryGetThreadForLine(line.ElementId, out var thread) ? thread : null, + diagnostics.Where(d => d.TargetId == line.ElementId).ToArray(), + lineNumberExcludingDocumentation, + new int[] {} + ); + } + else + { + CodeLineModel c = new CodeLineModel( + DiffLineKind.Unchanged, + line, + comments.TryGetThreadForLine(line.ElementId, out var thread) ? thread : null, + diagnostics.Where(d => d.TargetId == line.ElementId).ToArray(), + ++lineNumberExcludingDocumentation, + documentedByLines.ToArray() + ); + documentedByLines.Clear(); + return c; + } + }).ToArray(); } private int ComputeActiveConversations(CodeLine[] lines, ReviewCommentsModel comments) @@ -240,12 +261,11 @@ public async Task OnPostToggleApprovalAsync(string id, string rev return RedirectToPage(new { id = id }); } - public Dictionary GetRoutingData(string diffRevisionId = null, bool? showDocumentation = null, bool? showDiffOnly = null, string revisionId = null) + public Dictionary GetRoutingData(string diffRevisionId = null, bool? showDiffOnly = null, string revisionId = null) { var routingData = new Dictionary(); routingData["revisionId"] = revisionId; routingData["diffRevisionId"] = diffRevisionId; - routingData["doc"] = (showDocumentation ?? false).ToString(); routingData["diffOnly"] = (showDiffOnly ?? false).ToString(); return routingData; } diff --git a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/_CodeLine.cshtml b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/_CodeLine.cshtml index f02ca9cb7bc..9932f4b9bd2 100644 --- a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/_CodeLine.cshtml +++ b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/_CodeLine.cshtml @@ -24,14 +24,22 @@ hiddenSectionClass = isContent ? "d-none" : ""; } + string documentationRow = String.Empty; + string codeLineDisplay = String.Empty; + if(Model.CodeLine.IsDocumentation) + { + documentationRow = "code-line-documentation"; + codeLineDisplay = "hidden-row"; + } + var userPreference = ViewData["UserPreference"] as UserPreferenceModel; } - + - @if(userPreference.HideLineNumbers == true) + @if(userPreference.HideLineNumbers == true || Model.CodeLine.IsDocumentation) { } @@ -49,6 +57,14 @@ // Added for visual consistency } +
@Model.LineNumber + @if(Model.DocumentedByLines.Length > 0) + { + +   + + } + @if (isHeading) { diff --git a/src/dotnet/APIView/APIViewWeb/Repositories/PullRequestManager.cs b/src/dotnet/APIView/APIViewWeb/Repositories/PullRequestManager.cs index 72ad8ed1b9e..9c0e92dbf7d 100644 --- a/src/dotnet/APIView/APIViewWeb/Repositories/PullRequestManager.cs +++ b/src/dotnet/APIView/APIViewWeb/Repositories/PullRequestManager.cs @@ -316,8 +316,8 @@ private async Task CreateRevisionIfRequired(CodeFile codeFile, private async Task GetFormattedDiff(RenderedCodeFile renderedCodeFile, ReviewRevisionModel lastRevision, StringBuilder stringBuilder) { RenderedCodeFile autoReview = await _codeFileRepository.GetCodeFileAsync(lastRevision, false); - var autoReviewTextFile = autoReview.RenderText(showDocumentation: false, skipDiff: true); - var prCodeTextFile = renderedCodeFile.RenderText(showDocumentation: false, skipDiff: true); + var autoReviewTextFile = autoReview.RenderText(skipDiff: true); + var prCodeTextFile = renderedCodeFile.RenderText(skipDiff: true); var diffLines = InlineDiff.Compute(autoReviewTextFile, prCodeTextFile, autoReviewTextFile, prCodeTextFile); if (diffLines == null || diffLines.Length == 0 || diffLines.Count(l=>l.Kind != DiffLineKind.Unchanged) > 10) { diff --git a/src/dotnet/APIView/APIViewWeb/Repositories/ReviewManager.cs b/src/dotnet/APIView/APIViewWeb/Repositories/ReviewManager.cs index c2fd98d758d..d065edb63cb 100644 --- a/src/dotnet/APIView/APIViewWeb/Repositories/ReviewManager.cs +++ b/src/dotnet/APIView/APIViewWeb/Repositories/ReviewManager.cs @@ -435,8 +435,8 @@ public async Task IsReviewSame(ReviewRevisionModel revision, RenderedCodeF { //This will compare and check if new code file content is same as revision in parameter var lastRevisionFile = await _codeFileRepository.GetCodeFileAsync(revision, false); - var lastRevisionTextLines = lastRevisionFile.RenderText(showDocumentation: false, skipDiff: true); - var fileTextLines = renderedCodeFile.RenderText(showDocumentation: false, skipDiff: true); + var lastRevisionTextLines = lastRevisionFile.RenderText(skipDiff: true); + var fileTextLines = renderedCodeFile.RenderText(skipDiff: true); return lastRevisionTextLines.SequenceEqual(fileTextLines); } diff --git a/src/dotnet/APIView/APIViewWeb/wwwroot/icons/chevron-right.svg b/src/dotnet/APIView/APIViewWeb/wwwroot/icons/chevron-right.svg new file mode 100644 index 00000000000..7319afcfbe9 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/wwwroot/icons/chevron-right.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/wwwroot/icons/chevron-up.svg b/src/dotnet/APIView/APIViewWeb/wwwroot/icons/chevron-up.svg new file mode 100644 index 00000000000..74eda9a45bf --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/wwwroot/icons/chevron-up.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file