// 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; using System.Drawing; using System.IO; using System.Text.RegularExpressions; using System.Windows.Forms; using Common; using Markdig; using MarkdownPreviewHandler.Properties; using MarkdownPreviewHandler.Telemetry.Events; using Microsoft.PowerToys.Telemetry; using PreviewHandlerCommon; namespace MarkdownPreviewHandler { /// /// Win Form Implementation for Markdown Preview Handler. /// public class MarkdownPreviewHandlerControl : FormHandlerControl { /// /// Extension to modify markdown AST. /// private readonly HTMLParsingExtension extension; /// /// Markdig Pipeline builder. /// private readonly MarkdownPipelineBuilder pipelineBuilder; /// /// Markdown HTML header. /// private readonly string htmlHeader = "
"; /// /// Markdown HTML footer. /// private readonly string htmlFooter = "
"; /// /// RichTextBox control to display if external images are blocked. /// private RichTextBox infoBar; /// /// Extended Browser Control to display markdown html. /// private WebBrowserExt browser; /// /// True if external image is blocked, false otherwise. /// private bool infoBarDisplayed = false; /// /// Initializes a new instance of the class. /// public MarkdownPreviewHandlerControl() { this.extension = new HTMLParsingExtension(this.ImagesBlockedCallBack); this.pipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions().UseEmojiAndSmiley(); this.pipelineBuilder.Extensions.Add(this.extension); } /// /// Start the preview on the Control. /// /// Path to the file. public override void DoPreview(T dataSource) { this.infoBarDisplayed = false; try { if (!(dataSource is string filePath)) { throw new ArgumentException($"{nameof(dataSource)} for {nameof(MarkdownPreviewHandler)} must be a string but was a '{typeof(T)}'"); } string fileText = File.ReadAllText(filePath); Regex imageTagRegex = new Regex(@"<[ ]*img.*>"); if (imageTagRegex.IsMatch(fileText)) { this.infoBarDisplayed = true; } this.extension.BaseUrl = Path.GetDirectoryName(filePath); MarkdownPipeline pipeline = this.pipelineBuilder.Build(); string parsedMarkdown = Markdown.ToHtml(fileText, pipeline); string markdownHTML = $"{this.htmlHeader}{parsedMarkdown}{this.htmlFooter}"; this.InvokeOnControlThread(() => { this.browser = new WebBrowserExt { DocumentText = markdownHTML, Dock = DockStyle.Fill, IsWebBrowserContextMenuEnabled = false, ScriptErrorsSuppressed = true, ScrollBarsEnabled = true, AllowNavigation = false, }; this.Controls.Add(this.browser); if (this.infoBarDisplayed) { this.infoBar = this.GetTextBoxControl(Resources.BlockedImageInfoText); this.Resize += this.FormResized; this.Controls.Add(this.infoBar); } }); PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewed()); } catch (Exception e) { PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewError { Message = e.Message }); this.InvokeOnControlThread(() => { this.Controls.Clear(); this.infoBarDisplayed = true; this.infoBar = this.GetTextBoxControl(Resources.MarkdownNotPreviewedError); this.Resize += this.FormResized; this.Controls.Add(this.infoBar); }); } finally { base.DoPreview(dataSource); } } /// /// Gets a textbox control. /// /// Message to be displayed in textbox. /// An object of type . private RichTextBox GetTextBoxControl(string message) { RichTextBox richTextBox = new RichTextBox { Text = message, BackColor = Color.LightYellow, Multiline = true, Dock = DockStyle.Top, ReadOnly = true, }; richTextBox.ContentsResized += this.RTBContentsResized; richTextBox.ScrollBars = RichTextBoxScrollBars.None; richTextBox.BorderStyle = BorderStyle.None; return richTextBox; } /// /// Callback when RichTextBox is resized. /// /// Reference to resized control. /// Provides data for the resize event. private void RTBContentsResized(object sender, ContentsResizedEventArgs e) { RichTextBox richTextBox = (RichTextBox)sender; richTextBox.Height = e.NewRectangle.Height + 5; } /// /// Callback when form is resized. /// /// Reference to resized control. /// Provides data for the event. private void FormResized(object sender, EventArgs e) { if (this.infoBarDisplayed) { this.infoBar.Width = this.Width; } } /// /// Callback when image is blocked by extension. /// private void ImagesBlockedCallBack() { this.infoBarDisplayed = true; } } }