* Refactor Review Badge

* Refactor Review Page UI

* Adjust APIRevisions Page

* APIRevisions Search and Filter

* Updated to Revisions Context Blade

* Add APIRevision Functioning

* Add RequestVerificationToken for APIRevisionDelete

* Refinde APIRevision Context

* Refine APIRevision Context

* Improvements to Revisions Page

* Update to APIREvisions Page

* Update to Samples Page

* Rename and Delete Samples

* Add conversiation info badge
This commit is contained in:
Chidozie Ononiwu 2024-03-05 14:00:10 -08:00 коммит произвёл GitHub
Родитель 77b852436b
Коммит 3c08b3d0ec
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
48 изменённых файлов: 1939 добавлений и 1438 удалений

Просмотреть файл

@ -1,13 +1,11 @@
@import "../shared/mixins.scss";
#conversation-main-container {
@include fixed-page-heights;
color: var(--base-text-color);
padding-right: 0px;
padding-left: 0px;
@include main-content-container;
}
.conversiation-center {
overflow: auto;
color: var(--base-text-color);
height: calc(100vh - 145px);
}

Просмотреть файл

@ -49,8 +49,8 @@
#index-offcanvas-menu-content {
padding: 130px 60px 10px 20px;
min-height: calc(100vh - 40px);
max-height: calc(100vh - 40px);
min-height: calc(100vh - 30px);
max-height: calc(100vh - 30px);
overflow-y: auto;
}

Просмотреть файл

@ -1,35 +1,19 @@
@import "../shared/mixins.scss";
#review-info-bar > .SumoSelect:nth-of-type(even) {
width: 20%;
#review-info-bar {
font-size: small;
background-color: var(--base-fg-color);
}
#review-info-bar > .SumoSelect:nth-of-type(odd) {
width: 10%;
}
#revision-select ~ .optWrapper {
width: auto;
min-width: 250px;
}
#diff-select ~ .optWrapper {
width: auto;
min-width: 250px;
}
#revision-select {
width: 20%;
}
#diff-select {
width: 20%;
.breadcrumb .icon-language {
min-height: 20px;
width: 20px;
}
#review-left {
max-width: none;
min-width: 10px;
height: calc(100vh - 220px);
height: calc(100vh - 120px);
overflow: auto;
max-height: 100vh;
padding: 5px 0px 5px 10px;
@ -43,7 +27,7 @@
#review-right {
max-width: 100%;
min-width: 100px;
height: calc(100vh - 220px);
height: calc(100vh - 120px);
padding: 0px;
overflow: auto;
background-color: var(--base-fg-color);
@ -51,17 +35,8 @@
contain: size layout paint;
}
#review-offcanvas-menu-content {
padding: 10px 20px 10px 20px;
min-height: calc(100vh - 190px);
max-height: calc(100vh - 190px);
overflow-y: auto;
margin-top: 150px;
}
.review-approved {
border: solid 2px var(--success-color);
box-shadow: var(--box-shadow-success);
@include review-approval-border;
border-radius: 3px;
}

Просмотреть файл

@ -1,13 +1,61 @@
@import "../shared/mixins.scss";
#add-revision-button {
@include bottom-right-floating;
@include btn-circle;
@include btn-circle-xl;
#revisions-main-container {
@include main-content-container;
.revisions-list-container {
.card {
cursor: pointer;
}
.card:hover {
box-shadow: var(--box-shadow-link) !important;
}
}
}
#revisions-main-container {
@include fixed-page-heights;
padding-right: 0px;
padding-left: 0px;
.revisions-list-container {
overflow: auto;
height: 78dvh;
.revision-actions {
align-self: center;
margin-right: 15px;
display: none;
}
.revision-indicator-checks, .revision-actions {
align-self: center;
margin-right: 20px;
}
.card {
flex-direction: row;
overflow: hidden;
background-color: var(--base-fg-color);
.card-body {
padding: 0.5rem 0.75rem;
font-size: small;
}
img {
width: 80px;
height: 80px;
}
.edit-revision-label {
max-width: 500px;
}
}
.card.True {
@include review-approval-border;
}
}
.revisions-list-container > .card:hover {
.revision-actions {
display: inline-flex;
}
}

Просмотреть файл

@ -1,19 +1,20 @@
@import "../shared/mixins.scss";
#add-sample-button {
@include bottom-right-floating;
@include btn-circle;
@include btn-circle-xl;
#samples-main-container {
@include main-content-container;
}
#samples-main-container {
@include fixed-page-heights;
padding-right: 0px;
padding-left: 0px;
#edit-samples-context, #upload-samples-context {
@include offcanvas-context-large
}
.samples-center {
overflow: auto;
height: calc(100vh - 120px);
padding: 0px;
background-color: var(--base-fg-color);
scroll-behavior: smooth;
contain: size layout paint;
}
.usage-sample + .border + .rounded {
@ -33,4 +34,10 @@
.usage-sample .internal {
display: inline-block;
}
}
.edit-samples-content {
textarea {
height: 60dvh;
}
}

Просмотреть файл

