mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-27 14:59:16 +08:00
[Peek][PreviewPane]Show Copy entry in right-click copy menu (#33845)
## Summary of the Pull Request Fixes two bugs: - Peek: Missing "Copy" menu-item for all WebView2 previewers. - PreviewPane: Missing "Copy" menu-item for markdown files only. ## Detailed Description of the Pull Request / Additional comments The issues are: - Peek: - When not using Monaco (markdown, html) - the default WebView2 context menu has been disabled. I have enabled it and then disabled ALL menu-items other than "Copy" (such as "Back"). - When using Monaco + Release (other code files) - current code tries to use the Monaco context menu, but it is somehow disabled at runtime. I spent MANY hours trying to find out why but without success. It works fine when I view the generated html + js files in a browser or in a Debug build or in PreviewPane. But I couldn't find the root cause. Trying to fix it by enabling the WebView2 context menu instead doesn't work as for whatever reason, WebView2 doesn't generate a "Copy" menu-item (it thinks there's no selected text when there is). So in this case, the only thing I could get to work was generating context menu-items via WebView2 callbacks that call JS functions. As a bonus, this way of doing it also allows "Toggle text wrapping" to work. - PreviewPane: - Markdown - the default WebView2 context menu has been disabled. Like for Peek, I have enabled it and then disabled ALL menu-items other than "Copy" (such as "Back"). - Monaco (other code files) - this already just works fine, so I've left it as is. I *could* make it work the same way as I've done for Peek for consistency, but I've chosen to leave it as is since it works. ![image](https://github.com/user-attachments/assets/d758ada7-bb62-4f40-bef7-ad08ffb83786) ![image](https://github.com/user-attachments/assets/4e0baa7e-632f-412a-b2b1-b9f666277ca7)
This commit is contained in:
parent
ac14ad3458
commit
84def18ed5
@ -9,7 +9,7 @@
|
||||
// `theme` can be "vs" for light theme or "vs-dark" for dark theme
|
||||
// `lang` is the language of the file
|
||||
// `wrap` if the editor is wrapping or not
|
||||
|
||||
|
||||
var theme = ("[[PT_THEME]]" == "dark") ? "vs-dark" : "vs";
|
||||
var lang = "[[PT_LANG]]";
|
||||
var wrap = ([[PT_WRAP]] == 1) ? true : false;
|
||||
@ -19,11 +19,29 @@
|
||||
var stickyScroll = ([[PT_STICKY_SCROLL]] == 1) ? true : false;
|
||||
|
||||
var fontSize = [[PT_FONT_SIZE]];
|
||||
|
||||
var contextMenu = ([[PT_CONTEXTMENU]] == 1) ? true : false;
|
||||
|
||||
var editor;
|
||||
|
||||
// Code taken from https://stackoverflow.com/a/30106551/14774889
|
||||
var code = decodeURIComponent(atob(base64code).split('').map(function(c) {
|
||||
var code = decodeURIComponent(atob(base64code).split('').map(function (c) {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
|
||||
function runToggleTextWrapCommand() {
|
||||
if (wrap) {
|
||||
editor.updateOptions({ wordWrap: 'off' })
|
||||
} else {
|
||||
editor.updateOptions({ wordWrap: 'on' })
|
||||
}
|
||||
wrap = !wrap;
|
||||
}
|
||||
|
||||
function runCopyCommand() {
|
||||
editor.focus();
|
||||
document.execCommand('copy');
|
||||
}
|
||||
|
||||
</script>
|
||||
<!-- Set browser to Edge-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
@ -33,32 +51,33 @@
|
||||
<title>Previewer for developer Files</title>
|
||||
<style>
|
||||
/* Fits content to window size */
|
||||
html, body{
|
||||
padding:0;
|
||||
html, body {
|
||||
padding: 0;
|
||||
}
|
||||
#container,.monaco-editor {
|
||||
position:fixed;
|
||||
height:100%;
|
||||
left:0;
|
||||
top:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
|
||||
#container, .monaco-editor {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.overflowingContentWidgets{
|
||||
|
||||
.overflowingContentWidgets {
|
||||
/*Hides alert box */
|
||||
display:none!important
|
||||
}
|
||||
display: none !important
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body oncontextmenu="onContextMenu()">
|
||||
<body>
|
||||
<!-- Container for the editor -->
|
||||
<div id="container"></div>
|
||||
<!-- Script -->
|
||||
<script src="http://[[PT_URL]]/monacoSRC/min/vs/loader.js"></script>
|
||||
<script src="http://[[PT_URL]]/monacoSpecialLanguages.js" type="module"></script>
|
||||
<script type="module">
|
||||
var editor;
|
||||
<script type="module">
|
||||
import { registerAdditionalLanguages } from 'http://[[PT_URL]]/monacoSpecialLanguages.js';
|
||||
import { customTokenColors } from 'http://[[PT_URL]]/customTokenColors.js';
|
||||
require.config({ paths: { vs: 'http://[[PT_URL]]/monacoSRC/min/vs' } });
|
||||
@ -80,8 +99,9 @@
|
||||
language: lang, // Sets language of the code
|
||||
readOnly: true, // Sets to readonly
|
||||
theme: 'theme', // Sets editor theme
|
||||
minimap: {enabled: false}, // Disables minimap
|
||||
minimap: { enabled: false }, // Disables minimap
|
||||
lineNumbersMinChars: '3', // Width of the line numbers
|
||||
contextmenu: contextMenu,
|
||||
scrollbar: {
|
||||
// Deactivate shadows
|
||||
shadows: false,
|
||||
@ -90,7 +110,7 @@
|
||||
vertical: 'auto',
|
||||
horizontal: 'auto',
|
||||
},
|
||||
stickyScroll: {enabled: stickyScroll},
|
||||
stickyScroll: { enabled: stickyScroll },
|
||||
fontSize: fontSize,
|
||||
wordWrap: (wrap ? 'on' : 'off') // Word wraps
|
||||
});
|
||||
@ -117,12 +137,7 @@
|
||||
// Method that will be executed when the action is triggered.
|
||||
// @param editor The editor instance is passed in as a convenience
|
||||
run: function (ed) {
|
||||
if (wrap) {
|
||||
editor.updateOptions({ wordWrap: 'off' })
|
||||
} else {
|
||||
editor.updateOptions({ wordWrap: 'on' })
|
||||
}
|
||||
wrap = !wrap;
|
||||
runToggleTextWrapCommand();
|
||||
}
|
||||
});
|
||||
|
||||
@ -151,4 +166,4 @@
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -3,20 +3,26 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Peek.Common.Constants;
|
||||
using Peek.Common.Helpers;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.System;
|
||||
using Windows.UI;
|
||||
|
||||
using Control = System.Windows.Controls.Control;
|
||||
|
||||
namespace Peek.FilePreviewer.Controls
|
||||
{
|
||||
public sealed partial class BrowserControl : UserControl, IDisposable
|
||||
public sealed partial class BrowserControl : Microsoft.UI.Xaml.Controls.UserControl, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper private Uri where we cache the last navigated page
|
||||
@ -67,6 +73,25 @@ namespace Peek.FilePreviewer.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CustomContextMenuProperty = DependencyProperty.Register(
|
||||
nameof(CustomContextMenu),
|
||||
typeof(bool),
|
||||
typeof(BrowserControl),
|
||||
null);
|
||||
|
||||
public bool CustomContextMenu
|
||||
{
|
||||
get
|
||||
{
|
||||
return (bool)GetValue(CustomContextMenuProperty);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetValue(CustomContextMenuProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public BrowserControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
@ -78,6 +103,7 @@ namespace Peek.FilePreviewer.Controls
|
||||
if (PreviewBrowser.CoreWebView2 != null)
|
||||
{
|
||||
PreviewBrowser.CoreWebView2.DOMContentLoaded -= CoreWebView2_DOMContentLoaded;
|
||||
PreviewBrowser.CoreWebView2.ContextMenuRequested -= CoreWebView2_ContextMenuRequested;
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +171,7 @@ namespace Peek.FilePreviewer.Controls
|
||||
PreviewBrowser.DefaultBackgroundColor = Color.FromArgb(0, 0, 0, 0);
|
||||
|
||||
PreviewBrowser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true;
|
||||
PreviewBrowser.CoreWebView2.Settings.AreDevToolsEnabled = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.AreHostObjectsAllowed = false;
|
||||
PreviewBrowser.CoreWebView2.Settings.IsGeneralAutofillEnabled = false;
|
||||
@ -164,6 +190,7 @@ namespace Peek.FilePreviewer.Controls
|
||||
|
||||
PreviewBrowser.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
|
||||
PreviewBrowser.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
|
||||
PreviewBrowser.CoreWebView2.ContextMenuRequested += CoreWebView2_ContextMenuRequested;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -173,6 +200,87 @@ namespace Peek.FilePreviewer.Controls
|
||||
Navigate();
|
||||
}
|
||||
|
||||
private List<Control> GetContextMenuItems(CoreWebView2 sender, CoreWebView2ContextMenuRequestedEventArgs args)
|
||||
{
|
||||
var menuItems = args.MenuItems;
|
||||
|
||||
if (menuItems.IsReadOnly)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (CustomContextMenu)
|
||||
{
|
||||
MenuItem CreateCommandMenuItem(string resourceId, string commandName)
|
||||
{
|
||||
MenuItem commandMenuItem = new()
|
||||
{
|
||||
Header = ResourceLoaderInstance.ResourceLoader.GetString(resourceId),
|
||||
IsEnabled = true,
|
||||
};
|
||||
|
||||
commandMenuItem.Click += async (s, ex) =>
|
||||
{
|
||||
await sender.ExecuteScriptAsync($"{commandName}()");
|
||||
};
|
||||
|
||||
return commandMenuItem;
|
||||
}
|
||||
|
||||
// When using Monaco, we show menu items that call the appropriate JS functions -
|
||||
// WebView2 isn't able to show a "Copy" menu item of its own.
|
||||
return [
|
||||
CreateCommandMenuItem("ContextMenu_Copy", "runCopyCommand"),
|
||||
new Separator(),
|
||||
CreateCommandMenuItem("ContextMenu_ToggleTextWrapping", "runToggleTextWrapCommand"),
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuItem CreateMenuItemFromWebViewMenuItem(CoreWebView2ContextMenuItem webViewMenuItem)
|
||||
{
|
||||
MenuItem menuItem = new()
|
||||
{
|
||||
Header = webViewMenuItem.Label.Replace('&', '_'), // replace with '_' so it is underlined in the label
|
||||
IsEnabled = webViewMenuItem.IsEnabled,
|
||||
InputGestureText = webViewMenuItem.ShortcutKeyDescription,
|
||||
};
|
||||
|
||||
menuItem.Click += (_, _) =>
|
||||
{
|
||||
args.SelectedCommandId = webViewMenuItem.CommandId;
|
||||
};
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
// When not using Monaco, we keep the "Copy" menu item from WebView2's default context menu.
|
||||
return menuItems.Where(menuItem => menuItem.Name == "copy")
|
||||
.Select(CreateMenuItemFromWebViewMenuItem)
|
||||
.ToList<Control>();
|
||||
}
|
||||
}
|
||||
|
||||
private void CoreWebView2_ContextMenuRequested(CoreWebView2 sender, CoreWebView2ContextMenuRequestedEventArgs args)
|
||||
{
|
||||
var deferral = args.GetDeferral();
|
||||
args.Handled = true;
|
||||
|
||||
var menuItems = GetContextMenuItems(sender, args);
|
||||
|
||||
if (menuItems.Count != 0)
|
||||
{
|
||||
var contextMenu = new ContextMenu();
|
||||
contextMenu.Closed += (_, _) => deferral.Complete();
|
||||
contextMenu.IsOpen = true;
|
||||
|
||||
foreach (var menuItem in menuItems)
|
||||
{
|
||||
contextMenu.Items.Add(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CoreWebView2_DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args)
|
||||
{
|
||||
// If the file being previewed is HTML or HTM, reset the background color to its original state.
|
||||
@ -202,7 +310,7 @@ namespace Peek.FilePreviewer.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private async void PreviewBrowser_NavigationStarting(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args)
|
||||
private async void PreviewBrowser_NavigationStarting(WebView2 sender, CoreWebView2NavigationStartingEventArgs args)
|
||||
{
|
||||
if (_navigatedUri == null)
|
||||
{
|
||||
@ -218,7 +326,7 @@ namespace Peek.FilePreviewer.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviewWV2_NavigationCompleted(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs args)
|
||||
private void PreviewWV2_NavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
|
||||
{
|
||||
if (args.IsSuccess)
|
||||
{
|
||||
|
@ -62,6 +62,7 @@
|
||||
<controls:BrowserControl
|
||||
x:Name="BrowserPreview"
|
||||
x:Load="True"
|
||||
CustomContextMenu="{x:Bind BrowserPreviewer.CustomContextMenu, Mode=OneWay}"
|
||||
DOMContentLoaded="BrowserPreview_DOMContentLoaded"
|
||||
FlowDirection="LeftToRight"
|
||||
IsDevFilePreview="{x:Bind BrowserPreviewer.IsDevFilePreview, Mode=OneWay}"
|
||||
|
@ -11,5 +11,7 @@ namespace Peek.FilePreviewer.Previewers.Interfaces
|
||||
public Uri? Preview { get; }
|
||||
|
||||
public bool IsDevFilePreview { get; }
|
||||
|
||||
public bool CustomContextMenu { get; }
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ namespace Peek.FilePreviewer.Previewers
|
||||
|
||||
html = html.Replace("[[PT_LANG]]", vsCodeLangSet, StringComparison.InvariantCulture);
|
||||
html = html.Replace("[[PT_WRAP]]", wrapText ? "1" : "0", StringComparison.InvariantCulture);
|
||||
html = html.Replace("[[PT_CONTEXTMENU]]", "0", StringComparison.InvariantCulture);
|
||||
html = html.Replace("[[PT_STICKY_SCROLL]]", stickyScroll ? "1" : "0", StringComparison.InvariantCulture);
|
||||
html = html.Replace("[[PT_THEME]]", theme, StringComparison.InvariantCulture);
|
||||
html = html.Replace("[[PT_FONT_SIZE]]", fontSize.ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCulture);
|
||||
|
@ -43,6 +43,9 @@ namespace Peek.FilePreviewer.Previewers
|
||||
[ObservableProperty]
|
||||
private bool isDevFilePreview;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool customContextMenu;
|
||||
|
||||
private bool disposed;
|
||||
|
||||
public WebBrowserPreviewer(IFileSystemItem file, IPreviewSettings previewSettings)
|
||||
@ -107,9 +110,14 @@ namespace Peek.FilePreviewer.Previewers
|
||||
{
|
||||
bool isHtml = File.Extension == ".html" || File.Extension == ".htm";
|
||||
bool isMarkdown = File.Extension == ".md";
|
||||
IsDevFilePreview = MonacoHelper.SupportedMonacoFileTypes.Contains(File.Extension);
|
||||
|
||||
if (IsDevFilePreview && !isHtml && !isMarkdown)
|
||||
bool supportedByMonaco = MonacoHelper.SupportedMonacoFileTypes.Contains(File.Extension);
|
||||
bool useMonaco = supportedByMonaco && !isHtml && !isMarkdown;
|
||||
|
||||
IsDevFilePreview = supportedByMonaco;
|
||||
CustomContextMenu = useMonaco;
|
||||
|
||||
if (useMonaco)
|
||||
{
|
||||
var raw = await ReadHelper.Read(File.Path.ToString());
|
||||
Preview = new Uri(MonacoHelper.PreviewTempFile(raw, File.Extension, TempFolderPath.Path, _previewSettings.SourceCodeTryFormat, _previewSettings.SourceCodeWrapText, _previewSettings.SourceCodeStickyScroll, _previewSettings.SourceCodeFontSize));
|
||||
|
@ -318,4 +318,12 @@
|
||||
<value>Length: {0}</value>
|
||||
<comment>{0} is the duration of the audio read from file metadata</comment>
|
||||
</data>
|
||||
<data name="ContextMenu_Copy" xml:space="preserve">
|
||||
<value>Copy</value>
|
||||
<comment>Copy selected text to clipboard</comment>
|
||||
</data>
|
||||
<data name="ContextMenu_ToggleTextWrapping" xml:space="preserve">
|
||||
<value>Toggle text wrapping</value>
|
||||
<comment>Toggle whether text in pane is word-wrapped</comment>
|
||||
</data>
|
||||
</root>
|
@ -143,7 +143,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown
|
||||
await _browser.EnsureCoreWebView2Async(_webView2Environment).ConfigureAwait(true);
|
||||
_browser.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, AssemblyDirectory, CoreWebView2HostResourceAccessKind.Deny);
|
||||
_browser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
|
||||
_browser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
|
||||
_browser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true;
|
||||
_browser.CoreWebView2.Settings.AreDevToolsEnabled = false;
|
||||
_browser.CoreWebView2.Settings.AreHostObjectsAllowed = false;
|
||||
_browser.CoreWebView2.Settings.IsGeneralAutofillEnabled = false;
|
||||
@ -162,6 +162,23 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown
|
||||
}
|
||||
};
|
||||
|
||||
_browser.CoreWebView2.ContextMenuRequested += (object sender, CoreWebView2ContextMenuRequestedEventArgs args) =>
|
||||
{
|
||||
var menuItems = args.MenuItems;
|
||||
|
||||
if (!menuItems.IsReadOnly)
|
||||
{
|
||||
var copyMenuItem = menuItems.FirstOrDefault(menuItem => menuItem.Name == "copy");
|
||||
|
||||
menuItems.Clear();
|
||||
|
||||
if (copyMenuItem != null)
|
||||
{
|
||||
menuItems.Add(copyMenuItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// WebView2.NavigateToString() limitation
|
||||
// See https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.navigatetostring?view=webview2-dotnet-1.0.864.35#remarks
|
||||
// While testing the limit, it turned out it is ~1.5MB, so to be on a safe side we go for 1.5m bytes
|
||||
|
@ -396,6 +396,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco
|
||||
_html = FilePreviewCommon.MonacoHelper.ReadIndexHtml();
|
||||
_html = _html.Replace("[[PT_LANG]]", _vsCodeLangSet, StringComparison.InvariantCulture);
|
||||
_html = _html.Replace("[[PT_WRAP]]", _settings.Wrap ? "1" : "0", StringComparison.InvariantCulture);
|
||||
_html = _html.Replace("[[PT_CONTEXTMENU]]", "1", StringComparison.InvariantCulture);
|
||||
_html = _html.Replace("[[PT_THEME]]", Settings.GetTheme(), StringComparison.InvariantCulture);
|
||||
_html = _html.Replace("[[PT_STICKY_SCROLL]]", _settings.StickyScroll ? "1" : "0", StringComparison.InvariantCulture);
|
||||
_html = _html.Replace("[[PT_FONT_SIZE]]", _settings.FontSize.ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCulture);
|
||||
|
Loading…
Reference in New Issue
Block a user