mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-02 17:29:43 +01:00
Compare commits
2 Commits
main
...
issue/4435
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb19bb9966 | ||
|
|
33142a556c |
15
.github/actions/spell-check/expect.txt
vendored
15
.github/actions/spell-check/expect.txt
vendored
@@ -635,7 +635,6 @@ GMEM
|
||||
GNumber
|
||||
googleai
|
||||
googlegemini
|
||||
Gotchas
|
||||
gpedit
|
||||
gpo
|
||||
GPOCA
|
||||
@@ -648,6 +647,8 @@ GSM
|
||||
gtm
|
||||
guiddata
|
||||
GUITHREADINFO
|
||||
Gotcha
|
||||
Gotchas
|
||||
GValue
|
||||
gwl
|
||||
GWLP
|
||||
@@ -893,9 +894,9 @@ Lclean
|
||||
Ldone
|
||||
Ldr
|
||||
LEFTALIGN
|
||||
leftclick
|
||||
LEFTSCROLLBAR
|
||||
LEFTTEXT
|
||||
leftclick
|
||||
LError
|
||||
LEVELID
|
||||
LExit
|
||||
@@ -1021,12 +1022,9 @@ MENUITEMINFO
|
||||
MENUITEMINFOW
|
||||
MERGECOPY
|
||||
MERGEPAINT
|
||||
Metacharacter
|
||||
metadatamatters
|
||||
Metadatas
|
||||
Metacharacter
|
||||
metafile
|
||||
Metacharacter
|
||||
mfc
|
||||
Mgmt
|
||||
Microwaved
|
||||
@@ -1073,7 +1071,7 @@ mouseutils
|
||||
MOVESIZEEND
|
||||
MOVESIZESTART
|
||||
MRM
|
||||
Mrt
|
||||
MRT
|
||||
mru
|
||||
MSAL
|
||||
msc
|
||||
@@ -1491,9 +1489,7 @@ regfile
|
||||
REGISTERCLASSFAILED
|
||||
REGISTRYHEADER
|
||||
REGISTRYPREVIEWEXT
|
||||
registryroot
|
||||
regkey
|
||||
regroot
|
||||
regsvr
|
||||
REINSTALLMODE
|
||||
releaseblog
|
||||
@@ -1538,6 +1534,7 @@ riid
|
||||
RKey
|
||||
RNumber
|
||||
rollups
|
||||
ROOTOWNER
|
||||
rop
|
||||
ROUNDSMALL
|
||||
ROWSETEXT
|
||||
@@ -2174,4 +2171,4 @@ Zoneszonabletester
|
||||
Zoomin
|
||||
zoomit
|
||||
ZOOMITX
|
||||
Zorder
|
||||
Zorder
|
||||
|
||||
@@ -91,7 +91,6 @@ extends:
|
||||
official: true
|
||||
codeSign: true
|
||||
runTests: false
|
||||
buildTests: false
|
||||
signingIdentity:
|
||||
serviceName: $(SigningServiceName)
|
||||
appId: $(SigningAppId)
|
||||
|
||||
@@ -258,7 +258,6 @@ jobs:
|
||||
-restore -graph
|
||||
/p:RestorePackagesConfig=true
|
||||
/p:CIBuild=true
|
||||
/p:BuildTests=${{ parameters.buildTests }}
|
||||
/bl:$(LogOutputDirectory)\build-0-main.binlog
|
||||
${{ parameters.additionalBuildOptions }}
|
||||
$(MSBuildCacheParameters)
|
||||
|
||||
@@ -59,7 +59,6 @@ stages:
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
msBuildCacheIsReadOnly: ${{ parameters.msBuildCacheIsReadOnly }}
|
||||
runTests: ${{ parameters.runTests }}
|
||||
buildTests: true
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
|
||||
@@ -79,9 +78,7 @@ stages:
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Preview
|
||||
${{ else }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Latest
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildConfigurations: [Release]
|
||||
official: false
|
||||
codeSign: false
|
||||
|
||||
@@ -90,15 +90,9 @@ if ($noticeMatch.Success) {
|
||||
$currentNoticePackageList = ""
|
||||
}
|
||||
|
||||
# Test-only packages that are allowed to be in NOTICE.md but not in the build
|
||||
# (e.g., when BuildTests=false, these packages won't appear in the NuGet list)
|
||||
$allowedExtraPackages = @(
|
||||
"- Moq"
|
||||
)
|
||||
|
||||
if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
|
||||
{
|
||||
Write-Host -ForegroundColor Yellow "Notice.md does not exactly match NuGet list. Analyzing differences..."
|
||||
Write-Host -ForegroundColor Red "Notice.md does not match NuGet list."
|
||||
|
||||
# Show detailed differences
|
||||
$generatedPackages = $returnList -split "`r`n|`n" | Where-Object { $_.Trim() -ne "" } | Sort-Object
|
||||
@@ -111,7 +105,7 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
|
||||
# Find packages in proj file list but not in NOTICE.md
|
||||
$missingFromNotice = $generatedPackages | Where-Object { $noticePackages -notcontains $_ }
|
||||
if ($missingFromNotice.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "MissingFromNotice (ERROR - these must be added to NOTICE.md):"
|
||||
Write-Host -ForegroundColor Red "MissingFromNotice:"
|
||||
foreach ($pkg in $missingFromNotice) {
|
||||
Write-Host -ForegroundColor Red " $pkg"
|
||||
}
|
||||
@@ -120,23 +114,10 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
|
||||
|
||||
# Find packages in NOTICE.md but not in proj file list
|
||||
$extraInNotice = $noticePackages | Where-Object { $generatedPackages -notcontains $_ }
|
||||
|
||||
# Filter out allowed extra packages (test-only dependencies)
|
||||
$unexpectedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -notcontains $_ }
|
||||
$allowedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -contains $_ }
|
||||
|
||||
if ($allowedExtra.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Green "ExtraInNotice (OK - allowed test-only packages):"
|
||||
foreach ($pkg in $allowedExtra) {
|
||||
Write-Host -ForegroundColor Green " $pkg"
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($unexpectedExtra.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "ExtraInNotice (ERROR - unexpected packages in NOTICE.md):"
|
||||
foreach ($pkg in $unexpectedExtra) {
|
||||
Write-Host -ForegroundColor Red " $pkg"
|
||||
if ($extraInNotice.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Yellow "ExtraInNotice:"
|
||||
foreach ($pkg in $extraInNotice) {
|
||||
Write-Host -ForegroundColor Yellow " $pkg"
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
@@ -146,17 +127,10 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
|
||||
Write-Host " Proj file list has $($generatedPackages.Count) packages"
|
||||
Write-Host " NOTICE.md has $($noticePackages.Count) packages"
|
||||
Write-Host " MissingFromNotice: $($missingFromNotice.Count) packages"
|
||||
Write-Host " ExtraInNotice (allowed): $($allowedExtra.Count) packages"
|
||||
Write-Host " ExtraInNotice (unexpected): $($unexpectedExtra.Count) packages"
|
||||
Write-Host " ExtraInNotice: $($extraInNotice.Count) packages"
|
||||
Write-Host ""
|
||||
|
||||
# Fail if there are missing packages OR unexpected extra packages
|
||||
if ($missingFromNotice.Count -gt 0 -or $unexpectedExtra.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "FAILED: NOTICE.md mismatch detected."
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host -ForegroundColor Green "PASSED: NOTICE.md matches (with allowed test-only packages)."
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
<Project ToolsVersion="4.0"
|
||||
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<!-- Skip building C++ test projects when BuildTests=false -->
|
||||
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
|
||||
<UsePrecompiledHeaders>false</UsePrecompiledHeaders>
|
||||
<RunCodeAnalysis>false</RunCodeAnalysis>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Project configurations -->
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
|
||||
@@ -19,39 +19,6 @@
|
||||
<PlatformTarget>$(Platform)</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
Completely skip building test projects when BuildTests=false (e.g., Release pipeline).
|
||||
This avoids InternalsVisibleTo/signing issues by not compiling test code at all.
|
||||
Match: projects ending in Test, Tests, UnitTests, UITests, FuzzTests, or in a folder named Tests.
|
||||
Also matches projects starting with UnitTests- (e.g., UnitTests-CommonLib).
|
||||
Also removes all PackageReference/ProjectReference to prevent NuGet restore and dependency builds.
|
||||
Note: Checking both 'false' and 'False' to handle YAML boolean serialization.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(BuildTests)' == 'false' or '$(BuildTests)' == 'False'">
|
||||
<_ProjectName>$(MSBuildProjectName)</_ProjectName>
|
||||
<!-- Match any project ending with "Test" or "Tests" (covers UnitTests, UITests, FuzzTests, etc.) -->
|
||||
<_IsSkippedTestProject Condition="$(_ProjectName.EndsWith('Test'))">true</_IsSkippedTestProject>
|
||||
<_IsSkippedTestProject Condition="$(_ProjectName.EndsWith('Tests'))">true</_IsSkippedTestProject>
|
||||
<!-- Match projects starting with UnitTests- or UITest- prefix -->
|
||||
<_IsSkippedTestProject Condition="$(_ProjectName.StartsWith('UnitTests-'))">true</_IsSkippedTestProject>
|
||||
<_IsSkippedTestProject Condition="$(_ProjectName.StartsWith('UITest-'))">true</_IsSkippedTestProject>
|
||||
<!-- Match projects in a Tests folder -->
|
||||
<_IsSkippedTestProject Condition="$(MSBuildProjectDirectory.Contains('\Tests\'))">true</_IsSkippedTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
|
||||
<EnableDefaultItems>false</EnableDefaultItems>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateGlobalUsings>false</GenerateGlobalUsings>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<!-- Disable all code analysis for skipped test projects -->
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<RunAnalyzers>false</RunAnalyzers>
|
||||
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<Version>$(Version).0</Version>
|
||||
<RepositoryUrl>https://github.com/microsoft/PowerToys</RepositoryUrl>
|
||||
@@ -63,9 +30,7 @@
|
||||
<_PropertySheetDisplayName>PowerToys.Root.Props</_PropertySheetDisplayName>
|
||||
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj' and '$(_IsSkippedTestProject)' != 'true'">
|
||||
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -28,41 +28,4 @@
|
||||
<PropertyGroup Condition="'$(IgnoreExperimentalWarnings)' == 'true'">
|
||||
<NoWarn>$(NoWarn);CS8305;SA1500;CA1852</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Skipped test projects when BuildTests=false: no-op build and remove references.
|
||||
This must be in targets (not props) so it runs AFTER the project file adds its items. -->
|
||||
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
|
||||
<BuildDependsOn />
|
||||
<CoreBuildDependsOn />
|
||||
<RebuildDependsOn />
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- For C# projects: remove all items -->
|
||||
<ItemGroup Condition="'$(_IsSkippedTestProject)' == 'true' and '$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<PackageReference Remove="@(PackageReference)" />
|
||||
<ProjectReference Remove="@(ProjectReference)" />
|
||||
<Reference Remove="@(Reference)" />
|
||||
<Compile Remove="@(Compile)" />
|
||||
<Content Remove="@(Content)" />
|
||||
<EmbeddedResource Remove="@(EmbeddedResource)" />
|
||||
<None Remove="@(None)" />
|
||||
<Using Remove="@(Using)" />
|
||||
<GlobalUsing Remove="@(GlobalUsing)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- For C++ projects (vcxproj): remove all compile/link items to prevent build -->
|
||||
<ItemGroup Condition="'$(_IsSkippedTestProject)' == 'true' and '$(MSBuildProjectExtension)' == '.vcxproj'">
|
||||
<ClCompile Remove="@(ClCompile)" />
|
||||
<ClInclude Remove="@(ClInclude)" />
|
||||
<Link Remove="@(Link)" />
|
||||
<Lib Remove="@(Lib)" />
|
||||
<ProjectReference Remove="@(ProjectReference)" />
|
||||
<None Remove="@(None)" />
|
||||
<ResourceCompile Remove="@(ResourceCompile)" />
|
||||
<Midl Remove="@(Midl)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Note: For C++ skipped test projects, build is effectively skipped by removing all compile items above.
|
||||
We don't define empty Build/Rebuild/Clean targets here because MSBuild Target definitions with Condition
|
||||
on the Target element still override the default targets even when condition is false. -->
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -18,28 +18,13 @@ Advanced Paste is a PowerToys module that provides enhanced clipboard pasting wi
|
||||
|
||||
TODO: Add implementation details
|
||||
|
||||
### Paste with AI Preview
|
||||
|
||||
The "Show preview" setting (`ShowCustomPreview`) controls whether AI-generated results are displayed in a preview window before pasting. **The preview feature does not consume additional AI credits**—the preview displays the same AI response that was already generated, cached locally from a single API call.
|
||||
|
||||
The implementation flow:
|
||||
1. User initiates "Paste with AI" action
|
||||
2. A single AI API call is made via `ExecutePasteFormatAsync`
|
||||
3. The result is cached in `GeneratedResponses`
|
||||
4. If preview is enabled, the cached result is displayed in the preview UI
|
||||
5. User can paste the cached result without any additional API calls
|
||||
|
||||
See the `ExecutePasteFormatAsync(PasteFormat, PasteActionSource)` method in `OptionsViewModel.cs` for the implementation.
|
||||
|
||||
## Debugging
|
||||
|
||||
TODO: Add debugging information
|
||||
|
||||
## Settings
|
||||
|
||||
| Setting | Description |
|
||||
|---------|-------------|
|
||||
| `ShowCustomPreview` | When enabled, shows AI-generated results in a preview window before pasting. Does not affect AI credit consumption. |
|
||||
TODO: Add settings documentation
|
||||
|
||||
## Future Improvements
|
||||
|
||||
|
||||
@@ -367,12 +367,6 @@
|
||||
</RegistryKey>
|
||||
<File Id="BgcodePreviewHandler_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.BgcodePreviewHandler.resources.dll" />
|
||||
</Component>
|
||||
<Component Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" Guid="$(var.CompGUIDPrefix)23">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="CmdPalExtPowerToys_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="CmdPalExtPowerToys_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\Microsoft.CmdPal.Ext.PowerToys.resources.dll" />
|
||||
</Component>
|
||||
<?undef IdSafeLanguage?>
|
||||
<?undef CompGUIDPrefix?>
|
||||
<?endforeach?>
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Reflection;
|
||||
using AdvancedPaste.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace AdvancedPaste.UnitTests.HelpersTests;
|
||||
|
||||
[TestClass]
|
||||
public sealed class MarkdownHelperTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper method to invoke the private CleanHtml method for testing.
|
||||
/// </summary>
|
||||
private static string InvokeCleanHtml(string html)
|
||||
{
|
||||
var methodInfo = typeof(MarkdownHelper).GetMethod("CleanHtml", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
return (string)methodInfo!.Invoke(null, [html])!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to invoke the private ConvertHtmlToMarkdown method for testing.
|
||||
/// </summary>
|
||||
private static string InvokeConvertHtmlToMarkdown(string html)
|
||||
{
|
||||
var methodInfo = typeof(MarkdownHelper).GetMethod("ConvertHtmlToMarkdown", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
return (string)methodInfo!.Invoke(null, [html])!;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CleanHtml_GoogleSheetsWrapper_RemovesWrapperPreservesTable()
|
||||
{
|
||||
// Arrange - Google Sheets HTML with wrapper element
|
||||
const string googleSheetsHtml = @"<google-sheets-html-origin>
|
||||
<style type=""text/css""><!--td {border: 1px solid #cccccc;}--></style>
|
||||
<table xmlns=""http://www.w3.org/1999/xhtml"" cellspacing=""0"" cellpadding=""0"" dir=""ltr"" border=""1"" data-sheets-root=""1"">
|
||||
<colgroup><col width=""100""><col width=""100""></colgroup>
|
||||
<tbody><tr><td>A</td><td>B</td></tr><tr><td>1</td><td>2</td></tr></tbody>
|
||||
</table>
|
||||
</google-sheets-html-origin>";
|
||||
|
||||
// Act
|
||||
string cleanedHtml = InvokeCleanHtml(googleSheetsHtml);
|
||||
|
||||
// Assert - wrapper and style should be removed, table preserved
|
||||
Assert.IsFalse(cleanedHtml.Contains("google-sheets-html-origin"), "Google Sheets wrapper should be removed");
|
||||
Assert.IsFalse(cleanedHtml.Contains("<style"), "Style element should be removed");
|
||||
Assert.IsFalse(cleanedHtml.Contains("<colgroup"), "Colgroup element should be removed");
|
||||
Assert.IsTrue(cleanedHtml.Contains("<table"), "Table element should be preserved");
|
||||
Assert.IsTrue(cleanedHtml.Contains("<td>A</td>"), "Table content should be preserved");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CleanHtml_GoogleSheetsHtml_ConvertsToMarkdownTable()
|
||||
{
|
||||
// Arrange - Google Sheets HTML with wrapper element
|
||||
const string googleSheetsHtml = @"<google-sheets-html-origin>
|
||||
<style type=""text/css""><!--td {border: 1px solid #cccccc;}--></style>
|
||||
<table xmlns=""http://www.w3.org/1999/xhtml"" cellspacing=""0"" cellpadding=""0"" dir=""ltr"" border=""1"" data-sheets-root=""1"">
|
||||
<colgroup><col width=""100""><col width=""100""></colgroup>
|
||||
<tbody><tr><td>A</td><td>B</td></tr><tr><td>1</td><td>2</td></tr></tbody>
|
||||
</table>
|
||||
</google-sheets-html-origin>";
|
||||
|
||||
// Act
|
||||
string cleanedHtml = InvokeCleanHtml(googleSheetsHtml);
|
||||
string markdown = InvokeConvertHtmlToMarkdown(cleanedHtml);
|
||||
|
||||
// Assert - should produce valid Markdown table
|
||||
Assert.IsTrue(markdown.Contains("|"), "Markdown should contain table pipes");
|
||||
Assert.IsTrue(markdown.Contains("A") && markdown.Contains("B"), "Markdown should contain table content");
|
||||
Assert.IsTrue(markdown.Contains("1") && markdown.Contains("2"), "Markdown should contain table data");
|
||||
Assert.IsFalse(markdown.Contains("<google-sheets-html-origin>"), "Markdown should not contain HTML wrapper");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CleanHtml_ExcelHtml_ConvertsToMarkdownTable()
|
||||
{
|
||||
// Arrange - Typical Excel HTML (for regression testing)
|
||||
const string excelHtml = @"<table border=""0"" cellpadding=""0"" cellspacing=""0"" width=""192"">
|
||||
<tbody><tr height=""20""><td height=""20"" width=""64"">Name</td><td width=""64"">Value</td><td width=""64"">Status</td></tr>
|
||||
<tr height=""20""><td height=""20"">Item1</td><td>100</td><td>Active</td></tr>
|
||||
<tr height=""20""><td height=""20"">Item2</td><td>200</td><td>Inactive</td></tr>
|
||||
</tbody></table>";
|
||||
|
||||
// Act
|
||||
string cleanedHtml = InvokeCleanHtml(excelHtml);
|
||||
string markdown = InvokeConvertHtmlToMarkdown(cleanedHtml);
|
||||
|
||||
// Assert - Excel HTML should still convert correctly
|
||||
Assert.IsTrue(markdown.Contains("|"), "Markdown should contain table pipes");
|
||||
Assert.IsTrue(markdown.Contains("Name"), "Markdown should contain header content");
|
||||
Assert.IsTrue(markdown.Contains("Item1") && markdown.Contains("Item2"), "Markdown should contain row data");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CleanHtml_StyleElement_IsRemoved()
|
||||
{
|
||||
// Arrange
|
||||
const string htmlWithStyle = @"<html><head><style>body { color: red; }</style></head><body><p>Text</p></body></html>";
|
||||
|
||||
// Act
|
||||
string cleanedHtml = InvokeCleanHtml(htmlWithStyle);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(cleanedHtml.Contains("<style"), "Style element should be removed");
|
||||
Assert.IsTrue(cleanedHtml.Contains("<p>Text</p>") || cleanedHtml.Contains("Text"), "Content should be preserved");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CleanHtml_ColgroupElement_IsRemoved()
|
||||
{
|
||||
// Arrange
|
||||
const string htmlWithColgroup = @"<table><colgroup><col width=""100""></colgroup><tbody><tr><td>Data</td></tr></tbody></table>";
|
||||
|
||||
// Act
|
||||
string cleanedHtml = InvokeCleanHtml(htmlWithColgroup);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(cleanedHtml.Contains("<colgroup"), "Colgroup element should be removed");
|
||||
Assert.IsTrue(cleanedHtml.Contains("<table"), "Table element should be preserved");
|
||||
Assert.IsTrue(cleanedHtml.Contains("Data"), "Table content should be preserved");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CleanHtml_ScriptElement_IsRemoved()
|
||||
{
|
||||
// Arrange
|
||||
const string htmlWithScript = @"<html><body><script>alert('test');</script><p>Content</p></body></html>";
|
||||
|
||||
// Act
|
||||
string cleanedHtml = InvokeCleanHtml(htmlWithScript);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(cleanedHtml.Contains("<script"), "Script element should be removed");
|
||||
Assert.IsTrue(cleanedHtml.Contains("Content"), "Content should be preserved");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CleanHtml_NestedGoogleSheetsTable_PreservesNestedContent()
|
||||
{
|
||||
// Arrange - More complex Google Sheets HTML
|
||||
const string complexGoogleSheetsHtml = @"<google-sheets-html-origin>
|
||||
<style type=""text/css"">td {border: 1px solid #ccc;}</style>
|
||||
<table data-sheets-root=""1"">
|
||||
<colgroup><col width=""100""><col width=""150""><col width=""100""></colgroup>
|
||||
<tbody>
|
||||
<tr><td>Header1</td><td>Header2</td><td>Header3</td></tr>
|
||||
<tr><td>Row1Col1</td><td>Row1Col2</td><td>Row1Col3</td></tr>
|
||||
<tr><td>Row2Col1</td><td>Row2Col2</td><td>Row2Col3</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</google-sheets-html-origin>";
|
||||
|
||||
// Act
|
||||
string cleanedHtml = InvokeCleanHtml(complexGoogleSheetsHtml);
|
||||
string markdown = InvokeConvertHtmlToMarkdown(cleanedHtml);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(markdown.Contains("<google-sheets-html-origin>"), "Wrapper should be removed from markdown");
|
||||
Assert.IsTrue(markdown.Contains("Header1"), "Headers should be preserved");
|
||||
Assert.IsTrue(markdown.Contains("Row1Col1"), "Row data should be preserved");
|
||||
Assert.IsTrue(markdown.Contains("Row2Col3"), "All cells should be preserved");
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,23 @@ namespace AdvancedPaste.Helpers
|
||||
{
|
||||
Logger.LogTrace();
|
||||
|
||||
// Unwrap Google Sheets wrapper element (preserve children, remove wrapper)
|
||||
foreach (var googleSheetsWrapper in node.DescendantsAndSelf("google-sheets-html-origin").ToArray())
|
||||
{
|
||||
var parent = googleSheetsWrapper.ParentNode;
|
||||
if (parent == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var child in googleSheetsWrapper.ChildNodes.ToArray())
|
||||
{
|
||||
parent.InsertBefore(child, googleSheetsWrapper);
|
||||
}
|
||||
|
||||
googleSheetsWrapper.Remove();
|
||||
}
|
||||
|
||||
// Remove specific elements by tag name, CSS class, or other attributes
|
||||
// Example: Remove all <script> elements
|
||||
foreach (var scriptNode in node.DescendantsAndSelf("script").ToArray())
|
||||
@@ -64,6 +81,18 @@ namespace AdvancedPaste.Helpers
|
||||
scriptNode.Remove();
|
||||
}
|
||||
|
||||
// Remove style elements (CSS not relevant for Markdown)
|
||||
foreach (var styleNode in node.DescendantsAndSelf("style").ToArray())
|
||||
{
|
||||
styleNode.Remove();
|
||||
}
|
||||
|
||||
// Remove colgroup elements (column width info not needed for Markdown)
|
||||
foreach (var colgroupNode in node.DescendantsAndSelf("colgroup").ToArray())
|
||||
{
|
||||
colgroupNode.Remove();
|
||||
}
|
||||
|
||||
// Ignore specific elements like <sup> elements
|
||||
foreach (var ignoredElement in node.DescendantsAndSelf("sup").ToArray())
|
||||
{
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
using WorkspacesEditor.Utils;
|
||||
|
||||
namespace WorkspacesEditor
|
||||
{
|
||||
/// <summary>
|
||||
@@ -14,40 +11,9 @@ namespace WorkspacesEditor
|
||||
/// </summary>
|
||||
public partial class OverlayWindow : Window
|
||||
{
|
||||
private int _targetX;
|
||||
private int _targetY;
|
||||
private int _targetWidth;
|
||||
private int _targetHeight;
|
||||
|
||||
public OverlayWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
SourceInitialized += OnWindowSourceInitialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the target bounds for the overlay window.
|
||||
/// The window will be positioned using DPI-unaware context after initialization.
|
||||
/// </summary>
|
||||
public void SetTargetBounds(int x, int y, int width, int height)
|
||||
{
|
||||
_targetX = x;
|
||||
_targetY = y;
|
||||
_targetWidth = width;
|
||||
_targetHeight = height;
|
||||
|
||||
// Set initial WPF properties (will be corrected after HWND creation)
|
||||
Left = x;
|
||||
Top = y;
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
private void OnWindowSourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
// Reposition window using DPI-unaware context to match the virtual coordinates.
|
||||
// This fixes overlay positioning on mixed-DPI multi-monitor setups.
|
||||
NativeMethods.SetWindowPositionDpiUnaware(this, _targetX, _targetY, _targetWidth, _targetHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace WorkspacesEditor.Utils
|
||||
{
|
||||
@@ -19,39 +17,6 @@ namespace WorkspacesEditor.Utils
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
|
||||
|
||||
private const uint SWP_NOZORDER = 0x0004;
|
||||
private const uint SWP_NOACTIVATE = 0x0010;
|
||||
|
||||
private static readonly IntPtr DPI_AWARENESS_CONTEXT_UNAWARE = new IntPtr(-1);
|
||||
|
||||
/// <summary>
|
||||
/// Positions a WPF window using DPI-unaware context to match the virtual coordinates.
|
||||
/// This fixes overlay positioning on mixed-DPI multi-monitor setups.
|
||||
/// </summary>
|
||||
public static void SetWindowPositionDpiUnaware(Window window, int x, int y, int width, int height)
|
||||
{
|
||||
var helper = new WindowInteropHelper(window).Handle;
|
||||
if (helper != IntPtr.Zero)
|
||||
{
|
||||
// Temporarily switch to DPI-unaware context to position window.
|
||||
IntPtr oldContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
|
||||
try
|
||||
{
|
||||
SetWindowPos(helper, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetThreadDpiAwarenessContext(oldContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("USER32.DLL")]
|
||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
|
||||
@@ -495,10 +495,10 @@ namespace WorkspacesEditor.ViewModels
|
||||
{
|
||||
var bounds = screen.Bounds;
|
||||
OverlayWindow overlayWindow = new OverlayWindow();
|
||||
|
||||
// Use DPI-unaware positioning to fix overlay on mixed-DPI multi-monitor setups
|
||||
overlayWindow.SetTargetBounds(bounds.Left, bounds.Top, bounds.Width, bounds.Height);
|
||||
|
||||
overlayWindow.Top = bounds.Top;
|
||||
overlayWindow.Left = bounds.Left;
|
||||
overlayWindow.Width = bounds.Width;
|
||||
overlayWindow.Height = bounds.Height;
|
||||
overlayWindow.ShowActivated = true;
|
||||
overlayWindow.Topmost = true;
|
||||
overlayWindow.Show();
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\CoreCommonProps.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<EnableCoreMrtTooling>false</EnableCoreMrtTooling>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Common" />
|
||||
|
||||
@@ -84,13 +84,7 @@
|
||||
<WarningsNotAsErrors>IL2081;$(WarningsNotAsErrors)</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- InternalsVisibleTo with public key for CI builds (signed assemblies) -->
|
||||
<ItemGroup Condition="'$(CIBuild)'=='true'">
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.CommandPalette.Extensions.Toolkit.UnitTests, PublicKey=002400000c80000014010000060200000024000052534131000800000100010085aad0bef0688d1b994a0d78e1fd29fc24ac34ed3d3ac3fb9b3d0c48386ba834aa880035060a8848b2d8adf58e670ed20914be3681a891c9c8c01eef2ab22872547c39be00af0e6c72485d7cfd1a51df8947d36ceba9989106b58abe79e6a3e71a01ed6bdc867012883e0b1a4d35b1b5eeed6df21e401bb0c22f2246ccb69979dc9e61eef262832ed0f2064853725a75485fa8a3efb7e027319c86dec03dc3b1bca2b5081bab52a627b9917450dfad534799e1c7af58683bdfa135f1518ff1ea60e90d7b993a6c87fd3dd93408e35d1296f9a7f9a97c5db56c0f3cc25ad11e9777f94d138b3cea53b9a8331c2e6dcb8d2ea94e18bf1163ff112a22dbd92d429a" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- InternalsVisibleTo without public key for local builds (unsigned assemblies) -->
|
||||
<ItemGroup Condition="'$(CIBuild)'!='true'">
|
||||
<InternalsVisibleTo Include="Microsoft.CommandPalette.Extensions.Toolkit.UnitTests" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -112,8 +112,8 @@
|
||||
<controls:IsEnabledTextBlock x:Uid="LightSwitch_SunriseText" VerticalAlignment="Center" />
|
||||
<NumberBox
|
||||
AutomationProperties.AutomationId="SunriseOffset_LightSwitch"
|
||||
Maximum="{x:Bind ViewModel.SunriseOffsetMax, Mode=OneWay}"
|
||||
Minimum="{x:Bind ViewModel.SunriseOffsetMin, Mode=OneWay}"
|
||||
Maximum="60"
|
||||
Minimum="-60"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
Value="{x:Bind ViewModel.SunriseOffset, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
@@ -121,8 +121,8 @@
|
||||
<controls:IsEnabledTextBlock x:Uid="LightSwitch_SunsetText" VerticalAlignment="Center" />
|
||||
<NumberBox
|
||||
AutomationProperties.AutomationId="SunsetOffset_LightSwitch"
|
||||
Maximum="{x:Bind ViewModel.SunsetOffsetMax, Mode=OneWay}"
|
||||
Minimum="{x:Bind ViewModel.SunsetOffsetMin, Mode=OneWay}"
|
||||
Maximum="60"
|
||||
Minimum="-60"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
Value="{x:Bind ViewModel.SunsetOffset, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -4121,7 +4121,7 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<value>Advanced AI</value>
|
||||
</data>
|
||||
<data name="Oobe_AdvancedPaste.Description" xml:space="preserve">
|
||||
<value>Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, Markdown, or JSON directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an opt-in AI feature that can use an online or local language model endpoint. Note: this will replace the formatted text in your clipboard with the selected format.</value>
|
||||
<value>Advanced Paste is a tool to put your clipboard content into any format you need, focused towards developer workflows. It can paste as plain text, markdown, or json directly with the UX or with a direct keystroke invoke. These are fully locally executed. In addition, it has an AI powered option that is 100% opt-in and requires an Open AI key. Note: this will replace the formatted text in your clipboard with the selected format.</value>
|
||||
</data>
|
||||
<data name="Oobe_AdvancedPaste.Title" xml:space="preserve">
|
||||
<value>Advanced Paste</value>
|
||||
|
||||
@@ -232,8 +232,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
NotifyPropertyChanged();
|
||||
|
||||
OnPropertyChanged(nameof(LightTimeTimeSpan));
|
||||
OnPropertyChanged(nameof(SunriseOffsetMin));
|
||||
OnPropertyChanged(nameof(SunsetOffsetMin));
|
||||
|
||||
if (ScheduleMode == "SunsetToSunrise")
|
||||
{
|
||||
@@ -254,8 +252,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
NotifyPropertyChanged();
|
||||
|
||||
OnPropertyChanged(nameof(DarkTimeTimeSpan));
|
||||
OnPropertyChanged(nameof(SunriseOffsetMax));
|
||||
OnPropertyChanged(nameof(SunsetOffsetMax));
|
||||
|
||||
if (ScheduleMode == "SunsetToSunrise")
|
||||
{
|
||||
@@ -274,7 +270,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
ModuleSettings.Properties.SunriseOffset.Value = value;
|
||||
OnPropertyChanged(nameof(LightTimeTimeSpan));
|
||||
OnPropertyChanged(nameof(SunsetOffsetMin));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,49 +283,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
ModuleSettings.Properties.SunsetOffset.Value = value;
|
||||
OnPropertyChanged(nameof(DarkTimeTimeSpan));
|
||||
OnPropertyChanged(nameof(SunriseOffsetMax));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int SunriseOffsetMin
|
||||
{
|
||||
get
|
||||
{
|
||||
// Minimum: don't let adjusted sunrise go before 00:00
|
||||
return -LightTime;
|
||||
}
|
||||
}
|
||||
|
||||
public int SunriseOffsetMax
|
||||
{
|
||||
get
|
||||
{
|
||||
// Maximum: adjusted sunrise must stay before adjusted sunset
|
||||
int adjustedSunset = DarkTime + SunsetOffset;
|
||||
return Math.Max(0, adjustedSunset - LightTime - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int SunsetOffsetMin
|
||||
{
|
||||
get
|
||||
{
|
||||
// Minimum: adjusted sunset must stay after adjusted sunrise
|
||||
int adjustedSunrise = LightTime + SunriseOffset;
|
||||
return Math.Min(0, adjustedSunrise - DarkTime + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int SunsetOffsetMax
|
||||
{
|
||||
get
|
||||
{
|
||||
// Maximum: don't let adjusted sunset go past 23:59 (1439 minutes)
|
||||
return 1439 - DarkTime;
|
||||
}
|
||||
}
|
||||
|
||||
// === Computed projections (OneWay bindings only) ===
|
||||
public TimeSpan LightTimeTimeSpan
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user