mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-15 07:40:18 +01:00
Compare commits
1 Commits
issue/4435
...
issue/3296
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
240083bede |
@@ -1,171 +0,0 @@
|
||||
// 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)
|
||||
?? throw new InvalidOperationException($"Method 'CleanHtml' not found on {nameof(MarkdownHelper)}. The method may have been renamed or removed.");
|
||||
return (string?)methodInfo.Invoke(null, [html])
|
||||
?? throw new InvalidOperationException("CleanHtml returned null unexpectedly.");
|
||||
}
|
||||
|
||||
/// <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)
|
||||
?? throw new InvalidOperationException($"Method 'ConvertHtmlToMarkdown' not found on {nameof(MarkdownHelper)}. The method may have been renamed or removed.");
|
||||
return (string?)methodInfo.Invoke(null, [html])
|
||||
?? throw new InvalidOperationException("ConvertHtmlToMarkdown returned null unexpectedly.");
|
||||
}
|
||||
|
||||
[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_ComplexGoogleSheetsTable_PreservesAllContent()
|
||||
{
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,8 @@ internal sealed class IntegrationTestUserSettings : IUserSettings
|
||||
|
||||
public bool ShowCustomPreview => false;
|
||||
|
||||
public bool ShowAIPaste => true;
|
||||
|
||||
public bool CloseAfterLosingFocus => false;
|
||||
|
||||
public bool EnableClipboardPreview => true;
|
||||
|
||||
@@ -251,7 +251,8 @@
|
||||
Margin="20,0,20,0"
|
||||
x:FieldModifier="public"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomAIServiceEnabled, Mode=OneWay}"
|
||||
TabIndex="0">
|
||||
TabIndex="0"
|
||||
Visibility="{x:Bind ViewModel.ShowAIPasteSection, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<controls:PromptBox.Footer>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace AdvancedPaste.Settings
|
||||
|
||||
public bool ShowCustomPreview { get; }
|
||||
|
||||
public bool ShowAIPaste { get; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; }
|
||||
|
||||
public bool EnableClipboardPreview { get; }
|
||||
|
||||
@@ -57,23 +57,6 @@ 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())
|
||||
@@ -81,18 +64,6 @@ 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())
|
||||
{
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace AdvancedPaste.Settings
|
||||
|
||||
public bool ShowCustomPreview { get; private set; }
|
||||
|
||||
public bool ShowAIPaste { get; private set; }
|
||||
|
||||
public bool CloseAfterLosingFocus { get; private set; }
|
||||
|
||||
public bool EnableClipboardPreview { get; private set; }
|
||||
@@ -54,6 +56,7 @@ namespace AdvancedPaste.Settings
|
||||
|
||||
IsAIEnabled = false;
|
||||
ShowCustomPreview = true;
|
||||
ShowAIPaste = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
EnableClipboardPreview = true;
|
||||
PasteAIConfiguration = new PasteAIConfiguration();
|
||||
@@ -109,6 +112,7 @@ namespace AdvancedPaste.Settings
|
||||
|
||||
IsAIEnabled = properties.IsAIEnabled;
|
||||
ShowCustomPreview = properties.ShowCustomPreview;
|
||||
ShowAIPaste = properties.ShowAIPaste;
|
||||
CloseAfterLosingFocus = properties.CloseAfterLosingFocus;
|
||||
EnableClipboardPreview = properties.EnableClipboardPreview;
|
||||
PasteAIConfiguration = properties.PasteAIConfiguration ?? new PasteAIConfiguration();
|
||||
|
||||
@@ -234,6 +234,8 @@ namespace AdvancedPaste.ViewModels
|
||||
|
||||
public bool ShowClipboardHistoryButton => ClipboardHistoryEnabled;
|
||||
|
||||
public bool ShowAIPasteSection => _userSettings.ShowAIPaste && IsAllowedByGPO;
|
||||
|
||||
public bool HasIndeterminateTransformProgress => double.IsNaN(TransformProgress);
|
||||
|
||||
private PasteFormats CustomAIFormat =>
|
||||
@@ -320,6 +322,7 @@ namespace AdvancedPaste.ViewModels
|
||||
OnPropertyChanged(nameof(AIProviders));
|
||||
OnPropertyChanged(nameof(AllowedAIProviders));
|
||||
OnPropertyChanged(nameof(ShowClipboardPreview));
|
||||
OnPropertyChanged(nameof(ShowAIPasteSection));
|
||||
|
||||
NotifyActiveProviderChanged();
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"properties":{"IsAIEnabled":{"value":false},"ShowCustomPreview":{"value":true},"CloseAfterLosingFocus":{"value":false},"advanced-paste-ui-hotkey":{"win":true,"ctrl":false,"alt":false,"shift":true,"code":86,"key":""},"paste-as-plain-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":79,"key":""},"paste-as-markdown-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":77,"key":""},"paste-as-json-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":74,"key":""},"custom-actions":{"value":[]},"additional-actions":{"image-to-text":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-file":{"isShown":true,"paste-as-txt-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-png-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-html-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}},"transcode":{"isShown":true,"transcode-to-mp3":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"transcode-to-mp4":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}}},"paste-ai-configuration":{"active-provider-id":"","providers":[],"use-shared-credentials":true}},"name":"AdvancedPaste","version":"1"}
|
||||
{"properties":{"IsAIEnabled":{"value":false},"ShowCustomPreview":{"value":true},"ShowAIPaste":{"value":true},"CloseAfterLosingFocus":{"value":false},"advanced-paste-ui-hotkey":{"win":true,"ctrl":false,"alt":false,"shift":true,"code":86,"key":""},"paste-as-plain-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":79,"key":""},"paste-as-markdown-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":77,"key":""},"paste-as-json-hotkey":{"win":true,"ctrl":true,"alt":true,"shift":false,"code":74,"key":""},"custom-actions":{"value":[]},"additional-actions":{"image-to-text":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-file":{"isShown":true,"paste-as-txt-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-png-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"paste-as-html-file":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}},"transcode":{"isShown":true,"transcode-to-mp3":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true},"transcode-to-mp4":{"shortcut":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"isShown":true}}},"paste-ai-configuration":{"active-provider-id":"","providers":[],"use-shared-credentials":true}},"name":"AdvancedPaste","version":"1"}
|
||||
@@ -26,6 +26,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
AdditionalActions = new();
|
||||
IsAIEnabled = false;
|
||||
ShowCustomPreview = true;
|
||||
ShowAIPaste = true;
|
||||
CloseAfterLosingFocus = false;
|
||||
EnableClipboardPreview = true;
|
||||
PasteAIConfiguration = new();
|
||||
@@ -73,6 +74,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool ShowCustomPreview { get; set; }
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool ShowAIPaste { get; set; }
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool CloseAfterLosingFocus { get; set; }
|
||||
|
||||
|
||||
@@ -183,6 +183,9 @@
|
||||
<tkcontrols:SettingsCard Name="AdvancedPasteShowCustomPreviewSettingsCard" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_ShowCustomPreviewSettingsCard" IsChecked="{x:Bind ViewModel.ShowCustomPreview, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard Name="AdvancedPasteShowAIPasteSettingsCard" ContentAlignment="Left">
|
||||
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_ShowAIPasteSettingsCard" IsChecked="{x:Bind ViewModel.ShowAIPaste, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
@@ -4117,6 +4117,12 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<data name="AdvancedPaste_ShowCustomPreviewSettingsCard.Description" xml:space="preserve">
|
||||
<value>Preview the output of AI formats and Image to text before pasting</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_ShowAIPasteSettingsCard.Header" xml:space="preserve">
|
||||
<value>Show AI paste section</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_ShowAIPasteSettingsCard.Description" xml:space="preserve">
|
||||
<value>Show the AI paste input box in the Advanced Paste window</value>
|
||||
</data>
|
||||
<data name="AdvancedPaste_EnableAdvancedAI.Text" xml:space="preserve">
|
||||
<value>Advanced AI</value>
|
||||
</data>
|
||||
|
||||
@@ -543,6 +543,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowAIPaste
|
||||
{
|
||||
get => _advancedPasteSettings.Properties.ShowAIPaste;
|
||||
set
|
||||
{
|
||||
if (value != _advancedPasteSettings.Properties.ShowAIPaste)
|
||||
{
|
||||
_advancedPasteSettings.Properties.ShowAIPaste = value;
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CloseAfterLosingFocus
|
||||
{
|
||||
get => _advancedPasteSettings.Properties.CloseAfterLosingFocus;
|
||||
@@ -1221,6 +1234,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
OnPropertyChanged(nameof(ShowCustomPreview));
|
||||
}
|
||||
|
||||
if (target.ShowAIPaste != source.ShowAIPaste)
|
||||
{
|
||||
target.ShowAIPaste = source.ShowAIPaste;
|
||||
OnPropertyChanged(nameof(ShowAIPaste));
|
||||
}
|
||||
|
||||
if (target.CloseAfterLosingFocus != source.CloseAfterLosingFocus)
|
||||
{
|
||||
target.CloseAfterLosingFocus = source.CloseAfterLosingFocus;
|
||||
|
||||
Reference in New Issue
Block a user