diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index df6aea54e26..6619e354abc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -24,7 +24,7 @@ # Eng Sys ########### /eng/ @azure/azure-sdk-eng -/eng/common/ @sima-zhu @weshaggard @benbp +/eng/common/ @konrad-jamrozik @weshaggard @benbp /eng/common/TestResources/ @benbp @weshaggard @heaths ########### diff --git a/doc/common/matrix_generator.md b/doc/common/matrix_generator.md index 5e33c34f386..59b9d8c210a 100644 --- a/doc/common/matrix_generator.md +++ b/doc/common/matrix_generator.md @@ -671,7 +671,7 @@ Given a matrix like below with `JavaTestVersion` marked as a non-sparse paramete { "matrix": { "Agent": { - "windows-2022": { "OSVmImage": "MMS2022", "Pool": "azsdk-pool-mms-win-2022-general" }, + "windows-2022": { "OSVmImage": "windows-2022", "Pool": "azsdk-pool-mms-win-2022-general" }, "ubuntu-2204": { "OSVmImage": "MMSUbuntu22.04", "Pool": "azsdk-pool-mms-ubuntu-2204-general" }, "macos-11": { "OSVmImage": "macos-11", "Pool": "Azure Pipelines" } }, diff --git a/eng/common-tests/matrix-generator/samples/matrix.json b/eng/common-tests/matrix-generator/samples/matrix.json index 4104051fb26..c7bf8225d44 100644 --- a/eng/common-tests/matrix-generator/samples/matrix.json +++ b/eng/common-tests/matrix-generator/samples/matrix.json @@ -21,7 +21,7 @@ ], "exclude": [ { - "OSVmImage": "MMS2022", + "OSVmImage": "windows-2022", "framework": "netcoreapp2.1" } ] diff --git a/eng/common/pipelines/templates/jobs/perf.yml b/eng/common/pipelines/templates/jobs/perf.yml index ff2d5316255..d1204f284ce 100644 --- a/eng/common/pipelines/templates/jobs/perf.yml +++ b/eng/common/pipelines/templates/jobs/perf.yml @@ -2,18 +2,21 @@ parameters: - name: JobName type: string default: 'Perf' +- name: TimeoutInMinutes + type: number + default: '360' - name: LinuxPool type: string - default: 'azsdk-pool-mms-ubuntu-2004-perf' + default: 'azsdk-pool-mms-ubuntu-2204-perf' - name: LinuxVmImage type: string - default: 'MMSUbuntu20.04' + default: 'ubuntu-22.04' - name: WindowsPool type: string - default: 'azsdk-pool-mms-win-2019-perf' + default: 'azsdk-pool-mms-win-2022-perf' - name: WindowsVmImage type: string - default: 'MMS2019' + default: 'windows-2022' - name: Language type: string default: '' @@ -65,7 +68,7 @@ parameters: jobs: - job: ${{ parameters.JobName }} - timeoutInMinutes: 360 + timeoutInMinutes: ${{ parameters.TimeoutInMinutes }} strategy: matrix: ${{ if contains(parameters.OperatingSystems, 'Linux') }}: @@ -109,7 +112,7 @@ jobs: - template: /eng/common/pipelines/templates/steps/verify-agent-os.yml parameters: AgentImage: $(OSVmImage) - + - ${{ parameters.InstallLanguageSteps }} - template: /eng/common/TestResources/deploy-test-resources.yml diff --git a/eng/common/pipelines/templates/stages/archetype-sdk-tool-pwsh.yml b/eng/common/pipelines/templates/stages/archetype-sdk-tool-pwsh.yml index 0b46240cc7e..e01694adf03 100644 --- a/eng/common/pipelines/templates/stages/archetype-sdk-tool-pwsh.yml +++ b/eng/common/pipelines/templates/stages/archetype-sdk-tool-pwsh.yml @@ -22,7 +22,7 @@ stages: matrix: Windows: Pool: 'azsdk-pool-mms-win-2022-general' - Image: 'MMS2022' + Image: 'windows-2022' Linux: Pool: azsdk-pool-mms-ubuntu-2204-general Image: MMSUbuntu22.04 diff --git a/eng/common/pipelines/templates/steps/check-spelling.yml b/eng/common/pipelines/templates/steps/check-spelling.yml index a25fd944411..0c489f08935 100644 --- a/eng/common/pipelines/templates/steps/check-spelling.yml +++ b/eng/common/pipelines/templates/steps/check-spelling.yml @@ -18,8 +18,8 @@ steps: - task: NodeTool@0 condition: and(succeededOrFailed(), ne(variables['Skip.SpellCheck'],'true')) inputs: - versionSpec: 18.x - displayName: Use Node.js 18.x + versionSpec: 18.13.0 + displayName: Use Node.js 18.13.0 - task: PowerShell@2 displayName: Check spelling (cspell) diff --git a/eng/common/scripts/Cadl-Project-Sync.ps1 b/eng/common/scripts/Cadl-Project-Sync.ps1 index 79527b5b11c..c6e8cf5ce53 100644 --- a/eng/common/scripts/Cadl-Project-Sync.ps1 +++ b/eng/common/scripts/Cadl-Project-Sync.ps1 @@ -44,10 +44,10 @@ function GetGitRemoteValue([string]$repo) { $gitRemotes = (git remote -v) foreach ($remote in $gitRemotes) { if ($remote.StartsWith("origin")) { - if ($remote -match 'https://github.com/\S+[\.git]') { + if ($remote -match 'https://github.com/\S+') { $result = "https://github.com/$repo.git" break - } elseif ($remote -match "git@github.com:\S+[\.git]"){ + } elseif ($remote -match "git@github.com:\S+"){ $result = "git@github.com:$repo.git" break } else { diff --git a/eng/common/scripts/copy-docs-to-blobstorage.ps1 b/eng/common/scripts/copy-docs-to-blobstorage.ps1 index f037dcf51e4..135d3f2cc8a 100644 --- a/eng/common/scripts/copy-docs-to-blobstorage.ps1 +++ b/eng/common/scripts/copy-docs-to-blobstorage.ps1 @@ -1,5 +1,6 @@ # Note, due to how `Expand-Archive` is leveraged in this script, # powershell core is a requirement for successful execution. +[CmdletBinding()] param ( $AzCopy, $DocLocation, @@ -199,9 +200,9 @@ function Upload-Blobs LogDebug "Final Dest $($DocDest)/$($PkgName)/$($DocVersion)" LogDebug "Release Tag $($ReleaseTag)" - # Use the step to replace default branch link to release tag link + # Use the step to replace default branch link to release tag link if ($ReleaseTag) { - foreach ($htmlFile in (Get-ChildItem $DocDir -include *.html -r)) + foreach ($htmlFile in (Get-ChildItem $DocDir -include *.html -r)) { $fileContent = Get-Content -Path $htmlFile -Raw $updatedFileContent = $fileContent -replace $RepoReplaceRegex, "`${1}$ReleaseTag" @@ -209,22 +210,22 @@ function Upload-Blobs Set-Content -Path $htmlFile -Value $updatedFileContent -NoNewLine } } - } + } else { LogWarning "Not able to do the default branch link replacement, since no release tag found for the release. Please manually check." - } - + } + LogDebug "Uploading $($PkgName)/$($DocVersion) to $($DocDest)..." & $($AzCopy) cp "$($DocDir)/**" "$($DocDest)/$($PkgName)/$($DocVersion)$($SASKey)" --recursive=true --cache-control "max-age=300, must-revalidate" - + LogDebug "Handling versioning files under $($DocDest)/$($PkgName)/versioning/" $versionsObj = (Update-Existing-Versions -PkgName $PkgName -PkgVersion $DocVersion -DocDest $DocDest) - $latestVersion = $versionsObj.LatestGAPackage + $latestVersion = $versionsObj.LatestGAPackage if (!$latestVersion) { - $latestVersion = $versionsObj.LatestPreviewPackage + $latestVersion = $versionsObj.LatestPreviewPackage } LogDebug "Fetching the latest version $latestVersion" - + if ($UploadLatest -and ($latestVersion -eq $DocVersion)) { LogDebug "Uploading $($PkgName) to latest folder in $($DocDest)..." @@ -243,3 +244,9 @@ else See https://github.com/Azure/azure-sdk-tools/blob/main/doc/common/common_engsys.md#code-structure" } +# If we hit a failure then dump out the azcopy logs to help with debugging +if ($LASTEXITCODE) +{ + Write-Host "Copying failed with error code [$LASTEXITCODE]. Dumping the logs to help diagnose." + Get-ChildItem $env:UserProfile\.azcopy -Filter *.log | ForEach-Object { "LOG: " + $_; Get-Content $_; } +} \ No newline at end of file diff --git a/eng/common/scripts/get-codeowners.ps1 b/eng/common/scripts/get-codeowners.ps1 index 87271a6230d..a4a18ecea42 100644 --- a/eng/common/scripts/get-codeowners.ps1 +++ b/eng/common/scripts/get-codeowners.ps1 @@ -1,25 +1,86 @@ +<# +.SYNOPSIS +A script that given as input $TargetPath param, returns the owners +of that path, as determined by CODEOWNERS file passed in $CodeOwnersFileLocation +param. + +.PARAMETER TargetPath +Required*. Path to file or directory whose owners are to be determined from a +CODEOWNERS file. e.g. sdk/core/azure-amqp/ or sdk/core/foo.txt. + +*for backward compatibility, you might provide $TargetDirectory instead. + +.PARAMETER TargetDirectory +Obsolete. Replaced by $TargetPath. Kept for backward-compatibility. +If both $TargetPath and $TargetDirectory are provided, $TargetDirectory is +ignored. + +.PARAMETER CodeOwnerFileLocation +Optional. An absolute path to the CODEOWNERS file against which the $TargetPath param +will be checked to determine its owners. + +.PARAMETER ToolVersion +Optional. The NuGet package version of the package containing the "retrieve-codeowners" +tool, around which this script is a wrapper. + +.PARAMETER ToolPath +Optional. The place to check the "retrieve-codeowners" tool existence. + +.PARAMETER DevOpsFeed +Optional. The NuGet package feed from which the "retrieve-codeowners" tool is to be installed. + +NuGet feed: +https://dev.azure.com/azure-sdk/public/_artifacts/feed/azure-sdk-for-net/NuGet/Azure.Sdk.Tools.RetrieveCodeOwners + +Pipeline publishing the NuGet package to the feed, "tools - code-owners-parser": +https://dev.azure.com/azure-sdk/internal/_build?definitionId=3188 + +.PARAMETER VsoVariable +Optional. If provided, the determined owners, based on $TargetPath matched against CODEOWNERS file at $CodeOwnerFileLocation, +will be output to Azure DevOps pipeline log as variable named $VsoVariable. + +Reference: +https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch +https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#logging-command-format + +.PARAMETER IncludeNonUserAliases +Optional. Whether to include in the returned owners list aliases that are team aliases, e.g. Azure/azure-sdk-team + +.PARAMETER Test +Optional. Whether to run the script against hard-coded tests. + +#> param ( - [string]$TargetDirectory = "", # Code path to code owners. e.g sdk/core/azure-amqp - [string]$CodeOwnerFileLocation = (Resolve-Path $PSScriptRoot/../../../.github/CODEOWNERS), # The absolute path of CODEOWNERS file. - [string]$ToolVersion = "1.0.0-dev.20230108.6", - [string]$ToolPath = (Join-Path ([System.IO.Path]::GetTempPath()) "codeowners-tool-path"), # The place to check the tool existence. Put temp path as default - [string]$DevOpsFeed = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json", # DevOp tool feeds. - [string]$VsoVariable = "", # Option of write code owners into devop variable - [switch]$IncludeNonUserAliases, # Option to filter out the team alias in code owner list. e.g. Azure/azure-sdk-team - [switch]$Test #Run test functions against the script logic + [string]$TargetPath = "", + [string]$TargetDirectory = "", + # The path used assumes the script is located in azure-sdk-tools/eng/common/scripts/get-codeowners.ps1 + [string]$CodeOwnerFileLocation = (Resolve-Path $PSScriptRoot/../../../.github/CODEOWNERS), + # The $ToolVersion 1.0.0-dev.20230214.3 includes following PR: + # Use CodeownersFile.UseRegexMatcherDefault everywhere where applicable + remove obsolete tests + # https://github.com/Azure/azure-sdk-tools/pull/5437 + # + # but not this one: + # Remove the obsolete, prefix-based CODEOWNERS matcher & related tests + # https://github.com/Azure/azure-sdk-tools/pull/5431 + [string]$ToolVersion = "1.0.0-dev.20230223.4", + [string]$ToolPath = (Join-Path ([System.IO.Path]::GetTempPath()) "codeowners-tool-path"), + [string]$DevOpsFeed = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json", + [string]$VsoVariable = "", + [switch]$IncludeNonUserAliases, + [switch]$Test ) -function Get-CodeOwnersTool() +function Get-CodeownersTool() { - $command = Join-Path $ToolPath "retrieve-codeowners" - # Check if the retrieve-codeowners tool exsits or not. - if (Get-Command $command -errorAction SilentlyContinue) { - return $command + $codeownersToolCommand = Join-Path $ToolPath "retrieve-codeowners" + # Check if the retrieve-codeowners tool exists or not. + if (Get-Command $codeownersToolCommand -errorAction SilentlyContinue) { + return $codeownersToolCommand } if (!(Test-Path $ToolPath)) { New-Item -ItemType Directory -Path $ToolPath | Out-Null } - Write-Host "Installing the retrieve-codeowners tool under $ToolPath... " + Write-Host "Installing the retrieve-codeowners tool under tool path: $ToolPath ..." # Run command under tool path to avoid dotnet tool install command checking .csproj files. # This is a bug for dotnet tool command. Issue: https://github.com/dotnet/sdk/issues/9623 @@ -27,46 +88,64 @@ function Get-CodeOwnersTool() dotnet tool install --tool-path $ToolPath --add-source $DevOpsFeed --version $ToolVersion "Azure.Sdk.Tools.RetrieveCodeOwners" | Out-Null Pop-Location # Test to see if the tool properly installed. - if (!(Get-Command $command -errorAction SilentlyContinue)) { - Write-Error "The retrieve-codeowners tool is not properly installed. Please check your tool path. $ToolPath" + if (!(Get-Command $codeownersToolCommand -errorAction SilentlyContinue)) { + Write-Error "The retrieve-codeowners tool is not properly installed. Please check your tool path: $ToolPath" return } - return $command + return $codeownersToolCommand } -function Get-CodeOwners ([string]$targetDirectory, [string]$codeOwnerFileLocation, [bool]$includeNonUserAliases = $false) +function Get-Codeowners( + [string]$targetPath, + [string]$targetDirectory, + [string]$codeownersFileLocation, + [bool]$includeNonUserAliases = $false) { - $command = Get-CodeOwnersTool - # Filter out the non user alias from code owner list. - if($includeNonUserAliases) { - $codeOwnersString = & $command --target-directory $targetDirectory --code-owner-file-path $codeOwnerFileLocation 2>&1 - } - else { - $codeOwnersString = & $command --target-directory $targetDirectory --code-owner-file-path $codeOwnerFileLocation --filter-out-non-user-aliases 2>&1 + # Backward compaitiblity: if $targetPath is not provided, fall-back to the legacy $targetDirectory + if ([string]::IsNullOrWhiteSpace($targetPath)) { + $targetPath = $targetDirectory } - # Failed at the command of fetching code owners. - if ($LASTEXITCODE -ne 0) { - Write-Host $codeOwnersString + if ([string]::IsNullOrWhiteSpace($targetPath)) { + Write-Error "TargetPath (or TargetDirectory) parameter must be neither null nor whitespace." return ,@() } - - $codeOwnersJson = $codeOwnersString | ConvertFrom-Json - if (!$codeOwnersJson) { - Write-Host "No code owners returned from the path: $targetDirectory" + + $codeownersToolCommand = Get-CodeownersTool + Write-Host "Executing: & $codeownersToolCommand --target-path $targetPath --codeowners-file-path-or-url $codeownersFileLocation --exclude-non-user-aliases:$(!$includeNonUserAliases)" + $commandOutput = & $codeownersToolCommand ` + --target-path $targetPath ` + --codeowners-file-path-or-url $codeownersFileLocation ` + --exclude-non-user-aliases:$(!$includeNonUserAliases) ` + 2>&1 + + if ($LASTEXITCODE -ne 0) { + Write-Host "Command $codeownersToolCommand execution failed (exit code = $LASTEXITCODE). Output string: $commandOutput" return ,@() + } else + { + Write-Host "Command $codeownersToolCommand executed successfully (exit code = 0). Output string length: $($commandOutput.length)" } + +# Assert: $commandOutput is a valid JSON representing: +# - a single CodeownersEntry, if the $targetPath was a single path +# - or a dictionary of CodeownerEntries, keyes by each path resolved from a $targetPath glob path. +# +# For implementation details, see Azure.Sdk.Tools.RetrieveCodeOwners.Program.Main + +$codeownersJson = $commandOutput | ConvertFrom-Json if ($VsoVariable) { - $codeOwners = $codeOwnersJson.Owners -join "," - Write-Host "##vso[task.setvariable variable=$VsoVariable;]$codeOwners" + $codeowners = $codeownersJson.Owners -join "," + Write-Host "##vso[task.setvariable variable=$VsoVariable;]$codeowners" } - return ,@($codeOwnersJson.Owners) + return ,@($codeownersJson.Owners) } -function TestGetCodeOwner([string]$targetDirectory, [string]$codeOwnerFileLocation, [bool]$includeNonUserAliases = $false, [string[]]$expectReturn) { - Write-Host "Testing on $targetDirectory..." - $actualReturn = Get-CodeOwners -targetDirectory $targetDirectory -codeOwnerFileLocation $codeOwnerFileLocation -includeNonUserAliases $IncludeNonUserAliases +function TestGetCodeowners([string]$targetPath, [string]$codeownersFileLocation, [bool]$includeNonUserAliases = $false, [string[]]$expectReturn) { + Write-Host "Test: find owners matching '$targetPath' ..." + + $actualReturn = Get-Codeowners -targetPath $targetPath -codeownersFileLocation $codeownersFileLocation -includeNonUserAliases $IncludeNonUserAliases if ($actualReturn.Count -ne $expectReturn.Count) { Write-Error "The length of actual result is not as expected. Expected length: $($expectReturn.Count), Actual length: $($actualReturn.Count)." @@ -80,16 +159,19 @@ function TestGetCodeOwner([string]$targetDirectory, [string]$codeOwnerFileLocati } } -if($Test) { - $testFile = (Resolve-Path $PSScriptRoot/../../../tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/simple_path_CODEOWNERS) - TestGetCodeOwner -targetDirectory "sdk" -codeOwnerFileLocation $testFile -includeNonUserAliases $true -expectReturn @("person1", "person2") - TestGetCodeOwner -targetDirectory "sdk/noPath" -codeOwnerFileLocation $testFile -includeNonUserAliases $true -expectReturn @("person1", "person2") - TestGetCodeOwner -targetDirectory "/sdk/azconfig" -codeOwnerFileLocation $testFile -includeNonUserAliases $true -expectReturn @("person3", "person4") - TestGetCodeOwner -targetDirectory "/sdk/azconfig/package" -codeOwnerFileLocation $testFile -includeNonUserAliases $true $testFile -expectReturn @("person3", "person4") - TestGetCodeOwner -targetDirectory "/sd" -codeOwnerFileLocation $testFile -includeNonUserAliases $true -expectReturn @() - TestGetCodeOwner -targetDirectory "/sdk/testUser/" -codeOwnerFileLocation $testFile -expectReturn @("azure-sdk") +if ($Test) { + # Most of tests here have been removed; now instead we should run tests from RetrieveCodeOwnersProgramTests, and in a way as explained in: + # https://github.com/Azure/azure-sdk-tools/issues/5434 + # https://github.com/Azure/azure-sdk-tools/pull/5103#discussion_r1068680818 + Write-Host "Running reduced test suite at `$PSScriptRoot of $PSSCriptRoot. Please see https://github.com/Azure/azure-sdk-tools/issues/5434 for more." + + $azSdkToolsCodeowners = (Resolve-Path "$PSScriptRoot/../../../.github/CODEOWNERS") + TestGetCodeowners -targetPath "eng/common/scripts/get-codeowners.ps1" -codeownersFileLocation $azSdkToolsCodeowners -includeNonUserAliases $true -expectReturn @("konrad-jamrozik", "weshaggard", "benbp") + + $testCodeowners = (Resolve-Path "$PSScriptRoot/../../../tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/test_CODEOWNERS") + TestGetCodeowners -targetPath "tools/code-owners-parser/Azure.Sdk.Tools.RetrieveCodeOwners.Tests/TestData/InputDir/a.txt" -codeownersFileLocation $testCodeowners -includeNonUserAliases $true -expectReturn @("2star") exit 0 } else { - return Get-CodeOwners -targetDirectory $TargetDirectory -codeOwnerFileLocation $CodeOwnerFileLocation -includeNonUserAliases $IncludeNonUserAliases + return Get-Codeowners -targetPath $TargetPath -targetDirectory $TargetDirectory -codeownersFileLocation $CodeOwnerFileLocation -includeNonUserAliases $IncludeNonUserAliases } diff --git a/eng/pipelines/agent-pool-migration.yml b/eng/pipelines/agent-pool-migration.yml index c04646f6739..68efebbfc96 100644 --- a/eng/pipelines/agent-pool-migration.yml +++ b/eng/pipelines/agent-pool-migration.yml @@ -78,14 +78,14 @@ parameters: - MigrateFrom: windows2019 MigrateTo: windows2022 - MigrateFrom: MMS2019 - MigrateTo: MMS2022 + MigrateTo: windows-2022 - name: ShortForMigrateFrom type: string default: '' - name: ShortForMigrateTo type: string default: '' -# The value here is github alias: e.g. sima-zhu +# The value here is github alias: e.g. konrad-jamrozik # Users to assign to the PR after opening. Users should be a comma-separated list. e.g. "user1,usertwo,user3" - name: GithubAssignessAlias type: string diff --git a/eng/pipelines/apiview-review-gen-swagger.yml b/eng/pipelines/apiview-review-gen-swagger.yml index 4c3b02587f3..0ea9c01bc5d 100644 --- a/eng/pipelines/apiview-review-gen-swagger.yml +++ b/eng/pipelines/apiview-review-gen-swagger.yml @@ -4,7 +4,7 @@ trigger: none pool: name: azsdk-pool-mms-win-2022-general - vmImage: MMS2022 + vmImage: windows-2022 variables: SwaggerParserInstallPath: $(Pipeline.Workspace)/SwaggerApiParser @@ -27,7 +27,7 @@ jobs: - script: > dotnet tool install Azure.Sdk.Tools.SwaggerApiParser - --version 1.0.5-dev.20230131.3 + --version 1.0.7-dev.20230211.1 --add-source https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json --tool-path $(SwaggerParserInstallPath) workingDirectory: $(SwaggerParserInstallPath) @@ -39,4 +39,4 @@ jobs: APIViewURL: $(APIViewURL) StorageContainerUrl: $(StorageContainerUrl) ApiviewGenScript: './Create-Apiview-Token-Swagger.ps1' - ParserPath: $(SwaggerParserInstallPath)/SwaggerApiParser \ No newline at end of file + ParserPath: $(SwaggerParserInstallPath)/SwaggerApiParser diff --git a/eng/pipelines/mirror-repos.yml b/eng/pipelines/mirror-repos.yml index ef8069337a0..45998ca606e 100644 --- a/eng/pipelines/mirror-repos.yml +++ b/eng/pipelines/mirror-repos.yml @@ -68,12 +68,6 @@ jobs: Azure/azure-sdk-for-c-pr: azure-sdk/azure-sdk-for-c: - Azure/azure-cli-extensions: - TargetRepos: - Azure/azure-cli-extensions-pr: - azure-sdk/azure-cli-extensions: - azure-sdk/azure-cli-extensions-pr: - Azure/azure-libraries-for-java: TargetRepos: Azure/azure-libraries-for-java-pr: @@ -86,10 +80,6 @@ jobs: TargetRepos: azure-sdk/azure-docs-sdk-java: - Azure/azure-powershell: - TargetRepo: - azure-sdk/azure-powershell: - Azure/azure-resource-manager-schemas: TargetRepos: azure-sdk/azure-resource-manager-schemas: diff --git a/eng/pipelines/templates/jobs/azuresdkpartnerdrops-to-nugetfeed.yml b/eng/pipelines/templates/jobs/azuresdkpartnerdrops-to-nugetfeed.yml index 0735c4f7816..7dc5a8b6852 100644 --- a/eng/pipelines/templates/jobs/azuresdkpartnerdrops-to-nugetfeed.yml +++ b/eng/pipelines/templates/jobs/azuresdkpartnerdrops-to-nugetfeed.yml @@ -4,7 +4,7 @@ resources: - repository: azure-sdk-build-tools type: git name: internal/azure-sdk-build-tools - ref: refs/tags/azure-sdk-build-tools_20230206.1 + ref: refs/tags/azure-sdk-build-tools_20230221.3 parameters: - name: BuildToolsRepoPath @@ -39,7 +39,7 @@ jobs: - job: AzurePartnerDropsToNuget pool: name: azsdk-pool-mms-win-2022-general - vmImage: MMS2022 + vmImage: windows-2022 steps: - checkout: self diff --git a/eng/pipelines/templates/stages/archetype-sdk-tool-dotnet.yml b/eng/pipelines/templates/stages/archetype-sdk-tool-dotnet.yml index 2b0ec52dc91..e863c2d674f 100644 --- a/eng/pipelines/templates/stages/archetype-sdk-tool-dotnet.yml +++ b/eng/pipelines/templates/stages/archetype-sdk-tool-dotnet.yml @@ -3,7 +3,7 @@ resources: - repository: azure-sdk-build-tools type: git name: internal/azure-sdk-build-tools - ref: refs/tags/azure-sdk-build-tools_20230206.1 + ref: refs/tags/azure-sdk-build-tools_20230221.3 parameters: - name: ToolDirectory @@ -15,9 +15,6 @@ parameters: - name: TestDirectory type: string default: '' - - name: DotNetCoreVersion - type: string - default: '' - name: NoWarn type: boolean default: false @@ -65,9 +62,7 @@ stages: steps: - template: /eng/pipelines/templates/steps/install-dotnet.yml - parameters: - DotNetCoreVersion: ${{ parameters.DotNetCoreVersion }} - + - script: 'dotnet pack /p:ArtifactsPackagesDir=$(packagesToPublishDir) $(Warn) -c Release' displayName: 'Build and Package' workingDirectory: '${{ coalesce(parameters.PackageDirectory, parameters.ToolDirectory) }}' @@ -115,8 +110,6 @@ stages: steps: - template: /eng/pipelines/templates/steps/install-dotnet.yml - parameters: - DotNetCoreVersion: ${{ parameters.DotNetCoreVersion }} - template: /eng/pipelines/templates/steps/produce-net-standalone-packs.yml parameters: @@ -130,7 +123,7 @@ stages: matrix: Windows: Pool: azsdk-pool-mms-win-2022-general - Image: MMS2022 + Image: windows-2022 Linux: Pool: azsdk-pool-mms-ubuntu-2204-general Image: MMSUbuntu22.04 @@ -144,8 +137,6 @@ stages: steps: - template: /eng/pipelines/templates/steps/install-dotnet.yml - parameters: - DotNetCoreVersion: ${{ parameters.DotNetCoreVersion }} - script: 'dotnet test /p:ArtifactsPackagesDir=$(Build.ArtifactStagingDirectory) $(Warn) --logger trx' displayName: 'Test' @@ -161,7 +152,7 @@ stages: condition: succeededOrFailed() inputs: testResultsFiles: '**/*.trx' - testRunTitle: $(System.JobDisplayName) ${{ parameters.DotNetCoreVersion }} + testRunTitle: $(System.JobDisplayName) testResultsFormat: 'VSTest' mergeTestResults: true diff --git a/eng/pipelines/templates/steps/install-dotnet.yml b/eng/pipelines/templates/steps/install-dotnet.yml index 8c9af6a3c8e..9e2c9d2769e 100644 --- a/eng/pipelines/templates/steps/install-dotnet.yml +++ b/eng/pipelines/templates/steps/install-dotnet.yml @@ -1,29 +1,32 @@ -parameters: - # Use this parameter if you want to override the .NET SDK set by global.json - - name: DotNetCoreVersion - type: string - default: '' - steps: - # We set DOTNET_ROLL_FORWARD so that .NET assemblies targeting older versions of .NET can run on newer ones. - # See also: - # https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet#options-for-running-an-application - # https://learn.microsoft.com/en-us/dotnet/core/runtime-discovery/troubleshoot-app-launch?pivots=os-windows#required-framework-not-found - # https://aka.ms/dotnet/app-launch-failed - # https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=powershell - # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&tabs=powershell - - pwsh: | - echo "##vso[task.setvariable variable=DOTNET_ROLL_FORWARD;]Major" - displayName: "Set DOTNET_ROLL_FORWARD to Major" - # https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/use-dotnet-v2?view=azure-pipelines + + # We aim to keep .NET SDK coming from global.json to bethe newest, to enable us to use the latest & greatest, + # and allow us to gradually migrate our .NET sources to such version. + # About global.json: https://learn.microsoft.com/en-us/dotnet/core/tools/global-json + - task: UseDotNet@2 # About UseDotNet@2 task: https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/use-dotnet-v2?view=azure-pipelines + displayName: "Use .NET SDK from global.json" + retryCountOnTaskFailure: 3 + inputs: + useGlobalJson: true + + # We install .NET 6.0.x SDK in addition to .NET coming from global.json because most of our tools target 6.0.x. + # Once we migrate all tools to a newer .NET version, we should update this to install that version instead. - task: UseDotNet@2 - displayName: "Use .NET SDK ${{ coalesce( parameters.DotNetCoreVersion, 'from global.json') }}" + displayName: "Use .NET SDK 6.0.x" retryCountOnTaskFailure: 3 inputs: - ${{ if eq( parameters.DotNetCoreVersion, '') }}: - useGlobalJson: true - ${{ else }}: - version: ${{ parameters.DotNetCoreVersion }} + # We must install sdk, not just runtime, as it is required by some of our tools, like test-proxy. + # Specifically, test-proxy requires asp.net core runtime, which is installed only when sdk option + # is selected, per: https://github.com/microsoft/azure-pipelines-tasks/issues/14405 + # + # For additional context, see: + # https://github.com/Azure/azure-sdk-tools/pull/5405#discussion_r1105006774 + # https://github.com/Azure/azure-sdk-tools/pull/5405 + packageType: sdk + version: 6.0.x + # performMultiLevelLookup comes into play when given .NET executable target runtime is different + # than the installed .NET SDK. Without this, such runtime would not be found. + performMultiLevelLookup: true # Future work: add NuGet packages caching. See: -# https://github.com/Azure/azure-sdk-tools/issues/5086 \ No newline at end of file +# https://github.com/Azure/azure-sdk-tools/issues/5086 diff --git a/eng/pipelines/templates/variables/globals.yml b/eng/pipelines/templates/variables/globals.yml index 91adfb03423..808bbdb3278 100644 --- a/eng/pipelines/templates/variables/globals.yml +++ b/eng/pipelines/templates/variables/globals.yml @@ -1,5 +1,5 @@ variables: OfficialBuildId: $(Build.BuildNumber) skipComponentGovernanceDetection: true - NotificationsCreatorVersion: '1.0.0-dev.20230127.4' - PipelineOwnersExtractorVersion: '1.0.0-dev.20230105.1' + NotificationsCreatorVersion: '1.0.0-dev.20230223.2' + PipelineOwnersExtractorVersion: '1.0.0-dev.20230211.1' diff --git a/src/dotnet/APIView/APIViewIntegrationTests/APIViewIntegrationTests.csproj b/src/dotnet/APIView/APIViewIntegrationTests/APIViewIntegrationTests.csproj index 3d1a332b3e4..3e763401d35 100644 --- a/src/dotnet/APIView/APIViewIntegrationTests/APIViewIntegrationTests.csproj +++ b/src/dotnet/APIView/APIViewIntegrationTests/APIViewIntegrationTests.csproj @@ -19,7 +19,6 @@ - all diff --git a/src/dotnet/APIView/APIViewIntegrationTests/TestsBaseFixture.cs b/src/dotnet/APIView/APIViewIntegrationTests/TestsBaseFixture.cs index 39f75cff0ad..49ad8d37c9c 100644 --- a/src/dotnet/APIView/APIViewIntegrationTests/TestsBaseFixture.cs +++ b/src/dotnet/APIView/APIViewIntegrationTests/TestsBaseFixture.cs @@ -11,12 +11,8 @@ using Microsoft.AspNetCore.Authorization; using Moq; using System.IO; -using System.Net; using System.Threading.Tasks; using System.Security.Claims; -using SendGrid; -using SendGrid.Helpers.Mail; -using System.Threading; using Azure.Storage.Blobs.Models; using APIView.Identity; using APIViewWeb.Managers; @@ -83,11 +79,7 @@ public TestsBaseFixture() authorizationServiceMoq.Setup(_ => _.AuthorizeAsync(It.IsAny(), It.IsAny(), It.IsAny>())) .ReturnsAsync(AuthorizationResult.Success); - var sendGridClientMock = new Mock(); - sendGridClientMock.Setup(_ => _.SendEmailAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new Response(HttpStatusCode.OK, null, null)); - - var notificationManager = new NotificationManager(config, cosmosReviewRepository, cosmosUserProfileRepository, sendGridClientMock.Object); + var notificationManager = new NotificationManager(config, cosmosReviewRepository, cosmosUserProfileRepository); var devopsArtifactRepositoryMoq = new Mock(); devopsArtifactRepositoryMoq.Setup(_ => _.DownloadPackageArtifact(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) diff --git a/src/dotnet/APIView/APIViewUITests/SmokeTests.cs b/src/dotnet/APIView/APIViewUITests/SmokeTests.cs index 5009e923ffb..1d026428680 100644 --- a/src/dotnet/APIView/APIViewUITests/SmokeTests.cs +++ b/src/dotnet/APIView/APIViewUITests/SmokeTests.cs @@ -100,9 +100,9 @@ public async Task SmokeTest_CSharp() driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(WaitTime); driver.Navigate().GoToUrl(_fixture._endpoint); - var createReviewBtn = driver.FindElement(By.XPath("//button[@data-target='#uploadModel']")); + var createReviewBtn = driver.FindElement(By.XPath("//button[@data-bs-target='#uploadModel']")); createReviewBtn.Click(); - var fileSelector = driver.FindElement(By.Id("Upload_Files")); + var fileSelector = driver.FindElement(By.Id("uploadReviewFile")); fileSelector.SendKeys(fileAPath); var uploadBtn = driver.FindElement(By.XPath("//div[@class='modal-footer']/button[@type='submit']")); uploadBtn.Click(); @@ -120,7 +120,7 @@ public async Task SmokeTest_CSharp() Assert.Equal("Reviews - apiview.dev", driver.Title); // Select C# language - var languageSelector = driver.FindElement(By.Id("language-filter-bootstraps-select")); + var languageSelector = driver.FindElement(By.Id("language-filter-select")); var languageSelectElement = new SelectElement(languageSelector); languageSelectElement.SelectByText("C#"); var reviewNames = driver.FindElements(By.ClassName("review-name")); diff --git a/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj b/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj index 98bf7a89b6e..67b108ea9e7 100644 --- a/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj +++ b/src/dotnet/APIView/APIViewUnitTests/APIViewUnitTests.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -18,7 +18,7 @@ - + diff --git a/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj b/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj index c848698ed42..9653c108b6d 100644 --- a/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj +++ b/src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj @@ -37,10 +37,10 @@ NU1701 - + - + all @@ -50,8 +50,6 @@ - - diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/go.scss b/src/dotnet/APIView/APIViewWeb/Client/css/go.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/java.scss b/src/dotnet/APIView/APIViewWeb/Client/css/java.scss deleted file mode 100644 index 8c8cb50a877..00000000000 --- a/src/dotnet/APIView/APIViewWeb/Client/css/java.scss +++ /dev/null @@ -1,47 +0,0 @@ -.icon-assembly { - background: url(/icons/java/common/assembly.svg) center left no-repeat !important; -} - -.icon-gradle { - background: url(/icons/java/common/gradle.svg) center left no-repeat !important; -} - -.icon-maven { - background: url(/icons/java/common/maven.svg) center left no-repeat !important; -} - -.icon-namespace { - background: url(/icons/java/common/namespace.svg) center left no-repeat !important; -} - -.icon-spring { - background: url(/icons/java/common/spring.svg) center left no-repeat !important; -} - -.icon-unknown { - background: url(/icons/java/common/unknown.svg) center left no-repeat !important; -} - -.icon-android { - background: url(/icons/java/common/android.svg) center left no-repeat !important; -} - -.icon-annotation { - background: url(/icons/java/java/annotation.svg) center left no-repeat !important; -} - -.icon-class { - background: url(/icons/java/java/class.svg) center left no-repeat !important; -} - -.icon-enum { - background: url(/icons/java/java/enum.svg) center left no-repeat !important; -} - -.icon-interface { - background: url(/icons/java/java/interface.svg) center left no-repeat !important; -} - -.icon-module { - background: url(/icons/java/common/module.svg) center left no-repeat !important; -} diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/javascript.scss b/src/dotnet/APIView/APIViewWeb/Client/css/javascript.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/json.scss b/src/dotnet/APIView/APIViewWeb/Client/css/json.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/kotlin.scss b/src/dotnet/APIView/APIViewWeb/Client/css/kotlin.scss deleted file mode 100644 index 7fb601db96c..00000000000 --- a/src/dotnet/APIView/APIViewWeb/Client/css/kotlin.scss +++ /dev/null @@ -1,64 +0,0 @@ -.icon-assembly { - background: url(/icons/java/common/assembly.svg) center left no-repeat !important; -} - -.icon-gradle { - background: url(/icons/java/common/gradle.svg) center left no-repeat !important; -} - -.icon-maven { - background: url(/icons/java/common/maven.svg) center left no-repeat !important; -} - -.icon-namespace { - background: url(/icons/java/common/namespace.svg) center left no-repeat !important; -} - -.icon-spring { - background: url(/icons/java/common/spring.svg) center left no-repeat !important; -} - -.icon-unknown { - background: url(/icons/java/common/unknown.svg) center left no-repeat !important; -} - -.icon-android { - background: url(/icons/java/common/android.svg) center left no-repeat !important; -} - -.icon-annotation { - background: url(/icons/java/kotlin/annotation.svg) center left no-repeat !important; -} - -.icon-class { - background: url(/icons/java/kotlin/class.svg) center left no-repeat !important; -} - -.icon-enum { - background: url(/icons/java/kotlin/enum.svg) center left no-repeat !important; -} - -.icon-interface { - background: url(/icons/java/kotlin/interface.svg) center left no-repeat !important; -} - -.icon-function { - background: url(/icons/java/kotlin/function.svg) center left no-repeat !important; -} - -.icon-kotlinLanguage { - background: url(/icons/java/kotlin/kotlinLanguage.svg) center left no-repeat !important; -} - -.icon-object { - background: url(/icons/java/kotlin/object.svg) center left no-repeat !important; -} - -.icon-property { - background: url(/icons/java/kotlin/property.svg) center left no-repeat !important; -} - -.icon-module { - background: url(/icons/java/common/module.svg) center left no-repeat !important; -} - diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/main.scss b/src/dotnet/APIView/APIViewWeb/Client/css/main.scss new file mode 100644 index 00000000000..0ec77affb81 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/main.scss @@ -0,0 +1,19 @@ +@import "./shared/layout.scss"; +@import "./shared/theme-colors.scss"; +@import "./shared/mixins.scss"; +@import "./shared/sumo-select.scss"; +@import "./shared/bootstraps-overrides.scss"; +@import "./shared/off-canvas.scss"; +@import "./shared/icons.scss"; +@import "./shared/codeline-navigation.scss"; +@import "./shared/codeline.scss"; +@import "./shared/comments.scss"; +@import "./pages/conversation.scss"; +@import "./pages/delete.scss"; +@import "./pages/index.scss"; +@import "./pages/legacy-review.scss"; +@import "./pages/profile.scss"; +@import "./pages/requested-reviews.scss"; +@import "./pages/review.scss"; +@import "./pages/revisions.scss"; +@import "./pages/samples.scss"; diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/pages/conversation.scss b/src/dotnet/APIView/APIViewWeb/Client/css/pages/conversation.scss new file mode 100644 index 00000000000..e96279e4874 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/pages/conversation.scss @@ -0,0 +1,13 @@ +@import "../shared/mixins.scss"; + +#conversation-main-container { + @include fixed-page-heights; + color: var(--base-text-color); + padding-right: 0px; + padding-left: 0px; +} + +.conversiation-center { + overflow: auto; + color: var(--base-text-color); +} diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/c.scss b/src/dotnet/APIView/APIViewWeb/Client/css/pages/delete.scss similarity index 100% rename from src/dotnet/APIView/APIViewWeb/Client/css/c.scss rename to src/dotnet/APIView/APIViewWeb/Client/css/pages/delete.scss diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/pages/index.scss b/src/dotnet/APIView/APIViewWeb/Client/css/pages/index.scss new file mode 100644 index 00000000000..61ec68d9efb --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/pages/index.scss @@ -0,0 +1,64 @@ +@import "../shared/mixins.scss"; + +#create-review-button { + @include bottom-right-floating; + @include btn-circle; + @include btn-circle-xl; +} + +#reviews-table { + td { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 1px solid var(--border-color); + } + + thead th { + border-bottom: 2px solid var(--border-color); + } + + tr:hover { + color: var(--base-text-color); + } + + .cst-bdr-left { + border-left: 1px solid var(--border-color); + } + + color: var(--base-text-color); +} + +#reviews-table-container { + border: 1px solid var(--border-color); + box-shadow: var(--box-shadow-sm); + background-color: var(--base-fg-color); +} + +#reviews-table-search-container { + width: 90%; +} + +#reviews-table-search-box { + border: 1px solid var(--primary-color); +} + +#index-main-container { + @include main-content-container; +} + +#index-offcanvas-menu-content { + padding: 130px 60px 10px 20px; + min-height: calc(100vh - 40px); + max-height: calc(100vh - 40px); + overflow-y: auto; +} + +#index-offcanvas-menu-content > .input-group { + flex-wrap: unset; +} + +.nav-cst-theme { + background-color: var(--base-bg-color); + box-shadow: var(--box-shadow-sm); +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/cplusplus.scss b/src/dotnet/APIView/APIViewWeb/Client/css/pages/legacy-review.scss similarity index 100% rename from src/dotnet/APIView/APIViewWeb/Client/css/cplusplus.scss rename to src/dotnet/APIView/APIViewWeb/Client/css/pages/legacy-review.scss diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/csharp.scss b/src/dotnet/APIView/APIViewWeb/Client/css/pages/profile.scss similarity index 100% rename from src/dotnet/APIView/APIViewWeb/Client/css/csharp.scss rename to src/dotnet/APIView/APIViewWeb/Client/css/pages/profile.scss diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/pages/requested-reviews.scss b/src/dotnet/APIView/APIViewWeb/Client/css/pages/requested-reviews.scss new file mode 100644 index 00000000000..46800d16adb --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/pages/requested-reviews.scss @@ -0,0 +1,2 @@ +body { +} diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/pages/review.scss b/src/dotnet/APIView/APIViewWeb/Client/css/pages/review.scss new file mode 100644 index 00000000000..af616f12ac6 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/pages/review.scss @@ -0,0 +1,88 @@ +@import "../shared/mixins.scss"; + +#review-info-bar > .SumoSelect { + width: 20%; +} + +#revision-select ~ .optWrapper { + width: auto; + min-width: 250px; +} + +#diff-select ~ .optWrapper { + width: auto; + min-width: 250px; +} + +#revision-select { + width: 20%; +} + +#diff-select { + width: 20%; +} + +#review-left { + max-width: none; + min-width: 10px; + height: calc(100vh - 220px); + overflow: auto; + max-height: 100vh; + padding: 5px 0px 5px 10px; + background-color: var(--base-fg-color); +} + +#review-main-container { + @include main-content-container; +} + +#review-right { + max-width: 100%; + min-width: 100px; + height: calc(100vh - 220px); + padding: 0px; + overflow: auto; + background-color: var(--base-fg-color); + scroll-behavior: smooth; + 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); + border-radius: 3px; +} + +.show-diffonly-switch, .show-documentation-switch { + color: var(--base-text-color) !important; +} + +.gutter { + &:hover { + background-color: var(--base-fg-color); + } + + &.gutter-horizontal { + cursor: col-resize; + } +} + +.option-approved > label, .option-pending > label { + font-size: small; +} + +.option-approved > label::after { + content: ""; + font-family: "FontAwesome"; + margin-left: 10px; + color: var(--success-color); + font-size: medium; +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/pages/revisions.scss b/src/dotnet/APIView/APIViewWeb/Client/css/pages/revisions.scss new file mode 100644 index 00000000000..29d2be50704 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/pages/revisions.scss @@ -0,0 +1,13 @@ +@import "../shared/mixins.scss"; + +#add-revision-button { + @include bottom-right-floating; + @include btn-circle; + @include btn-circle-xl; +} + +#revisions-main-container { + @include fixed-page-heights; + padding-right: 0px; + padding-left: 0px; +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/pages/samples.scss b/src/dotnet/APIView/APIViewWeb/Client/css/pages/samples.scss new file mode 100644 index 00000000000..9e04249426d --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/pages/samples.scss @@ -0,0 +1,36 @@ +@import "../shared/mixins.scss"; + +#add-sample-button { + @include bottom-right-floating; + @include btn-circle; + @include btn-circle-xl; +} + +#samples-main-container { + @include fixed-page-heights; + padding-right: 0px; + padding-left: 0px; +} + +.samples-center { + overflow: auto; +} + +.usage-sample + .border + .rounded { + margin-top: 15px !important; +} + +.usage-sample .code-line { + padding: 0px; + margin: 0px; + font-size: 85%; + line-height: 10px; +} + +.usage-sample .line-number { + font-size: 125%; +} + +.usage-sample .internal { + display: inline-block; +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/python.scss b/src/dotnet/APIView/APIViewWeb/Client/css/python.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/bootstraps-overrides.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/bootstraps-overrides.scss new file mode 100644 index 00000000000..eab3efe78dc --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/bootstraps-overrides.scss @@ -0,0 +1,227 @@ +.text-success { + color: var(--success-color) !important; +} + +.btn-primary { + background-color: var(--link-color); + border-color: var(--link-color); + color: var(--primary-btn-color); +} + +.btn-primary:hover, .btn-primary:active { + background-color: var(--link-active); + border-color: var(--link-active); + color: var(--primary-btn-color); +} + +.btn-primary:disabled, .btn-primary.disabled { + background-color: var(--link-color); + border-color: var(--link-color); + color: var(--primary-btn-color); +} + +.btn-check:focus + .btn-primary, .btn-primary:focus { + background-color: var(--link-active); + border-color: var(--link-active); + color: var(--primary-btn-color); +} + +.btn-outline-primary { + color: var(--link-color); + border-color: var(--link-color); +} + +.btn-outline-primary:hover, .btn-outline-primary:active { + background-color: var(--link-active); + border-color: var(--link-active); + color: var(--primary-btn-color); +} + +.btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: var(--link-color); +} + +.btn-check:checked + .btn-outline-primary:focus, .btn-check:active + .btn-outline-primary:focus, .btn-outline-primary:active:focus, .btn-outline-primary.active:focus, .btn-outline-primary.dropdown-toggle.show:focus { + box-shadow: 0 0 0 0.25rem var(--outer-glow); +} + + +.btn-check:checked + .btn-outline-primary, .btn-check:active + .btn-outline-primary, .btn-outline-primary:active, .btn-outline-primary.active, .btn-outline-primary.dropdown-toggle.show { + background-color: var(--link-active); + border-color: var(--link-active); + color: var(--primary-btn-color); +} + +.btn-check:focus + .btn-outline-primary, .btn-outline-primary:focus { + box-shadow: var(--box-shadow-link); +} + +.page-link { + background-color: var(--base-fg-color); + border: 1px solid var(--border-color); + color: var(--link-color); +} + +.page-link:hover { + background-color: var(--base-bg-color); + border: 1px solid var(--border-color); + color: var(--link-color); +} + +.page-item.active .page-link { + background-color: var(--link-color); + border: 1px solid var(--border-color); +} + +.page-item.disabled .page-link { + background-color: var(--base-fg-color); + border: 1px solid var(--border-color); + color: var(--link-color); +} + +.border { + border: 1px solid var(--border-color) !important; +} + +.border-top { + border-top: 1px solid var(--border-color) !important; +} + +.border-bottom { + border-bottom: 1px solid var(--border-color) !important; +} + +.list-group-item { + color: var(--base-text-color); + background-color: var(--base-fg-color); + border: 1px solid var(--border-color); + word-wrap: break-word; +} + +.form-control { + color: var(--base-text-color); + background-color: var(--base-fg-color); + border: 1px solid var(--border-color); +} + +.form-control::before { + color: var(--base-text-color); + background-color: var(--base-bg-color); + border-color: var(--border-color); +} + +.form-control:focus { + color: var(--base-text-color); + background-color: var(--base-fg-color); + border-color: var(--border-color); + box-shadow: 0 0 0 0.25rem var(--outer-glow); +} + +.form-control:disabled, .form-control[readonly] { + background-color: var(--base-fg-color); +} + +.form-check-input { + background-color: var(--base-bg-color); + border: 1px solid var(--border-color); +} + +.form-check-input:checked { + background-color: var(--link-color); + border-color: var(--link-color); +} + +.form-check-input:focus { + border-color: var(--link-color); + box-shadow: var(--box-shadow-link); +} + +.form-switch .form-check-input { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='grey'/%3e%3c/svg%3e"); +} + +.form-switch .form-check-input:focus { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='grey'/%3e%3c/svg%3e"); +} + +.form-check-input:checked[type=checkbox] { + background-image: var(--svg-check-box); +} + +.form-switch .form-check-input:checked { + background-image: var(--svg-switch); +} + +.badge { + color: var(--base-text-color); + background-color: var(--base-fg-color); + border-color: var(--border-color); + border-radius: 0.25rem; +} + +.input-group-text { + color: var(--base-text-color); + background-color: var(--base-fg-color); + border-color: var(--border-color); +} + +select { + color: var(--base-text-color); + background-color: var(--base-fg-color); + border-color: var(--border-color); +} + +.dropdown-menu { + color: var(--base-text-color); + background-color: var(--base-fg-color); + border: 1px solid var(--border-color); +} + +.dropdown-header { + color: var(--base-text-color); +} + +.dropdown-item:hover, .dropdown-item:focus { + color: var(--base-text-color); + background-color: var(--base-bg-color); +} + +.table { + color: var(--base-text-color); +} + +.nav-link { + padding: 0.2rem 1rem; +} + +.modal-content { + color: var(--base-text-color); + background-color: var(--base-bg-color); + border: 1px solid var(--border-color); +} + +.modal-header { + border-bottom: 1px solid var(--border-color); +} + +.modal-footer { + border-top: 1px solid var(--border-color); +} + +.card { + color: var(--base-text-color); + background-color: var(--base-bg-color); + border: 1px solid var(--border-color); +} + +.btn-close { + background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='grey'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat; +} + +.text-muted { + color: var(--text-muted-color) !important; +} + +pre { + color: var(--base-text-color); +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/codeline-navigation.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/codeline-navigation.scss new file mode 100644 index 00000000000..45ed5d2bc5a --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/codeline-navigation.scss @@ -0,0 +1,44 @@ +.namespace-view { + font-size: 14px; + font-family: monospace; + padding-right: 10px +} + +.nav-list { + list-style-type: none; + padding-left: 0px; +} + +.nav-list-children { + list-style-type: none; + text-overflow: ellipsis; + padding-left: 0; + margin-bottom: 0 !important; // prevents flickering in stickySidebar +} + +.nav-list-children > li { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.nav-list-group .nav-list-children { + padding-left: 14px; +} + +.nav-list-toggle { + padding-left: 14px; + background: url(/icons/down.png) center left no-repeat; +} + +.nav-list-collapsed > .nav-list-toggle { + background: url(/icons/right.png) center left no-repeat; +} + +.nav-list-collapsed > .nav-list-children { + display: none; +} + +.navbar-expand-sm .navbar-nav .dropdown-menu { + z-index: 1030; +} diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/codeline.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/codeline.scss new file mode 100644 index 00000000000..b2fe4a20fbc --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/codeline.scss @@ -0,0 +1,314 @@ +.code-window { + font-size: 14px; + line-height: 1.5; + border-collapse: separate; + tab-size: 8; + border-spacing: 0; + background-color: var(--base-fg-color); // prevents code-window from overlapping stickySidebar + width: 200%; + transform: translate3d(0, 0, 0); /*Force use of GPU to improve scrolling*/ +} + +.code-lines-header { + border-bottom: 1px solid #DEE2E6; + vertical-align: middle; + padding-top: 9px; + padding-left: 10px; + font-size: small; + font-weight: normal; +} + +.code-line { + display: table-row; + font-family: Consolas, monospace; + line-height: 20px; +} + +.code-line:hover .line-details-button-cell .line-comment-button { + opacity: 1; +} + +.code-line .table .line-comment-button { + position: absolute; + left: -21px; +} + +.code-line .table tr:hover .line-comment-button { + opacity: 1; +} + +.code-line.active, .code-line tr.active { + animation: glow normal 1.5s ease-in-out; +} + +.code { + padding: 0px; + position: relative; + vertical-align: top; +} + +.code-inner { + height: 100%; + word-wrap: normal; + white-space: pre; + width: 80% +} + +.code-inner > svg { + float: left; +} + +.code-inner > svg > text { + fill: var(--text-muted-color); +} + +.code-inner > svg > line { + stroke: var(--text-muted-color); +} + +.code-inner > table { + display: inline-table; + position: relative; + width: 50%; + table-layout: auto; + white-space: normal; +} + +.code-inner td, .code-inner th, .code-inner thead, .code-inner tbody, .code-inner table, .code-inner tr { + border: 1px solid var(--border-color); +} + +.code-inner > .table thead th { + border-bottom: 1px solid var(--border-color); +} + +.code-inner .table > :not(:last-child) > :last-child > * { + border-bottom-color: var(--border-color); +} + +.deprecated { + text-decoration: line-through; +} + +.hidden-api { + color: var(--text-muted-color) !important; + font-style: italic; +} + +.hidden-row { + display: none; +} + +.hidden-row-via-filter { + display: none; +} + +.file-code-icon { + filter: var(--icon-color-filter); +} + +.line-number { + padding-left: 10px; + padding-right: 12px; + text-align: right; + white-space: nowrap; + width: auto; +} + +.line-comment-button:hover { + transform: scale(1); + text-decoration: none; +} + +.line-comment-button { + display: inline-block; + width: 21px; + height: 21px; + line-height: 21px; + color: #fff !important; + text-align: center; + text-indent: 0; + cursor: pointer; + background-color: #0366d6; + background-image: linear-gradient(#0372ef, #0366d6); + border-radius: 3px; + box-shadow: 0 1px 4px rgba(27, 31, 35, .15); + opacity: 0; + transition: transform .1s ease-in-out; + transform: scale(.8); + user-select: none; + text-decoration: none; +} + +.btn-link.line-comment-button { + border: 0; + opacity: 0; + padding: 0; + text-decoration: none; + user-select: none; + white-space: nowrap; +} + +.line-toggle-documentation-button { + display: block; + width: 100%; + height: 100%; + text-align: center; + cursor: pointer; + user-select: none; + position: relative; +} + +.line-toggle-documentation-button svg { + top: 0px; + left: 0px; +} + +.line-toggle-documentation-button i { + position: absolute; + top: 25%; + left: 25%; +} + +.line-details-button-cell { + width: 22px; + height: 22px; + min-width: 22px; + min-height: 22px; + padding: 0px; +} + +.line-details-button-cell svg > line { + stroke: var(--base-text-color); +} + +.line-details { + border-right: 1px solid var(--border-color); + background-color: var(--base-fg-color); + color: var(--text-muted-color); + z-index: 1; + position: sticky; + left: 0; + cursor: pointer; + width: 1%; + white-space: nowrap; + padding: 0px; +} + +.line-details.code-added { + background-color: var(--code-added); + color: var(--text-color); +} + +.line-details.code-removed { + background-color: var(--code-removed); + color: var(--text-color); +} + +.line-details.code-delta { + background-color: var(--code-delta); + color: var(--text-color); +} + +.line-details table { + width: 100%; +} + +.line-details td { + vertical-align: middle; +} + +.row-fold-caret i { + position: relative; + left: 6px; +} + +.row-fold-elipsis { + margin-left: 8px; +} + +.row-fold-elipsis i { + position: relative; + top: 2px; +} + + +/* Code rendering classes +----------------------------------------------------------------------*/ + +.class { + color: var(--class-color) !important; +} + +code { + color: var(--code-color) !important; +} + +.code-comment { + color: var(--code-comment) !important; +} + +.code-added { + background-color: rgba(46,160,67,0.3); +} + +.code-removed { + background-color: rgba(248,81,73,0.3); +} + +.code-delta { + background-color: rgba(248,201,67,0.3); +} + +.enum { + color: #8700BD !important; +} + +.keyword { + color: var(--keyword-color) !important; +} + +.name, a.name { + color: var(--name-color) !important; +} + +.specialName { + color: #A400EB !important; + font-weight: bold; +} + +.type { + color: #B93D3D !important; +} + +.value { + color: #008509 !important; +} + +.code-diagnostics-error { + background-color: var(--code-diagnostics-error-color); + border: 1px solid red; +} + +.code-diagnostics-warn { + background-color: var(--code-diagnostics-warn-color); + border: 1px solid #ffe066; +} + +.code-diagnostics-info { + background-color: var(--code-diagnostics-info-color); + border: 1px solid blue; +} + +.code-diagnostics p { + margin: 0; +} + +.code-diagnostics div { + position: sticky; + left: 0px; + padding-left: 10px; + max-width: 2000px; +} + +/*---------------------------------------------------------------------*/ diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/comments.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/comments.scss new file mode 100644 index 00000000000..16ab2cfcdc5 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/comments.scss @@ -0,0 +1,220 @@ +.btn-upvote span { + filter: grayscale(100%) opacity(30%); + + &.active { + filter: unset; + } +} + +.comment-thread-anchor { + visibility: hidden; +} + +.comment-thread-contents.new-thread-comment { + background-color: #FFF; + border: 0; + margin: -8px -16px; +} + +.comment-row { + border: 1px solid var(--border-color); + border-spacing: 0; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: var(--base-fg-color); +} + +.comment-cancel-button { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 14px; +} + +.comment-cell { + padding: 10px; + vertical-align: top; +} + +.comment-contents { + margin-bottom: 3px; + overflow-wrap: anywhere; + display: inline-grid; +} + +.comment-contents .fw-bold { + color: var(--base-text-color); +} + +.comment-header { + margin-bottom: 0px; + line-height: 0.8 !important; + display: inline; +} + +.comment-holder { + max-width: 780px; + position: sticky; + left: 10px; + z-index: 100; // for user suggestion box, position sticky creates new stacking order, so z-index has to be set here too +} + +.comment-icon { + border-radius: 3px; + display: inline-block; + line-height: 1; + overflow: hidden; + vertical-align: middle; +} + +.comment-submit-button { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 14px; + margin: 0 4px 0; +} + +.comments-resolved { + display: none; +} + +.new-comment-content { + position: relative; + margin: 0 4px 8px; +} + +.new-thread-comment { + border-radius: 3px; + line-height: 1.5; + border-collapse: separate; + tab-size: 8; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: var(--base-bg-color); +} + +.new-thread-comment-text { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 14px; + background-color: var(--base-fg-color); + max-width: 758px; +} + +.review-comment { + pre { + background-color: var(--base-bg-color); + } + + padding: 4px 16px; + position: relative; +} + +.review-comment:first-child { + padding-top: 16px; +} + +.review-comment p { + margin-bottom: 0; +} + +.review-comment blockquote { + padding-left: 14px; + padding-right: 14px; + border-left-color: #E9E5E2; + border-left-style: solid; + border-left-width: 3.5px; + color: #6A737D; +} + +.review-thread-reply { + background-color: var(--base-bg-color); + border-radius: 0 0 3px 3px; + border-top: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-color); + padding: 8px 16px; + display: table; + width: 100%; +} + +.review-thread-reply-button { + cursor: text; + display: inline-block; + min-height: 28px; + padding: 3px 8px; + width: 100%; +} + +.reply-cell { + display: table-cell; +} + +.rounded-left-0 { + border-top-left-radius: 0px !important; + border-bottom-left-radius: 0px !important; +} + +.rounded-1 { + border-radius: 3px !important; +} + +form.comment { + padding: 12px; +} + +/* Tag suggestion box +----------------------------------------------------------------------*/ +.tag-user-suggestion { + min-width: 180px; + padding: 0; + margin: 0; + cursor: pointer; + background: var(--bg-light); + border: 1px solid var(--border-color); + border-radius: 6px; + box-shadow: var(--focus-shadow); + z-index: 100; + list-style-type: none; + box-sizing: border-box; +} + +.tag-user-suggestion-username { + color: var(--text-color); + text-decoration: none; + padding: 4px 8px; + border-bottom: 1px solid var(--border-color); + box-sizing: border-box; +} + +.tag-user-suggestion-username:first-child { + border-top-left-radius: 6px; + border-top-right-radius: 6px; +} + +.tag-user-suggestion-username:hover { + background-color: var(--focus-shadow); +} + +// shows what option is selected during arrow navigation +.tag-user-suggestion-username-targetted { + background-color: var(--focus-shadow); +} + +// mirror box +.new-thread-comment-text-mirror { + position: absolute; + top: 0; + left: -9999px; + overflow: auto; + overflow-wrap: break-word; + white-space: pre-wrap; + box-sizing: border-box; + font-family: "Segoe UI",Tahoma,Geneva,Verdana,sans-serif; + font-size: 14px; + font-weight: 400; + max-height: 77px; + letter-spacing: normal; + line-height: 21px; + padding: 0.75rem 0.375rem; + border-width: 1px; + border-radius: 4px; + border-style: solid; + border-color: white; + text-indent: 0px; + text-transform: none; + word-spacing: 0px; +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/icons.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/icons.scss new file mode 100644 index 00000000000..156b12e9a09 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/icons.scss @@ -0,0 +1,244 @@ +.icon-language { + min-height: 34px; + width: 34px; + display: inline-block; + vertical-align: middle; +} + +.java-variant { + &.icon-assembly { + background: url(/icons/java/common/assembly.svg) center left no-repeat !important; + } + + &.icon-gradle { + background: url(/icons/java/common/gradle.svg) center left no-repeat !important; + } + + &.icon-maven { + background: url(/icons/java/common/maven.svg) center left no-repeat !important; + } + + &.icon-namespace { + background: url(/icons/java/common/namespace.svg) center left no-repeat !important; + } + + &.icon-spring { + background: url(/icons/java/common/spring.svg) center left no-repeat !important; + } + + &.icon-unknown { + background: url(/icons/java/common/unknown.svg) center left no-repeat !important; + } + + &.icon-android { + background: url(/icons/java/common/android.svg) center left no-repeat !important; + } + + &.icon-annotation { + background: url(/icons/java/java/annotation.svg) center left no-repeat !important; + } + + &.icon-class { + background: url(/icons/java/java/class.svg) center left no-repeat !important; + } + + &.icon-enum { + background: url(/icons/java/java/enum.svg) center left no-repeat !important; + } + + &.icon-interface { + background: url(/icons/java/java/interface.svg) center left no-repeat !important; + } + + &.icon-module { + background: url(/icons/java/common/module.svg) center left no-repeat !important; + } +} + +.kotlin-variant { + &.icon-assembly { + background: url(/icons/java/common/assembly.svg) center left no-repeat !important; + } + + &.icon-gradle { + background: url(/icons/java/common/gradle.svg) center left no-repeat !important; + } + + &.icon-maven { + background: url(/icons/java/common/maven.svg) center left no-repeat !important; + } + + &.icon-namespace { + background: url(/icons/java/common/namespace.svg) center left no-repeat !important; + } + + &.icon-spring { + background: url(/icons/java/common/spring.svg) center left no-repeat !important; + } + + &.icon-unknown { + background: url(/icons/java/common/unknown.svg) center left no-repeat !important; + } + + &.icon-android { + background: url(/icons/java/common/android.svg) center left no-repeat !important; + } + + &.icon-annotation { + background: url(/icons/java/kotlin/annotation.svg) center left no-repeat !important; + } + + &.icon-class { + background: url(/icons/java/kotlin/class.svg) center left no-repeat !important; + } + + &.icon-enum { + background: url(/icons/java/kotlin/enum.svg) center left no-repeat !important; + } + + &.icon-interface { + background: url(/icons/java/kotlin/interface.svg) center left no-repeat !important; + } + + &.icon-function { + background: url(/icons/java/kotlin/function.svg) center left no-repeat !important; + } + + &.icon-kotlinLanguage { + background: url(/icons/java/kotlin/kotlinLanguage.svg) center left no-repeat !important; + } + + &.icon-object { + background: url(/icons/java/kotlin/object.svg) center left no-repeat !important; + } + + &.icon-property { + background: url(/icons/java/kotlin/property.svg) center left no-repeat !important; + } + + &.icon-module { + background: url(/icons/java/common/module.svg) center left no-repeat !important; + } +} + +.c-variant, .cplusplus-variant, .csharp-variant, .go-variant, .javascript-variant, .json-variant, .python-variant, .swagger-variant, .swift-variant, .xml-variant { + &.icon-class { + background: url(/icons/class.png) center left no-repeat; + } + + &.icon-interface { + background: url(/icons/interface.png) center left no-repeat; + } + + &.icon-struct { + background: url(/icons/struct.png) center left no-repeat; + } + + &.icon-enum { + background: url(/icons/enum.png) center left no-repeat; + } + + &.icon-delegate { + background: url(/icons/delegate.png) center left no-repeat; + } + + &.icon-unknown { + background: url(/icons/type.png) center left no-repeat; + } + + &.icon-assembly { + background: url(/icons/assembly.png) center left no-repeat; + } + + &.icon-namespace { + background: url(/icons/namespace.png) center left no-repeat; + } + + &.icon-package { + background: url(/icons/package.png) center left no-repeat; + } + + &.icon-method { + background: url(/icons/method.png) center left no-repeat; + } +} + +.icon-csharp { + @extend .icon-language; + background: url(/icons/csharp-original.svg) center center no-repeat; +} + +.icon-javascript { + @extend .icon-language; + background: url(/icons/javascript-original.svg) center center no-repeat; +} + +.icon-python { + @extend .icon-language; + background: url(/icons/python-original.svg) center center no-repeat; +} + +.icon-c { + @extend .icon-language; + background: url(/icons/c-original.svg) center center no-repeat; +} + +.icon-cplusplus { + @extend .icon-language; + background: url(/icons/cplusplus-original.svg) center center no-repeat; +} + +.icon-go { + @extend .icon-language; + background: url(/icons/go-original.svg) center center no-repeat; +} + +.icon-java { + @extend .icon-language; + background: url(/icons/java-original.svg) center center no-repeat; +} + +.icon-java-spring { + @extend .icon-language; + background: url(/icons/java-spring-original.svg) center center no-repeat; +} + +.icon-java-android { + @extend .icon-language; + background: url(/icons/java-android-original.svg) center center no-repeat; +} + +.icon-swift { + @extend .icon-language; + background: url(/icons/swift-original.svg) center center no-repeat; +} + +.icon-kotlin { + @extend .icon-language; + background: url(/icons/kotlin-original.svg) center center no-repeat; +} + +.icon-json { + @extend .icon-language; + background: url(/icons/json-original.svg) center center no-repeat; +} + +.icon-swagger { + @extend .icon-language; + background: url(/icons/swagger-original.svg) center center no-repeat; +} + +.icon-comments { + cursor: pointer; + position: absolute; + right: -17px; + top: 3px; +} + +.icon-chevron-right { + background: url(/icons/chevron-right.svg) center center no-repeat; +} + +.icon-chevron-up { + background: url(/icons/chevron-up.svg) center center no-repeat; +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/layout.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/layout.scss new file mode 100644 index 00000000000..e38d1e855ed --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/layout.scss @@ -0,0 +1,250 @@ +@import "../shared/mixins.scss"; + +#apiview-logo { + width: 30px; +} + +@include placeholder { + font-style: italic; +} + +@keyframes glow { + 0% { + background-color: rgba(0,0,0,0) + } + + 25% { + background-color: #fff6b3 + } + + 100% { + background-color: rgba(0,0,0,0) + } +} + +body { + background-color: var(--base-bg-color); + color: var(--base-text-color); +} + +p { + color: var(--base-text-color); +} + +a:link { + color: var(--link-color); + text-decoration: none; +} + +a:active { + color: var(--link-active); +} + +a:hover { + color: var(--link-active); + text-decoration: underline; +} + +a:visited { + color: var(--link-visited); +} + +button.accept-policy { + font-size: 1rem; + line-height: inherit; +} + +form.comment { + padding: 12px; +} + +html { + font-size: 14px; +} + +@media (min-width: 768px) { + html { + font-size: 16px; + } +} + +.footer { + position: fixed; + bottom: 0; + width: 100%; + white-space: nowrap; + height: 40px; + line-height: 40px; + z-index: 3; + background: var(--base-bg-color); +} + +.nav-pills .nav-link.active, .nav-pills .show > .nav-link { + background-color: var(--link-color); + color: var(--primary-btn-color); +} + +.nav-link:hover, .nav-link:focus { + text-decoration: none; + color: var(--link-active); +} + +.rounded-1 { + border-radius: 3px !important; +} + +.btn-xs { + height: 25px !important; + font-size: 15px; + vertical-align: middle; + padding: 0.01rem; +} + +.fixed-page-height { + height: 100vh; + overflow: hidden; +} + +.clickable { + cursor: pointer; +} + +.invisible { + visibility: hidden; +} + +.login-button { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 14px; +} + +/* Enable clear button in search input +------------------------------------------------------------------------------------------------------------------------------------------------------------*/ +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; + height: 24px; + width: 24px; + margin-left: .4em; + background-image: url("data:image/svg+xml;utf8,"); + cursor: pointer; +} + +/* Navbar Areas +------------------------------------------------------------------------------------------------------------------------------------------------------------*/ +.sub-header-content { + z-index: 2; + position: relative; + background-color: var(--base-bg-color); +} + +.main-nav-cst-theme { + background-color: var(--navbar-bg); + color: var(--navbar-text); + + a:hover { + text-decoration: none; + } + + a:link, a:visited { + color: var(--navbar-text); + } + + a:hover, a:active { + color: var(--navbar-link-active); + } + + border-bottom: 1px solid var(--border-color); + z-index: 2; +} + +/* PopOver +------------------------------------------------------------------------------------------------------------------------------------------------------------*/ +.popover { + border: 1px solid var(--border-color); +} + +.popover .popover-arrow::before, .popover .popover-arrow::after { + border-color: transparent !important; + border-top-color: var(--primary-color) !important; + border-bottom-color: var(--primary-color) !important; +} + +.popover-header { + background-color: var(--primary-color); + color: var(--primary-btn-color); + border-color: var(--primary-color); +} + +.popover-body { + background-color: var(--base-fg-color); + color: var(--base-text-color); + border-color: var(--border-color); +} + +/* Tooltips +------------------------------------------------------------------------------------------------------------------------------------------------------------*/ +.tooltip-inner { + background-color: var(--primary-color); + box-shadow: 0px 0px 4px var(--shadow-color); + color: var(--primary-btn-color); +} + +.tooltip.bs-tooltip-right .tooltip-arrow::before { + border-right-color: var(--primary-color) !important; +} + +.tooltip.bs-tooltip-left .tooltip-arrow::before { + border-left-color: var(--primary-color) !important; +} + +.tooltip.bs-tooltip-bottom .tooltip-arrow::before { + border-bottom-color: var(--primary-color) !important; +} + +.tooltip.bs-tooltip-top .tooltip-arrow::before { + border-top-color: var(--primary-color) !important; +} + +/* Scroll Bar +------------------------------------------------------------------------------------------------------------------------------------------------------------*/ +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar:horizontal { + height: 10px; +} + +::-webkit-scrollbar-thumb { + border: solid 3px var(--base-bg-color); + background-color: var(--text-muted-color); + border-radius: 6px; +} + +::-webkit-scrollbar-track-piece { + background: transparent; + background-color: var(--base-fg-color) +} + +::-webkit-scrollbar-corner { + background-color: var(--base-fg-color) +} + +/* File Select/Upload Input +------------------------------------------------------------------------------------------------------------------------------------------------------------*/ +.custom-file-label input[type=file] { + margin-left: -2px !important; +} + +.custom-file-label input[type=file]::-webkit-file-upload-button { + display: none; +} + +.custom-file-label input[type=file]::file-selector-button { + display: none; +} + +.custom-file-label:hover label { + background-color: var(--base-bg-color); + cursor: pointer; +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/mixins.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/mixins.scss new file mode 100644 index 00000000000..12503da503e --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/mixins.scss @@ -0,0 +1,51 @@ +@mixin bottom-right-floating { + position: fixed; + z-index: 10; + bottom: 10%; + right: 5%; +} + +@mixin btn-circle { + width: 45px; + height: 45px; + line-height: 45px; + text-align: center; + padding: 0; + border-radius: 50%; +} + +@mixin btn-circle-xl { + width: 100px; + height: 100px; + line-height: 1.2rem; + font-size: 1.0rem; + 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 placeholder { + ::-webkit-input-placeholder { + @content; + } + + ::-moz-placeholder { + @content; + } + + ::-moz-placeholder { + @content; + } + + ::-ms-input-placeholder { + @content; + } +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/off-canvas.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/off-canvas.scss new file mode 100644 index 00000000000..64012dc016e --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/off-canvas.scss @@ -0,0 +1,30 @@ +.offcanvas-menu { + height: 100vh; + width: 0; + position: fixed; + z-index: 1; + top: 0; + background-color: var(--base-bg-color); + color: var(--base-text-color); + overflow-x: hidden; + transition: 0.5s; + animation-timing-function: linear; +} + +.right-offcanvas { + @extend .offcanvas-menu; + right: 0; +} + +.offcanvas-menu-content { + border-left: 1px solid var(--border-color) !important; + transform: translate3d(0, 0, 0); +} + +.move-main-content-container-left { + margin-right: 340px; +} + +.show-offcanvas { + width: 340px; +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/sumo-select.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/sumo-select.scss new file mode 100644 index 00000000000..fb3eb349a27 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/sumo-select.scss @@ -0,0 +1,63 @@ +.SumoSelect .select-all { + height: 40px; + border-bottom: 1px solid var(--border-color); + background-color: var(--base-fg-color); +} + +.SumoSelect .select-all.partial > span i, .SumoSelect .select-all.selected > span i, .SumoSelect > .optWrapper.multiple > .options li.opt.selected span i { + background-color: var(--primary-color); + background-image: var(--svg-check-box); +} + +.SumoSelect > .CaptionCont { + border: 1px solid var(--border-color); + background-color: var(--base-fg-color); +} + + +.SumoSelect > .CaptionCont > label > i { + background-image: url(/icons/down.png); +} + +.SumoSelect.open > .CaptionCont, .SumoSelect:focus > .CaptionCont, .SumoSelect:hover > .CaptionCont { + box-shadow: 0 0 2px var(--outer-glow); + border-color: var(--outer-glow); +} + +.SumoSelect > .optWrapper { + background: var(--base-fg-color); + border: 1px solid var(--border-color); + box-shadow: 2px 3px 3px var(--shadow-color); +} + +.SumoSelect > .optWrapper > .MultiControls { + border-top: 1px solid var(--border-color); + background-color: var(--base-fg-color); + box-shadow: 0 0 2px var(--shadow-color); +} + +.SumoSelect > .optWrapper > .options li.opt { + border-bottom: 1px solid var(--border-color); +} + +.SumoSelect > .optWrapper > .options li.opt:hover { + background-color: var(--base-bg-color); +} + +.SumoSelect.open .search-txt { + color: var(--base-text-color); + background-color: var(--base-fg-color); +} + +.SumoSelect { + width: 100%; +} + +.SumoSelect > .optWrapper > .options { + max-height: 180px; +} + +.SumoSelect .select-all > span i, .SumoSelect > .optWrapper.multiple > .options li.opt span i { + border: 1px solid var(--border-color); + background-color: var(--base-bg-color); +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/shared/theme-colors.scss b/src/dotnet/APIView/APIViewWeb/Client/css/shared/theme-colors.scss new file mode 100644 index 00000000000..35fb2b6db06 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/css/shared/theme-colors.scss @@ -0,0 +1,163 @@ +@import "../../node_modules/bootstrap/scss/functions"; +@import "../../node_modules/bootstrap/scss/variables"; + +.light-theme { + /*----Base Colors-------------------------------------------------*/ + $base-bg-color: #fbfaff; + --base-bg-color: #{$base-bg-color}; + --base-fg-color: #{tint-color($base-bg-color, 100%)}; + $base-text-color: #212529; + --base-text-color: #{$base-text-color}; + --text-muted-color: #{tint-color($base-text-color, 30%)}; + --primary-color: #{$primary}; + --secondary-color: #{$secondary}; + --success-color: #{$success}; + --info-color: #{$info}; + --warning-color: #{$warning}; + --danger-color: #{$danger}; + --border-color: #{shade-color($base-bg-color, 20%)}; + --primary-btn-color: #fff; + /*----Links------------------------------------------------------*/ + --link-color: #{$primary}; + --link-active: #{shade-color($primary, 20%)}; + --link-visited: #{shade-color($primary, 40%)}; + /*----Shadows and Glows------------------------------------------*/ + --outer-glow: #{tint-color($primary, 80%)}; + --shadow-color: rgba(#{$black}, .15); + --box-shadow: 0 .5rem 1rem #{rgba($black, .15)}; + --box-shadow-sm: 0 .125rem .25rem #{rgba($black, .075)}; + --box-shadow-lg: 0 1rem 3rem #{rgba($black, .175)}; + --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%)}; + /*----Nav--------------------------------------------------------*/ + --navbar-bg: #{tint-color($base-bg-color, 100%)}; + --navbar-text: #{$base-text-color}; + --navbar-link-active: #{shade-color($primary, 20%)}; + --icon-color-filter: invert(0.0); + /*----Code Lines-------------------------------------------------*/ + --keyword-color: blue; + --class-color: #007e79; + --name-color: #{$base-text-color}; + --code-color: #d1377e; + --code-comment: green; + --code-diagnostics-error-color: lightpink; + --code-diagnostics-warn-color: lightyellow; + --code-diagnostics-info-color: lightblue; + --code-added: #cee9d3; + --code-removed: #fdcfce; + --code-delta: #fdefc6; + /*----SVG-------------------------------------------------*/ + --svg-check-box: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); + --svg-switch: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); +} + +.dark-theme { + /*----Base Colors-------------------------------------------------*/ + $base-bg-color: #010409; + --base-bg-color: #{$base-bg-color}; + --base-fg-color: #{tint-color($base-bg-color, 5%)}; + $base-text-color: #c9d1d9; + --base-text-color: #{$base-text-color}; + --text-muted-color: #{shade-color($base-text-color, 30%)}; + $primary-color: #0084ff; + --primary-color: #{$primary-color}; + $secondary-color: #6F8594; + --secondary-color: #{$secondary-color}; + --success-color: #{$success}; + $info-color: #05DBF2; + --info-color: #{$info-color}; + --warning-color: #{$orange}; + --danger-color: #{$danger}; + --border-color: #{tint-color($base-bg-color, 30%)}; + --primary-btn-color: #fff; + /*----Links------------------------------------------------------*/ + --link-color: #{$primary-color}; + --link-active: #{tint-color($primary-color, 20%)}; + --link-visited: #{tint-color($primary-color, 40%)}; + /*----Shadows and Glows------------------------------------------*/ + --outer-glow: #{tint-color($primary-color, 80%)}; + --shadow-color: rgba(#{$white}, .15); + --box-shadow: 0 .5rem 1rem #{rgba($white, .15)}; + --box-shadow-sm: 0 .125rem .25rem #{rgba($white, .075)}; + --box-shadow-lg: 0 1rem 3rem #{rgba($white, .175)}; + --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%)}; + /*----Nav--------------------------------------------------------*/ + --navbar-bg: #{tint-color($base-bg-color, 20%)}; + --navbar-text: #{tint-color($base-text-color, 40%)}; + --navbar-link-active: #{shade-color($base-text-color, 20%)}; + --icon-color-filter: invert(0.5); + /*----Code Lines-------------------------------------------------*/ + --keyword-color: #ff7b72; + --class-color: #d2a8ff; + --name-color: #{$base-text-color}; + --code-color: #db3984; + --code-comment: green; + --code-diagnostics-error-color: darkmagenta; + --code-diagnostics-warn-color: #846007; + --code-diagnostics-info-color: darkblue; + --code-added: #14432d; + --code-removed: #491d24; + --code-delta: #4c401a; + /*----SVG-------------------------------------------------*/ + --svg-check-box: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); + --svg-switch: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); +} + +.dark-solarized-theme { + /*----Base Colors-------------------------------------------------*/ + $base-bg-color: #2b2b2b; + --base-bg-color: #{$base-bg-color}; + --base-fg-color: #{tint-color($base-bg-color, 5%)}; + $base-text-color: white; + --base-text-color: #{$base-text-color}; + --text-muted-color: #{shade-color($base-text-color, 30%)}; + $primary-color: #FFBD00; + --primary-color: #{$primary-color}; + $secondary-color: #B7B6B7; + --secondary-color: #{$secondary-color}; + --success-color: #{$success}; + $info-color: #87B0E6; + --info-color: #{$info-color}; + $warning-color: #D97904; + --warning-color: #{$warning-color}; + $danger-color: #C80C07; + --danger-color: #{$danger-color}; + --border-color: #{tint-color($base-bg-color, 30%)}; + --primary-btn-color: #{$base-bg-color}; + /*----Links------------------------------------------------------*/ + --link-color: #{$primary-color}; + --link-active: #{tint-color($primary-color, 10%)}; + --link-visited: #{tint-color($primary-color, 20%)}; + /*----Shadows and Glows------------------------------------------*/ + --outer-glow: #{tint-color($primary-color, 80%)}; + --shadow-color: rgba(#{$gray-100}, .15); + --box-shadow: 0 .5rem 1rem #{rgba($gray-100, .15)}; + --box-shadow-sm: 0 .125rem .25rem #{rgba($gray-100, .075)}; + --box-shadow-lg: 0 1rem 3rem #{rgba($gray-100, .175)}; + --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%)}; + /*----Nav--------------------------------------------------------*/ + --navbar-bg: #{tint-color($base-bg-color, 20%)}; + --navbar-text: #{tint-color($base-text-color, 40%)}; + --navbar-link-active: #{shade-color($base-text-color, 20%)}; + --icon-color-filter: invert(0.5); + /*----Code Lines-------------------------------------------------*/ + --keyword-color: #d57c32; + --class-color: #ffc66d; + --name-color: #{$base-text-color}; + --code-color: #db3984; + --code-comment: #8a9898; + --code-diagnostics-error-color: #ff4c59; + --code-diagnostics-warn-color: #e3a710; + --code-diagnostics-info-color: #5197f0; + --code-added: #345039; + --code-removed: #5d3837; + --code-delta: #695b32; + /*----SVG-------------------------------------------------*/ + --svg-check-box: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='dimgray' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); + --svg-switch: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='dimgray'/%3e%3c/svg%3e"); +} \ No newline at end of file diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/site.scss b/src/dotnet/APIView/APIViewWeb/Client/css/site.scss deleted file mode 100644 index 0a759e4ab2a..00000000000 --- a/src/dotnet/APIView/APIViewWeb/Client/css/site.scss +++ /dev/null @@ -1,1259 +0,0 @@ - /* Themes -------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -.light-theme { - --background-color: #fff; - --border-color: #dee2e6; - --border-color-deeper: #6a757d; - --text-color: #212529; - --text-color-muted: #6e7781; - --bg-light: #f8f9fa; - --bg-light-hover: #e2e6ea; - --link-color: #006ee5; - --link-color-hover: #0056b3; - --btn-outline-dark: #343a40; - --monochrome-8: rgb(0 0 0 / 8%); - --monochrome-2: rgba(0, 0, 0, 0.2); - --monochrome-25: rgba(0, 0, 0, 0.25); - --monochrome-19: rgba(0, 0, 0, 0.19); - --monochrome-15: rgba(0, 0, 0, 0.15); - --monochrome-75: rgba(0, 0, 0, .075); - --monochrome-5: rgba(0, 0, 0, 0.5); - --monochrome-03: rgba(0, 0, 0, 0.03); - --monochrome-125: rgba(0, 0, 0, 0.125); - --focus-shadow: rgb(0 123 255 / 25%); - --muted-color: #6c757d; - --icon-color-filter: invert(0.0); - --keyword-color: blue; - --class-color: #007e79; - --name-color: var(---text-color); - --code-color: #d1377e; - --code-comment: green; - --code-diagnostics-error-color: lightpink; - --code-diagnostics-warn-color: lightyellow; - --code-diagnostics-info-color: lightblue; - --code-added: #cee9d3; - --code-removed: #fdcfce; - --code-delta: #fdefc6; -} - -.dark-theme { - --background-color: #010409; - --border-color: #30363d; - --border-color-deeper: #6d757f; - --text-color-muted: #9199a0; - --text-color: #c9d1d9; - --bg-light: #161b22; - --bg-light-hover: #30363d; - --link-color: #0084ff; - --link-color-hover: #a8d2ff; - --btn-outline-dark: #ccd6df; - --monochrome-8: rgba(255, 255, 255, 8%); - --monochrome-2: rgba(255, 255, 255, 0.2); - --monochrome-25: rgba(255, 255, 255, 0.25); - --monochrome-19: rgba(255, 255, 255, 0.19); - --monochrome-15: rgba(255, 255, 255, 0.15); - --monochrome-75: rgba(255, 255, 255, .075); - --monochrome-5: rgba(255, 255, 255, 0.5); - --monochrome-03: rgba(255, 255, 255, 0.03); - --monochrome-125: rgba(255, 255, 255, 0.125); - --focus-shadow: rgba(0, 123, 255, 0.642); - --muted-color: #717a83; - --icon-color-filter: invert(0.5); - --keyword-color: #ff7b72; - --class-color: #d2a8ff; - --name-color: var(---text-color); - --code-color: #db3984; - --code-comment: green; - --code-diagnostics-error-color: darkmagenta; - --code-diagnostics-warn-color: #846007; - --code-diagnostics-info-color: darkblue; - --code-added: #14432d; - --code-removed: #491d24; - --code-delta: #4c401a; -} - -.dark-solarized-theme { - --background-color: #2b2b2b; - --border-color: #42474d; - --border-color-deeper: #5d646c; - --text-color-muted: #acb2b9; - --text-color: white; - --bg-light: #3c3f41; - --bg-light-hover: #30363d; - --link-color: #bbbbbb; - --link-color-hover: #f4c56c; - --btn-outline-dark: #67a4f2; - --monochrome-8: rgba(255, 255, 255, 8%); - --monochrome-2: rgba(255, 255, 255, 0.2); - --monochrome-25: rgba(255, 255, 255, 0.25); - --monochrome-19: rgba(255, 255, 255, 0.19); - --monochrome-15: rgba(255, 255, 255, 0.15); - --monochrome-75: rgba(255, 255, 255, .075); - --monochrome-5: rgba(255, 255, 255, 0.5); - --monochrome-03: rgba(255, 255, 255, 0.03); - --monochrome-125: rgba(255, 255, 255, 0.125); - --focus-shadow: rgba(0, 123, 255, 0.642); - --muted-color: #717a83; - --icon-color-filter: invert(0.5); - --keyword-color: #d57c32; - --class-color: #ffc66d; - --name-color: #a9b7c6; - --code-color: #db3984; - --code-comment: #8a9898; - --code-diagnostics-error-color: #ff4c59; - --code-diagnostics-warn-color: #e3a710; - --code-diagnostics-info-color: #5197f0; - --code-added: #345039; - --code-removed: #5d3837; - --code-delta: #695b32; -} - -/* Mixins, Media, Include and Keyframes -------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -@mixin placeholder -{ - ::-webkit-input-placeholder { - @content; - } - ::-moz-placeholder { - @content; - } - ::-moz-placeholder { - @content; - } - ::-ms-input-placeholder { - @content; - } -} - -@include placeholder { - font-style: italic; -} - -@keyframes glow { - 0% { - background-color: rgba(0,0,0,0) - } - - 25% { - background-color: #fff6b3 - } - - 100% { - background-color: rgba(0,0,0,0) - } -} - -/* Color table -------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -$success-green: #208636; -$pale-green: #008484; - -/* HTML Tags -------------------------------------------------------------------------------------------------------------------------------------------------------------*/ -a { - color: var(--link-color); -} - -a:hover { - color: var(--link-color-hover); -} - -body { - /* Margin bottom by footer height */ - margin-bottom: 60px; - background-color: var(--background-color); - color: var(--text-color); -} - -button.accept-policy { - font-size: 1rem; - line-height: inherit; -} - -form.comment { - padding: 12px; -} - -html { - font-size: 14px; - position: relative; - min-height: 100%; -} - -@media (min-width: 768px) { - html { - font-size: 16px; - } -} - -/* Enable clear button in search input -----------------------------------------------------------------------*/ -input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; - height: 24px; - width: 24px; - margin-left: .4em; - background-image: url("data:image/svg+xml;utf8,"); - cursor: pointer; -} -/*---------------------------------------------------------------------*/ - -/* IDs -------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -/* Splitter -----------------------------------------------------------------------*/ -#review-left { - max-width: none; - min-width: 10px; - height: auto; - overflow: auto; - max-height: 100vh; - padding: 5px 0px 5px 10px; -} - -#review-right { - max-width: 100%; - min-width: 100px; - padding: 0px; - overflow-y: auto; -} -/*---------------------------------------------------------------------*/ - -#reviews-table td { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - border-top: 1px solid var(--border-color); -} - -#reviews-table-search-container { - min-width: 100px; - max-width: 40vw; -} - -/* Classes -------------------------------------------------------------------------------------------------------------------------------------------------------------*/ - -/* Classes Overridden -----------------------------------------------------------------------*/ -.badge-light { - color: var(--text-color); - background-color: var(--bg-light-hover); -} - -.bg-light { - background-color: var(--bg-light) !important; -} - -.border { - border: 1px solid var(--border-color) !important; -} - -.border-left { - border-left: 1px solid var(--border-color) !important; -} - -.border-top { - border-top: 1px solid var(--border-color) !important; -} - -.border-bottom { - border-bottom: 1px solid var(--border-color) !important; -} - -.bootstrap-select .dropdown-toggle .filter-option-inner-inner { - color: var(--text-color); -} - -.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after { - border-bottom: 6px solid var(--background-color); -} - -.btn-light { - color: var(--text-color); - background-color: var(--bg-light); - border-color: var(--bg-light); -} - -.btn-light:hover, .btn-light:active { - color: var(--text-color); - background-color: var(--bg-light-hover); -} - -.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, .show > .btn-light.dropdown-toggle { - color: var(--text-color); - background-color: var(--bg-light-hover); -} - -.btn-success { - background-color: $success-green; -} - -.card-header { - background-color: var(--monochrome-03); - border-bottom: 1px solid var(--monochrome-125); -} - -.custom-file-label { - color: var(--text-color); - background-color: var(--background-color); - border: 1px solid var(--border-color); -} - -.custom-file-label::after { - color: var(--text-color); - background-color: var(--bg-light); -} - -.custom-select { - color: var(--text-color); - background-color: var(--background-color); -} - -.close { - color: var(--text-color); -} - -.dropdown-menu { - color: var(--text-color); - background-color: var(--background-color); - border: 1px solid var(--monochrome-15); -} - -.dropdown-item { - color: var(--text-color); -} - -.dropdown-item:hover, .dropdown-item:focus { - color: var(--text-color); - background-color: var(--bg-light); -} - -.form-control { - color: var(--text-color); - background-color: var(--background-color); - border: 1px solid var(--border-color); -} - -.form-control { - color: var(--text-color); - background-color: var(--background-color); - border: 1px solid var(--border-color); -} - -.form-control:focus { - color: var(--text-color); - background-color: var(--background-color); - border-color: var(--border-color); - box-shadow: 0 0 0 0.2rem var(--focus-shadow); -} - -.list-group-item -{ - background-color: var(--background-color); - border: 1px solid var(--border-color); -} - -.modal-header { - border-bottom: 1px solid var(--border-color); -} - -.modal-content { - background-color: var(--background-color); - border: 1px solid var(--monochrome-2); -} - -.modal-footer { - border-top: 1px solid var(--border-color); -} - -.nav-pills .nav-link.active, .nav-pills .show > .nav-link { - background-color: var(--link-color); -} - -.shadow-sm { - box-shadow: 0 0.125rem 0.25rem var(--monochrome-8) !important; -} - -.table { - color: var(--text-color); -} - -.table th, .table td { - border-top: 1px solid var(--border-color); -} - -.table thead th { - border-bottom: 2px solid var(--border-color); -} - -.table-hover tbody tr:hover { - color: var(--text-color); - background-color: var(--monochrome-75); -} - -.text-black-50 { - color: var(--monochrome-5) !important; -} - -.text-dark { - color: var(--text-color) !important;; -} - -.text-muted { - color: var(--muted-color) !important; -} - -a.text-dark:focus, a.text-dark:hover { - color: var(--text-color) !important; -} - -.page-link { - background-color: var(--background-color); - border: 1px solid var(--border-color); -} - -.page-link:hover { - background-color: var(--bg-light-hover); - border-color: var(--border-color); -} - -.page-item.disabled .page-link { - color: var(--text-color); - background-color: var(--background-color); - border-color: var(--border-color); -} - -pre { - color: var(--text-color); -} - -.popover { - border-color: var(--border-color); -} - -.bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^="bottom"] > .arrow::after { - border-bottom-color: var(--background-color); -} - -.bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^="bottom"] > .arrow::before { - border-bottom-color: var(--monochrome-25); -} - -.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { - border-bottom: 1px solid var(--background-color); -} - -.popover-header { - background-color: var(--background-color); - color: var(--text-color); -} -.popover-body { - background-color: var(--background-color); - color: var(--text-color); -} - -/* Code rendering classes -----------------------------------------------------------------------*/ - -.class { - color: var(--class-color); -} - -code { - color: var(--code-color); -} - -.code-comment { - color: var(--code-comment); -} - -.code-added { - background-color: rgba(46,160,67,0.3); -} - -.code-removed { - background-color: rgba(248,81,73,0.3); -} - -.code-delta { - background-color: rgba(248,201,67,0.3); -} - -.enum { - color: #8700BD; -} - -.keyword { - color: var(--keyword-color); -} - -.name, a.name { - color: var(--name-color); -} - -.specialName { - color: #A400EB; - font-weight: bold; -} - -.type { - color: #B93D3D; -} - -.value { - color: #008509; -} - -/*---------------------------------------------------------------------*/ - -.apiview-logo { - width: 30px; -} - -.bootstraps-select-label { - padding: 4px 5px 3px 2px; -} - -.bottom-right-floating { - position:fixed; - z-index: 10; - bottom:10%; - right:5%; -} - -.btn-upvote span { - filter: grayscale(100%) opacity(30%); - - &.active { - filter: unset; - } -} - -.btn-link.line-comment-button { - border: 0; - opacity: 0; - padding: 0; - text-decoration: none; - user-select: none; - white-space: nowrap; -} - -.btn-primary { - background-color: var(--link-color); -} - -.btn-outline-primary { - color: var(--link-color); - border-color: var(--link-color); -} - -.btn-outline-dark { - color: var(--btn-outline-dark); - border-color: var(--btn-outline-dark); -} - -.btn-circle { - width: 45px; - height: 45px; - line-height: 45px; - text-align: center; - padding: 0; - border-radius: 50%; -} - -.btn-circle-xl { - width: 100px; - height: 100px; - line-height: 1.2rem; - font-weight: bold; - font-size: 1.0rem; - box-shadow: 0 4px 8px 0 var(--monochrome-2), 0 6px 20px 0 var(--monochrome-19); -} - -.btn-xs { - height: 25px !important; - font-size: 15px; - vertical-align: middle; - padding: 0.01rem; -} - -/* Diagnostics -----------------------------------------------------------------------*/ -.code-diagnostics-error { - background-color: var(--code-diagnostics-error-color); - border: 1px solid red; -} - -.code-diagnostics-warn { - background-color: var(--code-diagnostics-warn-color); - border: 1px solid #ffe066; -} - -.code-diagnostics-info { - background-color: var(--code-diagnostics-info-color); - border: 1px solid blue; -} - -.code-diagnostics p { - margin: 0; -} - -.code-diagnostics div { - position: sticky; - left: 0px; - padding-left: 10px; - max-width: 2000px; -} -/*---------------------------------------------------------------------*/ - -.clickable { - cursor: pointer; -} - -.comment-thread-anchor { - visibility: hidden; -} - -.comment-thread-contents.new-thread-comment { - background-color: #FFF; - border: 0; - margin: -8px -16px; -} - -.comment-row { - border: 1px solid var(--border-color); - border-spacing: 0; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background-color: var(--background-color); -} - -.comment-cancel-button { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - font-size: 14px; -} - -.comment-cell { - padding: 10px; - vertical-align: top; -} - -.comment-contents { - margin-bottom: 3px; - overflow-wrap: break-word; -} - -.comment-header { - margin-bottom: 0px; - line-height: 0.8 !important; - display: inline; -} - -.comment-holder { - max-width: 780px; - position: sticky; - left: 10px; - z-index: 100; // for user suggestion box, position sticky creates new stacking order, so z-index has to be set here too -} - -.comment-icon { - border-radius: 3px; - display: inline-block; - line-height: 1; - overflow: hidden; - vertical-align: middle; -} - -.comment-submit-button { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - font-size: 14px; - margin: 0 4px 0; -} - -.comments-resolved { - display: none; -} - -.code-window { - font-size: 14px; - line-height: 1.5; - border-collapse: separate; - tab-size: 8; - border-spacing: 0; - background-color: var(--background-color); // prevents code-window from overlapping stickySidebar - width: 200%; -} - -.code-lines-header { - border-bottom: 1px solid #DEE2E6; - vertical-align: middle; - padding-top: 9px; - padding-left: 10px; - font-size: small; - font-weight: normal; -} - -.code-line { - display: table-row; - font-family: Consolas, monospace; - line-height: 20px; -} - -.code-line:hover .line-details-button-cell .line-comment-button { - opacity: 1; -} - -.code-line .table .line-comment-button { - position: absolute; - left: -21px; -} - -.code-line .table tr:hover .line-comment-button { - opacity: 1; -} - -.code-line.active, .code-line tr.active { - animation: glow normal 1.5s ease-in-out; -} - -.code { - padding: 0px; - position: relative; - vertical-align: top; -} - -.code-inner { - word-wrap: normal; - white-space: pre; - width: 80% -} - -.code-inner > svg { - float: left; -} - -.code-inner > svg > text { - fill: var(--text-color); -} - -.code-inner > svg > line { - stroke: var(--border-color-deeper); -} - -.code-inner > table { - display: inline-table; - position: relative; - width: 50%; - table-layout: auto; - white-space: normal; -} - -.code-inner td, .code-inner th, .code-inner thead, .code-inner tbody, .code-inner table, .code-inner tr { - border: 1px solid var(--border-color-deeper); -} - -.code-inner > .table thead th { - border-bottom: 1px solid var(--border-color-deeper); -} - -.deprecated { - text-decoration: line-through; -} -.hidden-api { - color: var(--text-color-muted) !important; - font-style: italic; -} - -.file-code-icon { - filter: var(--icon-color-filter); -} - -.footer { - position: absolute; - bottom: 0; - width: 100%; - white-space: nowrap; - /* Set the fixed height of the footer here */ - height: 60px; - line-height: 60px; - /* Vertically center the text there */ -} - -.gutter { - &:hover { - background-color: var(--bg-light); - } - - &.gutter-horizontal { - cursor: col-resize; - } -} - -.hidden-row { - display: none; -} - -.hidden-row-via-filter { - display: none; -} - -/* Icons -----------------------------------------------------------------------*/ -.icon { - height: 16px; - width: 16px; - display: inline-block; - vertical-align: middle; -} - -.icon-class { - background: url(/icons/class.png) center left no-repeat; -} - -.icon-interface { - background: url(/icons/interface.png) center left no-repeat; -} - -.icon-struct { - background: url(/icons/struct.png) center left no-repeat; -} - -.icon-enum { - background: url(/icons/enum.png) center left no-repeat; -} - -.icon-delegate { - background: url(/icons/delegate.png) center left no-repeat; -} - -.icon-unknown { - background: url(/icons/type.png) center left no-repeat; -} - -.icon-assembly { - background: url(/icons/assembly.png) center left no-repeat; -} - -.icon-namespace { - background: url(/icons/namespace.png) center left no-repeat; -} - -.icon-package { - background: url(/icons/package.png) center left no-repeat; -} - -.icon-method { - background: url(/icons/method.png) center left no-repeat; -} - -.icon-comments { - cursor: pointer; - position: absolute; - right: -25px; -} - -.icon-chevron-right { - background: url(/icons/chevron-right.svg) center center no-repeat; -} - -.icon-chevron-up { - background: url(/icons/chevron-up.svg) center center no-repeat; -} - -.icon-language { - width: 34px; - height: 34px; -} - -.icon-csharp { - background: url(/icons/csharp-original.svg) center center no-repeat; -} - -.icon-javascript { - background: url(/icons/javascript-original.svg) center center no-repeat; -} - -.icon-python { - background: url(/icons/python-original.svg) center center no-repeat; -} - -.icon-c { - background: url(/icons/c-original.svg) center center no-repeat; -} - -.icon-cplusplus { - background: url(/icons/cplusplus-original.svg) center center no-repeat; -} - -.icon-go { - background: url(/icons/go-original.svg) center center no-repeat; -} - -.icon-java { - background: url(/icons/java-original.svg) center center no-repeat; -} - -.icon-java-spring { - background: url(/icons/java-spring-original.svg) center center no-repeat; -} - -.icon-java-android { - background: url(/icons/java-android-original.svg) center center no-repeat; -} - -.icon-swift { - background: url(/icons/swift-original.svg) center center no-repeat; -} - -.icon-kotlin { - background: url(/icons/kotlin-original.svg) center center no-repeat; -} - -.icon-json { - background: url(/icons/json-original.svg) center center no-repeat; -} - -.icon-swagger { - background: url(/icons/swagger-original.svg) center center no-repeat; -} -/*---------------------------------------------------------------------*/ -.invisible { - visibility: hidden; -} - -.info-text { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - font-size: 14px; -} - -.line-number { - padding-left: 10px; - padding-right: 12px; - text-align: right; - white-space: nowrap; - width: auto; -} - -.line-comment-button:hover { - transform: scale(1); -} - -.line-comment-button { - display: inline-block; - width: 21px; - height: 21px; - line-height: 21px; - color: #fff !important; - text-align: center; - text-indent: 0; - cursor: pointer; - background-color: #0366d6; - background-image: linear-gradient(#0372ef, #0366d6); - border-radius: 3px; - box-shadow: 0 1px 4px rgba(27, 31, 35, .15); - opacity: 0; - transition: transform .1s ease-in-out; - transform: scale(.8); - user-select: none; -} - -.line-toggle-documentation-button { - display: block; - width: 100%; - height: 100%; - text-align: center; - cursor: pointer; - user-select: none; - position: relative; -} - -.line-toggle-documentation-button svg { - top: 0px; - left: 0px; -} - -.line-toggle-documentation-button i { - position: absolute; - top: 25%; - left: 25%; -} - -.line-details-button-cell { - width: 22px; - height: 22px; - min-width: 22px; - min-height: 22px; - padding: 0px; -} - -.line-details-button-cell svg > line { - stroke: var(--text-color-muted); -} - -.line-details { - border-right: 1px solid var(--border-color); - background-color: var(--background-color); - color: var(--text-color-muted); - z-index: 1; - position: sticky; - left: 0; - cursor: pointer; - width: 1%; - white-space: nowrap; - padding: 0px; -} - -.line-details.code-added { - background-color: var(--code-added); - color: var(--text-color); -} - -.line-details.code-removed { - background-color: var(--code-removed); - color: var(--text-color); -} - -.line-details.code-delta { - background-color: var(--code-delta); - color: var(--text-color); -} - -.line-details table { - width: 100%; -} - -.line-details td { - vertical-align: middle; -} - -.row-fold-caret i { - position: relative; - left: 6px; -} - -.login-button { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - font-size: 14px; -} - -mark { - padding: 0; -} - -.media { - margin-bottom: 5px; -} - -/* Nav -----------------------------------------------------------------------*/ - -.nav-list { - list-style-type: none; - padding-left: 0px; -} - -.nav-list-children { - list-style-type: none; - text-overflow: ellipsis; - padding-left: 0; - margin-bottom: 0 !important; // prevents flickering in stickySidebar -} - -.nav-list-children>li { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.nav-list-toggle { - padding-left: 14px; - background: url(/icons/down.png) center left no-repeat; -} - -.nav-list-collapsed>.nav-list-toggle { - background: url(/icons/right.png) center left no-repeat; -} - -.nav-list-collapsed>.nav-list-children { - display: none; -} - -.nav-list-group .nav-list-children { - padding-left: 14px; -} - -.navbar-dark .navbar-nav .nav-link { - color: #fff; -} - -.navbar-expand-sm .navbar-nav .dropdown-menu { - z-index: 1030; -} -/*----------------------------------------------------------------------*/ - -.namespace-view { - font-size: 14px; - font-family: monospace; -} - -.new-comment-content { - position: relative; - margin: 0 4px 8px; -} - -.new-thread-comment { - border-radius: 3px; - line-height: 1.5; - border-collapse: separate; - tab-size: 8; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background-color: var(--bg-light); -} - -.new-thread-comment-text { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - font-size: 14px; - background-color: var(--bg-light); - max-width: 758px; -} - -.review-comment { - padding: 4px 16px; - position: relative; -} - -.review-comment:first-child { - padding-top: 16px; -} - -.review-comment p { - margin-bottom: 0; -} - -.review-comment blockquote { - padding-left: 14px; - padding-right: 14px; - border-left-color: #E9E5E2; - border-left-style: solid; - border-left-width: 3.5px; - color: #6A737D; -} - -.review-comment pre { - background-color: var(--bg-light-hover); -} - -.review-thread-reply { - background-color: var(--bg-light); - border-radius: 0 0 3px 3px; - border-top: 1px solid #E1E4E8; - border-bottom: 1px solid #E1E4E8; - padding: 8px 16px; - display: table; - width: 100%; -} - -.review-thread-reply-button { - cursor: text; - display: inline-block; - min-height: 28px; - padding: 3px 8px; - width: 100%; -} - -.reply-cell { - display: table-cell; -} - -.rounded-left-0 { - border-top-left-radius: 0px !important; - border-bottom-left-radius: 0px !important; -} - -.rounded-1 { - border-radius: 3px !important; -} - -.row-fold-elipsis { - margin-left: 8px; -} - -.row-fold-elipsis i { - position: relative; - top: 2px; -} - -/* Tag suggestion box -----------------------------------------------------------------------*/ -.tag-user-suggestion { - min-width: 180px; - padding: 0; - margin: 0; - cursor: pointer; - background: var(--bg-light); - border: 1px solid var(--border-color); - border-radius: 6px; - box-shadow: var(--focus-shadow); - z-index: 100; - list-style-type: none; - box-sizing: border-box; -} - -.tag-user-suggestion-username { - color: var(--text-color); - text-decoration: none; - padding: 4px 8px; - border-bottom: 1px solid var(--border-color); - box-sizing: border-box; -} - -.tag-user-suggestion-username:first-child { - border-top-left-radius: 6px; - border-top-right-radius: 6px; -} - -.tag-user-suggestion-username:hover { - background-color: var(--focus-shadow); -} - -// shows what option is selected during arrow navigation -.tag-user-suggestion-username-targetted { - background-color: var(--focus-shadow); -} - -// mirror box -.new-thread-comment-text-mirror { - position: absolute; - top: 0; - left: -9999px; - overflow: auto; - overflow-wrap: break-word; - white-space: pre-wrap; - box-sizing: border-box; - font-family: "Segoe UI",Tahoma,Geneva,Verdana,sans-serif; - font-size: 14px; - font-weight: 400; - max-height: 77px; - letter-spacing: normal; - line-height: 21px; - padding: 0.75rem 0.375rem; - border-width: 1px; - border-radius: 4px; - border-style: solid; - border-color: white; - text-indent: 0px; - text-transform: none; - word-spacing: 0px; -} - -.position-relative-top-1 { - position: relative; - top: 1px; -} - -.review-approved { - border: solid 1px #28a745; - border-radius: 3px; -} diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/swagger.scss b/src/dotnet/APIView/APIViewWeb/Client/css/swagger.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/swift.scss b/src/dotnet/APIView/APIViewWeb/Client/css/swift.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/usageSample.scss b/src/dotnet/APIView/APIViewWeb/Client/css/usageSample.scss deleted file mode 100644 index c1b43dc1bd9..00000000000 --- a/src/dotnet/APIView/APIViewWeb/Client/css/usageSample.scss +++ /dev/null @@ -1,22 +0,0 @@ -.usage-sample + .border + .rounded { - margin-top: 15px !important; -} - -.usage-sample .code-line { - padding: 0px; - margin: 0px; - font-size: 85%; - line-height: 10px; -} - -.usage-sample .line-number { - font-size: 125%; -} - -.code-inner { - height: 100%; -} - -.usage-sample .internal { - display: inline-block; -} diff --git a/src/dotnet/APIView/APIViewWeb/Client/css/xml.scss b/src/dotnet/APIView/APIViewWeb/Client/css/xml.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/dotnet/APIView/APIViewWeb/Client/package.json b/src/dotnet/APIView/APIViewWeb/Client/package.json index afccaa9873f..e40e0f723f0 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/package.json +++ b/src/dotnet/APIView/APIViewWeb/Client/package.json @@ -13,23 +13,24 @@ "acorn": "^8.0.0", "core-js": "^3.3.2", "jquery": "3.6.0", - "jquery-ui": "1.13.2", "underscore": "^1.13.4" }, "devDependencies": { "@types/jquery": "3.3.31", - "@types/jqueryui": "1.12.16", "@types/split.js": "^1.4.0", "@types/webpack-env": "^1.14.1", "@typescript-eslint/parser": "5.16.0", - "css-loader": "^3.2.0", + "bootstrap": "5.0.2", + "css-loader": "^3.6.0", "eslint": "^8.11.0", "eslint-plugin-vue": "^8.5.0", - "mini-css-extract-plugin": "^2.6.0", + "inspectpack": "^4.7.1", + "mini-css-extract-plugin": "^2.7.2", "sass": "^1.19.0", - "sass-loader": "^8.0.0", + "sass-loader": "^8.0.2", + "style-loader": "^3.3.1", "ts-loader": "^6.2.0", - "typescript": "^3.5.3", + "typescript": "^3.6.0", "webpack": "^5.70.0", "webpack-cli": "^4.9.2" }, diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/helpers.ts b/src/dotnet/APIView/APIViewWeb/Client/src/helpers.ts deleted file mode 100644 index 1accbe87a02..00000000000 --- a/src/dotnet/APIView/APIViewWeb/Client/src/helpers.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Updated Page Setting by Updating UserPreference -export function updatePageSettings(callBack) { - var hideLineNumbers = $("#hide-line-numbers").prop("checked"); - var hideLeftNavigation = $("#hide-left-navigation").prop("checked"); - var showHiddenApis = $("#show-hidden-api-checkbox").prop("checked"); - var uri = location.origin + `/userprofile/updatereviewpagesettings?hideLineNumbers=${hideLineNumbers}&hideLeftNavigation=${hideLeftNavigation}&showHiddenApis=${showHiddenApis}`; - - $.ajax({ - type: "PUT", - url: uri - }).done(callBack()); -} diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/main.ts b/src/dotnet/APIView/APIViewWeb/Client/src/main.ts new file mode 100644 index 00000000000..65a65675655 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/src/main.ts @@ -0,0 +1,9 @@ +import "./shared/comments.ts"; +import "./shared/file-input.ts"; +import "./shared/layout.ts"; +import "./pages/index.ts"; +import "./pages/review.ts"; +import "./pages/revisions.ts"; +import "./pages/user-profile.ts"; + + diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/reviews.ts b/src/dotnet/APIView/APIViewWeb/Client/src/pages/index.ts similarity index 71% rename from src/dotnet/APIView/APIViewWeb/Client/src/reviews.ts rename to src/dotnet/APIView/APIViewWeb/Client/src/pages/index.ts index 4e911b1a16d..df445fbbd8f 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/src/reviews.ts +++ b/src/dotnet/APIView/APIViewWeb/Client/src/pages/index.ts @@ -1,147 +1,168 @@ -$(() => { - const defaultPageSize = 50; - const reviewsFilterPartial = $( '#reviews-filter-partial' ); - const languageFilter = $( '#language-filter-bootstraps-select' ); - const stateFilter = $( '#state-filter-bootstraps-select' ); - const statusFilter = $( '#status-filter-bootstraps-select' ); - const typeFilter = $( '#type-filter-bootstraps-select' ); - const searchBox = $( '#reviews-table-search-box' ); - const searchButton = $( '#reviews-search-button' ); - const resetButton = $('#reset-filter-button'); - const languageSelect = $('#review-language-select'); - - // Import underscorejs - var _ = require('underscore'); - - // Enable tooltip - ($('[data-toggle="tooltip"]')).tooltip(); - - // Computes the uri string using the values of search, pagination and various filters - // Invokes partial page update to list of reviews using ajax - // Updates the uri displayed on the client - function updateListedReviews({ pageNo = 1, pageSize = defaultPageSize } = {}) - { - var uri = '?handler=reviewspartial'; - var searchQuery = searchBox.val() as string; - - if (searchQuery != null && searchQuery.trim() != '') - { - var searchTerms = searchQuery.trim().split(/\s+/); - searchTerms.forEach(function(value, index){ - uri = uri + '&search=' + encodeURIComponent(value); - }); - } - - languageFilter.children(":selected").each(function() { - uri = uri + '&languages=' + encodeURIComponent(`${$(this).val()}`); - }); - - stateFilter.children(":selected").each(function() { - uri = uri + '&state=' + encodeURIComponent(`${$(this).val()}`); - }); - - statusFilter.children(":selected").each(function() { - uri = uri + '&status=' + encodeURIComponent(`${$(this).val()}`); - }); - - typeFilter.children(":selected").each(function() { - uri = uri + '&type=' + encodeURIComponent(`${$(this).val()}`); - }); - - uri = uri + '&pageNo=' + encodeURIComponent(pageNo); - uri = uri + '&pageSize=' + encodeURIComponent(pageSize); - uri = encodeURI(uri); - - $.ajax({ - url: uri - }).done(function(partialViewResult) { - reviewsFilterPartial.html(partialViewResult); - history.pushState({}, '', uri.replace('handler=reviewspartial&', '')); - addPaginationEventHandlers(); // This ensures that the event handlers are re-added after ajax refresh - }); - } - - // Add custom behaviour and event to pagination buttons - function addPaginationEventHandlers() - { - $( '.page-link' ).each(function() { - $(this).on('click', function(event){ - event.preventDefault(); - var linkParts = $(this).prop('href').split('/'); - var pageNo = linkParts[linkParts.length - 1]; - if (pageNo !== null && pageNo !== undefined) - { - updateListedReviews({ pageNo: pageNo }); - } - }); - }); - } - - // Fetches data for populating dropdown options - function updateFilterDropDown(filter, query) - { - var uri = `?handler=reviews${query}`; - var urlParams = new URLSearchParams(location.search); - if (urlParams.has(query)) - { - urlParams.getAll(query).forEach(function(value, index) { - uri = uri + `&selected${query}=` + encodeURIComponent(value); - }); - } - $.ajax({ - url: uri - }).done(function(partialViewResult) { - filter.html(partialViewResult); - (filter).selectpicker('refresh'); - }); - } - - // Fetch content of dropdown on page load - $(document).ready(function() { - updateFilterDropDown(languageFilter, "languages"); // Pulls languages data from DB - addPaginationEventHandlers(); - }); - - // Update list of reviews when any dropdown is changed - [languageFilter, stateFilter, statusFilter, typeFilter].forEach(function(value, index) { - value.on('hidden.bs.select', function() { - updateListedReviews(); - }); - }); - - // Update list of reviews based on search input - searchBox.on('input', _.debounce(function(e) { - updateListedReviews(); - }, 600)); - - searchButton.on('click', function() { - updateListedReviews(); - }); - - // Reset list of reviews as well as filters - resetButton.on('click', function(e) { - (languageFilter).selectpicker('deselectAll'); - (stateFilter).selectpicker('deselectAll').selectpicker('val', 'Open'); - (statusFilter).selectpicker('deselectAll'); - (typeFilter).selectpicker('deselectAll'); - searchBox.val(''); - updateListedReviews(); - }); - - var prevLanguageValue = languageSelect.val(); - languageSelect.on('change', function (e) { - var val = $(this).val(); +import { rightOffCanvasNavToggle } from "../shared/off-canvas"; +import { updatePageSettings } from "../shared/helpers"; + +$(() => { + const defaultPageSize = 50; + const reviewsFilterPartial = $( '#reviews-filter-partial' ); + const languageFilter = $( '#language-filter-select' ); + const stateFilter = $( '#state-filter-select' ); + const statusFilter = $( '#status-filter-select' ); + const typeFilter = $( '#type-filter-select' ); + const searchBox = $( '#reviews-table-search-box' ); + const searchButton = $( '#reviews-search-button' ); + const resetButton = $('#reset-filter-button'); + const languageSelect = $('#review-language-select'); + + // Import underscorejs + var _ = require('underscore'); + + // Computes the uri string using the values of search, pagination and various filters + // Invokes partial page update to list of reviews using ajax + // Updates the uri displayed on the client + function updateListedReviews({ pageNo = 1, pageSize = defaultPageSize } = {}) + { + var uri = '?handler=reviewspartial'; + var searchQuery = searchBox.val() as string; + + if (searchQuery != null && searchQuery.trim() != '') + { + var searchTerms = searchQuery.trim().split(/\s+/); + searchTerms.forEach(function(value, index){ + uri = uri + '&search=' + encodeURIComponent(value); + }); + } + + languageFilter.children(":selected").each(function() { + uri = uri + '&languages=' + encodeURIComponent(`${$(this).val()}`); + }); + + stateFilter.children(":selected").each(function() { + uri = uri + '&state=' + encodeURIComponent(`${$(this).val()}`); + }); + + statusFilter.children(":selected").each(function() { + uri = uri + '&status=' + encodeURIComponent(`${$(this).val()}`); + }); + + typeFilter.children(":selected").each(function() { + uri = uri + '&type=' + encodeURIComponent(`${$(this).val()}`); + }); + + uri = uri + '&pageNo=' + encodeURIComponent(pageNo); + uri = uri + '&pageSize=' + encodeURIComponent(pageSize); + uri = encodeURI(uri); + + $.ajax({ + url: uri + }).done(function(partialViewResult) { + reviewsFilterPartial.html(partialViewResult); + history.pushState({}, '', uri.replace('handler=reviewspartial&', '')); + addPaginationEventHandlers(); // This ensures that the event handlers are re-added after ajax refresh + }); + } + + // Add custom behaviour and event to pagination buttons + function addPaginationEventHandlers() + { + $( '.page-link' ).each(function() { + $(this).on('click', function(event){ + event.preventDefault(); + var linkParts = $(this).prop('href').split('/'); + var pageNo = linkParts[linkParts.length - 1]; + if (pageNo !== null && pageNo !== undefined) + { + updateListedReviews({ pageNo: pageNo }); + } + }); + }); + } + + // Fetches data for populating dropdown options + function updateFilterDropDown(filter, query) + { + var uri = `?handler=reviews${query}`; + var urlParams = new URLSearchParams(location.search); + if (urlParams.has(query)) + { + urlParams.getAll(query).forEach(function(value, index) { + uri = uri + `&selected${query}=` + encodeURIComponent(value); + }); + } + $.ajax({ + url: uri + }).done(function(partialViewResult) { + filter.html(partialViewResult); + (filter).SumoSelect({ + selectAll: true, + captionFormat: '{0} languages Selected' + }); + }); + } + + // Fetch content of dropdown on page load + $(document).ready(function() { + updateFilterDropDown(languageFilter, "languages"); // Pulls languages data from DB + (stateFilter).SumoSelect({ selectAll: true }); + (statusFilter).SumoSelect({ selectAll: true }); + (typeFilter).SumoSelect({ selectAll: true }); + addPaginationEventHandlers(); + }); + + $("#uploadModel").on("show.bs.modal", function () { + (languageSelect).SumoSelect({ + placeholder: 'Language' + }); + }); + + // Update list of reviews when any dropdown is changed + [languageFilter, stateFilter, statusFilter, typeFilter].forEach(function(value, index) { + value.on('sumo:closed', function() { + updateListedReviews(); + }); + }); + + // Update list of reviews based on search input + searchBox.on('input', _.debounce(function(e) { + updateListedReviews(); + }, 600)); + + searchButton.on('click', function() { + updateListedReviews(); + }); + + // Reset list of reviews as well as filters + resetButton.on('click', function (e) { + ($('#language-filter-select')[0]).sumo.unSelectAll(); + ($('#state-filter-select')[0]).sumo.unSelectAll(); + ($('#state-filter-select')[0]).sumo.selectItem('Open'); + ($('#status-filter-select')[0]).sumo.unSelectAll(); + ($('#type-filter-select')[0]).sumo.unSelectAll(); + searchBox.val(''); + updateListedReviews(); + }); + + var prevLanguageValue = languageSelect.val(); + languageSelect.on('sumo:closed', function (e) { + var val = $(this).val() as string; if (val == "C++" || val == "C#") { - val = val.replace("C++", "Cpp").replace("C#", "Csharp"); - } - var helpName = "#help-" + val; - $(helpName).click(); - if (val == 'Cadl' || prevLanguageValue == 'Cadl') { - const fileSelectors = $(".package-selector"); - for (var i = 0; i < fileSelectors.length; i++) { - $(fileSelectors[i]).toggleClass("hidden-row"); - } - } - prevLanguageValue = val; - }); -}); + val = val.replace("C++", "Cpp").replace("C#", "Csharp"); + } + $("#uploadModel").find(".card-body > div").addClass("d-none"); + var helpName = "#" + val.toLowerCase() + "-help"; + $(helpName).removeClass("d-none"); + if (val == 'Cadl' || prevLanguageValue == 'Cadl') { + const fileSelectors = $(".package-selector"); + for (var i = 0; i < fileSelectors.length; i++) { + $(fileSelectors[i]).toggleClass("d-none"); + } + } + prevLanguageValue = val; + }); + + // Open / Close right Offcanvas Menu + $("#index-right-offcanvas-toggle").on('click', function () { + updatePageSettings(function () { + rightOffCanvasNavToggle("index-main-container"); + }); + }); +}); diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/review.ts b/src/dotnet/APIView/APIViewWeb/Client/src/pages/review.ts similarity index 88% rename from src/dotnet/APIView/APIViewWeb/Client/src/review.ts rename to src/dotnet/APIView/APIViewWeb/Client/src/pages/review.ts index d29246c48b4..ff038c28176 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/src/review.ts +++ b/src/dotnet/APIView/APIViewWeb/Client/src/pages/review.ts @@ -1,13 +1,14 @@ import Split from "split.js"; -import { updatePageSettings } from "./helpers"; +import { updatePageSettings, toggleCommentIcon } from "../shared/helpers"; +import { rightOffCanvasNavToggle } from "../shared/off-canvas"; $(() => { const SEL_DOC_CLASS = ".documentation"; const SHOW_DOC_CHECK_COMPONENT = "#show-documentation-component"; - const SHOW_DOC_CHECKBOX = ".show-doc-checkbox"; - const SHOW_DOC_HREF = ".show-document"; + const SHOW_DOC_CHECKBOX = ".show-documentation-checkbox"; + const SHOW_DOC_HREF = ".show-documentation-switch"; const SHOW_DIFFONLY_CHECKBOX = ".show-diffonly-checkbox"; - const SHOW_DIFFONLY_HREF = ".show-diffonly"; + const SHOW_DIFFONLY_HREF = ".show-diffonly-switch"; const TOGGLE_DOCUMENTATION = ".line-toggle-documentation-button"; const SEL_HIDDEN_CLASS = ".hidden-api-toggleable"; const SHOW_HIDDEN_CHECK_COMPONENT = "#show-hidden-api-component"; @@ -70,14 +71,20 @@ $(() => { if (caretDirection.endsWith("right")) { // In case the section passed has already been replaced with more rows if (sectionContent.length == 1) { - var sectionContentClass = sectionContent[0].className.replace(/\s/g, '.'); + const sectionContentClass = sectionContent[0].className.replace(/\s/g, '.'); + const sectionCommentClass = sectionContentClass.replace("code-line.", "comment-row."); sectionContent = $(`.${sectionContentClass}`); + sectionContent.push(...$(`.${sectionCommentClass}`)); } $.each(sectionContent, function (index, value) { let rowClasses = $(value).attr("class"); if (rowClasses) { - if (rowClasses.match(/lvl_1_/)) { // Only show first level rows of the section + if (rowClasses.match(/lvl_1_/)) { + if (rowClasses.match(/comment-row/) && !$("#show-comments-checkbox").prop("checked")) { + toggleCommentIcon($(value).attr("data-line-id"), true); + return; // Dont show comment row if show comments setting is unchecked + } disableCommentsOnInRowTables($(value)); $(value).removeClass("d-none"); $(value).find("svg").attr("height", `${$(value).height()}`); @@ -87,7 +94,6 @@ $(() => { // Update section heading icons to open state updateSectionHeadingIcons("OPEN", caretIcon, headingRow); - } else { $.each(sectionContent, function (index, value) { @@ -129,6 +135,11 @@ $(() => { // Show only immediate descendants if (startShowing) { if (rowClasses.match(new RegExp(`lvl_${Number(subSectionLevel) + 1}_`))) { + if (rowClasses.match(/comment-row/) && !$("#show-comments-checkbox").prop("checked")) { + toggleCommentIcon($(value).attr("data-line-id"), true); + return; // Dont show comment row if show comments setting is unchecked + } + disableCommentsOnInRowTables($(value)); $(value).removeClass("d-none"); let rowHeight = $(value).height() ?? 0; @@ -219,7 +230,7 @@ $(() => { if (sectionKeyB) uri = uri + '§ionKeyB=' + sectionKeyB; - const loadingMarkUp = "Loading..."; + const loadingMarkUp = "Loading..."; const failedToLoadMarkUp = ""; if (sectionContent.children(".spinner-border").length == 0) { sectionContent.children("td").after(loadingMarkUp); @@ -278,6 +289,12 @@ $(() => { } } + // Enable SumoSelect + $(document).ready(function () { + ($("#revision-select")).SumoSelect({ search: true, searchText: 'Search Revisions...' }); + ($("#diff-select")).SumoSelect({ search: true, searchText: 'Search Revisons for Diff...' }); + }); + /* ADD FUNCTIONS TO LEFT NAVIGATION --------------------------------------------------------------------------------------------------------------------------------------------------------*/ /* Enable expand/collapse of navigation groups */ @@ -411,7 +428,7 @@ $(() => { /* DROPDOWN FILTER FOR REVIEW, REVISIONS AND DIFF (UPDATES REVIEW PAGE ON CHANGE) --------------------------------------------------------------------------------------------------------------------------------------------------------*/ - $('#revisions-bootstraps-select, #review-bootstraps-select, #diff-bootstraps-select').each(function(index, value) { + $('#revision-select, #diff-select').each(function(index, value) { $(this).on('change', function() { var url = $(this).find(":selected").val(); if (url) @@ -459,8 +476,32 @@ $(() => { --------------------------------------------------------------------------------------------------------------------------------------------------------*/ addToggleEventHandlers(); - /* ENABLE TOOLTIP AND POPOVER + /* RIGHT OFFCANVAS OPERATIONS --------------------------------------------------------------------------------------------------------------------------------------------------------*/ - ($('[data-toggle="tooltip"]')).tooltip(); - ($('[data-toggle="popover"]')).popover(); + // Open / Close right Offcanvas Menu + $("#review-right-offcanvas-toggle").on('click', function () { + updatePageSettings(function () { + rightOffCanvasNavToggle("review-main-container"); + }); + }); + + // Toggle Subscribe Switch + $("#reviewSubscribeSwitch").on('change', function () { + $("#reviewSubscribeForm").submit(); + }); + // Toggle Close Switch + $("#reviewCloseSwitch").on('change', function () { + $("#reviewCloseForm").submit(); + }); + + // Manage Expand / Collapse State of options + [$("#approveCollapse"), $("#requestReviewersCollapse"), $("#reviewOptionsCollapse"), $("#pageSettingsCollapse"), $("#associatedPRCollapse"), $("#associatedReviewsCollapse")].forEach(function (value, index) { + const id = value.attr("id"); + value.on('hidden.bs.collapse', function () { + document.cookie = `${id}=hidden; max-age=${7 * 24 * 60 * 60}`; + }); + value.on('shown.bs.collapse', function () { + document.cookie = `${id}=shown; max-age=${7 * 24 * 60 * 60}`; + }); + }); }); diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/revisions.ts b/src/dotnet/APIView/APIViewWeb/Client/src/pages/revisions.ts similarity index 72% rename from src/dotnet/APIView/APIViewWeb/Client/src/revisions.ts rename to src/dotnet/APIView/APIViewWeb/Client/src/pages/revisions.ts index dab5970e368..14e5bb2dd3f 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/src/revisions.ts +++ b/src/dotnet/APIView/APIViewWeb/Client/src/pages/revisions.ts @@ -1,4 +1,6 @@ $(() => { + const revisionlanguageSelect = $('#revision-language-select'); + $(document).on("click", ".revision-rename-icon", e => { toggleNameField($(e.target)); }); @@ -17,11 +19,16 @@ $(() => { renameIcon.siblings(".revision-name-input").toggle(); } - const languageSelect = $('#revision-language-select'); - languageSelect.on('change', function (e) { + revisionlanguageSelect.on('change', function (e) { const fileSelectors = $(".package-file-selector"); for (var i = 0; i < fileSelectors.length; i++) { $(fileSelectors[i]).toggleClass("hidden-row"); } }); + + $("#uploadModel").on("show.bs.modal", function () { + (revisionlanguageSelect).SumoSelect({ + placeholder: 'Language' + }); + }); }); diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/user-profile.ts b/src/dotnet/APIView/APIViewWeb/Client/src/pages/user-profile.ts similarity index 79% rename from src/dotnet/APIView/APIViewWeb/Client/src/user-profile.ts rename to src/dotnet/APIView/APIViewWeb/Client/src/pages/user-profile.ts index aca849b4cb4..de75e347d2e 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/src/user-profile.ts +++ b/src/dotnet/APIView/APIViewWeb/Client/src/pages/user-profile.ts @@ -1,7 +1,11 @@ -import { updatePageSettings } from "./helpers"; +import { updatePageSettings } from "../shared/helpers"; $(() => { - const themeSelector = $( '#theme-selector' ); + const themeSelector = $('#theme-selector'); + const approvableLangSelect = $('#approvable-language-select'); + + (themeSelector).SumoSelect(); + (approvableLangSelect).SumoSelect({selectAll: true}); $(document).on("submit", "form[data-post-update='userProfile']", e => { const form = $(e.target); @@ -34,4 +38,4 @@ $(() => { body.addClass(newTheme); }); }); -}); \ No newline at end of file +}); diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/comments.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/comments.ts similarity index 89% rename from src/dotnet/APIView/APIViewWeb/Client/src/comments.ts rename to src/dotnet/APIView/APIViewWeb/Client/src/shared/comments.ts index e2903869124..308759920e6 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/src/comments.ts +++ b/src/dotnet/APIView/APIViewWeb/Client/src/shared/comments.ts @@ -1,8 +1,15 @@ +import { + updatePageSettings, getCodeRow, getCodeRowSectionClasses, + getRowSectionClasses, toggleCommentIcon +} from "../shared/helpers"; + $(() => { const INVISIBLE = "invisible"; const SEL_CODE_DIAG = ".code-diagnostics"; const SEL_COMMENT_ICON = ".icon-comments"; const SEL_COMMENT_CELL = ".comment-cell"; + const SHOW_COMMENTS_CHECK = "#show-comments-checkbox"; + const SHOW_SYS_COMMENTS_CHECK = "#show-system-comments-checkbox"; const COMMENT_CONTENT_BOX = ".new-comment-content"; const COMMENT_TEXTBOX = ".new-thread-comment-text"; @@ -11,8 +18,6 @@ $(() => { // simple github username match const githubLoginTagMatch = /(\s|^)@([a-zA-Z\d-]+)/g; - let MessageIconAddedToDom = false; - $(document).on("click", ".commentable", e => { var rowSectionClasses = getCodeRowSectionClasses(e.target.id); showCommentBox(e.target.id, rowSectionClasses); @@ -48,14 +53,18 @@ $(() => { e.preventDefault(); }); - $(document).on("click", "#show-comments-checkbox", e => { - ensureMessageIconInDOM(); - toggleAllCommentsVisibility(e.target.checked); + $(document).on("click", SHOW_COMMENTS_CHECK, e => { + updatePageSettings(function () { + const checked = $(SHOW_COMMENTS_CHECK).prop("checked"); + toggleAllCommentsVisibility(checked); + }); }); - $(document).on("click", "#show-system-comments-checkbox", e => { - ensureMessageIconInDOM(); - toggleAllDiagnosticsVisibility(e.target.checked); + $(document).on("click", SHOW_SYS_COMMENTS_CHECK, e => { + updatePageSettings(function () { + const checked = $(SHOW_SYS_COMMENTS_CHECK).prop("checked"); + toggleAllDiagnosticsVisibility(checked); + }); }); $(document).on("click", SEL_COMMENT_ICON, e => { @@ -319,6 +328,16 @@ $(() => { $(document).ready(function() { highlightCurrentRow(); addCommentThreadNavigation(); + $(SEL_COMMENT_CELL).each(function () { + const id = getElementId(this); + const checked = $(SHOW_COMMENTS_CHECK).prop("checked"); + toggleCommentIcon(id, !checked); + }); + $(SEL_CODE_DIAG).each(function () { + const id = getElementId(this); + const checked = $(SHOW_SYS_COMMENTS_CHECK).prop("checked"); + toggleCommentIcon(id, !checked); + }); }); $(document).on("click", ".comment-group-anchor-link", e => { @@ -344,8 +363,6 @@ $(() => { }); } - - function getReviewId(element: HTMLElement) { return getParentData(element, "data-review-id"); } @@ -377,25 +394,6 @@ $(() => { return $(sibling).prevAll("a").first().find("small"); } - function getCodeRowSectionClasses(id: string) { - var codeRow = getCodeRow(id); - var rowSectionClasses = ""; - if (codeRow) { - rowSectionClasses = getRowSectionClasses(codeRow[0].classList); - } - return rowSectionClasses; - } - - function getRowSectionClasses(classList: DOMTokenList) { - const rowSectionClasses: string[] = []; - for (const value of classList.values()) { - if (value == "section-loaded" || value.startsWith("code-line-section-content") || value.match(/lvl_[0-9]+_(parent|child)_[0-9]+/)) { - rowSectionClasses.push(value); - } - } - return rowSectionClasses.join(' '); - } - function getCommentId(element: HTMLElement) { return getParentData(element, "data-comment-id"); } @@ -423,10 +421,6 @@ $(() => { return $(`.comment-row[data-line-id='${id}']`); } - function getCodeRow(id: string) { - return $(`.code-line[data-line-id='${id}']`); - } - function getDiagnosticsRow(id: string) { return $(`.code-diagnostics[data-line-id='${id}']`); } @@ -544,38 +538,39 @@ $(() => { function toggleAllCommentsVisibility(showComments: boolean) { $(SEL_COMMENT_CELL).each(function () { - var id = getElementId(this); + const id = getElementId(this); if (id) { - getCommentsRow(id).toggle(showComments); - toggleCommentIcon(id, !showComments); + const tbRow = getCommentsRow(id); + const prevRow = tbRow.prev(".code-line"); + const nextRow = tbRow.next(".code-line"); + if ((prevRow != undefined && prevRow.hasClass("d-none")) && (nextRow != undefined && nextRow.hasClass("d-none"))) + return; + + (showComments) ? tbRow.removeClass("d-none") : tbRow.addClass("d-none"); + toggleCommentIcon(id, !showComments); } }); } function toggleAllDiagnosticsVisibility(showComments: boolean) { $(SEL_CODE_DIAG).each(function () { - var id = getElementId(this); + const id = getElementId(this); if (id) { - getDiagnosticsRow(id).toggle(showComments); - toggleCommentIcon(id, !showComments); + const tbRow = getDiagnosticsRow(id); + const prevRow = tbRow.prev(".code-line"); + const nextRow = tbRow.next(".code-line"); + if ((prevRow != undefined && prevRow.hasClass("d-none")) && (nextRow != undefined && nextRow.hasClass("d-none"))) + return; + + (showComments) ? tbRow.removeClass("d-none") : tbRow.addClass("d-none"); + toggleCommentIcon(id, !showComments); } }); } function toggleSingleCommentAndDiagnostics(id: string) { - getCommentsRow(id).toggle(); - getDiagnosticsRow(id).toggle(); - } - - function ensureMessageIconInDOM() { - if (!MessageIconAddedToDom) { - $(".comment-icon-cell").append(``); - MessageIconAddedToDom = true; - } - } - - function toggleCommentIcon(id, show: boolean) { - getCodeRow(id).find(SEL_COMMENT_ICON).toggleClass(INVISIBLE, !show); + getCommentsRow(id).toggleClass("d-none"); + getDiagnosticsRow(id).toggleClass("d-none"); } function getDisplayedCommentRows(commentRows: JQuery, clearCommentAnchors = false, returnFirst = false) { @@ -633,14 +628,14 @@ $(() => { var previousCommentThreadAnchor = "comment-thread-" + (index - 1); if (index == 0) { - commentNavigationButtons.append(``) + commentNavigationButtons.append(``) } else if (index == displayedCommentRows.length - 1) { - commentNavigationButtons.append(``) + commentNavigationButtons.append(``) } else { - commentNavigationButtons.append(``) - commentNavigationButtons.append(``) + commentNavigationButtons.append(``) + commentNavigationButtons.append(``) } }); } diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/file-input.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/file-input.ts similarity index 100% rename from src/dotnet/APIView/APIViewWeb/Client/src/file-input.ts rename to src/dotnet/APIView/APIViewWeb/Client/src/shared/file-input.ts diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/shared/helpers.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/helpers.ts new file mode 100644 index 00000000000..f998815c6f0 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/src/shared/helpers.ts @@ -0,0 +1,59 @@ +// Updated Page Setting by Updating UserPreference +export function updatePageSettings(callBack) { + var hideLineNumbers = $("#hide-line-numbers").prop("checked"); + if (hideLineNumbers != undefined) { hideLineNumbers = !hideLineNumbers; } + + var hideLeftNavigation = $("#hide-left-navigation").prop("checked"); + if (hideLeftNavigation != undefined) { hideLeftNavigation = !hideLeftNavigation; } + + var showHiddenApis = $("#show-hidden-api-checkbox").prop("checked"); + var showComments = $("#show-comments-checkbox").prop("checked"); + var showSystemComments = $("#show-system-comments-checkbox").prop("checked"); + + var hideReviewPageOptions = $("#review-right-offcanvas-toggle").prop("checked"); + if (hideReviewPageOptions != undefined) { hideReviewPageOptions = !hideReviewPageOptions; } + + var hideIndexPageOptions = $("#index-right-offcanvas-toggle").prop("checked"); + if (hideIndexPageOptions != undefined) { hideIndexPageOptions = !hideIndexPageOptions; } + + var uri = location.origin + `/userprofile/updatereviewpagesettings?` + + `hideLineNumbers=${hideLineNumbers}&` + + `hideLeftNavigation=${hideLeftNavigation}&` + + `showHiddenApis=${showHiddenApis}&` + + `hideReviewPageOptions=${hideReviewPageOptions}&` + + `hideIndexPageOptions=${hideIndexPageOptions}&` + + `showComments=${showComments}&` + + `showSystemComments=${showSystemComments}`; + + $.ajax({ + type: "PUT", + url: uri + }).done(callBack()); +} + +export function getCodeRow(id: string) { + return $(`.code-line[data-line-id='${id}']`); +} + +export function getCodeRowSectionClasses(id: string) { + var codeRow = getCodeRow(id); + var rowSectionClasses = ""; + if (codeRow) { + rowSectionClasses = getRowSectionClasses(codeRow[0].classList); + } + return rowSectionClasses; +} + +export function getRowSectionClasses(classList: DOMTokenList) { + const rowSectionClasses: string[] = []; + for (const value of classList.values()) { + if (value == "section-loaded" || value.startsWith("code-line-section-content") || value.match(/lvl_[0-9]+_(parent|child)_[0-9]+/)) { + rowSectionClasses.push(value); + } + } + return rowSectionClasses.join(' '); +} + +export function toggleCommentIcon(id, show: boolean) { + getCodeRow(id).find(".icon-comments").toggleClass("invisible", !show); +} diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/shared/layout.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/layout.ts new file mode 100644 index 00000000000..9b0f548edb1 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/src/shared/layout.ts @@ -0,0 +1,8 @@ +// Enable tooltip and Popovers +declare const bootstrap: any; + +const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') +const tooltipList = ([...tooltipTriggerList]).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) + +const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]') +const popoverList = ([...popoverTriggerList]).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)) diff --git a/src/dotnet/APIView/APIViewWeb/Client/src/shared/off-canvas.ts b/src/dotnet/APIView/APIViewWeb/Client/src/shared/off-canvas.ts new file mode 100644 index 00000000000..d8f208b7f3a --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Client/src/shared/off-canvas.ts @@ -0,0 +1,11 @@ +// Functions to Control right OffCanvas Menu +export function rightOffCanvasNavToggle(mainContainser: String) { + if ($(".right-offcanvas").css("width") == '0px') { + $(`#${mainContainser}`).addClass("move-main-content-container-left"); + $("#right-offcanvas-menu").addClass("show-offcanvas"); + } + else { + $("#right-offcanvas-menu").removeClass("show-offcanvas"); + $(`#${mainContainser}`).removeClass("move-main-content-container-left"); + } +} diff --git a/src/dotnet/APIView/APIViewWeb/Client/tsconfig.json b/src/dotnet/APIView/APIViewWeb/Client/tsconfig.json index c1a76b12743..c6faa1bcede 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/tsconfig.json +++ b/src/dotnet/APIView/APIViewWeb/Client/tsconfig.json @@ -14,9 +14,7 @@ "noImplicitAny": false, "types": [ "webpack-env", - "jquery", - "jqueryui", - "node" + "jquery" ], "paths": { "@/*": [ diff --git a/src/dotnet/APIView/APIViewWeb/Client/webpack.config.js b/src/dotnet/APIView/APIViewWeb/Client/webpack.config.js index 7b21a5f81e8..fe346158635 100644 --- a/src/dotnet/APIView/APIViewWeb/Client/webpack.config.js +++ b/src/dotnet/APIView/APIViewWeb/Client/webpack.config.js @@ -1,31 +1,13 @@ const path = require('path'); - const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const { DuplicatesPlugin } = require("inspectpack/plugin"); module.exports = { mode: "production", - entry: { - comments: './src/comments.ts', - revisions: './src/revisions.ts', - fileInput: './src/file-input.ts', - review: './src/review.ts', - reviews: './src/reviews.ts', - userProfile: './src/user-profile.ts', - site: './css/site.scss', - c: './css/c.scss', - cplusplus: './css/cplusplus.scss', - csharp: './css/csharp.scss', - go: './css/go.scss', - java: './css/java.scss', - javascript: './css/javascript.scss', - json: './css/json.scss', - kotlin: './css/kotlin.scss', - python: './css/python.scss', - swagger: './css/swagger.scss', - swift: './css/swift.scss', - xml: './css/xml.scss', - usagesample: './css/usageSample.scss' - }, + entry: [ + './src/main.ts', + './css/main.scss' + ], devtool: 'source-map', module: { rules: [ @@ -58,14 +40,18 @@ module.exports = { }, plugins: [ new MiniCssExtractPlugin({ - filename: '[name].css' + filename: 'main.css' }), + new DuplicatesPlugin({ + emitErrors: false, + verbose: false + }) ], resolve: { extensions: [ '.tsx', '.ts', '.js' ], }, output: { - filename: '[name].js', + filename: 'main.js', path: path.resolve(__dirname, '../wwwroot'), }, } diff --git a/src/dotnet/APIView/APIViewWeb/Controllers/CommentsController.cs b/src/dotnet/APIView/APIViewWeb/Controllers/CommentsController.cs index 7ba499dd5a7..1f6a2e43033 100644 --- a/src/dotnet/APIView/APIViewWeb/Controllers/CommentsController.cs +++ b/src/dotnet/APIView/APIViewWeb/Controllers/CommentsController.cs @@ -39,7 +39,6 @@ public async Task Add(string reviewId, string revisionId, string e foreach(string user in taggedUsers) { comment.TaggedUsers.Add(user); - await _notificationManager.NotifyUserOnCommentTag(user, comment); } await _commentsManager.AddCommentAsync(User, comment); diff --git a/src/dotnet/APIView/APIViewWeb/Controllers/UserProfileController.cs b/src/dotnet/APIView/APIViewWeb/Controllers/UserProfileController.cs index 3b056126755..e4465762d6f 100644 --- a/src/dotnet/APIView/APIViewWeb/Controllers/UserProfileController.cs +++ b/src/dotnet/APIView/APIViewWeb/Controllers/UserProfileController.cs @@ -19,13 +19,19 @@ public UserProfileController(IUserProfileManager userProfileManager, UserPrefere } [HttpPut] - public ActionResult UpdateReviewPageSettings(bool? hideLineNumbers = null, bool? hideLeftNavigation = null, bool? showHiddenApis = null) + public ActionResult UpdateReviewPageSettings(bool? hideLineNumbers = null, bool? hideLeftNavigation = null, + bool? showHiddenApis = null, bool? hideReviewPageOptions = null, bool? hideIndexPageOptions = null, + bool? showComments = null, bool? showSystemComments = null) { _userPreferenceCache.UpdateUserPreference(new UserPreferenceModel() { HideLeftNavigation = hideLeftNavigation, HideLineNumbers = hideLineNumbers, - ShowHiddenApis = showHiddenApis + ShowHiddenApis = showHiddenApis, + HideReviewPageOptions = hideReviewPageOptions, + HideIndexPageOptions = hideIndexPageOptions, + ShowComments = showComments, + ShowSystemComments = showSystemComments }, User); return Ok(); } diff --git a/src/dotnet/APIView/APIViewWeb/Helpers/AutoMapperProfiles.cs b/src/dotnet/APIView/APIViewWeb/Helpers/AutoMapperProfiles.cs index ed85101249c..3de53a6ea05 100644 --- a/src/dotnet/APIView/APIViewWeb/Helpers/AutoMapperProfiles.cs +++ b/src/dotnet/APIView/APIViewWeb/Helpers/AutoMapperProfiles.cs @@ -1,4 +1,4 @@ -using APIViewWeb.Models; +using APIViewWeb.Models; using AutoMapper; using Microsoft.CodeAnalysis.Diagnostics; @@ -17,7 +17,11 @@ public AutoMapperProfiles() .ForMember(dest => dest.HideLineNumbers, opt => opt.MapFrom((src, dest) => src._hideLineNumbers != null ? src._hideLineNumbers : dest._hideLineNumbers)) .ForMember(dest => dest.HideLeftNavigation, opt => opt.MapFrom((src, dest) => src._hideLeftNavigation != null ? src._hideLeftNavigation : dest._hideLeftNavigation)) .ForMember(dest => dest.Theme, opt => opt.MapFrom((src, dest) => src._theme != null ? src._theme : dest._theme)) - .ForMember(dest => dest.ShowHiddenApis, opt => opt.MapFrom((src, dest) => src._showHiddenApis != null ? src._showHiddenApis : dest._showHiddenApis)); + .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.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)); } } diff --git a/src/dotnet/APIView/APIViewWeb/Managers/CommentsManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/CommentsManager.cs index f94a44e9a10..38970c695eb 100644 --- a/src/dotnet/APIView/APIViewWeb/Managers/CommentsManager.cs +++ b/src/dotnet/APIView/APIViewWeb/Managers/CommentsManager.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Authorization; using Newtonsoft.Json; using Microsoft.Extensions.Options; +using Microsoft.TeamFoundation.Common; namespace APIViewWeb.Managers { @@ -91,6 +92,7 @@ public async Task AddCommentAsync(ClaimsPrincipal user, CommentModel comment) await _commentsRepository.UpsertCommentAsync(comment); if (!comment.IsResolve) { + await _notificationManager.NotifyUserOnCommentTag(comment); await _notificationManager.NotifySubscribersOnComment(user, comment); } } @@ -103,18 +105,16 @@ public async Task UpdateCommentAsync(ClaimsPrincipal user, string comment.Comment = commentText; comment.Username = user.GetGitHubLogin(); - var newTaggedUsers = new HashSet(); foreach (var taggedUser in taggedUsers) { - if (!comment.TaggedUsers.Contains(taggedUser)) + if (!string.IsNullOrEmpty(taggedUser)) { - await _notificationManager.NotifyUserOnCommentTag(taggedUser, comment); + comment.TaggedUsers.Add(taggedUser); } - newTaggedUsers.Add(taggedUser); } - comment.TaggedUsers = newTaggedUsers; await _commentsRepository.UpsertCommentAsync(comment); + await _notificationManager.NotifyUserOnCommentTag(comment); await _notificationManager.NotifySubscribersOnComment(user, comment); return comment; } diff --git a/src/dotnet/APIView/APIViewWeb/Managers/INotificationManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/INotificationManager.cs index 330b7d77dee..840383805d6 100644 --- a/src/dotnet/APIView/APIViewWeb/Managers/INotificationManager.cs +++ b/src/dotnet/APIView/APIViewWeb/Managers/INotificationManager.cs @@ -8,7 +8,7 @@ namespace APIViewWeb.Managers public interface INotificationManager { public Task NotifySubscribersOnComment(ClaimsPrincipal user, CommentModel comment); - public Task NotifyUserOnCommentTag(string username, CommentModel comment); + public Task NotifyUserOnCommentTag(CommentModel comment); public Task NotifyApproversOfReview(ClaimsPrincipal user, string reviewId, HashSet reviewers); public Task NotifySubscribersOnNewRevisionAsync(ReviewRevisionModel revision, ClaimsPrincipal user); public Task ToggleSubscribedAsync(ClaimsPrincipal user, string reviewId); diff --git a/src/dotnet/APIView/APIViewWeb/Managers/IPullRequestManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/IPullRequestManager.cs index 15ef8e785fe..eaa714555d0 100644 --- a/src/dotnet/APIView/APIViewWeb/Managers/IPullRequestManager.cs +++ b/src/dotnet/APIView/APIViewWeb/Managers/IPullRequestManager.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Threading.Tasks; +using APIViewWeb.Models; namespace APIViewWeb.Managers { @@ -8,5 +10,8 @@ public Task DetectApiChanges(string buildId, string artifactName, string string commitSha, string repoName, string packageName, int prNumber, string hostName, string codeFileName = null, string baselineCodeFileName = null, bool commentOnPR = true, string language = null, string project = "public"); public Task CleanupPullRequestData(); + + public Task> GetPullRequestsModel(string reviewId); + public Task> GetPullRequestsModel(int pullRequestNumber, string repoName); } } diff --git a/src/dotnet/APIView/APIViewWeb/Managers/NotificationManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/NotificationManager.cs index 8f46f38d09e..d6fbca8855b 100644 --- a/src/dotnet/APIView/APIViewWeb/Managers/NotificationManager.cs +++ b/src/dotnet/APIView/APIViewWeb/Managers/NotificationManager.cs @@ -4,52 +4,61 @@ using APIView.Identity; using APIViewWeb.Models; using Microsoft.Extensions.Configuration; -using SendGrid; -using SendGrid.Helpers.Mail; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Security.Claims; using System.Text; using System.Threading.Tasks; -using Microsoft.TeamFoundation.Common; using APIViewWeb.Repositories; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.ApplicationInsights; +using System.Net.Http; +using System.Net.Http.Json; namespace APIViewWeb.Managers { public class NotificationManager : INotificationManager { - private readonly string _endpoint; + private readonly string _apiviewEndpoint; private readonly ICosmosReviewRepository _reviewRepository; private readonly ICosmosUserProfileRepository _userProfileRepository; - private readonly ISendGridClient _sendGridClient; + private readonly IConfiguration _configuration; + private readonly string _testEmailToAddress; + private readonly string _emailSenderServiceUrl; private const string ENDPOINT_SETTING = "Endpoint"; - private const string SENDGRID_KEY_SETTING = "SendGrid:Key"; - private const string FROM_ADDRESS = "apiview-noreply@microsoft.com"; - private const string REPLY_TO_HEADER = "In-Reply-To"; - private const string REFERENCES_HEADER = "References"; - public NotificationManager(IConfiguration configuration, ICosmosReviewRepository reviewRepository, - ICosmosUserProfileRepository userProfileRepository, ISendGridClient sendGridClient = null) + static TelemetryClient _telemetryClient = new(TelemetryConfiguration.CreateDefault()); + + public NotificationManager(IConfiguration configuration, + ICosmosReviewRepository reviewRepository, + ICosmosUserProfileRepository userProfileRepository) { - _sendGridClient = sendGridClient ?? new SendGridClient(configuration[SENDGRID_KEY_SETTING]); - _endpoint = configuration.GetValue(ENDPOINT_SETTING); + _apiviewEndpoint = configuration.GetValue(ENDPOINT_SETTING); _reviewRepository = reviewRepository; _userProfileRepository = userProfileRepository; + _configuration = configuration; + _testEmailToAddress = configuration["apiview-email-test-address"] ?? ""; + _emailSenderServiceUrl = configuration["azure-sdk-emailer-url"] ?? ""; } public async Task NotifySubscribersOnComment(ClaimsPrincipal user, CommentModel comment) { var review = await _reviewRepository.GetReviewAsync(comment.ReviewId); - await SendEmailsAsync(review, user, GetPlainTextContent(comment), GetHtmlContent(comment, review)); + await SendEmailsAsync(review, user, GetHtmlContent(comment, review), comment.TaggedUsers); } - public async Task NotifyUserOnCommentTag(string username, CommentModel comment) + public async Task NotifyUserOnCommentTag(CommentModel comment) { - var review = await _reviewRepository.GetReviewAsync(comment.ReviewId); - var user = await _userProfileRepository.TryGetUserProfileAsync(username); - await SendUserEmailsAsync(review, user, GetCommentTagPlainTextContent(comment), GetCommentTagHtmlContent(comment, review)); + foreach (string username in comment.TaggedUsers) + { + if(string.IsNullOrEmpty(username)) continue; + var review = await _reviewRepository.GetReviewAsync(comment.ReviewId); + var user = await _userProfileRepository.TryGetUserProfileAsync(username); + await SendUserEmailsAsync(review, user, GetCommentTagHtmlContent(comment, review)); + } } public async Task NotifyApproversOfReview(ClaimsPrincipal user, string reviewId, HashSet reviewers) @@ -60,7 +69,6 @@ public async Task NotifyApproversOfReview(ClaimsPrincipal user, string reviewId, { var reviewerProfile = await _userProfileRepository.TryGetUserProfileAsync(reviewer); await SendUserEmailsAsync(review, reviewerProfile, - GetApproverReviewContentHeading(userProfile, false), GetApproverReviewHtmlContent(userProfile, review)); } } @@ -68,12 +76,10 @@ await SendUserEmailsAsync(review, reviewerProfile, public async Task NotifySubscribersOnNewRevisionAsync(ReviewRevisionModel revision, ClaimsPrincipal user) { var review = revision.Review; - var uri = new Uri($"{_endpoint}/Assemblies/Review/{review.ReviewId}"); - var plainTextContent = $"A new revision, {revision.DisplayName}," + - $" was uploaded by {revision.Author}."; + var uri = new Uri($"{_apiviewEndpoint}/Assemblies/Review/{review.ReviewId}"); var htmlContent = $"A new revision, {revision.DisplayName}," + $" was uploaded by {revision.Author}."; - await SendEmailsAsync(review, user, plainTextContent, htmlContent); + await SendEmailsAsync(review, user, htmlContent, null); } public async Task ToggleSubscribedAsync(ClaimsPrincipal user, string reviewId) @@ -116,10 +122,10 @@ public static string GetUserEmail(ClaimsPrincipal user) => private string GetApproverReviewHtmlContent(UserProfileModel user, ReviewModel review) { var reviewName = review.Name; - var reviewLink = new Uri($"{_endpoint}/Assemblies/Review/{review.ReviewId}"); + var reviewLink = new Uri($"{_apiviewEndpoint}/Assemblies/Review/{review.ReviewId}"); var poster = user.UserName; - var userLink = new Uri($"{_endpoint}/Assemblies/Profile/{poster}"); - var requestsLink = new Uri($"{_endpoint}/Assemblies/RequestedReviews/"); + var userLink = new Uri($"{_apiviewEndpoint}/Assemblies/Profile/{poster}"); + var requestsLink = new Uri($"{_apiviewEndpoint}/Assemblies/RequestedReviews/"); var sb = new StringBuilder(); sb.Append($"{poster}"); sb.Append($" requested you to review {reviewName}"); @@ -131,10 +137,10 @@ private string GetApproverReviewHtmlContent(UserProfileModel user, ReviewModel r private string GetCommentTagHtmlContent(CommentModel comment, ReviewModel review) { var reviewName = review.Name; - var reviewLink = new Uri($"{_endpoint}/Assemblies/Review/{review.ReviewId}#{Uri.EscapeDataString(comment.ElementId)}"); + var reviewLink = new Uri($"{_apiviewEndpoint}/Assemblies/Review/{review.ReviewId}#{Uri.EscapeDataString(comment.ElementId)}"); var commentText = comment.Comment; var poster = comment.Username; - var userLink = new Uri($"{_endpoint}/Assemblies/Profile/{poster}"); + var userLink = new Uri($"{_apiviewEndpoint}/Assemblies/Profile/{poster}"); var sb = new StringBuilder(); sb.Append($"{poster}"); sb.Append($" mentioned you in {reviewName}"); @@ -148,7 +154,7 @@ private string GetCommentTagHtmlContent(CommentModel comment, ReviewModel review private string GetHtmlContent(CommentModel comment, ReviewModel review) { - var uri = new Uri($"{_endpoint}/Assemblies/Review/{review.ReviewId}#{Uri.EscapeDataString(comment.ElementId)}"); + var uri = new Uri($"{_apiviewEndpoint}/Assemblies/Review/{review.ReviewId}#{Uri.EscapeDataString(comment.ElementId)}"); var sb = new StringBuilder(); sb.Append(GetContentHeading(comment, true)); sb.Append("