@ -63,6 +63,9 @@
.btn-check:focus + .btn-outline-primary, .btn-outline-primary:focus {
box-shadow: var(--box-shadow-link);
}
.btn-check:focus + .btn, .btn:focus {
box-shadow: var(--box-shadow-link);
}
.page-link {
background-color: var(--base-fg-color);
@ -91,6 +94,14 @@
border: 1px solid var(--border-color) !important;
}
.border-start {
border-left: 1px solid var(--border-color) !important;
}
.border-end {
border-right: 1px solid var(--border-color) !important;
}
.border-top {
border-top: 1px solid var(--border-color) !important;
}
@ -99,6 +110,21 @@
border-bottom: 1px solid var(--border-color) !important;
}
.breadcrumb {
margin-bottom: 0rem;
}
.breadcrumb-item + .breadcrumb-item::before {
margin-top: 0rem;
}
.breadcrumb-item + .breadcrumb-item::before {
padding-right: 0.25rem;
}
.breadcrumb-item + .breadcrumb-item {
padding-left: 0.25rem;
}
.list-group-item {
color: var(--base-text-color);
background-color: var(--base-fg-color);

Просмотреть файл

@ -1,6 +1,6 @@
.icon-language {
min-height: 34px;
width: 34px;
min-height: 31px;
width: 31px;
display: inline-block;
vertical-align: middle;
}
@ -165,67 +165,67 @@
.icon-csharp {
@extend .icon-language;
background: url(/icons/csharp-original.svg) center center no-repeat;
background: url(/icons/csharp-original.svg) center/contain no-repeat;
}
.icon-javascript {
@extend .icon-language;
background: url(/icons/javascript-original.svg) center center no-repeat;
background: url(/icons/javascript-original.svg) center/contain no-repeat;
}
.icon-python {
@extend .icon-language;
background: url(/icons/python-original.svg) center center no-repeat;
background: url(/icons/python-original.svg) center/contain no-repeat;
}
.icon-c {
@extend .icon-language;
background: url(/icons/c-original.svg) center center no-repeat;
background: url(/icons/c-original.svg) center/contain no-repeat;
}
.icon-cplusplus {
@extend .icon-language;
background: url(/icons/cplusplus-original.svg) center center no-repeat;
background: url(/icons/cplusplus-original.svg) center/contain no-repeat;
}
.icon-go {
@extend .icon-language;
background: url(/icons/go-original.svg) center center no-repeat;
background: url(/icons/go-original.svg) center/contain no-repeat;
}
.icon-java {
@extend .icon-language;
background: url(/icons/java-original.svg) center center no-repeat;
background: url(/icons/java-original.svg) center/contain no-repeat;
}
.icon-java-spring {
@extend .icon-language;
background: url(/icons/java-spring-original.svg) center center no-repeat;
background: url(/icons/java-spring-original.svg) center/contain no-repeat;
}
.icon-java-android {
@extend .icon-language;
background: url(/icons/java-android-original.svg) center center no-repeat;
background: url(/icons/java-android-original.svg) center/contain no-repeat;
}
.icon-swift {
@extend .icon-language;
background: url(/icons/swift-original.svg) center center no-repeat;
background: url(/icons/swift-original.svg) center/contain no-repeat;
}
.icon-kotlin {
@extend .icon-language;
background: url(/icons/kotlin-original.svg) center center no-repeat;
background: url(/icons/kotlin-original.svg) center/contain no-repeat;
}
.icon-json {
@extend .icon-language;
background: url(/icons/json-original.svg) center center no-repeat;
background: url(/icons/json-original.svg) center/contain no-repeat;
}
.icon-swagger {
@extend .icon-language;
background: url(/icons/swagger-original.svg) center center no-repeat;
background: url(/icons/swagger-original.svg) center/contain no-repeat;
}
.icon-comments {
@ -236,9 +236,9 @@
}
.icon-chevron-right {
background: url(/icons/chevron-right.svg) center center no-repeat;
background: url(/icons/chevron-right.svg) center/contain no-repeat;
}
.icon-chevron-up {
background: url(/icons/chevron-up.svg) center center no-repeat;
background: url(/icons/chevron-up.svg) center/contain no-repeat;
}

Просмотреть файл

@ -77,8 +77,8 @@ html {
bottom: 0;
width: 100%;
white-space: nowrap;
height: 40px;
line-height: 40px;
height: 30px;
line-height: 30px;
z-index: 3;
background: var(--base-bg-color);
}
@ -93,6 +93,11 @@ html {
color: var(--link-active);
}
.navbar-brand {
padding-top: 0rem;
padding-bottom: 0rem;
}
.rounded-1 {
border-radius: 3px !important;
}
@ -144,6 +149,8 @@ input[type="search"]::-webkit-search-cancel-button {
.main-nav-cst-theme {
background-color: var(--navbar-bg);
color: var(--navbar-text);
padding-top: 0px;
padding-bottom: 0px;
a:hover {
text-decoration: none;
@ -193,11 +200,11 @@ input[type="search"]::-webkit-search-cancel-button {
color: var(--primary-btn-color);
}
.tooltip.bs-tooltip-right .tooltip-arrow::before {
.tooltip.bs-tooltip-end .tooltip-arrow::before {
border-right-color: var(--primary-color) !important;
}
.tooltip.bs-tooltip-left .tooltip-arrow::before {
.tooltip.bs-tooltip-start .tooltip-arrow::before {
border-left-color: var(--primary-color) !important;
}

Просмотреть файл

@ -22,16 +22,33 @@
box-shadow: var(--box-shadow-sm);
}
@mixin fixed-page-heights {
height: calc(100vh - 190px);
overflow-y: auto;
}
@mixin main-content-container {
transition: margin-right .5s;
width: auto;
}
@mixin review-offcanvas-menu-content {
min-height: calc(100vh - 100px);
max-height: calc(100vh - 100px);
overflow-y: auto;
margin-top: 70px;
}
@mixin review-approval-border {
border: solid 2px var(--success-color);
box-shadow: var(--box-shadow-success);
}
@mixin offcanvas-context-large {
width: 50dvw;
background-color: var(--base-bg-color);
}
@mixin offcanvas-context-small {
width: 30dvw;
background-color: var(--base-bg-color);
}
@mixin placeholder {
::-webkit-input-placeholder {
@content;

Просмотреть файл

@ -1,3 +1,5 @@
@import "../shared/mixins.scss";
.offcanvas-menu {
height: 100vh;
width: 0;
@ -16,15 +18,76 @@
right: 0;
}
.offcanvas-menu-content {
.left-offcanvas {
@extend .offcanvas-menu;
left: 0;
}
.right-offcanvas-menu-content {
border-left: 1px solid var(--border-color) !important;
transform: translate3d(0, 0, 0);
}
.left-offcanvas-menu-content {
border-right: 1px solid var(--border-color) !important;
transform: translate3d(0, 0, 0);
.badge {
font-size: 0.55em;
top: 5px;
}
}
#right-offcanvas-menu-content {
padding: 10px 20px 10px 20px;
@include review-offcanvas-menu-content;
}
#left-offcanvas-menu-content {
padding: 10px 0px 10px 5px;
@include review-offcanvas-menu-content;
.btn-check:focus + .btn, .btn:focus {
box-shadow: var(--box-shadow-left);
}
.btn {
border-radius: 0px;
}
.btn.active {
box-shadow: var(--box-shadow-left);
}
}
#review-right-offcanvas-toggle, #samples-right-offcanvas-toggle, #revisions-right-offcanvas-toggle {
+ .btn-sm {
padding: 0.05rem 0.4rem;
font-size: 0.775rem;
border-radius: 0.2rem;
}
}
#apiRevisions-context, #samplesRevisions-context {
@include offcanvas-context-large
}
#add-apirevision-context {
@include offcanvas-context-small
}
.move-main-content-container-left {
margin-right: 340px;
}
.show-offcanvas {
.move-main-content-container-right {
margin-left: 60px;
}
.show-right-offcanvas {
width: 340px;
}
.show-left-offcanvas {
width: 60px;
}

Просмотреть файл

@ -30,6 +30,7 @@
--box-shadow-inset: inset 0 1px 2px #{rgba($black, .075)};
--box-shadow-success: 0 0 0 0.1rem #{rgba($success, 10%)};
--box-shadow-link: 0 0 0 0.2rem #{rgba($primary, 50%)};
--box-shadow-left: -5px 0px 0px 0rem #{rgba($primary, 100%)};
/*----Nav--------------------------------------------------------*/
--navbar-bg: #{tint-color($base-bg-color, 100%)};
--navbar-text: #{$base-text-color};
@ -84,6 +85,7 @@
--box-shadow-inset: inset 0 1px 2px #{rgba($white, .075)};
--box-shadow-success: 0 0 0 0.1rem #{rgba($success, 10%)};
--box-shadow-link: 0 0 0 0.2rem #{rgba($primary-color, 50%)};
--box-shadow-left: -5px 0px 0px 0rem #{rgba($primary-color, 100%)};
/*----Nav--------------------------------------------------------*/
--navbar-bg: #{tint-color($base-bg-color, 20%)};
--navbar-text: #{tint-color($base-text-color, 40%)};
@ -140,6 +142,7 @@
--box-shadow-inset: inset 0 1px 2px #{rgba($gray-100, .075)};
--box-shadow-success: 0 0 0 0.1rem #{rgba($success, 10%)};
--box-shadow-link: 0 0 0 0.2rem #{rgba($primary-color, 50%)};
--box-shadow-left: -5px 0px 0px 0rem #{rgba($primary-color, 100%)};
/*----Nav--------------------------------------------------------*/
--navbar-bg: #{tint-color($base-bg-color, 20%)};
--navbar-text: #{tint-color($base-text-color, 40%)};

Просмотреть файл

@ -6,5 +6,6 @@ import "./pages/index.ts";
import "./pages/review.ts";
import "./pages/revisions.ts";
import "./pages/user-profile.ts";
import "./pages/samples.ts";

Просмотреть файл

@ -490,20 +490,6 @@ export function addClickEventToClassesInSections() {
});
}
/**
* Add Select Event Handlers to API Revision Select
*/
export function addSelectEventToAPIRevisionSelect() {
$('#revision-select, #diff-select').each(function (index, value) {
$(this).on('change', function () {
var url = $(this).find(":selected").val();
if (url) {
window.location.href = url as string;
}
});
});
}
/**
* Check if targetAnchor is present, if its not present, expand the section and scroll to the targetAnchor
* @param { String } uriHash the hash/id of the anchor we are looking for

Просмотреть файл

@ -1,4 +1,3 @@
import { rightOffCanvasNavToggle } from "../shared/off-canvas";
import * as rvM from "./review.module"
@ -18,12 +17,6 @@ $(() => {
// Run when document is ready
$(function() {
// Enable SumoSelect
(<any>$("#revision-select")).SumoSelect({ search: true, searchText: 'Search Revisions...' });
(<any>$("#diff-select")).SumoSelect({ search: true, searchText: 'Search Revisons for Diff...' });
(<any>$("#revision-type-select")).SumoSelect();
(<any>$("#diff-revision-type-select")).SumoSelect();
// Update codeLine Section state after page refresh
const shownSectionHeadingLineNumbers = sessionStorage.getItem("shownSectionHeadingLineNumbers");
@ -34,7 +27,6 @@ $(() => {
// Scroll ids into view for Ids hidden in collapsed sections
const uriHash = location.hash;
console.log(`Initial uriHash: ${uriHash}`);
if (uriHash) {
let targetAnchorId = uriHash.replace('#', '');
targetAnchorId = decodeURIComponent(targetAnchorId);
@ -165,45 +157,6 @@ $(() => {
$(this).get(0).scrollIntoView({ block: "center"});
});
/* DROPDOWN FILTER FOR REVIEW, REVISIONS AND DIFF (UPDATES REVIEW PAGE ON CHANGE)
--------------------------------------------------------------------------------------------------------------------------------------------------------*/
rvM.addSelectEventToAPIRevisionSelect();
$('#revision-type-select, #diff-revision-type-select').each(function(index, value) {
$(this).on('change', function () {
const pageIds = hp.getReviewAndRevisionIdFromUrl(window.location.href);
const reviewId = pageIds["reviewId"];
const apiRevisionId = pageIds["revisionId"];
const select = (index == 0) ? $('#revision-select') : $('#diff-select');
const text = (index == 0) ? 'Revisions' : 'Revisions for Diff';
let uri = (index == 0) ? '?handler=APIRevisionsPartial' : '?handler=APIDiffRevisionsPartial';
uri = uri + `&reviewId=${reviewId}`;
uri = uri + `&apiRevisionId=${apiRevisionId}`;
uri = uri + '&apiRevisionType=' + $(this).find(":selected").val();
$.ajax({
url: uri
}).done(function (partialViewResult) {
console.log(partialViewResult);
const id = select.attr('id');
const selectUpdate = $(`<select placeholder="Select ${text}..." id="${id}" aria-label="${text} Select"></select>`);
selectUpdate.html(partialViewResult);
select.parent().replaceWith(selectUpdate);
(<any>$(`#${id}`)).SumoSelect({ placeholder: `Select ${text}...`, search: true, searchText: `Search ${text}...` })
// Disable Diff Revision Select until a revision is selected
if (index == 0)
{
(<any>$('#diff-revision-type-select')[0]).sumo.disable();
(<any>$('#diff-select')[0]).sumo.disable();
}
rvM.addSelectEventToAPIRevisionSelect();
});
});
});
/* BUTTON FOR REQUEST REVIEW (CHANGES BETWEEN REQUEST ALL AND REQUEST SELECTED IN THE REQUEST APPROVAL SECTION)
--------------------------------------------------------------------------------------------------------------------------------------------------------*/
$('.selectReviewerForRequest').on("click", function () {

Просмотреть файл

@ -1,19 +1,336 @@
import * as hp from "../shared/helpers";
import { rightOffCanvasNavToggle } from "../shared/off-canvas";
$(() => {
$(document).on("click", ".revision-rename-icon", e => {
toggleNameField($(e.target));
});
$(document).on("click", ".cancel-revision-rename", e => {
var icon = $(e.target).parent().siblings(".revision-rename-icon");
toggleNameField(icon);
});
$(document).on("click", ".submit-revision-rename", e => {
$(e.target).parents(".revision-rename-form").submit();
});
const apiRevisionsSearchBox = $("#apiRevisions-search");
const samplesRevisionsSearchBox = $("#samplesRevisions-search");
const apiRevisionsSearchContext = $(".api-revisions.revisions-list-container .card-body");
const samplesRevisionsSearchContext = $(".samples-revisions.revisions-list-container .card-body");
const apiRevisionTypeFilter = ["#manual-apirevisions-check", "#automatic-apirevisions-check", "#pullrequest-apirevisions-check"];
function toggleNameField(renameIcon: JQuery) {
renameIcon.toggle();
renameIcon.siblings(".revision-name-input").toggle();
}
function makeActiveAPIRevisionEventHandler(event) {
const trigger = $(event.currentTarget);
let context = "api"; // Can be API or Sample
let classSelect = ".bi.bi-clock-history";
let classTxt = "bi bi-clock-history";
if (trigger.find(".bi.bi-puzzle").length > 0) {
context = "sample";
classSelect = ".bi.bi-puzzle";
classTxt = "bi bi-puzzle";
}
const activeCard = $(`.revisions-list-container ${classSelect}.active-rev`).closest(".card");
activeCard.find(`${classSelect}.active-rev`).remove();
if (context == "api") {
activeCard.find(".btn-group").prepend(`<button type="button" class="btn btn-sm btn-outline-primary make-diff" data-bs-toggle="tooltip" title="Make Diff"><i class="bi bi-file-diff mr-1"></i></button>`);
}
activeCard.find(".btn-group").prepend(`<button type="button" class="btn btn-sm btn-outline-primary make-active" data-bs-toggle="tooltip" title="Make Active"><i class="${classTxt} mr-1"></i></button>`);
activeCard.find(".btn-group .make-active").on("click", makeActiveAPIRevisionEventHandler);
if (context == "api") {
activeCard.find(".btn-group .make-diff").on("click", makeDiffAPIRevisionEventHandler);
}
trigger.closest(".card").children(".revision-indicator-checks").append(`<i class="${classTxt} active-rev mr-1"></i>`);
trigger.siblings(".make-diff").remove();
trigger.remove();
$(".revisions-list-container").addClass("revisions-changed");
$(".tooltip").remove();
}
function makeDiffAPIRevisionEventHandler(event) {
const trigger = $(event.currentTarget);
const diffCard = $(".revisions-list-container .bi.bi-file-diff.diff-rev").closest(".card");
diffCard.find(".bi.bi-file-diff.diff-rev").remove();
diffCard.find(".btn-group").prepend(`<button type="button" class="btn btn-sm btn-outline-primary make-diff" data-bs-toggle="tooltip" title="Make Diff"><i class="bi bi-file-diff mr-1"></i></button>`);
diffCard.find(".btn-group").prepend(`<button type="button" class="btn btn-sm btn-outline-primary make-active" data-bs-toggle="tooltip" title="Make Active"><i class="bi bi-clock-history mr-1"></i></button>`);
diffCard.find(".btn-group .make-active").on("click", makeActiveAPIRevisionEventHandler);
diffCard.find(".btn-group .make-diff").on("click", makeDiffAPIRevisionEventHandler);
trigger.closest(".card").children(".revision-indicator-checks").append(`<i class="bi bi-file-diff diff-rev mr-1"></i>`);
trigger.siblings(".make-active").remove();
trigger.remove();
$(".revisions-list-container").addClass("revisions-changed");
$(".tooltip").remove();
}
function clearDiffAPIRevisionEventHandler(event) {
const trigger = $(event.currentTarget);
const diffCard = $(".revisions-list-container .bi.bi-file-diff.diff-rev").closest(".card");
diffCard.find(".bi.bi-file-diff.diff-rev").remove();
trigger.closest(".btn-group").prepend(`<button type="button" class="btn btn-sm btn-outline-primary make-diff" data-bs-toggle="tooltip" title="Make Diff"><i class="bi bi-file-diff mr-1"></i></button>`);
trigger.closest(".btn-group").prepend(`<button type="button" class="btn btn-sm btn-outline-primary make-active" data-bs-toggle="tooltip" title="Make Active"><i class="bi bi-clock-history mr-1"></i></button>`);
diffCard.find(".btn-group .make-active").on("click", makeActiveAPIRevisionEventHandler);
diffCard.find(".btn-group .make-diff").on("click", makeDiffAPIRevisionEventHandler);
trigger.remove();
$(".revisions-list-container").addClass("revisions-changed");
$(".tooltip").remove();
}
function exitAPIRevisionRename(apiRevisionCard) {
apiRevisionCard.find(".card-title").removeClass("d-none");
apiRevisionCard.find(".card-subtitle").removeClass("d-none");
apiRevisionCard.find(".edit-revision-label").addClass("d-none");
}
function handleShownRevisionContext(context) {
$("#left-offcanvas-menu-content").find('[data-bs-original-title="API"]').removeClass("active");
$("#left-offcanvas-menu-content").find('[data-bs-original-title="Samples"]').removeClass("active");
$("#left-offcanvas-menu-content").find(`[data-bs-target="${context}"]`).addClass("active");
if (context == "#apiRevisions-context" || context == "#samplesRevisions-context") {
$(".revisions-list-container").removeClass("revisions-changed");
}
}
function handleHiddenRevisionContext(context) {
if (context == "#apiRevisions-context" || context == "#add-apirevision-context") {
$("#left-offcanvas-menu-content").find('[data-bs-original-title="API"]').addClass("active");
}
if (context == "#samplesRevisions-context") {
$("#left-offcanvas-menu-content").find('[data-bs-original-title="Samples"]').addClass("active");
}
$("#left-offcanvas-menu-content").find(`[data-bs-target="${context}"]`).removeClass("active");
}
function handleHideRevisionContext(context) {
let classSelect = ".bi.bi-clock-history";
if (context == "#samplesRevisions-context") {
classSelect = ".bi.bi-puzzle";
}
$(context).on("hide.bs.offcanvas", function (event) {
const activeRevisionId = $(`.revisions-list-container ${classSelect}.active-rev`).closest(".card").attr("data-id");
let diffRevisionId = "";
const diffIcon = $(".revisions-list-container .bi.bi-file-diff.diff-rev");
if (diffIcon.length > 0) {
diffRevisionId = diffIcon.closest(".card").attr("data-id")!;
}
const url = new URL(window.location.href);
const currRevisionId = hp.getReviewAndRevisionIdFromUrl(url.href)["revisionId"];
if (!currRevisionId || url.searchParams.has("revisionId")) {
url.searchParams.set("revisionId", activeRevisionId!);
}
else {
url.pathname = url.pathname.replace(currRevisionId, activeRevisionId!);
}
if (diffRevisionId) {
url.searchParams.set("diffRevisionId", diffRevisionId!);
}
else {
url.searchParams.delete("diffRevisionId");
}
if ($(".revisions-list-container").hasClass("revisions-changed")) {
if (window.location.href != url.href) {
window.location.href = url.href;
}
}
});
}
function searchRevisions(searchBox, searchContext) {
const searchText = (searchBox.val() as string).toUpperCase();
(<any>searchContext.closest(".card").show()).unmark();
if (searchText) {
(<any>searchContext).mark(searchText, {
done: function () {
searchContext.not(":has(mark)").closest(".card").hide();
}
})
}
}
function deleteRevisions(target) {
const id = hp.getReviewAndRevisionIdFromUrl(window.location.href)["reviewId"];
const revisionCard = $(target).closest(".card");
const revisionsId = revisionCard.attr("data-id");
var antiForgeryToken = $("input[name=__RequestVerificationToken]").val();
let url = `/Assemblies/Revisions/${id}/${revisionsId}`;
let ajax = {
type: "DELETE",
headers: {
"RequestVerificationToken": antiForgeryToken as string
},
success: function () {
revisionCard.remove();
}
};
if ($(target).closest(".revisions-list-container").hasClass("samples-revisions")) {
url = `/Assemblies/Samples/${id}/${revisionsId}?handler=Upload`;
const data = {
Deleting: true,
ReviewId: id,
SampleId: revisionsId
};
ajax["data"] = data;
ajax["type"] = "POST";
}
ajax["url"] = url;
$.ajax(ajax);
}
function renameRevision(target) {
const id = hp.getReviewAndRevisionIdFromUrl(window.location.href)["reviewId"];
const revisionCard = $(target).closest(".card");
const updatedLabel = revisionCard.find(".edit-revision-label > input").val();
const revisionsId = revisionCard.attr("data-id");
var antiForgeryToken = $("input[name=__RequestVerificationToken]").val();
let url = `/Assemblies/Revisions/${id}/${revisionsId}?handler=Rename&newLabel=${updatedLabel}`;
let ajax = {
type: "POST",
headers: {
"RequestVerificationToken": antiForgeryToken as string
},
success: function (data) {
revisionCard.find(".card-title").text(data);
exitAPIRevisionRename(revisionCard);
}
};
if ($(target).closest(".revisions-list-container").hasClass("samples-revisions")) {
url = `/Assemblies/Samples/${id}/${revisionsId}?handler=Upload`;
const data = {
Renaming: true,
RevisionTitle: updatedLabel,
ReviewId: id,
SampleId: revisionsId
};
ajax["data"] = data;
}
ajax["url"] = url;
$.ajax(ajax);
}
/* RIGHT OFFCANVAS OPERATIONS
--------------------------------------------------------------------------------------------------------------------------------------------------------*/
// Open / Close right Offcanvas Menu
$("#revisions-right-offcanvas-toggle").on('click', function () {
hp.updatePageSettings(function () {
rightOffCanvasNavToggle("revisions-main-container");
});
});
/* MANAGE APIREVISIONS IN CONTEXT OF REVIEW PAGE
--------------------------------------------------------------------------------------------------------------------------------------------------------*/
// Toggle active class for left side offcanvas buttons
["#apiRevisions-context", "#add-apirevision-context", "#samplesRevisions-context"].forEach(function (value, index) {
$(value).on("shown.bs.offcanvas", function () {
handleShownRevisionContext(value);
});
$(value).on("hidden.bs.offcanvas", function (event) {
handleHiddenRevisionContext(value);
event.stopPropagation();
});
if (value == "#apiRevisions-context" || value == "#samplesRevisions-context") {
handleHideRevisionContext(value);
}
});
// Search / Filter APIRevisions
apiRevisionsSearchBox.on("input", function () {
apiRevisionTypeFilter.forEach(function (value, index) {
$(value).removeAttr('checked');
});
searchRevisions(apiRevisionsSearchBox, apiRevisionsSearchContext);
});
samplesRevisionsSearchBox.on("input", function () {
searchRevisions(samplesRevisionsSearchBox, samplesRevisionsSearchContext);
});
// Filter by APIRevision Type
apiRevisionTypeFilter.forEach(function (value, index) {
$(value).on("change", function (event) {
apiRevisionsSearchBox.val('');
const manualChecked = $(apiRevisionTypeFilter[0]).is(":checked");
const autoChecked = $(apiRevisionTypeFilter[1]).is(":checked");
const prChecked = $(apiRevisionTypeFilter[2]).is(":checked");
if ((manualChecked && autoChecked && prChecked) || (!manualChecked && !autoChecked && !prChecked)) {
$(".api-revisions.revisions-list-container .card").show();
}
else {
$(".api-revisions.revisions-list-container .card-subtitle").each(function (index, element) {
if ((manualChecked && element.innerText.includes("Type: Manual")) ||
(autoChecked && element.innerText.includes("Type: Automatic")) ||
(prChecked && element.innerText.includes("Type: PullRequest ")) ||
(manualChecked && autoChecked && prChecked)) {
$(element).closest(".card").show();
}
else {
$(element).closest(".card").hide();
}
});
}
});
});
// Set Active or Diff APIRevision
$(".revisions-list-container .card .btn.make-active").on("click", makeActiveAPIRevisionEventHandler);
$(".revisions-list-container .card .btn.make-diff").on("click", makeDiffAPIRevisionEventHandler);
$(".revisions-list-container .card .btn.clear-diff").on("click", clearDiffAPIRevisionEventHandler);
// Delete API Revision
$(".revisions-list-container .delete").on("click", function (e) {
e.stopPropagation();
deleteRevisions(this);
});
// Rename API Revision Label
$(".revisions-list-container .rename").on("click", function (e) {
e.stopPropagation();
const apiRevisionCard = $(this).closest(".card");
apiRevisionCard.find(".card-title").addClass("d-none");
apiRevisionCard.find(".card-subtitle").addClass("d-none");
apiRevisionCard.find(".edit-revision-label").removeClass("d-none");
});
$(".revisions-list-container .cancel-rename").on("click", function (e) {
e.stopPropagation();
const apiRevisionCard = $(this).closest(".card");
exitAPIRevisionRename(apiRevisionCard);
});
$(".revisions-list-container .enter-rename").on("click", function (e) {
e.stopPropagation();
renameRevision(this);
});
$(".edit-revision-label").on("click", function (e) {
e.stopPropagation();
});
// Open API Revision in new tab
$("#revisions-main-container .revisions-list-container .card").on("click", function () {
const apiRevisionsId = $(this).attr("data-id");
const revisionContainer = $(this).closest(".revisions-list-container");
if (revisionContainer.hasClass("api-revisions")) {
const uri = (window.location.href).replace("/Revisions/", "/Review/") + `?revisionId=${apiRevisionsId}`;
window.open(uri, "_blank");
}
if (revisionContainer.hasClass("samples-revisions")) {
const uri = (window.location.href).replace("/Revisions/", "/Samples/") + `?revisionId=${apiRevisionsId}`;
window.open(uri, "_blank");
}
});
});

Просмотреть файл

@ -0,0 +1,13 @@
import * as hp from "../shared/helpers";
import { rightOffCanvasNavToggle } from "../shared/off-canvas";
$(() => {
/* RIGHT OFFCANVAS OPERATIONS
--------------------------------------------------------------------------------------------------------------------------------------------------------*/
// Open / Close right Offcanvas Menu
$("#samples-right-offcanvas-toggle").on('click', function () {
hp.updatePageSettings(function () {
rightOffCanvasNavToggle("samples-main-container");
});
});
});

Просмотреть файл

@ -390,10 +390,12 @@ $(() => {
highlightCurrentRow(inlineRow, true);
});
$("#jump-to-first-comment").on("click", function () {
$("#jump-to-first-comment").on("click", function (e) {
e.preventDefault();
var commentRows = $('.comment-row');
var displayedCommentRows = hp.getDisplayedCommentRows(commentRows, false, true);
$(displayedCommentRows[0])[0].scrollIntoView();
e.stopPropagation();
});
function highlightCurrentRow(rowElement: JQuery<HTMLElement> = $(), isInlineRow: boolean = false) {

Просмотреть файл

@ -244,14 +244,17 @@ export function getDisplayedCommentRows(commentRows: JQuery<HTMLElement>, clearC
* @returns result dictionary of "reviewId" and "revisionId", if they exist; undefined otherwise
*/
export function getReviewAndRevisionIdFromUrl(uri) {
const regex = /.+(Review|Conversation|Revisions|Samples)\/([a-zA-Z0-9]+)(\?revisionId=([a-zA-Z0-9]+))?/;
const regex = /.+(Review|Conversation|Revisions|Samples)\/([a-zA-Z0-9]+)(\/([a-zA-Z0-9]+)|\?revisionId=([a-zA-Z0-9]+))?/;
const match = uri.match(regex);
const result = {}
if (match) {
result["reviewId"] = match[2];
result["revisionId"] = match[4]; // undefined if latest revision
result["revisionId"] = match[4]; // undefined if latest revision, or searchParam used
if (!result["revisionId"]) {
result["revisionId"] = match[5];
}
}
return result;

Просмотреть файл

@ -2,10 +2,21 @@
export function rightOffCanvasNavToggle(mainContainser: String) {
if ($(".right-offcanvas").css("width") == '0px') {
$(`#${mainContainser}`).addClass("move-main-content-container-left");
$("#right-offcanvas-menu").addClass("show-offcanvas");
$("#right-offcanvas-menu").addClass("show-right-offcanvas");
}
else {
$("#right-offcanvas-menu").removeClass("show-offcanvas");
$("#right-offcanvas-menu").removeClass("show-right-offcanvas");
$(`#${mainContainser}`).removeClass("move-main-content-container-left");
}
}
export function leftOffCanvasNavToggle(mainContainser: String) {
if ($(".left-offcanvas").css("width") == '0px') {
$(`#${mainContainser}`).addClass("move-main-content-container-right");
$("#left-offcanvas-menu").addClass("show-left-offcanvas");
}
else {
$("#left-offcanvas-menu").removeClass("show-left-offcanvas");
$(`#${mainContainser}`).removeClass("move-main-content-container-right");
}
}

Просмотреть файл

@ -20,6 +20,8 @@ namespace APIViewWeb.Helpers
.ForMember(dest => dest.ShowHiddenApis, opt => opt.MapFrom((src, dest) => src._showHiddenApis != null ? src._showHiddenApis : dest._showHiddenApis))
.ForMember(dest => dest.HideReviewPageOptions, opt => opt.MapFrom((src, dest) => src._hideReviewPageOptions != null ? src._hideReviewPageOptions : dest._hideReviewPageOptions))
.ForMember(dest => dest.HideIndexPageOptions, opt => opt.MapFrom((src, dest) => src._hideIndexPageOptions != null ? src._hideIndexPageOptions : dest._hideIndexPageOptions))
.ForMember(dest => dest.HideSamplesPageOptions, opt => opt.MapFrom((src, dest) => src._hideSamplesPageOptions != null ? src._hideSamplesPageOptions : dest._hideSamplesPageOptions))
.ForMember(dest => dest.HideRevisionsPageOptions, opt => opt.MapFrom((src, dest) => src._hideRevisionsPageOptions != null ? src._hideRevisionsPageOptions : dest._hideRevisionsPageOptions))
.ForMember(dest => dest.ShowComments, opt => opt.MapFrom((src, dest) => src._showComments != null ? src._showComments : dest._showComments))
.ForMember(dest => dest.ShowSystemComments, opt => opt.MapFrom((src, dest) => src._showSystemComments != null ? src._showSystemComments : dest._showSystemComments));
}

Просмотреть файл

@ -14,6 +14,8 @@ using APIViewWeb.Managers.Interfaces;
using APIViewWeb.Hubs;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace APIViewWeb.Helpers
@ -368,7 +370,7 @@ namespace APIViewWeb.Helpers
reviewPageContent.Review = review;
reviewPageContent.Navigation = activeRevisionRenderableCodeFile.CodeFile.Navigation;
reviewPageContent.codeLines = codeLines;
reviewPageContent.APIRevisionsGrouped = apiRevisions.OrderByDescending(c => c.CreatedOn).GroupBy(r => r.APIRevisionType).ToDictionary(r => r.Key.ToString(), r => r.ToList());
reviewPageContent.APIRevisions = apiRevisions.OrderByDescending(c => c.CreatedOn);
reviewPageContent.ActiveAPIRevision = activeRevision;
reviewPageContent.DiffAPIRevision = diffRevision;
reviewPageContent.TotalActiveConversiations = comments.Threads.Count(t => !t.IsResolved);
@ -499,6 +501,29 @@ namespace APIViewWeb.Helpers
return label;
}
/// <summary>
/// Upload API Revision
/// </summary>
/// <param name="apiRevisionsManager"></param>
/// <param name="user"></param>
/// <param name="id"></param>
/// <param name="upload"></param>
/// <param name="label"></param>
/// <param name="filePath"></param>
/// <returns></returns>
public static async Task<APIRevisionListItemModel> UploadAPIRevisionAsync(IAPIRevisionsManager apiRevisionsManager, ClaimsPrincipal user, string id, [FromForm] IFormFile upload, [FromForm] string label, [FromForm] string filePath)
{
if (upload != null)
{
var openReadStream = upload.OpenReadStream();
return await apiRevisionsManager.AddAPIRevisionAsync(user, id, APIRevisionType.Manual, upload.FileName, label, openReadStream, language: null);
}
else
{
return await apiRevisionsManager.AddAPIRevisionAsync(user, id, APIRevisionType.Manual, filePath, label, null);
}
}
/// <summary>
/// Create DiffOnly Lines
/// </summary>

Просмотреть файл

@ -19,7 +19,7 @@ namespace APIViewWeb.LeanModels
public ReviewListItemModel Review { get; set; }
public NavigationItem[] Navigation { get; set; }
public CodeLineModel[] codeLines { get; set; }
public Dictionary<string, List<APIRevisionListItemModel>> APIRevisionsGrouped { get; set; }
public IEnumerable<APIRevisionListItemModel> APIRevisions { get; set; }
public APIRevisionListItemModel ActiveAPIRevision { get; set; }
public APIRevisionListItemModel DiffAPIRevision { get; set; }
public int TotalActiveConversiations { get; set; }

Просмотреть файл

@ -251,7 +251,7 @@ namespace APIViewWeb.Managers
/// <param name="language"></param>
/// <param name="awaitComputeDiff"></param>
/// <returns></returns>
public async Task AddAPIRevisionAsync(
public async Task<APIRevisionListItemModel> AddAPIRevisionAsync(
ClaimsPrincipal user,
string reviewId,
APIRevisionType apiRevisionType,
@ -262,7 +262,7 @@ namespace APIViewWeb.Managers
bool awaitComputeDiff = false)
{
var review = await _reviewsRepository.GetReviewAsync(reviewId);
await AddAPIRevisionAsync(user, review, apiRevisionType, name, label, fileStream, language, awaitComputeDiff);
return await AddAPIRevisionAsync(user, review, apiRevisionType, name, label, fileStream, language, awaitComputeDiff);
}
/// <summary>

Просмотреть файл

@ -22,7 +22,7 @@ namespace APIViewWeb.Managers.Interfaces
public APIRevisionListItemModel GetNewAPIRevisionAsync(APIRevisionType apiRevisionType, string reviewId = null, string packageName = null, string language = null,
string label = null, int? prNumber = null, string createdBy = "azure-sdk");
public Task<bool> ToggleAPIRevisionApprovalAsync(ClaimsPrincipal user, string id, string revisionId = null, APIRevisionListItemModel apiRevision = null, string notes = "", string approver = "");
public Task AddAPIRevisionAsync(ClaimsPrincipal user, string reviewId, APIRevisionType apiRevisionType, string name, string label, Stream fileStream, string language = "", bool awaitComputeDiff = false);
public Task<APIRevisionListItemModel> AddAPIRevisionAsync(ClaimsPrincipal user, string reviewId, APIRevisionType apiRevisionType, string name, string label, Stream fileStream, string language = "", bool awaitComputeDiff = false);
public Task<APIRevisionListItemModel> AddAPIRevisionAsync(ClaimsPrincipal user, ReviewListItemModel review, APIRevisionType apiRevisionType, string name, string label, Stream fileStream, string language, bool awaitComputeDiff = false);
public Task RunAPIRevisionGenerationPipeline(List<APIRevisionGenerationPipelineParamModel> reviewGenParams, string language);
public Task SoftDeleteAPIRevisionAsync(ClaimsPrincipal user, string reviewId, string revisionId);

Просмотреть файл

@ -12,6 +12,7 @@ namespace APIViewWeb.Managers
public Task<string> GetSamplesRevisionContentAsync(string fileId);
public Task<SamplesRevisionModel> UpsertSamplesRevisionsAsync(ClaimsPrincipal user, string reviewId, string sample, string revisionTitle, string FileName = null);
public Task<SamplesRevisionModel> UpsertSamplesRevisionsAsync(ClaimsPrincipal user, string reviewId, Stream fileStream, string revisionTitle, string FileName);
public Task UpdateSamplesRevisionTitle(string reviewId, string sampleId, string newTitle);
public Task DeleteSamplesRevisionAsync(ClaimsPrincipal user, string reviewId, string sampleId);
}
}

Просмотреть файл

@ -87,6 +87,13 @@ namespace APIViewWeb.Managers
return await UpsertSamplesRevisionsAsync(user, reviewId, sample, revisionTitle, FileName);
}
public async Task UpdateSamplesRevisionTitle(string reviewId, string sampleId, string newTitle)
{
var samplesRevision = await _samplesRevisionsRepository.GetSamplesRevisionAsync(reviewId, sampleId);
samplesRevision.Title = newTitle;
await _samplesRevisionsRepository.UpsertSamplesRevisionAsync(samplesRevision);
}
public async Task DeleteSamplesRevisionAsync(ClaimsPrincipal user, string reviewId, string sampleId)
{
var samplesRevision = await _samplesRevisionsRepository.GetSamplesRevisionAsync(reviewId, sampleId);

Просмотреть файл

@ -9,10 +9,10 @@ namespace APIViewWeb.Pages.Assemblies
public class SamplesRevisionUploadModel
{
[BindProperty]
public string sampleString { get; set; }
public string SampleString { get; set; }
[BindProperty]
public string updateString { get; set; }
public string UpdateString { get; set; }
[BindProperty]
public string ReviewId { get; set; }
@ -23,9 +23,15 @@ namespace APIViewWeb.Pages.Assemblies
[BindProperty]
public bool Deleting { get; set; } = false;
[BindProperty]
public bool DeletingAndRedirect { get; set; } = false;
[BindProperty]
public bool Updating { get; set; } = false;
[BindProperty]
public bool Renaming { get; set; } = false;
[BindProperty]
public string SampleId { get; set; }

Просмотреть файл

@ -3,7 +3,6 @@
using System.Collections.Generic;
using APIViewWeb.LeanModels;
using CsvHelper.Configuration.Attributes;
using Newtonsoft.Json;
namespace APIViewWeb.Models
{
@ -19,6 +18,8 @@ namespace APIViewWeb.Models
internal bool? _showHiddenApis;
internal bool? _hideReviewPageOptions;
internal bool? _hideIndexPageOptions;
internal bool? _hideSamplesPageOptions;
internal bool? _hideRevisionsPageOptions;
internal bool? _showComments;
internal bool? _showSystemComments;
internal string _theme;
@ -94,6 +95,20 @@ namespace APIViewWeb.Models
set => _hideIndexPageOptions = value;
}
[Name("HideSamplesPageOptions")]
public bool? HideSamplesPageOptions
{
get => _hideSamplesPageOptions ?? false;
set => _hideSamplesPageOptions = value;
}
[Name("HideRevisionsPageOptions")]
public bool? HideRevisionsPageOptions
{
get => _hideRevisionsPageOptions ?? false;
set => _hideRevisionsPageOptions = value;
}
[Name("ShowComments")]
public bool? ShowComments
{

Просмотреть файл

@ -1,6 +1,7 @@
@page "{id?}"
@model APIViewWeb.Pages.Assemblies.ConversationModel
@using APIViewWeb.Helpers
@using APIViewWeb.LeanModels
@using APIViewWeb.Models
@{
Layout = "Shared/_Layout";
@ -8,60 +9,72 @@
TempData["UserPreference"] = PageModelHelpers.GetUserPreference(Model._preferenceCache, User);
}
<div class="container-fluid mx-0 px-0 sub-header-content">
<div class="row mx-1 px-0 py-2">
<div class="col-md-4">
<div class="input-group-sm input-group">
<partial name="Shared/_ReviewBadge" model="(Model.Review, Model.LatestAPIRevision)" />
</div>
</div>
<div class="col-md-4">
</div>
<div class="col-md-4">
<div class="row px-3 py-2 border-bottom" id="review-info-bar">
<partial name="Shared/_ReviewBadge" model="(Model.Review, default(APIRevisionListItemModel), default(APIRevisionListItemModel), default(UserPreferenceModel))" />
</div>
</div>
@{
var leftOffCanvasClass = " show-left-offcanvas";
var mainContainerClass = " move-main-content-container-right";
}
<div id="left-offcanvas-menu" class="left-offcanvas@(leftOffCanvasClass)">
<div class="left-offcanvas-menu-content" id="left-offcanvas-menu-content">
<div class="btn-group-vertical" role="group" aria-label="Vertical button group">
<a type="button" class="btn btn-lg btn-link mb-2" asp-page="Review" asp-route-id="@Model.Review.Id"><i class="bi bi-braces" data-bs-toggle="tooltip" data-bs-placement="right" title="API"></i></a>
<a type="button" class="btn btn-lg btn-link mb-2" asp-page="Revisions" asp-route-id="@Model.Review.Id"><i class="bi bi-clock-history" data-bs-toggle="tooltip" data-bs-placement="right" title="Revisons"></i></a>
<a type="button" class="btn btn-lg btn-link mb-2" data-bs-toggle="tooltip" data-bs-placement="right" title="API" active-if="@TempData["page"].Equals("conversation")"><i class="bi bi-chat-left-dots"></i></a>
<a type="button" class="btn btn-lg btn-link mb-2" asp-page="Samples" asp-route-id="@Model.Review.Id"><i class="bi bi-puzzle" data-bs-toggle="tooltip" data-bs-placement="right" title="Samples"></i></a>
</div>
</div>
<partial name="Shared/_ReviewNavBar" />
</div>
<div class="container" id="conversation-main-container">
<div class="row mx-0 px-0 py-2">
<div class="col-lg-12 px-0" data-review-id=@Model.Review.Id>
@if (!Model.Threads.Any() && !Model.UsageSampleThreads.Any())
{
<div class="text-muted">There are no comments in the review.</div>
<div class="container-fluid pt-2@(mainContainerClass)" id="conversation-main-container">
<div class="row g-2" data-review-id="@Model.Review.Id">
@if (!Model.Threads.Any() && !Model.UsageSampleThreads.Any())
{
<div class="text-muted">There are no comments in the review.</div>
}
else
{
@if (Model.Threads.Any())
{
<div class="col mx-3">
<h6 class="ms-3">APIRevisions Comments</h6>
@if (Model.Threads.Any())
{
<div class="border rounded conversiation-center">
@foreach (var revision in Model.Threads)
{
var divId = $"rev-{revision.Key.Id}";
<div class="card-header p-2 clickable" id="header-@revision.Key.Id" data-toggle="collapse" data-target="#@divId">
@PageModelHelpers.ResolveRevisionLabel(revision.Key)
</div>
<div id=@divId class="collapse show" data-revision-id=@revision.Key.Id aria-labelledby="header-@revision.Key.Id">
<table class="code-window">
@foreach (var thread in revision.Value)
{
var elementId = thread.LineId;
<tr class="code-line"><td class="code p-2" style="word-break: break-all;"><a class="comment-url" asp-page="Review"
asp-route-id=@Model.Review.Id
asp-route-revisionId=@revision.Key.Id
asp-fragment=@Uri.EscapeDataString(elementId)>@elementId</a>
</td>
</tr>
<partial name="_CommentThreadPartial" model="@thread" />
}
</table>
</div>
}
</div>
}
</div>
}
else
{
@if (Model.Threads.Any())
{
<div class="border rounded conversiation-center">
@foreach (var revision in Model.Threads)
{
var divId = $"rev-{revision.Key.Id}";
<div class="card-header p-2 clickable" id="header-@revision.Key.Id" data-toggle="collapse" data-target="#@divId">
PageModelHelpers.ResolveRevisionLabel(@revision.Key.Label)
</div>
<div id=@divId class="collapse show" data-revision-id=@revision.Key.Id aria-labelledby="header-@revision.Key.Id">
<table class="code-window">
@foreach (var thread in revision.Value)
{
var elementId = thread.LineId;
<tr class="code-line"><td class="code p-2" style="word-break: break-all;"><a class="comment-url" asp-page="Review"
asp-route-id=@Model.Review.Id
asp-route-revisionId=@revision.Key.Id
asp-fragment=@Uri.EscapeDataString(elementId)>@elementId</a>
</td>
</tr>
<partial name="_CommentThreadPartial" model="@thread" />
}
</table>
</div>
}
</div>
}
@if (Model.UsageSampleThreads.Count > 0)
{
var skipped = 1;
<br />
@if (Model.UsageSampleThreads.Any())
{
var skipped = 1;
<div class="col mx-3">
<h6 class="ms-3">Sample Revisions Comments</h6>
<div class="border rounded conversiation-center">
@foreach (var revision in Model.UsageSampleThreads.Reverse())
{
@ -76,14 +89,14 @@
{
displayName += " - " + revision.Key.sampleRevision.OriginalFileName;
}
<div class="card-header p-2 text-black-50 clickable" id="header-@revision.Key.sampleRevisionNumber" data-toggle="collapse" data-target="#@divId">
<div class="card-header p-2 clickable" id="header-@revision.Key.sampleRevisionNumber" data-toggle="collapse" data-target="#@divId">
@displayName
</div>
<div id=@divId class="collapse show" data-revision-id=@revision.Key.sampleRevisionNumber aria-labelledby="header-@revision.Key.sampleRevisionNumber">
<table class="code-window">
@foreach (var thread in revision.Value.OrderBy(e => int.Parse(e.LineId.Split("-").Last())).GroupBy(x => x.LineId).Select(g => g.First()))
{
int indexA = Model.SampleLines.Count() - (revision.Key.sampleRevisionNumber - skipped);
int indexA = Model.SampleLines.Count() - Math.Abs(revision.Key.sampleRevisionNumber - skipped);
int indexB = int.Parse(thread.Comments.First().ElementId.Split("-").Last()) - 1;
@if (thread.Comments.Any())
{
@ -106,10 +119,9 @@
</div>
}
</div>
}
</div>
}
</div>
}
<partial name="_CommentFormPartial" model="@Model.TaggableUsers" />
</div>
</div>

Просмотреть файл

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ApiView;
using APIView;
using APIViewWeb.LeanModels;
using APIViewWeb.Managers;
using APIViewWeb.Managers.Interfaces;

Просмотреть файл

@ -33,12 +33,16 @@
</div>
@{
var offCanvasClass = " show-offcanvas";
var offCanvasClass = " show-right-offcanvas";
var mainContainerClass = " move-main-content-container-left";
if (userPreference.HideIndexPageOptions.HasValue && userPreference.HideIndexPageOptions == true)
{
offCanvasClass = String.Empty;
mainContainerClass = string.Empty;
}
}
<div id="right-offcanvas-menu" class="right-offcanvas@(offCanvasClass)">
<div class="offcanvas-menu-content" id="index-offcanvas-menu-content">
<div class="right-offcanvas-menu-content" id="index-offcanvas-menu-content">
<p class="h6">Filters</p>
<div class="input-group input-group-sm mb-2">
<span class="input-group-text">Languages</span>
@ -66,11 +70,6 @@
<hr class="border">
</div>
</div>
@{
var mainContainerClass = " move-main-content-container-left";
if (userPreference.HideIndexPageOptions.HasValue && userPreference.HideIndexPageOptions == true)
mainContainerClass = String.Empty;
}
<div class="container-fluid mb-5@(mainContainerClass)" id="index-main-container">
<div class="modal fade" id="uploadModel" tabindex="-1" role="dialog">

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -10,6 +10,7 @@ using APIViewWeb.Managers.Interfaces;
using APIViewWeb.Models;
using APIViewWeb.Repositories;
using Microsoft.ApplicationInsights.AspNetCore.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.SignalR;
@ -17,7 +18,6 @@ using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Configuration;
using Microsoft.TeamFoundation.Common;
namespace APIViewWeb.Pages.Assemblies
{
public class ReviewPageModel : PageModel
@ -130,7 +130,7 @@ namespace APIViewWeb.Pages.Assemblies
return RedirectToPage("Index", new { notificationMessage = ReviewContent.NotificationMessage });
}
if (ReviewContent.APIRevisionsGrouped == null || !ReviewContent.APIRevisionsGrouped.Any())
if (ReviewContent.APIRevisions == null || !ReviewContent.APIRevisions.Any())
{
return RedirectToPage("LegacyReview", new { id = id });
}
@ -302,6 +302,28 @@ namespace APIViewWeb.Pages.Assemblies
await _notificationManager.NotifyApproversOfReview(User, apiRevisionId, reviewers);
return RedirectToPage(new { id = id, revisionId = apiRevisionId });
}
/// <summary>
/// Upload APIRevisions
/// </summary>
/// <param name="id"></param>
/// <param name="upload"></param>
/// <param name="label"></param>
/// <param name="filePath"></param>
/// <returns></returns>
public async Task<IActionResult> OnPostUploadAsync(string id, [FromForm] IFormFile upload, [FromForm] string label, [FromForm] string filePath)
{
if (!ModelState.IsValid)
{
return RedirectToPage();
}
var apiRevision = await PageModelHelpers.UploadAPIRevisionAsync(_apiRevisionsManager, User, id, upload, label, filePath);
return RedirectToPage(new { id = id, revisionId = apiRevision.Id });
}
/// <summary>
/// Get Routing Data for a Review
/// </summary>

Просмотреть файл

@ -1,4 +1,4 @@
@page "{id?}"
@page "{id?}/{revisionId?}"
@model APIViewWeb.Pages.Assemblies.RevisionsPageModel
@using APIViewWeb.Helpers
@using APIViewWeb.LeanModels;
@ -6,183 +6,81 @@
@{
Layout = "Shared/_Layout";
ViewData["Title"] = "Revisions";
TempData["UserPreference"] = PageModelHelpers.GetUserPreference(Model._preferenceCache, User);
var userPreference = PageModelHelpers.GetUserPreference(Model._preferenceCache, User);
TempData["UserPreference"] = userPreference;
}
<div class="container-fluid mx-0 px-0 sub-header-content">
<div class="row mx-1 px-0 py-2">
<div class="col-md-4">
<div class="input-group-sm input-group">
<partial name="Shared/_ReviewBadge" model="(Model.Review, Model.LatestAPIRevision)" />
</div>
</div>
<div class="col-md-4">
</div>
<div class="col-md-4">
</div>
<div class="row px-3 py-2 border-bottom" id="review-info-bar">
<partial name="Shared/_ReviewBadge" model="(Model.Review, default(APIRevisionListItemModel), default(APIRevisionListItemModel), default(UserPreferenceModel))" />
</div>
<partial name="Shared/_ReviewNavBar" />
</div>
<div class="container" id="revisions-main-container">
<div class="row">
<div id="add-revision-button">
<button type="button" class="btn btn-primary" id="add-revision-button" data-bs-toggle="modal" data-bs-target="#uploadModel"><small>ADD</small><br><i class="fas fa-sm fa-plus"></i><br><small>REVISION</small></button>
</div>
</div>
@{
var rightOffCanvasClass = " show-right-offcanvas";
var leftOffCanvasClass = " show-left-offcanvas";
var mainContainerLeftClass = " move-main-content-container-left";
var mainContainerRightClass = " move-main-content-container-right";
if (userPreference.HideRevisionsPageOptions.HasValue && userPreference.HideRevisionsPageOptions == true)
{
rightOffCanvasClass = String.Empty;
mainContainerRightClass = String.Empty;
}
var mainContainerClass = mainContainerLeftClass + mainContainerRightClass;
}
<div class="modal fade" id="uploadModel" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<form asp-page-handler="Upload" method="post" enctype="multipart/form-data">
<div class="modal-header">
<h5 class="modal-title">Upload file</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@if (!(LanguageServiceHelpers.MapLanguageAliases(new List<string> { "TypeSpec" })).Contains(Model.Review.Language))
{
<div class="input-group mb-3 custom-file-label">
<label class="input-group-text small" for="uploadRevisionFile">Select file to add</label>
<input name="upload" for="upload" type="file" class="form-control" id="uploadRevisionFile">
</div>
}
else
{
<ul class="nav nav-pills nav-fill mb-3" id="typespec-pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="typespec-pills-tsp-tab" data-bs-toggle="pill" data-bs-target="#typespec-pills-tsp" type="button" role="tab" aria-controls="typespec-pills-tsp" aria-selected="true">TypeSpec</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="typespec-pills-json-tab" data-bs-toggle="pill" data-bs-target="#typespec-pills-json" type="button" role="tab" aria-controls="typespec-pills-json" aria-selected="false">Json</button>
</li>
</ul>
<div class="tab-content mb-3" id="typespec-pills-tabcontent">
<div class="tab-pane fade show active" id="typespec-pills-tsp" role="tabpanel" aria-labelledby="typespec-pills-tsp-tab" tabindex="0">
<div class="input-group custom-file-label">
<label class="input-group-text small" for="uploadRevisionFile">Select file to add</label>
<input name="upload" for="upload" type="file" class="form-control" id="uploadRevisionFile">
</div>
</div>
<div class="tab-pane fade" id="typespec-pills-json" role="tabpanel" aria-labelledby="typespec-pills-json-tab" tabindex="0">
<input asp-for="FilePath" class="form-control" type="text" placeholder="Package root e.g https://github.com/Azure/azure-rest-api-specs/specification/cognitiveservices/AnomalyDetector/">
</div>
</div>
}
<div>
<input asp-for="Label" class="form-control" type="text" placeholder="Enter an optional revision label">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary" onclick="this.form.submit(); this.disabled = true;"><i class="fas fa-cloud-upload-alt"></i> Upload</button>
</div>
</form>
</div>
</div>
</div>
<div class="row mx-0 px-0 py-2">
<div class="col-lg-12 mx-0 px-0">
@if (Model.APIRevisions.ContainsKey("Automatic"))
{
<ul class="list-group">
<li class="list-group-item list-group-item-heading fw-bold">Automatic APIRevisions</li>
@foreach (var revision in Model.APIRevisions["Automatic"])
{
<li class="list-group-item">
<form class="form-inline revision-rename-form float-left" asp-page-handler="Rename" method="post">
<span asp-resource="@revision" asp-requirement="@RevisionOwnerRequirement.Instance">
<a href="#" class="text-decoration-none revision-rename-icon pr-2">✎</a>
<input type="hidden" name="revisionId" value="@revision.Id" />
<span class="revision-name-input form-group-lg pr-2" style="display:none">
<input type="text" class="form-control" name="newLabel" value="@revision.Label" placeholder="Enter a revision label" />
<a href="#" class="text-decoration-none submit-revision-rename">✔️</a>
<a href="#" class="text-decoration-none cancel-revision-rename">❌</a>
</span>
</span>
<span class="revision-name-label">@revision.Label</span>
<span class="font-italic pl-3">
<span>@revision.APIRevisionType.ToString(), Created: </span>
<span date="@revision.CreatedOn"></span>
<span>By: @revision.CreatedBy</span>
</span>
</form>
<form asp-resource="@revision" asp-requirement="@RevisionOwnerRequirement.Instance"
class="form-inline float-right" asp-page-handler="Delete" method="post">
<input type="hidden" name="revisionId" value="@revision.Id" />
<input type="submit" class="btn btn-danger btn-sm" value="Delete" />
</form>
</li>
}
</ul>
}
@if (Model.APIRevisions.ContainsKey("PullRequest"))
{
<ul class="list-group mt-2">
<li class="list-group-item list-group-item-heading fw-bold">PullRequest APIRevisions</li>
@foreach (var revision in Model.APIRevisions["PullRequest"])
{
<li class="list-group-item">
<form class="form-inline revision-rename-form float-left" asp-page-handler="Rename" method="post">
<span asp-resource="@revision" asp-requirement="@RevisionOwnerRequirement.Instance">
<a href="#" class="text-decoration-none revision-rename-icon pr-2">✎</a>
<input type="hidden" name="revisionId" value="@revision.Id" />
<span class="revision-name-input form-group-lg pr-2" style="display:none">
<input type="text" class="form-control" name="newLabel" value="@revision.Label" placeholder="Enter a revision label" />
<a href="#" class="text-decoration-none submit-revision-rename">✔️</a>
<a href="#" class="text-decoration-none cancel-revision-rename">❌</a>
</span>
</span>
<span class="revision-name-label">@revision.Label</span>
<span class="font-italic pl-3">
<span>@revision.APIRevisionType.ToString(), Created: </span>
<span date="@revision.CreatedOn"></span>
<span>By: @revision.CreatedBy</span>
</span>
</form>
<form asp-resource="@revision" asp-requirement="@RevisionOwnerRequirement.Instance"
class="form-inline float-right" asp-page-handler="Delete" method="post">
<input type="hidden" name="revisionId" value="@revision.Id" />
<input type="submit" class="btn btn-danger btn-sm" value="Delete" />
</form>
</li>
}
</ul>
}
@if (Model.APIRevisions.ContainsKey("Manual"))
{
<ul class="list-group mt-2">
<li class="list-group-item list-group-item-heading fw-bold">Manual APIRevisions</li>
@foreach (var revision in Model.APIRevisions["Manual"])
{
<li class="list-group-item">
<form class="form-inline revision-rename-form float-left" asp-page-handler="Rename" method="post">
<span asp-resource="@revision" asp-requirement="@RevisionOwnerRequirement.Instance">
<a href="#" class="text-decoration-none revision-rename-icon pr-2">✎</a>
<input type="hidden" name="revisionId" value="@revision.Id" />
<span class="revision-name-input form-group-lg pr-2" style="display:none">
<input type="text" class="form-control" name="newLabel" value="@revision.Label" placeholder="Enter a revision label" />
<a href="#" class="text-decoration-none submit-revision-rename">✔️</a>
<a href="#" class="text-decoration-none cancel-revision-rename">❌</a>
</span>
</span>
<span class="revision-name-label">@revision.Label</span>
<span class="font-italic pl-3">
<span>@revision.APIRevisionType.ToString(), Created: </span>
<span date="@revision.CreatedOn"></span>
<span>By: @revision.CreatedBy</span>
</span>
</form>
<form asp-resource="@revision" asp-requirement="@RevisionOwnerRequirement.Instance"
class="form-inline float-right" asp-page-handler="Delete" method="post">
<input type="hidden" name="revisionId" value="@revision.Id" />
<input type="submit" class="btn btn-danger btn-sm" value="Delete" />
</form>
</li>
}
</ul>
}
<div id="left-offcanvas-menu" class="left-offcanvas@(leftOffCanvasClass)">
<div class="left-offcanvas-menu-content" id="left-offcanvas-menu-content">
<div class="btn-group-vertical" role="group" aria-label="Vertical button group">
<a type="button" class="btn btn-lg btn-link mb-2" asp-page="Review" asp-route-id="@Model.Review.Id"><i class="bi bi-braces" data-bs-toggle="tooltip" data-bs-placement="right" title="API"></i></a>
<a type="button" class="btn btn-lg btn-link mb-2" active-if="@TempData["page"].Equals("revisions")"><i class="bi bi-clock-history" data-bs-toggle="tooltip" data-bs-placement="right" title="Revisons"></i></a>
<a type="button" class="btn btn-lg btn-link mb-2" asp-page="Conversation" asp-route-id="@Model.Review.Id"><i class="bi bi-chat-left-dots" data-bs-toggle="tooltip" data-bs-placement="right" title="Conversiations"></i></a>
<a type="button" class="btn btn-lg btn-link mb-2" asp-page="Samples" asp-route-id="@Model.Review.Id"><i class="bi bi-puzzle" data-bs-toggle="tooltip" data-bs-placement="right" title="Samples"></i></a>
</div>
</div>
</div>
<div id="right-offcanvas-menu" class="right-offcanvas@(rightOffCanvasClass)">
<div class="right-offcanvas-menu-content" id="right-offcanvas-menu-content">
<p class="h6">
<a data-bs-toggle="collapse" href="#revisionsUpdateCollapse" aria-expanded="true" aria-controls="revisionsUpdateCollapse">Update&nbsp;&nbsp;<i class="fa-solid fa-ellipsis"></i></a>
</p>
@{
var revisionsUpdateCollapseState = " show";
if (Request.Cookies.ContainsKey("revisionsUpdateCollapse"))
{
if (!Request.Cookies["revisionsUpdateCollapse"].Equals("shown"))
revisionsUpdateCollapseState = String.Empty;
}
}
<ul class="list-group collapse mb-3@(revisionsUpdateCollapseState)" id="samplesInfoCollapse">
<li class="list-group-item text-center">
<div class="d-grid gap-2 my-2">
<button type="button" class="btn btn-primary" data-bs-toggle="offcanvas" data-bs-target="#add-apirevision-context" aria-controls="add-apirevision-context">Add APIRevisions</button>
<button type="button" class="btn btn-primary" data-bs-toggle="offcanvas" data-bs-target="#upload-samples-context" aria-controls="upload-samples-context">Add Samples Revisions</button>
</div>
</li>
</ul>
</div>
</div>
<div class="container-fluid pt-2@(mainContainerClass)" id="revisions-main-container">
<div class="row g-2">
<div class="col">
<h6 class="ms-3">APIRevisions</h6>
<partial name="Shared/_APIRevisionsPartial" model="(Model.Review, Model.APIRevisions, default(APIRevisionListItemModel), default(APIRevisionListItemModel))" />
</div>
@if (Model.SamplesRevisions.Any())
{
<div class="col">
<h6 class="ms-3">Sample Revisions</h6>
<partial name="Shared/_SamplesRevisionsPartial" model="(Model.Review, Model.SamplesRevisions, default(SamplesRevisionModel))" />
</div>
}
else
{
<partial name="Shared/_AddSamplesRevisionsPartial" model="Model.Review" />
}
</div>
</div>

Просмотреть файл

@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using APIViewWeb.Helpers;
using APIViewWeb.LeanModels;
using APIViewWeb.Managers;
using APIViewWeb.Managers.Interfaces;
using APIViewWeb.Models;
using APIViewWeb.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@ -16,66 +16,52 @@ namespace APIViewWeb.Pages.Assemblies
{
private readonly IReviewManager _reviewManager;
private readonly IAPIRevisionsManager _apiRevisionsManager;
private readonly ISamplesRevisionsManager _samplesRevisionsManager;
public readonly UserPreferenceCache _preferenceCache;
public RevisionsPageModel(
IReviewManager manager,
IAPIRevisionsManager reviewRevisionsManager,
ISamplesRevisionsManager samplesRevisionsManager,
UserPreferenceCache preferenceCache)
{
_reviewManager = manager;
_apiRevisionsManager = reviewRevisionsManager;
_samplesRevisionsManager = samplesRevisionsManager;
_preferenceCache = preferenceCache;
}
public ReviewListItemModel Review { get; set; }
public APIRevisionListItemModel LatestAPIRevision { get; set; }
public Dictionary<string, List<APIRevisionListItemModel>> APIRevisions { get; set; }
[FromForm]
public string Label { get; set; }
[FromForm]
public string FilePath { get; set; }
[FromForm]
public string Language { get; set; }
public IEnumerable<SamplesRevisionModel> SamplesRevisions { get; set; }
public IEnumerable<APIRevisionListItemModel> APIRevisions { get; set; }
public async Task<IActionResult> OnGetAsync(string id)
{
TempData["Page"] = "revisions";
Review = await _reviewManager.GetReviewAsync(User, id);
LatestAPIRevision = await _apiRevisionsManager.GetLatestAPIRevisionsAsync(Review.Id);
var revisions = await _apiRevisionsManager.GetAPIRevisionsAsync(Review.Id);
APIRevisions = revisions.GroupBy(r => r.APIRevisionType).ToDictionary(r => r.Key.ToString(), r => r.ToList());
APIRevisions = (await _apiRevisionsManager.GetAPIRevisionsAsync(Review.Id)).OrderByDescending(c => c.CreatedOn);
SamplesRevisions = (await _samplesRevisionsManager.GetSamplesRevisionsAsync(Review.Id)).OrderByDescending(c => c.CreatedOn);
return Page();
}
public async Task<IActionResult> OnPostUploadAsync(string id, [FromForm] IFormFile upload)
/// <summary>
/// Upload APIRevisions
/// </summary>
/// <param name="id"></param>
/// <param name="upload"></param>
/// <param name="label"></param>
/// <param name="filePath"></param>
/// <returns></returns>
public async Task<IActionResult> OnPostUploadAsync(string id, [FromForm] IFormFile upload, [FromForm] string label, [FromForm] string filePath)
{
if (!ModelState.IsValid)
{
return RedirectToPage();
}
if (upload != null)
{
var openReadStream = upload.OpenReadStream();
await _apiRevisionsManager.AddAPIRevisionAsync(User, id, APIRevisionType.Manual, upload.FileName, Label, openReadStream, language: Language);
}
else
{
await _apiRevisionsManager.AddAPIRevisionAsync(User, id, APIRevisionType.Manual, FilePath, Label, null);
}
return RedirectToPage();
}
public async Task<IActionResult> OnPostDeleteAsync(string id, string revisionId)
{
await _apiRevisionsManager.SoftDeleteAPIRevisionAsync(User, id, revisionId);
await PageModelHelpers.UploadAPIRevisionAsync(_apiRevisionsManager, User, id, upload, label, filePath);
return RedirectToPage();
}
@ -83,8 +69,19 @@ namespace APIViewWeb.Pages.Assemblies
public async Task<IActionResult> OnPostRenameAsync(string id, string revisionId, string newLabel)
{
await _apiRevisionsManager.UpdateAPIRevisionLabelAsync(User, revisionId, newLabel);
return Content(newLabel);
}
return RedirectToPage();
/// <summary>
/// Delete API Revision
/// </summary>
/// <param name="id"></param>
/// <param name="revisionId"></param>
/// <returns></returns>
public async Task<IActionResult> OnDeleteAsync(string id, string revisionId)
{
await _apiRevisionsManager.SoftDeleteAPIRevisionAsync(User, id, revisionId);
return new NoContentResult();
}
}
}

Просмотреть файл

@ -10,89 +10,86 @@
TempData["UserPreference"] = userPreference;
}
@{
var rightOffCanvasClass = " show-right-offcanvas";
var leftOffCanvasClass = " show-left-offcanvas";
var mainContainerLeftClass = " move-main-content-container-left";
var mainContainerRightClass = " move-main-content-container-right";
if (userPreference.HideSamplesPageOptions.HasValue && userPreference.HideSamplesPageOptions == true)
{
rightOffCanvasClass = String.Empty;
mainContainerRightClass = String.Empty;
}
var mainContainerClass = mainContainerLeftClass + mainContainerRightClass;
}
<div class="container-fluid mx-0 px-0 sub-header-content">
<div class="row px-3 py-2 border-bottom" id="review-info-bar">
<partial name="Shared/_ReviewBadge" model="(Model.Review, default(APIRevisionListItemModel), default(APIRevisionListItemModel), default(UserPreferenceModel))" />
</div>
</div>
<div id="left-offcanvas-menu" class="left-offcanvas@(leftOffCanvasClass)">
<div class="left-offcanvas-menu-content" id="left-offcanvas-menu-content">
<div class="btn-group-vertical" role="group" aria-label="Vertical button group">
<a type="button" class="btn btn-lg btn-link mb-2" asp-page="Review" asp-route-id="@Model.Review.Id"><i class="bi bi-braces" data-bs-toggle="tooltip" data-bs-placement="right" title="API"></i></a>
<a type="button" class="btn btn-lg btn-link mb-2" data-bs-toggle="offcanvas" data-bs-target="#samplesRevisions-context" aria-controls="samplesRevisions-context"><i class="bi bi-clock-history" data-bs-toggle="tooltip" data-bs-placement="right" title="Revisons"></i></a>
<a type="button" class="btn btn-lg btn-link mb-2" asp-page="Conversation" asp-route-id="@Model.Review.Id"><i class="bi bi-chat-left-dots" data-bs-toggle="tooltip" data-bs-placement="right" title="Conversiations"></i></a>
<a type="button" class="btn btn-lg btn-link mb-2" data-bs-toggle="tooltip" data-bs-placement="right" title="Samples" active-if="@TempData["page"].Equals("samples")"><i class="bi bi-puzzle"></i></a>
</div>
</div>
</div>
<div id="right-offcanvas-menu" class="right-offcanvas@(rightOffCanvasClass)">
<div class="right-offcanvas-menu-content" id="right-offcanvas-menu-content">
<p class="h6">
<a data-bs-toggle="collapse" href="#samplesUpdateCollapse" aria-expanded="true" aria-controls="samplesUpdateCollapse">Update&nbsp;&nbsp;<i class="fa-solid fa-ellipsis"></i></a>
</p>
@{
var samplesUpdateCollapseState = " show";
if (Request.Cookies.ContainsKey("samplesUpdateCollapse"))
{
if (!Request.Cookies["samplesUpdateCollapse"].Equals("shown"))
samplesUpdateCollapseState = String.Empty;
}
}
<ul class="list-group collapse mb-3@(samplesUpdateCollapseState)" id="samplesInfoCollapse">
<li class="list-group-item text-center">
<span class="small text-muted"><i>Uploaded by <strong> <a username="@Model.ActiveSampleRevision.CreatedBy"> @Model.ActiveSampleRevision.CreatedBy </a></strong></i></span>
<div class="d-grid gap-2 my-2">
@if (Model.SampleContent != null && Model.SampleContent.Any())
{
<button type="button" class="btn btn-outline-primary" data-bs-toggle="offcanvas" data-bs-target="#edit-samples-context" aria-controls="edit-samples-context">Edit</button>
@if (Model.ActiveSampleRevision.CreatedBy == User.GetGitHubLogin())
{
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModel">Delete</button>
}
}
<button type="button" class="btn btn-primary" data-bs-toggle="offcanvas" data-bs-target="#upload-samples-context" aria-controls="upload-samples-context">Add Sample</button>
</div>
</li>
</ul>
</div>
</div>
@if (Model.ActiveSampleRevision != null && (Model.ActiveSampleRevision.FileId == "Bad Deployment" || Model.ActiveSampleRevision.FileId == "File Content Missing")) // If the samples container does not exist
{
<div class="container-fluid mx-0 px-0 sub-header-content">
<div class="row mx-1 px-0 py-2">
<div class="col-md-6">
<div class="input-group-sm input-group">
<partial name="Shared/_ReviewBadge" model="(Model.Review, default(APIRevisionListItemModel))" />
<div class="container-fluid pt-2@(mainContainerClass)" id="samples-main-container">
<div class="row mx-0 px-0">
<div class="col-lg-12 mx-0 px-0">
<div class="text-muted">
<p> <strong> Usage Samples are not available for this APIView deployment. </strong></p>
<p> Please check <a href="https://github.com/Azure/azure-sdk-tools/blob/main/src/dotnet/APIView/APIViewWeb/CONTRIBUTING.md">CONTRIBUTING.md</a> to see how to enable them. </p>
</div>
</div>
<div class="col-md-3">
</div>
<div class="col-md-3">
</div>
</div>
<partial name="Shared/_ReviewNavBar" />
</div>
<div class="container">
<div class="row mx-1 px-0 py-3">
<div class="text-muted">
<p> <strong> Usage Samples are not available for this APIView deployment. </strong></p>
<p> Please check <a href="https://github.com/Azure/azure-sdk-tools/blob/main/src/dotnet/APIView/APIViewWeb/CONTRIBUTING.md">CONTRIBUTING.md</a> to see how to enable them. </p>
</div>
</div>
</div>
}
else
{
<div class="container-fluid mx-0 px-0 sub-header-content">
<div class="row mx-1 px-0 py-2">
<div class="col-md-8">
<div class="input-group-sm input-group" id="review-info-bar">
<partial name="Shared/_ReviewBadge" model="(Model.Review, default(APIRevisionListItemModel))"/>
@if (Model.SampleRevisions.Any())
{
@if (Model.SampleContent.Length > 0)
{
<span class="input-group-text">Revision:</span>
<select id="revision-select" aria-label="Revision Select">
@foreach (var revision in Model.SampleRevisions)
{
var urlValue = @Url.ActionLink("Samples", "Assemblies", new
{
id = @Model.Review.Id,
revisionId = @revision.FileId,
});
string revTitle = revision.Title == null ? $"Created On {revision.CreatedOn}" : $"Created On: {revision.Title}";
if (revision.FileId == Model.ActiveSampleRevision.FileId)
{
<option selected value="@urlValue">@revTitle</option>
}
else
{
<option value="@urlValue">@revTitle</option>
}
}
</select>
}
@if (Model.SampleContent.Length > 0)
{
<button type="button" class="btn btn-sm border shadow-sm btn-light" data-bs-toggle="modal" data-bs-target="#updateModel"><i class="fa-solid fa-pen-to-square"></i>&nbsp;&nbsp;Edit</button>
@if (Model.ActiveSampleRevision.CreatedBy == User.GetGitHubLogin())
{
<button type="button" class="btn btn-sm shadow-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModel"><i class="fas fa-times-circle"></i>&nbsp;&nbsp;Delete</button>
}
}
}
</div>
</div>
<div class="col-md-1">
</div>
<div class="col-md-3">
</div>
</div>
<partial name="Shared/_ReviewNavBar" />
</div>
<div class="container-fluid" id="samples-main-container">
<div class="row mx-0 px-0 py-2">
<div class="col-lg-12 mx-0" data-review-id=@Model.Review.Id>
<div id="add-sample-button">
<button type="button" class="btn btn-primary" id="add-sample-button" data-bs-toggle="modal" data-bs-target="#uploadModel"><small>ADD</small><br><i class="fas fa-sm fa-plus"></i><br><small>SAMPLE</small></button>
</div>
<div class="container-fluid pt-2@(mainContainerClass)" id="samples-main-container">
<div class="row mx-0 px-0">
<div class="col-lg-12 mx-0 px-0" data-review-id=@Model.Review.Id>
@if (Model.SampleRevisions.Any())
{
@if (Model.SampleContent.Length == 0)
@ -113,7 +110,6 @@ else
</tbody>
</table>
</div>
<i> Sample uploaded by <strong> <a username="@Model.ActiveSampleRevision.CreatedBy"> @Model.ActiveSampleRevision.CreatedBy </a></strong></i>
}
}
else
@ -126,138 +122,73 @@ else
<partial name="_SampleCommentFormPartial" />
</div>
</div>
}
<partial name="Shared/_AddSamplesRevisionsPartial" model="Model.Review" />
<div class="container-fluid">
<div class="modal fade" id="uploadModel" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add Usage Sample</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="form-group">
<label>Pick an upload method:</label>
<ul class="nav nav-pills nav-fill mb-3">
<li class="nav-item">
<a class="nav-link active" id="home-tab" data-bs-toggle="tab" href="#md-text">Markdown Text</a>
</li>
<li class="nav-item">
<a class="nav-link" id="profile-tab" data-bs-toggle="tab" href="#md-file">Markdown File</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content border rounded px-3">
<div class="tab-pane active pt-2" id="md-text">
<form asp-page-handler="Upload" method="post">
<div class="form-group ">
<label asp-for="Upload.RevisionTitle"> Enter a sample title (Optional) </label>
<input asp-for="Upload.RevisionTitle" class="form-control" type="text" /> <br />
<textarea asp-for="Upload.sampleString" class="new-thread-comment-text form-control" rows="25" placeholder="Enter your markdown formatted usage sample here"></textarea>
<input asp-for="Upload.ReviewId" hidden value="@Model.Review.Id" />
<div class="d-flex flex-row-reverse pt-3 mb-3">
<button type="submit" class="btn btn-primary" onclick="this.form.submit(); this.disabled = true;"><i class="fa-solid fa-floppy-disk"></i>&nbsp;&nbsp;Save</button>
<button type="button" class="btn btn-outline-secondary me-1" data-bs-dismiss="modal">Close</button>
</div>
</div>
</form>
</div>
<div class="tab-pane pt-2" id="md-file">
<form asp-page-handler="Upload" method="post" enctype="multipart/form-data">
<label asp-for="Upload.RevisionTitle"> Enter a sample title (Optional) </label>
<input asp-for="Upload.RevisionTitle" class="form-control" type="text" /> <br />
<input asp-for="Upload.ReviewId" hidden value="@Model.Review.Id" />
<div class="input-group mb-3 custom-file-label">
<label for="uploadMarkdownSample" class="input-group-text small mb-0">Select usage sample for this review</label>
<input asp-for="Upload.File" type="file" class="form-control" id="uploadMarkdownSample">
</div>
<div class="d-flex flex-row-reverse pt-3 mb-3">
<button type="submit" class="btn btn-primary" onclick="this.form.submit(); this.disabled = true;"><i class="fas fa-cloud-upload-alt"></i>&nbsp;&nbsp;Upload</button>
<button type="button" class="btn btn-outline-secondary me-1" data-bs-dismiss="modal">Close</button>
</div>
</form>
</div>
</div>
</div>
<div class="offcanvas offcanvas-end border-start" tabindex="-1" id="edit-samples-context" aria-labelledby="edit-samples-context-label">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasLabel">Update Usage Samples</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<form asp-page-handler="Upload" method="post">
<div class="form-group">
<div class="border rounded p-3">
<label asp-for="Upload.RevisionTitle"> Enter a sample title (Optional) </label>
<input asp-for="Upload.RevisionTitle" class="form-control" type="text" /> <br />
<div class="edit-samples-content">
<textarea asp-for="Upload.UpdateString" class="form-control" rows="25"></textarea>
</div>
<input asp-for="Upload.ReviewId" hidden value="@Model.Review.Id" />
<input asp-for="Upload.Updating" hidden type="text" value="@true" />
<p class="text-muted"> This will create a new revision of the sample. </p>
</div>
</div>
</div>
<div class="d-grid gap-2 my-2">
<button type="submit" class="btn btn-primary" onclick="this.form.submit(); this.disabled = true;">Save</button>
</div>
</form>
</div>
</div>
<div class="container-fluid">
<div class="modal fade" id="deleteModel" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete Usage Sample</h5>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tab-content">
<p class="text-center"> This action can not be undone. </p>
</div>
</div>
<div class="modal-footer">
<div class="form-group">
<form asp-page-handler="Upload" method="post">
<input asp-for="Upload.sampleString" hidden value="@null" />
<input asp-for="Upload.ReviewId" hidden value="@Model.Review.Id" />
<input asp-for="Upload.Deleting" hidden type="text" value="@true" />
<input asp-for="Upload.SampleId" hidden type="text" value="@Model.ActiveSampleRevision.Id" />
<input asp-for="Upload.FileId" hidden type="text" value="@Model.ActiveSampleRevision.FileId" />
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Close</button>
<button type="submit" name="submit" value="Submit" class="btn btn-outline-danger"><i class="fas fa-times-circle"></i>&nbsp;&nbsp;Delete</button>
</form>
</div>
</div>
<div class="modal fade" id="deleteModel" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete Usage Sample</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tab-content">
<p class="text-center"> This action can not be undone. </p>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="modal fade" id="updateModel" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-footer">
<div class="form-group">
<form asp-page-handler="Upload" method="post">
<div class="modal-header">
<h5 class="modal-title">Update Usage Sample</h5>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<div class="tab-content border rounded p-3">
<div class="tab-pane active" id="md-text">
<label asp-for="Upload.RevisionTitle"> Enter a sample title (Optional) </label>
<input asp-for="Upload.RevisionTitle" class="form-control" type="text" /> <br />
<div class="form-group">
<div class="new-sample-content">
<textarea asp-for="Upload.updateString" class="new-thread-comment-text form-control" rows="25"></textarea>
</div>
<input asp-for="Upload.ReviewId" hidden value="@Model.Review.Id" />
<input asp-for="Upload.Updating" hidden type="text" value="@true" />
</div>
<p class="text-muted"> This will create a new revision of the sample. </p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary" onclick="this.form.submit(); this.disabled = true;"><i class="fa-solid fa-pen-to-square"></i>&nbsp;&nbsp;Save</button>
</div>
<input asp-for="Upload.SampleString" hidden value="@null" />
<input asp-for="Upload.ReviewId" hidden value="@Model.Review.Id" />
<input asp-for="Upload.DeletingAndRedirect" hidden type="text" value="@true" />
<input asp-for="Upload.SampleId" hidden type="text" value="@Model.ActiveSampleRevision.Id" />
<input asp-for="Upload.FileId" hidden type="text" value="@Model.ActiveSampleRevision.FileId" />
<button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">Close</button>
<button type="submit" name="submit" value="Submit" class="btn btn-outline-danger">Delete</button>
</form>
</div>
</div>
</div>
</div>
}
</div>
<div class="offcanvas offcanvas-end border-start" tabindex="-1" id="samplesRevisions-context" aria-labelledby="samplesRevisions-contex-label">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="samplesRevisions-contex-label"><a asp-page="Revisions" asp-route-id="@Model.Review.Id">Revisions</a></h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<partial name="Shared/_SamplesRevisionsPartial" model="(Model.Review, Model.SampleRevisions, Model.ActiveSampleRevision)" />
</div>
</div>

Просмотреть файл

@ -7,12 +7,14 @@ using ApiView;
using APIView;
using APIViewWeb.LeanModels;
using APIViewWeb.Managers;
using APIViewWeb.Managers.Interfaces;
using APIViewWeb.Models;
using APIViewWeb.Repositories;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Azure.Cosmos;
using Octokit;
namespace APIViewWeb.Pages.Assemblies
{
@ -58,13 +60,13 @@ namespace APIViewWeb.Pages.Assemblies
// This try-catch is for the case that the deployment is set up incorrectly for usage samples
try
{
SampleRevisions = await _samplesRevisionsManager.GetSamplesRevisionsAsync(Review.Id);
SampleRevisions = (await _samplesRevisionsManager.GetSamplesRevisionsAsync(Review.Id)).OrderBy(s => s.CreatedOn);
if (SampleRevisions != null && SampleRevisions.Any())
{
if (revisionId != null)
{
ActiveSampleRevision = SampleRevisions.Where(s => s.FileId == revisionId).First();
ActiveSampleRevision = SampleRevisions.Where(s => s.Id == revisionId).First();
}
else
{
@ -74,7 +76,7 @@ namespace APIViewWeb.Pages.Assemblies
Comments = await _commentsManager.GetUsageSampleCommentsAsync(Review.Id);
SampleContent = ParseLines(ActiveSampleRevision.FileId, Comments).Result;
SampleOriginal = await _samplesRevisionsManager.GetSamplesRevisionContentAsync(ActiveSampleRevision.OriginalFileId);
Upload.updateString = SampleOriginal;
Upload.UpdateString = SampleOriginal;
if (SampleContent == null)
{
// Potentially bad blob setup, potentially erroneous file fetch
@ -115,32 +117,45 @@ namespace APIViewWeb.Pages.Assemblies
await AssertAccess(User);
var file = Upload.File;
string sampleString = Upload.sampleString;
string reviewId = Upload.ReviewId;
string revisionTitle = Upload.RevisionTitle;
SamplesRevisionModel sampleRevision = null;
if (file != null)
if (Upload.File != null)
{
using (var openReadStream = file.OpenReadStream())
using (var openReadStream = Upload.File.OpenReadStream())
{
await _samplesRevisionsManager.UpsertSamplesRevisionsAsync(User, reviewId, openReadStream, revisionTitle, file.FileName);
sampleRevision = await _samplesRevisionsManager.UpsertSamplesRevisionsAsync(User, Upload.ReviewId, openReadStream, Upload.RevisionTitle, Upload.File.FileName);
}
}
else if (sampleString != null)
else if (Upload.SampleString != null)
{
await _samplesRevisionsManager.UpsertSamplesRevisionsAsync(User, reviewId, sampleString, revisionTitle);
sampleRevision = await _samplesRevisionsManager.UpsertSamplesRevisionsAsync(User, Upload.ReviewId, Upload.SampleString, Upload.RevisionTitle);
}
else if (Upload.Updating)
{
await _samplesRevisionsManager.UpsertSamplesRevisionsAsync(User, reviewId, Upload.updateString, revisionTitle);
sampleRevision = await _samplesRevisionsManager.UpsertSamplesRevisionsAsync(User, Upload.ReviewId, Upload.UpdateString, Upload.RevisionTitle);
}
else if (Upload.Deleting)
else if (Upload.Deleting || Upload.DeletingAndRedirect)
{
await _samplesRevisionsManager.DeleteSamplesRevisionAsync(User, reviewId, Upload.SampleId);
await _samplesRevisionsManager.DeleteSamplesRevisionAsync(User, Upload.ReviewId, Upload.SampleId);
if (Upload.Deleting)
{
return new NoContentResult();
}
return RedirectToPage();
}
else if (Upload.Renaming)
{
await _samplesRevisionsManager.UpdateSamplesRevisionTitle(Upload.ReviewId, Upload.SampleId, Upload.RevisionTitle);
return Content(Upload.RevisionTitle);
}
return RedirectToPage();
if (sampleRevision != null)
{
return RedirectToPage(new { id = sampleRevision.ReviewId, revisionId = sampleRevision.Id });
}
return StatusCode(500);
}
private async Task<CodeLineModel[]> ParseLines(string fileId, ReviewCommentsModel comments)

Просмотреть файл

@ -0,0 +1,95 @@
@using APIViewWeb.Helpers
@using APIViewWeb.LeanModels;
@model (ReviewListItemModel review, IEnumerable<APIRevisionListItemModel> APIRevisions, APIRevisionListItemModel activeAPIRevision, APIRevisionListItemModel diffAPIRevision)
<div class="row g-4">
<div class="col mx-3">
<form>
<div class="d-grid gap-2">
@if (Model.activeAPIRevision != null)
{
<button type="button" class="btn btn-primary" data-bs-toggle="offcanvas" data-bs-target="#add-apirevision-context" aria-controls="add-apirevision-context">Add APIRevision</button>
}
<div class="btn-group" role="group" aria-label="APIRevisions Select">
<input type="checkbox" class="btn-check" id="manual-apirevisions-check" autocomplete="off">
<label class="btn btn-outline-primary" for="manual-apirevisions-check">Manual</label>
<input type="checkbox" class="btn-check" id="automatic-apirevisions-check" autocomplete="off">
<label class="btn btn-outline-primary" for="automatic-apirevisions-check">Automatic</label>
<input type="checkbox" class="btn-check" id="pullrequest-apirevisions-check" autocomplete="off">
<label class="btn btn-outline-primary" for="pullrequest-apirevisions-check">Pull Request</label>
</div>
<div class="input-group mb-3">
<span class="input-group-text"><i class="fa-solid fa-magnifying-glass"></i></span>
<input type="search" placeholder="Search.." class="form-control" id="apiRevisions-search" aria-label="apirevision search">
</div>
</div>
</form>
<div class="api-revisions revisions-list-container p-2">
@foreach (var apiRevision in Model.APIRevisions)
{
<div class="card @apiRevision.IsApproved my-2" data-id="@apiRevision.Id">
<img username="@apiRevision.CreatedBy" size="105" aria-label="GitHub User Avatar" />
<div class="card-body">
<h6 class="card-title">@apiRevision.Label</h6>
@if (User.GetGitHubLogin() == apiRevision.CreatedBy)
{
<div class="input-group input-group-sm mb-1 edit-revision-label d-none">
<input type="text" class="form-control" value="@apiRevision.Label" aria-label="Edit APIRevison Label">
<button class="input-group-text enter-rename"><i class="bi bi-check"></i></button>
<button class="input-group-text cancel-rename"><i class="bi bi-x"></i></button>
</div>
}
<p class="card-subtitle mb-1 text-body-secondary"><b>Created: </b><span date="@apiRevision.CreatedOn"></span> , <b>By: </b>@apiRevision.CreatedBy</p>
<p class="card-subtitle mb-0 text-body-secondary"><b>Last Updated: </b><span date="@apiRevision.LastUpdatedOn"></span> , <b>Type: </b>@apiRevision.APIRevisionType , <b>Version: </b>@apiRevision.Files[0].PackageVersion</p>
</div>
<div class="revision-actions">
<div class="btn-group animate__animated animate__fadeIn" role="group" aria-label="apiRevision action buttons">
@if (Model.activeAPIRevision != null)
{
@if (apiRevision.Id != Model.activeAPIRevision.Id && (Model.diffAPIRevision == null || Model.diffAPIRevision.Id != apiRevision.Id))
{
<button type="button" class="btn btn-sm btn-outline-primary make-active" data-bs-toggle="tooltip" title="Make Active"><i class="bi bi-clock-history mr-1"></i></button>
}
@if ((Model.diffAPIRevision == null || Model.diffAPIRevision.Id != apiRevision.Id) && Model.activeAPIRevision.Id != apiRevision.Id)
{
<button type="button" class="btn btn-sm btn-outline-primary make-diff" data-bs-toggle="tooltip" title="Make Diff"><i class="bi bi-file-diff mr-1"></i></button>
}
@if (Model.diffAPIRevision != null && apiRevision.Id == Model.diffAPIRevision.Id)
{
<button type="button" class="btn btn-sm btn-outline-primary clear-diff" data-bs-toggle="tooltip" title="Clear Diff"><i class="bi bi-file-earmark-minus"></i></button>
}
}
@if (User.GetGitHubLogin() == apiRevision.CreatedBy)
{
<button type="button" class="btn btn-sm btn-outline-primary rename" data-bs-toggle="tooltip" title="Rename"><i class="bi bi-pencil-square"></i></button>
<button type="button" class="btn btn-sm btn-outline-primary delete" data-bs-toggle="tooltip" title="Delete"><i class="bi bi-x-circle text-danger"></i></button>
}
</div>
</div>
<div class="revision-indicator-checks animate__animated animate__slideInLeft">
@if (apiRevision.IsApproved)
{
<i class="fas fa-check-circle text-success mr-1"></i>
}
@if (Model.activeAPIRevision != null)
{
@if (apiRevision.Id == Model.activeAPIRevision.Id)
{
<i class="bi bi-clock-history active-rev mr-1"></i>
}
@if (Model.diffAPIRevision != null && Model.diffAPIRevision.Id == apiRevision.Id)
{
<i class="bi bi-file-diff diff-rev mr-1"></i>
}
}
</div>
</div>
}
</div>
</div>
</div>
<partial name="_AddAPIRevisionsPartial" model="Model.review" />

Просмотреть файл

@ -0,0 +1,49 @@
@using APIViewWeb.Helpers
@using APIViewWeb.LeanModels;
@model ReviewListItemModel
<div class="offcanvas offcanvas-end border-start" tabindex="-1" id="add-apirevision-context" aria-labelledby="add-apirevision-context-label">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="add-apirevision-context-label">Add APIRevision</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<form asp-page-handler="Upload" method="post" enctype="multipart/form-data">
<div class="d-grid gap-2">
@if (!(LanguageServiceHelpers.MapLanguageAliases(new List<string> { "TypeSpec" })).Contains(Model.Language))
{
<div class="input-group custom-file-label">
<label class="input-group-text small" for="uploadRevisionFile">Select file to add</label>
<input name="upload" for="upload" type="file" class="form-control" id="uploadRevisionFile">
</div>
}
else
{
<ul class="nav nav-pills nav-fill" id="typespec-pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="typespec-pills-tsp-tab" data-bs-toggle="pill" data-bs-target="#typespec-pills-tsp" type="button" role="tab" aria-controls="typespec-pills-tsp" aria-selected="true">TypeSpec</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="typespec-pills-json-tab" data-bs-toggle="pill" data-bs-target="#typespec-pills-json" type="button" role="tab" aria-controls="typespec-pills-json" aria-selected="false">Json</button>
</li>
</ul>
<div class="tab-content" id="typespec-pills-tabcontent">
<div class="tab-pane fade show active" id="typespec-pills-tsp" role="tabpanel" aria-labelledby="typespec-pills-tsp-tab" tabindex="0">
<div class="input-group custom-file-label">
<label class="input-group-text small" for="uploadRevisionFile">Select file to add</label>
<input name="upload" for="upload" type="file" class="form-control" id="uploadRevisionFile">
</div>
</div>
<div class="tab-pane fade" id="typespec-pills-json" role="tabpanel" aria-labelledby="typespec-pills-json-tab" tabindex="0">
<input name="filePath" for="filePath" class="form-control" type="text" placeholder="Package root e.g https://github.com/Azure/azure-rest-api-specs/specification/cognitiveservices/AnomalyDetector/">
</div>
</div>
}
<div>
<input name="label" for="label" class="form-control" type="text" placeholder="Enter an optional revision label">
</div>
<button type="submit" class="btn btn-primary" onclick="this.form.submit(); this.disabled = true;">Upload</button>
</div>
</form>
</div>
</div>

Просмотреть файл

@ -0,0 +1,58 @@
@using APIViewWeb.Helpers;
@using APIViewWeb.LeanModels;
@using APIViewWeb.Pages.Assemblies;
@model ReviewListItemModel
<div class="offcanvas offcanvas-end border-start" tabindex="-1" id="upload-samples-context" aria-labelledby="upload-samples-contextt-label">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasLabel">Add Usage Samples</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div class="form-group">
<label>Pick an upload method:</label>
<ul class="nav nav-pills nav-fill mb-3">
<li class="nav-item">
<a class="nav-link active" id="home-tab" data-bs-toggle="tab" href="#md-text">Markdown Text</a>
</li>
<li class="nav-item">
<a class="nav-link" id="profile-tab" data-bs-toggle="tab" href="#md-file">Markdown File</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content border rounded px-3">
<div class="tab-pane active pt-2" id="md-text">
<form asp-page="Samples" asp-page-handler="Upload" method="post">
<div class="form-group ">
<label for="uploadSamplesRevisionTitleA">Enter a sample title (Optional) </label>
<input name="RevisionTitle" id="uploadSamplesRevisionTitleA" class="form-control" type="text" /> <br />
<div class="edit-samples-content">
<textarea name="SampleString" class="form-control" rows="25" placeholder="Enter your markdown formatted usage sample here"></textarea>
</div>
<input name="ReviewId" hidden value="@Model.Id" />
<div class="d-grid gap-2 my-2">
<button type="submit" class="btn btn-primary" onclick="this.form.submit(); this.disabled = true;">Save</button>
</div>
</div>
</form>
</div>
<div class="tab-pane pt-2" id="md-file">
<form asp-page-handler="Upload" method="post" enctype="multipart/form-data">
<label for="uploadSamplesRevisionTitleB"> Enter a sample title (Optional) </label>
<input name="RevisionTitle" id="uploadSamplesRevisionTitleB" class="form-control" type="text" /> <br />
<input name="ReviewId" hidden value="@Model.Id" />
<div class="input-group mb-3 custom-file-label">
<label for="uploadMarkdownSample" class="input-group-text small mb-0">Select usage sample for this review</label>
<input name="File" type="file" class="form-control" id="uploadMarkdownSample">
</div>
<div class="d-grid gap-2 my-2">
<button type="submit" class="btn btn-primary" onclick="this.form.submit(); this.disabled = true;">Upload</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>

Просмотреть файл

@ -17,6 +17,7 @@
crossorigin="anonymous"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" />
</environment>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<!-- Soma Select -->
@ -46,9 +47,6 @@
<div class="navbar-collapse collapse">
<ul class="navbar-nav me-auto">
@if (User.Identity.IsAuthenticated) {
<li class="nav-item">
<a class="nav-link" asp-area="" asp-page="/Assemblies/Index">Reviews</a>
</li>
<li class="nav-item">
<span asp-resource="@Model" asp-requirement="@ApproverRequirement.Instance">
<a class="nav-link" asp-area="" asp-page="/Assemblies/RequestedReviews">Requested Reviews</a>

Просмотреть файл

@ -3,39 +3,83 @@
@using System.Text.RegularExpressions;
@using APIViewWeb.Helpers;
@using APIViewWeb.LeanModels;
@model (ReviewListItemModel review, APIRevisionListItemModel activeRevision);
@{
var packageName = Model.review.PackageName;
}
@if (Model.review.Language != null)
{
string iconClassName = "icon-" + PageModelHelpers.GetLanguageCssSafeName(Model.review.Language);
string langVariant = String.Empty;
@if (Model.activeRevision != default(APIRevisionListItemModel) && !string.IsNullOrEmpty(Model.activeRevision.Files.First().LanguageVariant) && Model.activeRevision.Files.First().LanguageVariant.ToLower() != "default")
@using APIViewWeb.Models
@model (ReviewListItemModel review, APIRevisionListItemModel activeAPIRevision, APIRevisionListItemModel diffAPIRevision, UserPreferenceModel userPreference);
<div class="col-md-11">
<nav style="--bs-breadcrumb-divider: '-';" aria-label="breadcrumb">
<ol class="breadcrumb">
@{
string iconClassName = "icon-" + PageModelHelpers.GetLanguageCssSafeName(Model.review.Language);
string langVariant = String.Empty;
}
@if (Model.activeAPIRevision != null && !string.IsNullOrEmpty(Model.activeAPIRevision.Files.First().LanguageVariant) && Model.activeAPIRevision.Files.First().LanguageVariant.ToLower() != "default")
{
langVariant = @Model.activeAPIRevision.Files.First().LanguageVariant.ToLower();
iconClassName += "-" + @Model.activeAPIRevision.Files.First().LanguageVariant.ToLower();
}
<li class="breadcrumb-item"><span role="img" class="icon-language @iconClassName" aria-label="@Model.review.Language @langVariant"></span> @Model.review.PackageName</li>
@if (Model.review.IsApproved)
{
<li class="breadcrumb-item" data-bs-toggle="tooltip" title="Approved for First Release"><i class="fa-regular fa-circle-check text-success"></i></li>
}
@if (Model.activeAPIRevision != null)
{
<li class="breadcrumb-item"><i class="bi bi-clock-history"></i> @PageModelHelpers.ResolveRevisionLabel(Model.activeAPIRevision, false)</li>
@if (Model.activeAPIRevision.IsApproved)
{
<li class="breadcrumb-item" data-bs-toggle="tooltip" title="APIRevision is Approved"><i class="fa-regular fa-circle-check text-success"></i></li>
}
}
@if (Model.diffAPIRevision != null)
{
<li class="breadcrumb-item"><i class="bi bi-file-diff"></i> @PageModelHelpers.ResolveRevisionLabel(Model.diffAPIRevision, false)</li>
@if (Model.diffAPIRevision.IsApproved)
{
<li class="breadcrumb-item" data-bs-toggle="tooltip" title="Diff APIRevision is Approved"><i class="fa-regular fa-circle-check text-success"></i></li>
}
}
</ol>
</nav>
</div>
<div class="col-1">
@if (TempData["page"].Equals("api"))
{
langVariant = @Model.activeRevision.Files.First().LanguageVariant.ToLower();
iconClassName += "-" + @Model.activeRevision.Files.First().LanguageVariant.ToLower();
if (Model.userPreference != null && Model.userPreference.HideReviewPageOptions.HasValue && Model.userPreference.HideReviewPageOptions == true)
{
<input type="checkbox" class="btn-check" id="review-right-offcanvas-toggle" autocomplete="off">
}
else
{
<input type="checkbox" checked class="btn-check" id="review-right-offcanvas-toggle" autocomplete="off">
}
<label class="btn btn-sm btn-outline-primary float-end" accesskey="m" for="review-right-offcanvas-toggle"><i class="fa fa-bars"></i></label>
}
<span role="img" class="input-group-text icon-language @iconClassName" aria-label="@Model.review.Language @langVariant"></span>
}
@{
var packageFieldWidth = 3;
if (packageName != null)
@if (TempData["page"].Equals("revisions"))
{
packageFieldWidth += packageName.Length;
if (Model.userPreference != null && Model.userPreference.HideRevisionsPageOptions.HasValue && Model.userPreference.HideRevisionsPageOptions == true)
{
<input type="checkbox" class="btn-check" id="revisions-right-offcanvas-toggle" autocomplete="off">
}
else
{
<input type="checkbox" checked class="btn-check" id="revisions-right-offcanvas-toggle" autocomplete="off">
}
<label class="btn btn-sm btn-outline-primary float-end" accesskey="m" for="revisions-right-offcanvas-toggle"><i class="fa fa-bars"></i></label>
}
}
<input type="text" class="form-control fw-bold" style="max-width: @(packageFieldWidth)ch; font-family: monospace;" value="@(packageName ?? "")" aria-label="Package Name of Review" readonly>
@if (packageName != null)
{
@if (Model.review.IsApproved)
@if (TempData["page"].Equals("samples"))
{
<span class="input-group-text" data-bs-toggle="tooltip" title="Approved for First Release">
<i class="fa-regular fa-circle-check text-success"></i>
</span>
if (Model.userPreference != null && Model.userPreference.HideSamplesPageOptions.HasValue && Model.userPreference.HideSamplesPageOptions == true)
{
<input type="checkbox" class="btn-check" id="samples-right-offcanvas-toggle" autocomplete="off">
}
else
{
<input type="checkbox" checked class="btn-check" id="samples-right-offcanvas-toggle" autocomplete="off">
}
<label class="btn btn-sm btn-outline-primary float-end" accesskey="m" for="samples-right-offcanvas-toggle"><i class="fa fa-bars"></i></label>
}
}
</div>

Просмотреть файл

@ -1,36 +0,0 @@
@using APIViewWeb.Models
@{
var id = ViewContext.RouteData.Values["id"];
var userPreference = TempData["UserPreference"] as UserPreferenceModel;
}
<div class="row mx-1 p-0 mt-0 border-bottom">
<nav class="navbar pt-0">
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" active-if="@TempData["page"].Equals("api")" asp-page="Review" asp-route-id="@id">API</a>
</li>
<li class="nav-item">
<a class="nav-link" active-if="@TempData["page"].Equals("conversation")" asp-page="Conversation" asp-route-id="@id">Conversations</a>
</li>
<li class="nav-item">
<a class="nav-link" active-if="@TempData["page"].Equals("revisions")" asp-page="Revisions" asp-route-id="@id">Revisions</a>
</li>
<li class="nav-item">
<a class="nav-link" active-if="@TempData["page"].Equals("samples")" asp-page="Samples" asp-route-id="@id">Usage Samples</a>
</li>
</ul>
@if (TempData["page"].Equals("api"))
{
if (userPreference.HideReviewPageOptions.HasValue && userPreference.HideReviewPageOptions == true)
{
<input type="checkbox" class="btn-check" id="review-right-offcanvas-toggle" autocomplete="off">
}
else
{
<input type="checkbox" checked class="btn-check" id="review-right-offcanvas-toggle" autocomplete="off">
}
<label class="btn btn-sm btn-outline-primary float-end" accesskey="m" for="review-right-offcanvas-toggle"><i class="fa fa-bars"></i>&nbsp;&nbsp;Options</label>
}
</nav>
</div>

Просмотреть файл

@ -1,4 +1,3 @@
@using APIViewWeb.Helpers;
@using APIViewWeb.LeanModels;
@using APIViewWeb.Models
@ -27,16 +26,16 @@
@if (review.Language != null)
{
string iconClassName = "icon-" + PageModelHelpers.GetLanguageCssSafeName(@review.Language);
<span role="img" class="mx-1 icon icon-language @iconClassName" aria-label="@review.Language"></span>
<span role="img" class="m-1 icon icon-language @iconClassName" aria-label="@review.Language"></span>
}
<a class="review-name align-middle" asp-page="./Review" asp-route-id="@review.Id">@review.PackageName.Substring(0, @truncationIndex)</a>
@*
Disabling approval (first release / Package name approval) for now as it is of no benefit to users
@if (review.IsApproved == true)
{
<i class="fas fa-check-circle text-success ml-2"></i>
}
*@
Disabling approval (first release / Package name approval) for now as it is of no benefit to users
@if (review.IsApproved == true)
{
<i class="fas fa-check-circle text-success ml-2"></i>
}
*@
</td>
<td class="align-middle cst-bdr-left ps-3">
<a username="@review.CreatedBy">@review.CreatedBy</a>

Просмотреть файл

@ -1,58 +0,0 @@
@using APIViewWeb.Helpers
@using APIViewWeb.LeanModels
@using APIViewWeb.Models
@model (IEnumerable<APIRevisionListItemModel> revisions, APIRevisionListItemModel activeRevision, APIRevisionListItemModel diffRevision, bool forDiff, bool showDocumentation, bool showDiffOnly)
@if (@Model.revisions.Any())
{
var selectedRevisionLabel = String.Empty;
if (!@Model.forDiff && @Model.activeRevision != default(APIRevisionListItemModel))
{
selectedRevisionLabel = PageModelHelpers.ResolveRevisionLabel(@Model.activeRevision, false);
}
else if (@Model.diffRevision != default(APIRevisionListItemModel))
{
selectedRevisionLabel = PageModelHelpers.ResolveRevisionLabel(@Model.diffRevision, false);
}
if (String.IsNullOrEmpty(selectedRevisionLabel))
{
<option selected value=""></option>
}
@foreach (var revision in @Model.revisions)
{
var optionClass = (revision.IsApproved) ? "option-approved" : "option-pending";
var urlValue = @Url.ActionLink("Review", "Assemblies", new
{
id = revision.ReviewId,
revisionId = @revision.Id,
doc = @Model.showDocumentation
});
if (Model.forDiff)
{
urlValue = @Url.ActionLink("Review", "Assemblies", new
{
id = revision.ReviewId,
revisionId = @Model.activeRevision.Id,
diffOnly = Model.showDiffOnly,
doc = @Model.showDocumentation,
diffRevisionId = @revision.Id
});
}
var revisionLabel = PageModelHelpers.ResolveRevisionLabel(@revision, false);
if (!String.IsNullOrEmpty(selectedRevisionLabel) && revisionLabel == selectedRevisionLabel)
{
<option selected value="@urlValue" class="@optionClass">@revisionLabel</option>
}
else
{
<option value="@urlValue" class="@optionClass">@revisionLabel</option>
}
}
}

Просмотреть файл

@ -1,13 +1,13 @@
<div id="js-comment-form-template" class="d-none">
<div id="js-comment-form-template" class="d-none">
<div class="comment-form border-top new-thread-comment">
<form data-post-update="comments" class="new-thread-comment-form comment" method="post" asp-controller="Comments" asp-action="Add">
<div class="new-comment-content">
<textarea class="new-thread-comment-text form-control" name="commentText" rows="3" placeholder="Click Add Comment or press Ctrl+Enter to add your comment."></textarea>
</div>
<input name="usageSampleComment" value="true" hidden type="text" />
<button type="submit" name="submit" value="Submit" class="comment-submit-button btn btn-outline-dark">Add Comment</button>
<button type="button" name="cancel" value="Cancel" class="comment-cancel-button btn btn-outline-dark">Cancel</button>
<input type="checkbox" name="resolutionLock" checked class="custom-checkbox btn-outline-dark" />
<button type="submit" name="submit" value="Submit" class="comment-submit-button btn btn-outline-secondary">Add Comment</button>
<button type="button" name="cancel" value="Cancel" class="comment-cancel-button btn btn-outline-secondary">Cancel</button>
<input type="checkbox" name="resolutionLock" checked class="custom-checkbox btn-outline-secondary" />
<label for="resolutionLock" class="text-muted small"> Allow anyone to resolve conversation </label>
</form>
</div>
@ -20,8 +20,8 @@
</div>
<input type="hidden" class="js-comment-id" name="commentId" />
<input name="usageSampleComment" value="@true" hidden type="text" />
<button type="submit" name="submit" value="Submit" class="comment-submit-button btn btn-outline-dark">Save</button>
<button type="button" name="cancel" value="Cancel" class="comment-cancel-button btn btn-outline-dark">Cancel</button>
<button type="submit" name="submit" value="Submit" class="comment-submit-button btn btn-outline-secondary">Save</button>
<button type="button" name="cancel" value="Cancel" class="comment-cancel-button btn btn-outline-secondary">Cancel</button>
</form>
</div>
</div>

Просмотреть файл

@ -0,0 +1,84 @@
@using APIViewWeb.Helpers
@using APIViewWeb.LeanModels;
@using APIViewWeb.Pages.Assemblies
@model (ReviewListItemModel review, IEnumerable<SamplesRevisionModel> samplesRevisions, SamplesRevisionModel activeSamplesRevision)
<div class="row g-4">
<div class="col mx-3">
<form>
<div class="d-grid gap-2">
@if (Model.activeSamplesRevision != null)
{
<button type="button" class="btn btn-primary" data-bs-toggle="offcanvas" data-bs-target="#upload-samples-context" aria-controls="upload-samples-context">Add Samples Revision</button>
}
else
{
<div class="btn-group invisible" role="group" aria-label="Samples Revisions Select"> @*Added for asthetics*@
<input type="checkbox" class="btn-check" id="manual-apirevisions-check" autocomplete="off">
<label class="btn btn-outline-primary" for="manual-apirevisions-check">Manual</label>
<input type="checkbox" class="btn-check" id="automatic-apirevisions-check" autocomplete="off">
<label class="btn btn-outline-primary" for="automatic-apirevisions-check">Automatic</label>
<input type="checkbox" class="btn-check" id="pullrequest-apirevisions-check" autocomplete="off">
<label class="btn btn-outline-primary" for="pullrequest-apirevisions-check">Pull Request</label>
</div>
}
@if (Model.samplesRevisions.Any())
{
<div class="input-group mb-3">
<span class="input-group-text"><i class="fa-solid fa-magnifying-glass"></i></span>
<input type="search" placeholder="Search.." class="form-control" id="samplesRevisions-search" aria-label="samplesrevision search">
</div>
}
</div>
</form>
<div class="samples-revisions revisions-list-container p-2">
@foreach (var samplesRevision in Model.samplesRevisions)
{
<div class="card my-2" data-id="@samplesRevision.Id">
<img username="@samplesRevision.CreatedBy" size="105" aria-label="GitHub User Avatar" />
<div class="card-body">
<h6 class="card-title">@samplesRevision.Title</h6>
@if (User.GetGitHubLogin() == samplesRevision.CreatedBy)
{
<div class="input-group input-group-sm mb-1 edit-revision-label d-none">
<input type="text" class="form-control" value="@samplesRevision.Title" aria-label="Edit Samples Revision Label">
<button class="input-group-text enter-rename"><i class="bi bi-check"></i></button>
<button class="input-group-text cancel-rename"><i class="bi bi-x"></i></button>
</div>
}
<p class="card-subtitle mb-1 text-body-secondary"><b>Created: </b><span date="@samplesRevision.CreatedOn"></span> , <b>By: </b>@samplesRevision.CreatedBy</p>
</div>
<div class="revision-actions">
<div class="btn-group animate__animated animate__fadeIn" role="group" aria-label="Samples Revision action buttons">
@if (Model.activeSamplesRevision != null)
{
@if (samplesRevision.Id != Model.activeSamplesRevision.Id)
{
<button type="button" class="btn btn-sm btn-outline-primary make-active" data-bs-toggle="tooltip" title="Make Active"><i class="bi bi-puzzle"></i></button>
}
}
@if (User.GetGitHubLogin() == samplesRevision.CreatedBy)
{
<button type="button" class="btn btn-sm btn-outline-primary rename" data-bs-toggle="tooltip" title="Rename"><i class="bi bi-pencil-square"></i></button>
<button type="button" class="btn btn-sm btn-outline-primary delete" data-bs-toggle="tooltip" title="Delete"><i class="bi bi-x-circle text-danger"></i></button>
}
</div>
</div>
<div class="revision-indicator-checks animate__animated animate__slideInLeft">
@if (Model.activeSamplesRevision != null)
{
@if (samplesRevision.Id == Model.activeSamplesRevision.Id)
{
<i class="bi bi-puzzle active-rev mr-1"></i>
}
}
</div>
</div>
}
</div>
</div>
</div>
<partial name="Shared/_AddSamplesRevisionsPartial" model="@Model.review" />

Просмотреть файл

@ -27,17 +27,12 @@ using APIView.Identity;
using APIViewWeb.Managers;
using APIViewWeb.Hubs;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using APIViewWeb.LeanControllers;
using APIViewWeb.MiddleWare;
using Microsoft.OpenApi.Models;
using System.IO;
using Microsoft.Azure.Cosmos;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
using System.Collections.Generic;
using APIViewWeb.Helpers;
using APIViewWeb.Managers.Interfaces;
using WebMarkupMin.AspNetCore7;