From ae67d5247cc5d6c7ce4ad4a950220ea34bb24dab Mon Sep 17 00:00:00 2001 From: ema Date: Fri, 27 Mar 2026 10:45:20 +0800 Subject: [PATCH] Add Mermaid support and .mmd detection #1893 --- CHANGELOG.md | 13 ++-- .../MarkdownPanel.cs | 70 +++++++++++++++++++ .../QuickLook.Plugin.MarkdownViewer/Plugin.cs | 3 +- SUPPORTED_FORMATS.md | 3 +- 4 files changed, 81 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a687db7..4b6e0ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,9 @@ > Nevertheless, QuickLook has chosen to continue with an up-to-date update strategy. > > If you encounter any issues, you can refer to [#1362](https://github.com/QL-Win/QuickLook/issues/1362) and consider downgrading your LAVFilters version. - + - Improve LRC handling by merging duplicate timestamps [#1858](https://github.com/QL-Win/QuickLook/issues/1858) +- Improve the translation of Simplified Chinese by [@stxttkx](https://github.com/stxttkx) - Add PKCS7 extensions to supported file types (`.p7s` and `.pkcs7`) - Add support Fortran95 (`.f90`, `.f95`, `.f03`), GDScript (`.gd`), Diff (`.patch`, `.rej`), Razor (`.cshtml`, `.razor`), ActionScript (`.as`, `.mx`), Assembly (`.asm`), Ada (`.ada`, `.ads`, `.adb`), AutoHotkey (`.ahk`), Rhai (`.rhai`), C++ ( `.cu`, `.cuh`, `.hip`), Python (`.pyx`), PlantUML (`.puml`, `.plantuml`, `.pu`, `.uml`, `.iuml`, `.wsd`), Zig (`.zig`), Moji (`.moji`), GraphQL (`.graphql`, `.gql`, `.gqls`), Mermaid (`.mmd`, `.mermaid`), KQL (`.kql`), PromQL (`.promql`), ANTLR, Boo, Ceylon, ChucK, Clojure, Cocoa, CoffeeScript, Cool, and others syntax highlighting, including the dark mode theme - Add support Chromium `.pak` viewer and file extraction @@ -19,17 +20,17 @@ - Add `.cursorignore` extension to GitIgnore syntax - Add support for Python `.whl` and `.egg` archives - Add support `.psv` parsing in CsvViewer +- Add Mermaid (`.mermaid`) support and `.mmd` detection [#1893](https://github.com/QL-Win/QuickLook/issues/1893) +- Add plugin icon registration and include `QLPlugin.ico` designed by [@Shomnipotence](https://github.com/Shomnipotence) +- Add DICOM image support to ImageViewer plugin [#1866](https://github.com/QL-Win/QuickLook/issues/1866) `This is not a long-lasting built-in plugin` +- Add Romanian translation by [@Laszlo19](https://github.com/Laszlo19) - Fix markdown not supporting absolute resource paths - Fix option `False` not taking effect in Windows 10 [#1542](https://github.com/QL-Win/QuickLook/issues/1542) - Fix loop toggle resuming paused video playback [#1852](https://github.com/QL-Win/QuickLook/issues/1852) - Fix unhandled Exception with XLSX and CSV files in OfficeViewer resize with `RPC_E_CANTCALLOUT_ININPUTSYNCCALL` [#1854](https://github.com/QL-Win/QuickLook/issues/1854) - Fix command line relative path resolution [#1857](https://github.com/QL-Win/QuickLook/issues/1857) - Fix taskbar icon intermittently missing after Explorer restart [#1864](https://github.com/QL-Win/QuickLook/issues/1864) -- Fix `{Desktop composition is disabled}` exceptions in GetMonitorColorProfileFromWindow [#4](https://github.com/QL-Win/QuickLook.Common/pull/4) -- Add plugin icon registration and include `QLPlugin.ico` designed by [@Shomnipotence](https://github.com/Shomnipotence) -- Add DICOM image support to ImageViewer plugin [#1866](https://github.com/QL-Win/QuickLook/issues/1866) `This is not a long-lasting built-in plugin` -- Add Romanian translation by [@Laszlo19](https://github.com/Laszlo19) -- Updated the translation of Simplified Chinese by [@stxttkx](https://github.com/stxttkx) +- Fix `{Desktop composition is disabled}` exceptions in `GetMonitorColorProfileFromWindow` [#4](https://github.com/QL-Win/QuickLook.Common/pull/4) ## 4.4.0 diff --git a/QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/MarkdownPanel.cs b/QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/MarkdownPanel.cs index 3c0e7a3..880bd19 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/MarkdownPanel.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/MarkdownPanel.cs @@ -78,6 +78,7 @@ public class MarkdownPanel : WebpagePanel var bytes = File.ReadAllBytes(path); var encoding = CharsetDetector.DetectFromBytes(bytes).Detected?.Encoding ?? Encoding.Default; var content = encoding.GetString(bytes); + content = PrepareMarkdownContent(path, content); var template = ReadString("/md2html.html"); @@ -99,6 +100,75 @@ public class MarkdownPanel : WebpagePanel return html; } + private static string PrepareMarkdownContent(string path, string content) + { + var extension = Path.GetExtension(path); + + if (extension.Equals(".mermaid", StringComparison.OrdinalIgnoreCase)) + return WrapAsMermaidCodeFence(content); + + if (extension.Equals(".mmd", StringComparison.OrdinalIgnoreCase) + && IsLikelyMermaidDocument(content)) + return WrapAsMermaidCodeFence(content); + + return content; + } + + private static string WrapAsMermaidCodeFence(string content) + { + var normalized = content.Replace("\r\n", "\n").Trim('\n'); + return $"```mermaid\n{normalized}\n```"; + } + + private static bool IsLikelyMermaidDocument(string content) + { + if (string.IsNullOrWhiteSpace(content)) + return false; + + if (content.IndexOf("```mermaid", StringComparison.OrdinalIgnoreCase) >= 0) + return false; + + using var reader = new StringReader(content); + string line; + while ((line = reader.ReadLine()) != null) + { + var trimmed = line.Trim(); + if (trimmed.Length == 0 || trimmed.StartsWith("%%", StringComparison.Ordinal)) + continue; + + return trimmed.StartsWith("graph ", StringComparison.OrdinalIgnoreCase) + || trimmed.Equals("graph", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("flowchart ", StringComparison.OrdinalIgnoreCase) + || trimmed.Equals("flowchart", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("sequenceDiagram", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("classDiagram", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("stateDiagram", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("stateDiagram-v2", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("erDiagram", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("journey", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("gantt", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("pie", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("mindmap", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("timeline", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("gitGraph", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("quadrantChart", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("requirementDiagram", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("c4Context", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("c4Container", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("c4Component", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("c4Dynamic", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("c4Deployment", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("xychart-beta", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("block-beta", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("packet-beta", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("architecture-beta", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("kanban", StringComparison.OrdinalIgnoreCase) + || trimmed.StartsWith("sankey-beta", StringComparison.OrdinalIgnoreCase); + } + + return false; + } + protected override void WebView_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs args) { Debug.WriteLine($"[{args.Request.Method}] {args.Request.Uri}"); diff --git a/QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Plugin.cs index e36a9e6..9ac6c20 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Plugin.cs @@ -35,7 +35,8 @@ public sealed class Plugin : IViewer [ ".md", ".markdown", // The most common Markdown extensions ".mdx", // MDX (Markdown + JSX), used in React ecosystems - ".mmd", // MultiMarkdown (MMD), an extended version of Markdown + ".mmd", // MultiMarkdown (MMD), an extended version of Markdown or Mermaid diagram source file + ".mermaid", // Mermaid diagram source file ".mkd", ".mdwn", ".mdown", // Early Markdown variants, used by different parsers like Pandoc, Gitit, and Hakyll ".mdc", // A Markdown variant used by Cursor AI [Repeated format from ImageViewer] ".qmd", // Quarto Markdown, developed by RStudio for scientific computing and reproducible reports diff --git a/SUPPORTED_FORMATS.md b/SUPPORTED_FORMATS.md index 2e83d53..b41df93 100644 --- a/SUPPORTED_FORMATS.md +++ b/SUPPORTED_FORMATS.md @@ -224,7 +224,8 @@ Update not completed yet... ### Markdown files - `.md`, `.markdown` (Markdown) - `.mdx` (MDX: Markdown + JSX) -- `.mmd` (MultiMarkdown) +- `.mmd` (MultiMarkdown or Mermaid diagram source) +- `.mermaid` (Mermaid diagram source) - `.mkd`, `.mdwn`, `.mdown` (Markdown variants) - `.mdc` (Cursor AI Markdown) - `.qmd` (Quarto Markdown)