Skip to content

Commit

Permalink
[Peek][PreviewPane]Show Copy entry in right-click copy menu (microsof…
Browse files Browse the repository at this point in the history
…t#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)
  • Loading branch information
drawbyperpetual authored Jul 25, 2024
1 parent ac14ad3 commit 84def18
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 34 deletions.
69 changes: 42 additions & 27 deletions src/common/FilePreviewCommon/Assets/Monaco/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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" />
Expand All @@ -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' } });
Expand All @@ -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,
Expand All @@ -90,7 +110,7 @@
vertical: 'auto',
horizontal: 'auto',
},
stickyScroll: {enabled: stickyScroll},
stickyScroll: { enabled: stickyScroll },
fontSize: fontSize,
wordWrap: (wrap ? 'on' : 'off') // Word wraps
});
Expand All @@ -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();
}
});

Expand Down Expand Up @@ -151,4 +166,4 @@
}
</script>
</body>
</html>
</html>
116 changes: 112 additions & 4 deletions src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,6 +73,25 @@ public bool IsDevFilePreview
}
}

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();
Expand All @@ -78,6 +103,7 @@ public void Dispose()
if (PreviewBrowser.CoreWebView2 != null)
{
PreviewBrowser.CoreWebView2.DOMContentLoaded -= CoreWebView2_DOMContentLoaded;
PreviewBrowser.CoreWebView2.ContextMenuRequested -= CoreWebView2_ContextMenuRequested;
}
}

Expand Down Expand Up @@ -145,7 +171,7 @@ private async void PreviewWV2_Loaded(object sender, RoutedEventArgs e)
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;
Expand All @@ -164,6 +190,7 @@ private async void PreviewWV2_Loaded(object sender, RoutedEventArgs e)

PreviewBrowser.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
PreviewBrowser.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
PreviewBrowser.CoreWebView2.ContextMenuRequested += CoreWebView2_ContextMenuRequested;
}
catch (Exception ex)
{
Expand All @@ -173,6 +200,87 @@ private async void PreviewWV2_Loaded(object sender, RoutedEventArgs e)
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.
Expand Down Expand Up @@ -202,7 +310,7 @@ private async void CoreWebView2_NewWindowRequested(CoreWebView2 sender, CoreWebV
}
}

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)
{
Expand All @@ -218,7 +326,7 @@ private async void PreviewBrowser_NavigationStarting(WebView2 sender, Microsoft.
}
}

private void PreviewWV2_NavigationCompleted(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs args)
private void PreviewWV2_NavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
{
if (args.IsSuccess)
{
Expand Down
1 change: 1 addition & 0 deletions src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ public interface IBrowserPreviewer : IPreviewer, IPreviewTarget
public Uri? Preview { get; }

public bool IsDevFilePreview { get; }

public bool CustomContextMenu { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private static string InitializeIndexFileAndSelectedFile(string fileContent, str

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public partial class WebBrowserPreviewer : ObservableObject, IBrowserPreviewer,
[ObservableProperty]
private bool isDevFilePreview;

[ObservableProperty]
private bool customContextMenu;

private bool disposed;

public WebBrowserPreviewer(IFileSystemItem file, IPreviewSettings previewSettings)
Expand Down Expand Up @@ -107,9 +110,14 @@ await Dispatcher.RunOnUiThread(async () =>
{
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));
Expand Down
8 changes: 8 additions & 0 deletions src/modules/peek/Peek.UI/Strings/en-us/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Loading

0 comments on commit 84def18

Please sign in to comment.