"); @@ -158,94 +164,87 @@ private string GetHtmlContent(CommentModel comment, ReviewModel review) return sb.ToString(); } - private static string GetCommentTagPlainTextContent(CommentModel comment) - { - var sb = new StringBuilder(); - sb.Append(GetCommentTagContentHeading(comment, false)); - return sb.ToString(); - } - - private string GetPlainTextContent(CommentModel comment) - { - var sb = new StringBuilder(); - sb.Append(GetContentHeading(comment, false)); - sb.Append("\r\n"); - sb.Append(CommentMarkdownExtensions.MarkdownAsPlainText(comment.Comment)); - return sb.ToString(); - } - - private static string GetApproverReviewContentHeading(UserProfileModel user, bool includeHtml) => - $"{(includeHtml ? $"{user.UserName}" : $"{user.UserName}")} requested you to review API."; - - private static string GetCommentTagContentHeading(CommentModel comment, bool includeHtml) => - $"{(includeHtml ? $"{comment.Username}" : $"{comment.Username}")} tagged you in a comment."; - private static string GetContentHeading(CommentModel comment, bool includeHtml) => $"{(includeHtml ? $"{comment.Username}" : $"{comment.Username}")} commented on this review."; - private async Task SendUserEmailsAsync(ReviewModel review, UserProfileModel user, string plainTextContent, string htmlContent) + private async Task SendUserEmailsAsync(ReviewModel review, UserProfileModel user, string htmlContent) { - var userBackup = new ClaimsPrincipal(); - EmailAddress e; - if (!user.Email.IsNullOrEmpty()) + // Always send email to a test address when test address is configured. + if (string.IsNullOrEmpty(user.Email)) { - e = new EmailAddress(user.Email, user.UserName); + _telemetryClient.TrackTrace($"Email address is not available for user {user.UserName}. Not sending email."); + return; } - else + + await SendEmail(user.Email, $"Notification from APIView - {review.DisplayName}", htmlContent); + } + private async Task SendEmailsAsync(ReviewModel review, ClaimsPrincipal user, string htmlContent, ISet notifiedUsers) + { + var initiatingUserEmail = GetUserEmail(user); + // Find email address of already tagged users in comment + HashSet notifiedEmails = new HashSet(); + if (notifiedUsers != null) { - var backupEmail = GetUserEmail(userBackup); - if (!backupEmail.IsNullOrEmpty()) - { - e = new EmailAddress(backupEmail, user.UserName); - } - else + foreach (var username in notifiedUsers) { - return; + var email = await GetEmailAddress(username); + notifiedEmails.Add(email); } - } - var from = new EmailAddress(FROM_ADDRESS); - var msg = MailHelper.CreateSingleEmail( - from, - e, - user.UserName, - plainTextContent, - htmlContent); - var threadHeader = $"<{review.ReviewId}{FROM_ADDRESS}>"; - msg.AddHeader(REPLY_TO_HEADER, threadHeader); - msg.AddHeader(REFERENCES_HEADER, threadHeader); - await _sendGridClient.SendEmailAsync(msg); - } - private async Task SendEmailsAsync(ReviewModel review, ClaimsPrincipal user, string plainTextContent, string htmlContent) - { - var initiatingUserEmail = GetUserEmail(user); + } var subscribers = review.Subscribers.ToList() - .Where(e => e != initiatingUserEmail) // don't include the initiating user in the email - .Select(e => new EmailAddress(e)) + .Where(e => e != initiatingUserEmail && !notifiedEmails.Contains(e)) // don't include the initiating user and tagged users in the comment .ToList(); if (subscribers.Count == 0) { return; } - var from = new EmailAddress(FROM_ADDRESS, GetUserName(user)); - var msg = MailHelper.CreateMultipleEmailsToMultipleRecipients( - from, - subscribers, - Enumerable.Repeat(review.DisplayName, review.Subscribers.Count).ToList(), - plainTextContent, - htmlContent, - Enumerable.Repeat(new Dictionary(), review.Subscribers.Count).ToList()); - var threadHeader = $"<{review.ReviewId}{FROM_ADDRESS}>"; - msg.AddHeader(REPLY_TO_HEADER, threadHeader); - msg.AddHeader(REFERENCES_HEADER, threadHeader); - await _sendGridClient.SendEmailAsync(msg); + var emailToList = string.Join(",", subscribers); + if (string.IsNullOrEmpty(emailToList)) + { + _telemetryClient.TrackTrace($"Email address is not available for subscribers, review {review.ReviewId}. Not sending email."); + return; + } + + await SendEmail(emailToList, $"Update on APIView - {review.DisplayName} from {GetUserName(user)}", htmlContent); } + private async Task SendEmail(string emailToList, string subject, string content) + { + if (string.IsNullOrEmpty(_emailSenderServiceUrl)) + { + _telemetryClient.TrackTrace($"Email sender service URL is not configured. Email will not be sent to {emailToList} with subject: {subject}"); + return; + } + + var requestBody = new EmailModel(_testEmailToAddress ?? emailToList, subject, content); + var httpClient = new HttpClient(); + try + { + var response = await httpClient.PostAsync(_emailSenderServiceUrl, JsonContent.Create(requestBody)); + if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Accepted) + { + _telemetryClient.TrackTrace($"Failed to send email to user {emailToList} with subject: {subject}, status code: {response.StatusCode}, Details: {response.ToString}"); + } + } + catch (Exception ex) + { + _telemetryClient.TrackException(ex); + } + } private static string GetUserName(ClaimsPrincipal user) { var name = user.FindFirstValue(ClaimConstants.Name); return string.IsNullOrEmpty(name) ? user.FindFirstValue(ClaimConstants.Login) : name; } + + private async Task GetEmailAddress(string username) + { + if (string.IsNullOrEmpty(username)) + return ""; + var user = await _userProfileRepository.TryGetUserProfileAsync(username); + return user.Email; + } } } diff --git a/src/dotnet/APIView/APIViewWeb/Managers/PullRequestManager.cs b/src/dotnet/APIView/APIViewWeb/Managers/PullRequestManager.cs index 6976d5a00ea..9f2fb2694a4 100644 --- a/src/dotnet/APIView/APIViewWeb/Managers/PullRequestManager.cs +++ b/src/dotnet/APIView/APIViewWeb/Managers/PullRequestManager.cs @@ -178,6 +178,15 @@ public async Task CleanupPullRequestData() } } + public async Task> GetPullRequestsModel(string reviewId) { + return await _pullRequestsRepository.GetPullRequestsAsync(reviewId); + } + + public async Task> GetPullRequestsModel(int pullRequestNumber, string repoName) + { + return await _pullRequestsRepository.GetPullRequestsAsync(pullRequestNumber, repoName); + } + private async Task CreateOrUpdateComment(List prReviews, string repoOwner, string repoName, int prNumber, string hostName) { var existingComment = await GetExistingCommentForPackage(repoOwner, repoName, prNumber); @@ -317,11 +326,16 @@ private async Task CreateRevisionIfRequired(CodeFile codeFile, { // Check if API surface level matches with any revisions var renderedCodeFile = new RenderedCodeFile(codeFile); - if (await IsReviewSame(review, renderedCodeFile)) + // pullRequestModel.ReviewId == null means: First time getting a request to check for API changes in the given package for a PR + if (pullRequestModel.ReviewId == null) { - return; - } - + //No API changes detected from baseline + if (await IsReviewSame(review, renderedCodeFile)) + { + return; + } + } + // Below steps will remove last revision from previously created API review from the pull request and recreate new revision using latest token code file if (pullRequestModel.ReviewId != null) { //Refresh baseline using latest from automatic review diff --git a/src/dotnet/APIView/APIViewWeb/Models/CommentModel.cs b/src/dotnet/APIView/APIViewWeb/Models/CommentModel.cs index 1a84e52ea6d..fda162c07e1 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/CommentModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/CommentModel.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; using System.Collections.Generic; using Newtonsoft.Json; diff --git a/src/dotnet/APIView/APIViewWeb/Models/CommentThreadModel.cs b/src/dotnet/APIView/APIViewWeb/Models/CommentThreadModel.cs index afb8f5333d1..9e06446e552 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/CommentThreadModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/CommentThreadModel.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System.Collections.Generic; using System.Linq; namespace APIViewWeb.Models diff --git a/src/dotnet/APIView/APIViewWeb/Models/EmailModel.cs b/src/dotnet/APIView/APIViewWeb/Models/EmailModel.cs new file mode 100644 index 00000000000..1db4b4268a7 --- /dev/null +++ b/src/dotnet/APIView/APIViewWeb/Models/EmailModel.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using Newtonsoft.Json; + +namespace APIViewWeb.Models +{ + public class EmailModel + { + public string EmailTo { get; set; } + public string Subject { get; set; } + public string Body { get; set; } + + public EmailModel(string emailTo, string subject, string body) + { + EmailTo = emailTo; + Subject = subject; + Body = body; + } + + public string Serialize() + { + return JsonConvert.SerializeObject(this); + } + } +} diff --git a/src/dotnet/APIView/APIViewWeb/Models/GithubUser.cs b/src/dotnet/APIView/APIViewWeb/Models/GithubUser.cs index 5dcae02780e..717d27b7369 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/GithubUser.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/GithubUser.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using Newtonsoft.Json; namespace APIViewWeb.Models { diff --git a/src/dotnet/APIView/APIViewWeb/Models/OpenSourceUserInfo.cs b/src/dotnet/APIView/APIViewWeb/Models/OpenSourceUserInfo.cs index 2f9ad811513..2c3b20c4d0b 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/OpenSourceUserInfo.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/OpenSourceUserInfo.cs @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using System.Collections.Generic; using Newtonsoft.Json; diff --git a/src/dotnet/APIView/APIViewWeb/Models/PackageGroupMdel.cs b/src/dotnet/APIView/APIViewWeb/Models/PackageGroupMdel.cs index fc9ed6ddfe2..bbb7541f847 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/PackageGroupMdel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/PackageGroupMdel.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System.Collections.Generic; namespace APIViewWeb.Models { diff --git a/src/dotnet/APIView/APIViewWeb/Models/PackageModel.cs b/src/dotnet/APIView/APIViewWeb/Models/PackageModel.cs index 45e1e1ff9b9..d3a7f584352 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/PackageModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/PackageModel.cs @@ -1,4 +1,6 @@ -using CsvHelper.Configuration.Attributes; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using CsvHelper.Configuration.Attributes; namespace APIViewWeb.Models { diff --git a/src/dotnet/APIView/APIViewWeb/Models/ReviewDisplayModel.cs b/src/dotnet/APIView/APIViewWeb/Models/ReviewDisplayModel.cs index 9b4a425619d..bd86082f88c 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/ReviewDisplayModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/ReviewDisplayModel.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; namespace APIViewWeb.Models { diff --git a/src/dotnet/APIView/APIViewWeb/Models/ReviewGenPipelineParamModel.cs b/src/dotnet/APIView/APIViewWeb/Models/ReviewGenPipelineParamModel.cs index 1efd454fec3..bd3c42453ef 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/ReviewGenPipelineParamModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/ReviewGenPipelineParamModel.cs @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. namespace APIViewWeb.Models { public class ReviewGenPipelineParamModel diff --git a/src/dotnet/APIView/APIViewWeb/Models/ReviewType.cs b/src/dotnet/APIView/APIViewWeb/Models/ReviewType.cs index adfc09db149..ea3c45adb3f 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/ReviewType.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/ReviewType.cs @@ -1,5 +1,3 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. diff --git a/src/dotnet/APIView/APIViewWeb/Models/ServiceGroupModel.cs b/src/dotnet/APIView/APIViewWeb/Models/ServiceGroupModel.cs index 4ce8d8672a1..b321e081060 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/ServiceGroupModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/ServiceGroupModel.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System.Collections.Generic; namespace APIViewWeb.Models { diff --git a/src/dotnet/APIView/APIViewWeb/Models/UsageSampleModel.cs b/src/dotnet/APIView/APIViewWeb/Models/UsageSampleModel.cs index 52831e42b8f..fa5f967c8f9 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/UsageSampleModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/UsageSampleModel.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using Newtonsoft.Json; using System.Collections.Generic; namespace APIViewWeb diff --git a/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs b/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs index 7623a0c7dec..d4a9d26c92e 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/UserPreferenceModel.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System.Collections.Generic; using CsvHelper.Configuration.Attributes; using Newtonsoft.Json; @@ -14,6 +16,10 @@ public class UserPreferenceModel internal bool? _hideLineNumbers; internal bool? _hideLeftNavigation; internal bool? _showHiddenApis; + internal bool? _hideReviewPageOptions; + internal bool? _hideIndexPageOptions; + internal bool? _showComments; + internal bool? _showSystemComments; internal string _theme; public string UserName { get; set; } @@ -72,5 +78,33 @@ public bool? ShowHiddenApis { get => _showHiddenApis ?? false; set => _showHiddenApis = value; } + + [Name("HideReviewPageOptions")] + public bool? HideReviewPageOptions + { + get => _hideReviewPageOptions ?? false; + set => _hideReviewPageOptions = value; + } + + [Name("HideIndexPageOptions")] + public bool? HideIndexPageOptions + { + get => _hideIndexPageOptions ?? false; + set => _hideIndexPageOptions = value; + } + + [Name("ShowComments")] + public bool? ShowComments + { + get => _showComments ?? true; + set => _showComments = value; + } + + [Name("ShowSystemComments")] + public bool? ShowSystemComments + { + get => _showSystemComments ?? true; + set => _showSystemComments = value; + } } } diff --git a/src/dotnet/APIView/APIViewWeb/Models/UserProfileModel.cs b/src/dotnet/APIView/APIViewWeb/Models/UserProfileModel.cs index 946f38132d4..1a1a55a40d6 100644 --- a/src/dotnet/APIView/APIViewWeb/Models/UserProfileModel.cs +++ b/src/dotnet/APIView/APIViewWeb/Models/UserProfileModel.cs @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. using System.Collections.Generic; using System.Security.Claims; using Newtonsoft.Json; diff --git a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Conversation.cshtml b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Conversation.cshtml index 2b3d2f646ca..4d82a2d6234 100644 --- a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Conversation.cshtml +++ b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Conversation.cshtml @@ -3,101 +3,109 @@ @using APIViewWeb.Helpers @using APIViewWeb.Models @{ - Layout = "ReviewLayout"; + Layout = "Shared/_Layout"; ViewData["Title"] = "Conversation"; TempData["UserPreference"] = PageModelHelpers.GetUserPreference(Model._preferenceCache, User); } -
-
-
-
- +
+
+
+
+
-
-
- @if (!Model.Threads.Any() && !Model.UsageSampleThreads.Any()) +
+
+
+
+
+ +
+
+
+
+ @if (!Model.Threads.Any() && !Model.UsageSampleThreads.Any()) + { +
There are no comments in the review.
+ } + else + { + @if (Model.Threads.Any()) { -
There are no comments in the review.
+
+ @foreach (var revision in Model.Threads) + { + var divId = $"rev-{revision.Key.RevisionId}"; + +
+ @revision.Key.DisplayName +
+
+ + @foreach (var thread in revision.Value) + { + var elementId = thread.LineId; + + + + } +
@elementId +
+
+ } +
} - else + @if (Model.UsageSampleThreads.Count > 0) { - @if (Model.Threads.Any()) - { -
- @foreach (var revision in Model.Threads) - { - var divId = $"rev-{revision.Key.RevisionId}"; + var skipped = 1; +
+
+ @foreach (var revision in Model.UsageSampleThreads.Reverse()) + { + var divId = $"rev-{revision.Key.RevisionNumber}"; + var displayName = $"Usage sample - rev {@revision.Key.RevisionNumber}"; -
- @revision.Key.DisplayName -
-
- - @foreach (var thread in revision.Value) - { + @if (revision.Key.RevisionTitle != null) + { + displayName += " - " + @revision.Key.RevisionTitle; + } + else if (revision.Key.OriginalFileName != null) + { + displayName += " - " + revision.Key.OriginalFileName; + } +
+ @displayName +
+
+
+ @foreach (var thread in revision.Value.OrderBy(e => int.Parse(e.LineId.Split("-").Last()))) + { + @if(thread.Comments.Any()) + { var elementId = thread.LineId; - + - - } -
@elementId +
+ @Html.Raw(Model.SampleLines.ElementAt(revision.Key.RevisionNumber-skipped).ElementAt(int.Parse(thread.Comments.First().ElementId.Split("-").Last())-1)) +
-
- } -
- } - @if (Model.UsageSampleThreads.Count > 0) - { - var skipped = 1; -
-
- @foreach (var revision in Model.UsageSampleThreads.Reverse()) - { - var divId = $"rev-{revision.Key.RevisionNumber}"; - var displayName = $"Usage sample - rev {@revision.Key.RevisionNumber}"; - - @if (revision.Key.RevisionTitle != null) - { - displayName += " - " + @revision.Key.RevisionTitle; - } - else if (revision.Key.OriginalFileName != null) - { - displayName += " - " + revision.Key.OriginalFileName; - } -
- @displayName -
-
- - @foreach (var thread in revision.Value.OrderBy(e => int.Parse(e.LineId.Split("-").Last()))) - { - @if(thread.Comments.Any()) - { - var elementId = thread.LineId; - - - - } - } -
- @Html.Raw(Model.SampleLines.ElementAt(revision.Key.RevisionNumber-skipped).ElementAt(int.Parse(thread.Comments.First().ElementId.Split("-").Last())-1)) - -
-
- } -
- } + + } + +
+ } +
} + } -
+
diff --git a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Index.cshtml b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Index.cshtml index 9bea85f9483..178200068f5 100644 --- a/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Index.cshtml +++ b/src/dotnet/APIView/APIViewWeb/Pages/Assemblies/Index.cshtml @@ -7,257 +7,122 @@ @using APIViewWeb.Models @{ ViewData["Title"] = "Reviews"; - TempData["UserPreference"] = PageModelHelpers.GetUserPreference(Model._preferenceCache, User); + var userPreference = PageModelHelpers.GetUserPreference(Model._preferenceCache, User); + TempData["UserPreference"] = userPreference; } -@section Scripts -{ - - -} - +
+ +@{ + var offCanvasClass = " show-offcanvas"; + if (userPreference.HideIndexPageOptions.HasValue && userPreference.HideIndexPageOptions == true) + offCanvasClass = String.Empty; +} +
+
+

Filters

+
+ Languages + +
+
+ State + +
+
+ Status + +
+
+ Type + +
-
+ + +
+
+
+@{ + var mainContainerClass = " move-main-content-container-left"; + if (userPreference.HideIndexPageOptions.HasValue && userPreference.HideIndexPageOptions == true) + mainContainerClass = String.Empty; +} +