Compare commits

..

3 Commits

Author SHA1 Message Date
Gordon Lam (SH)
218079e596 test(AdvancedPaste): improve reflection helper robustness and rename test method
- Replace null-forgiving operators with explicit null checks and descriptive exception messages
- Rename CleanHtml_NestedGoogleSheetsTable_PreservesNestedContent to CleanHtml_ComplexGoogleSheetsTable_PreservesAllContent

Addresses PR review feedback for better test failure diagnostics and more accurate test naming.
2026-02-03 07:16:49 -08:00
Gordon Lam (SH)
fb19bb9966 fix(paste): address PR review comments
- Add null check for ParentNode in RemoveUnwantedElements to prevent potential NullReferenceException when wrapper is document root
- Remove unused HtmlAgilityPack import from test file
2026-01-31 09:35:25 -08:00
Gordon Lam (SH)
33142a556c fix(paste): handle Google Sheets HTML wrapper and remove style/colgroup elements 2026-01-31 09:29:59 -08:00
5 changed files with 205 additions and 20 deletions

View File

@@ -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

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// 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.
@@ -13,12 +13,12 @@ namespace Microsoft.PowerToys.FilePreviewCommon
/// <summary>
/// Markdown HTML header for light theme.
/// </summary>
private static readonly string HtmlLightHeader = "<!doctype html><style>body{width:100%;margin:0;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}.container{padding:5%}body img{max-width:100%;height:auto}body h1,body h2,body h3,body h4,body h5,body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}body h1,body h2{padding-bottom:.3em;border-bottom:1px solid #eaecef}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}body h3{font-size:1.25em}body h4{font-size:1em}body h5{font-size:.875em}body h6{font-size:.85em;color:#6a737d}pre{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;background-color:#f6f8fa;border-radius:3px;padding:16px;font-size:85%}a{color:#0366d6}strong{font-weight:600}em{font-style:italic}code{padding:.2em .4em;margin:0;font-size:85%;background-color:#f6f8fa;border-radius:3px}hr{border-color:#EEE -moz-use-text-color #FFF;border-style:solid none;border-width:.5px 0;margin:18px 0}table{display:block;width:100%;overflow:auto;border-spacing:0;border-collapse:collapse}tbody{display:table-row-group;vertical-align:middle;border-color:inherit;vertical-align:inherit;border-color:inherit}table tr{background-color:#fff;border-top:1px solid #c6cbd1}tr{display:table-row;vertical-align:inherit;border-color:inherit}table td,table th{padding:6px 13px;border:1px solid #dfe2e5}th{font-weight:600;display:table-cell;vertical-align:inherit;font-weight:bold;text-align:-internal-center}thead{display:table-header-group;vertical-align:middle;border-color:inherit}td{display:table-cell;vertical-align:inherit}code,pre,tt{font-family:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;color:#24292e;overflow-x:auto}pre code{display:block;font-size:inherit;color:inherit;word-break:normal}blockquote{background-color:#fff;border-radius:3px;padding:15px;font-size:14px;display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:40px;margin-inline-end:40px;padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5}input[type=\"checkbox\"]{appearance:none;-webkit-appearance:none;width:16px;height:16px;border:2px solid #6a737d;border-radius:3px;background-color:#fff;vertical-align:middle;margin-right:6px;cursor:default}input[type=\"checkbox\"]:checked{background-color:#0366d6;border-color:#0366d6;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23fff' d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center}</style><body><div class=\"container\">";
private static readonly string HtmlLightHeader = "<!doctype html><style>body{width:100%;margin:0;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}.container{padding:5%}body img{max-width:100%;height:auto}body h1,body h2,body h3,body h4,body h5,body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}body h1,body h2{padding-bottom:.3em;border-bottom:1px solid #eaecef}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}body h3{font-size:1.25em}body h4{font-size:1em}body h5{font-size:.875em}body h6{font-size:.85em;color:#6a737d}pre{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;background-color:#f6f8fa;border-radius:3px;padding:16px;font-size:85%}a{color:#0366d6}strong{font-weight:600}em{font-style:italic}code{padding:.2em .4em;margin:0;font-size:85%;background-color:#f6f8fa;border-radius:3px}hr{border-color:#EEE -moz-use-text-color #FFF;border-style:solid none;border-width:.5px 0;margin:18px 0}table{display:block;width:100%;overflow:auto;border-spacing:0;border-collapse:collapse}tbody{display:table-row-group;vertical-align:middle;border-color:inherit;vertical-align:inherit;border-color:inherit}table tr{background-color:#fff;border-top:1px solid #c6cbd1}tr{display:table-row;vertical-align:inherit;border-color:inherit}table td,table th{padding:6px 13px;border:1px solid #dfe2e5}th{font-weight:600;display:table-cell;vertical-align:inherit;font-weight:bold;text-align:-internal-center}thead{display:table-header-group;vertical-align:middle;border-color:inherit}td{display:table-cell;vertical-align:inherit}code,pre,tt{font-family:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;color:#24292e;overflow-x:auto}pre code{display:block;font-size:inherit;color:inherit;word-break:normal}blockquote{background-color:#fff;border-radius:3px;padding:15px;font-size:14px;display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:40px;margin-inline-end:40px;padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5}</style><body><div class=\"container\">";
/// <summary>
/// Markdown HTML header for dark theme.
/// </summary>
private static readonly string HtmlDarkHeader = "<!doctype html><style>body{width:100%;margin:0;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";font-size:1rem;font-weight:400;line-height:1.5;color:#d4d4d4;text-align:left;background-color:#1e1e1e}.container{padding:5%}body img{max-width:100%;height:auto}body h1,body h2,body h3,body h4,body h5,body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}body h1,body h2{padding-bottom:.3em;border-bottom:1px solid #474747}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}body h3{font-size:1.25em}body h4{font-size:1em}body h5{font-size:.875em}body h6{font-size:.85em;color:#d4d4d4}pre{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;background-color:#161616;border-radius:3px;padding:16px;font-size:85%}a{color:#0366d6}strong{font-weight:600}em{font-style:italic}code{padding:.2em .4em;margin:0;font-size:85%;background-color:#161616;border-radius:3px}hr{border-color:#EEE -moz-use-text-color #FFF;border-style:solid none;border-width:.5px 0;margin:18px 0}table{display:block;width:100%;overflow:auto;border-spacing:0;border-collapse:collapse}tbody{display:table-row-group;vertical-align:middle;border-color:inherit;vertical-align:inherit;border-color:inherit}table tr{background-color:#1e1e1e;border-top:1px solid #c6cbd1}tr{display:table-row;vertical-align:inherit;border-color:inherit}table td,table th{padding:6px 13px;border:1px solid #474747}th{font-weight:600;display:table-cell;vertical-align:inherit;font-weight:bold;text-align:-internal-center}thead{display:table-header-group;vertical-align:middle;border-color:inherit}td{display:table-cell;vertical-align:inherit}code,pre,tt{font-family:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;color:#d4d4d4;overflow-x:auto}pre code{display:block;font-size:inherit;color:inherit;word-break:normal}blockquote{background-color:#282828;border-radius:3px;padding:15px;font-size:14px;display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:40px;margin-inline-end:40px;padding:0 1em;color:#d4d4d4;border-left:.25em solid #d4d4d4}input[type=\"checkbox\"]{appearance:none;-webkit-appearance:none;width:16px;height:16px;border:2px solid #6b7280;border-radius:3px;background-color:#1e1e1e;vertical-align:middle;margin-right:6px;cursor:default}input[type=\"checkbox\"]:checked{background-color:#58a6ff;border-color:#58a6ff;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23fff' d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:center}</style><body><div class=\"container\">";
private static readonly string HtmlDarkHeader = "<!doctype html><style>body{width:100%;margin:0;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\";font-size:1rem;font-weight:400;line-height:1.5;color:#d4d4d4;text-align:left;background-color:#1e1e1e}.container{padding:5%}body img{max-width:100%;height:auto}body h1,body h2,body h3,body h4,body h5,body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}body h1,body h2{padding-bottom:.3em;border-bottom:1px solid #474747}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}body h3{font-size:1.25em}body h4{font-size:1em}body h5{font-size:.875em}body h6{font-size:.85em;color:#d4d4d4}pre{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;background-color:#161616;border-radius:3px;padding:16px;font-size:85%}a{color:#0366d6}strong{font-weight:600}em{font-style:italic}code{padding:.2em .4em;margin:0;font-size:85%;background-color:#161616;border-radius:3px}hr{border-color:#EEE -moz-use-text-color #FFF;border-style:solid none;border-width:.5px 0;margin:18px 0}table{display:block;width:100%;overflow:auto;border-spacing:0;border-collapse:collapse}tbody{display:table-row-group;vertical-align:middle;border-color:inherit;vertical-align:inherit;border-color:inherit}table tr{background-color:#1e1e1e;border-top:1px solid #c6cbd1}tr{display:table-row;vertical-align:inherit;border-color:inherit}table td,table th{padding:6px 13px;border:1px solid #474747}th{font-weight:600;display:table-cell;vertical-align:inherit;font-weight:bold;text-align:-internal-center}thead{display:table-header-group;vertical-align:middle;border-color:inherit}td{display:table-cell;vertical-align:inherit}code,pre,tt{font-family:SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;color:#d4d4d4;overflow-x:auto}pre code{display:block;font-size:inherit;color:inherit;word-break:normal}blockquote{background-color:#282828;border-radius:3px;padding:15px;font-size:14px;display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:40px;margin-inline-end:40px;padding:0 1em;color:#d4d4d4;border-left:.25em solid #d4d4d4}</style><body><div class=\"container\">";
/// <summary>
/// Markdown HTML footer.

View File

@@ -0,0 +1,171 @@
// 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");
}
}

View File

@@ -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())
{

View File

@@ -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>