mirror of
https://github.com/QL-Win/QuickLook.git
synced 2025-09-10 09:19:06 +00:00
Merge pull request #1490 from KamilDev/feature/improve-markdown-viewer
Improved MarkdownViewer
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2021 Paddy Xu and Frank Becker
|
||||
// Copyright © 2021 Paddy Xu and Frank Becker
|
||||
//
|
||||
// This file is part of QuickLook program.
|
||||
//
|
||||
@@ -17,8 +17,11 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
@@ -44,10 +47,12 @@ namespace QuickLook.Plugin.HtmlViewer
|
||||
{
|
||||
CreationProperties = new CoreWebView2CreationProperties
|
||||
{
|
||||
UserDataFolder = Path.Combine(SettingHelper.LocalDataPath, @"WebView2_Data\\")
|
||||
}
|
||||
UserDataFolder = Path.Combine(SettingHelper.LocalDataPath, @"WebView2_Data\\"),
|
||||
},
|
||||
DefaultBackgroundColor = OSThemeHelper.AppsUseDarkTheme() ? Color.FromArgb(255, 32, 32, 32) : Color.White, // Prevent white flash in dark mode
|
||||
};
|
||||
_webView.NavigationStarting += NavigationStarting_CancelNavigation;
|
||||
_webView.NavigationCompleted += WebView_NavigationCompleted;
|
||||
Content = _webView;
|
||||
}
|
||||
}
|
||||
@@ -80,7 +85,134 @@ namespace QuickLook.Plugin.HtmlViewer
|
||||
return;
|
||||
|
||||
var newUri = new Uri(e.Uri);
|
||||
if (newUri != _currentUri) e.Cancel = true;
|
||||
if (newUri == _currentUri) return;
|
||||
e.Cancel = true;
|
||||
|
||||
// Open in default browser
|
||||
try
|
||||
{
|
||||
if (!Uri.TryCreate(e.Uri, UriKind.Absolute, out var uri))
|
||||
{
|
||||
Debug.WriteLine($"Invalid URI format: {e.Uri}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Safe schemes can open directly
|
||||
if (uri.Scheme == Uri.UriSchemeHttp ||
|
||||
uri.Scheme == Uri.UriSchemeHttps ||
|
||||
uri.Scheme == Uri.UriSchemeMailto)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(uri.AbsoluteUri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Failed to open URL: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask user for unsafe schemes. Use dispatcher to avoid blocking thread.
|
||||
string associatedApp = GetAssociatedAppForScheme(uri.Scheme);
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
!string.IsNullOrEmpty(associatedApp) ?
|
||||
$"The following link will open in {associatedApp}:\n{e.Uri}" : $"The following link will open:\n{e.Uri}",
|
||||
!string.IsNullOrEmpty(associatedApp) ?
|
||||
$"Open {associatedApp}?" : "Open custom URI?",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Information);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(e.Uri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Failed to open URL: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Failed to open URL: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#region Get Associated App For Scheme
|
||||
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern uint AssocQueryString(
|
||||
AssocF flags,
|
||||
AssocStr str,
|
||||
string pszAssoc,
|
||||
string pszExtra,
|
||||
[Out] StringBuilder pszOut,
|
||||
ref uint pcchOut);
|
||||
|
||||
[Flags]
|
||||
private enum AssocF
|
||||
{
|
||||
None = 0,
|
||||
VerifyExists = 0x1
|
||||
}
|
||||
|
||||
private enum AssocStr
|
||||
{
|
||||
Command = 1,
|
||||
Executable = 2,
|
||||
FriendlyAppName = 4
|
||||
}
|
||||
|
||||
private string GetAssociatedAppForScheme(string scheme)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to get friendly app name first
|
||||
uint pcchOut = 0;
|
||||
AssocQueryString(AssocF.None, AssocStr.FriendlyAppName, scheme, null, null, ref pcchOut);
|
||||
|
||||
if (pcchOut > 0)
|
||||
{
|
||||
StringBuilder pszOut = new StringBuilder((int)pcchOut);
|
||||
AssocQueryString(AssocF.None, AssocStr.FriendlyAppName, scheme, null, pszOut, ref pcchOut);
|
||||
|
||||
var appName = pszOut.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(appName))
|
||||
return appName;
|
||||
}
|
||||
|
||||
// Fall back to executable name if friendly name is not available
|
||||
pcchOut = 0;
|
||||
AssocQueryString(AssocF.None, AssocStr.Executable, scheme, null, null, ref pcchOut);
|
||||
|
||||
if (pcchOut > 0)
|
||||
{
|
||||
StringBuilder pszOut = new StringBuilder((int)pcchOut);
|
||||
AssocQueryString(AssocF.None, AssocStr.Executable, scheme, null, pszOut, ref pcchOut);
|
||||
|
||||
var exeName = pszOut.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(exeName))
|
||||
return Path.GetFileName(exeName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Failed to get associated app: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
|
||||
{
|
||||
_webView.DefaultBackgroundColor = Color.White; // Reset to white after page load to match expected default behavior
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@@ -0,0 +1,54 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$ResourcesDir,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$OutputFile
|
||||
)
|
||||
|
||||
# Get all files in the Resources directory
|
||||
$files = Get-ChildItem -Path $ResourcesDir -Recurse -File | Sort-Object -Property FullName
|
||||
|
||||
# Create SHA256 hasher
|
||||
$sha256 = [System.Security.Cryptography.SHA256]::Create()
|
||||
|
||||
# Create memory stream to combine all file contents
|
||||
$memoryStream = New-Object System.IO.MemoryStream
|
||||
|
||||
# Add each file's path and contents to the hash
|
||||
foreach ($file in $files) {
|
||||
# Get relative path and convert to lowercase for consistent hashing
|
||||
$relativePath = $file.FullName.Substring($ResourcesDir.Length + 1).ToLowerInvariant()
|
||||
$pathBytes = [System.Text.Encoding]::UTF8.GetBytes("QuickLook.Plugin.MarkdownViewer.Resources.$relativePath".Replace("\", "/"))
|
||||
$memoryStream.Write($pathBytes, 0, $pathBytes.Length)
|
||||
|
||||
# Add file contents
|
||||
$fileBytes = [System.IO.File]::ReadAllBytes($file.FullName)
|
||||
$memoryStream.Write($fileBytes, 0, $fileBytes.Length)
|
||||
}
|
||||
|
||||
# Calculate final hash
|
||||
$hashBytes = $sha256.ComputeHash($memoryStream.ToArray())
|
||||
$hash = [BitConverter]::ToString($hashBytes).Replace("-", "").ToLowerInvariant()
|
||||
|
||||
# Generate C# code
|
||||
$code = @"
|
||||
// <auto-generated>
|
||||
// This file was generated during build to indicate changes to embedded resources.
|
||||
// </auto-generated>
|
||||
|
||||
namespace QuickLook.Plugin.MarkdownViewer
|
||||
{
|
||||
internal static class EmbeddedResourcesHash
|
||||
{
|
||||
internal const string Hash = "$hash";
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
# Write to output file
|
||||
[System.IO.File]::WriteAllText($OutputFile, $code)
|
||||
|
||||
# Cleanup
|
||||
$memoryStream.Dispose()
|
||||
$sha256.Dispose()
|
@@ -1,4 +1,4 @@
|
||||
// Copyright © 2017 Paddy Xu
|
||||
// Copyright © 2017 Paddy Xu
|
||||
//
|
||||
// This file is part of QuickLook program.
|
||||
//
|
||||
@@ -16,12 +16,14 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using QuickLook.Common.Helpers;
|
||||
using QuickLook.Common.Plugin;
|
||||
using QuickLook.Plugin.HtmlViewer;
|
||||
using UtfUnknown;
|
||||
@@ -30,17 +32,27 @@ namespace QuickLook.Plugin.MarkdownViewer
|
||||
{
|
||||
public class Plugin : IViewer
|
||||
{
|
||||
private WebpagePanel _panel;
|
||||
private WebpagePanel? _panel;
|
||||
private string? _currentHtmlPath;
|
||||
|
||||
private static readonly string _resourcePath = Path.Combine(SettingHelper.LocalDataPath, "QuickLook.Plugin.MarkdownViewer");
|
||||
private static readonly string _resourcePrefix = "QuickLook.Plugin.MarkdownViewer.Resources.";
|
||||
private static readonly ResourceManager _resourceManager = new ResourceManager(_resourcePath, _resourcePrefix);
|
||||
|
||||
public int Priority => 0;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Initialize resources and handle versioning
|
||||
_resourceManager.InitializeResources();
|
||||
|
||||
// Clean up any temporary HTML files if QuickLook was forcibly terminated
|
||||
CleanupTempFiles();
|
||||
}
|
||||
|
||||
public bool CanHandle(string path)
|
||||
{
|
||||
return !Directory.Exists(path) && new[] {".md", ".rmd", ".markdown"}.Any(path.ToLower().EndsWith);
|
||||
return !Directory.Exists(path) && new[] { ".md", ".rmd", ".markdown" }.Any(path.ToLower().EndsWith);
|
||||
}
|
||||
|
||||
public void Prepare(string path, ContextObject context)
|
||||
@@ -54,29 +66,85 @@ namespace QuickLook.Plugin.MarkdownViewer
|
||||
context.ViewerContent = _panel;
|
||||
context.Title = Path.GetFileName(path);
|
||||
|
||||
_panel.NavigateToHtml(GenerateMarkdownHtml(path));
|
||||
var htmlPath = GenerateMarkdownHtml(path);
|
||||
_panel.NavigateToFile(htmlPath);
|
||||
_panel.Dispatcher.Invoke(() => { context.IsBusy = false; }, DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private string GenerateMarkdownHtml(string path)
|
||||
{
|
||||
var templatePath = Path.Combine(_resourcePath, "md2html.html");
|
||||
|
||||
if (!File.Exists(templatePath))
|
||||
throw new FileNotFoundException($"Required template file md2html.html not found in extracted resources at {templatePath}");
|
||||
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
var encoding = CharsetDetector.DetectFromBytes(bytes).Detected?.Encoding ?? Encoding.Default;
|
||||
var content = encoding.GetString(bytes);
|
||||
|
||||
var template = File.ReadAllText(templatePath);
|
||||
var html = template.Replace("{{content}}", content);
|
||||
|
||||
// Generate unique filename and ensure it doesn't exist
|
||||
string outputPath;
|
||||
do
|
||||
{
|
||||
var uniqueId = Guid.NewGuid().ToString("N").Substring(0, 8);
|
||||
var outputFileName = $"temp_{uniqueId}.html";
|
||||
outputPath = Path.Combine(_resourcePath, outputFileName);
|
||||
} while (File.Exists(outputPath));
|
||||
|
||||
// Clean up previous file if it exists
|
||||
CleanupTempHtmlFile();
|
||||
|
||||
File.WriteAllText(outputPath, html);
|
||||
_currentHtmlPath = outputPath;
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
#region Cleanup
|
||||
private void CleanupTempHtmlFile()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_currentHtmlPath) && File.Exists(_currentHtmlPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(_currentHtmlPath);
|
||||
}
|
||||
catch (IOException) { } // Ignore deletion errors
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupTempFiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tempFiles = Directory.GetFiles(_resourcePath, "temp_*.html");
|
||||
foreach (var file in tempFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
catch (IOException) { } // Ignore deletion errors
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Failed to clean up temporary HTML files: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
CleanupTempHtmlFile();
|
||||
|
||||
_panel?.Dispose();
|
||||
_panel = null;
|
||||
}
|
||||
|
||||
private string GenerateMarkdownHtml(string path)
|
||||
{
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
var encoding = CharsetDetector.DetectFromBytes(bytes).Detected?.Encoding ?? Encoding.Default;
|
||||
|
||||
var md = encoding.GetString(bytes);
|
||||
md = WebUtility.HtmlEncode(md);
|
||||
|
||||
var html = Resources.md2html.Replace("{{content}}", md);
|
||||
|
||||
return html;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -73,12 +73,28 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\**" />
|
||||
<EmbeddedResource Include="Resources\**\*">
|
||||
<LogicalName>QuickLook.Plugin.MarkdownViewer.Resources.%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\**" />
|
||||
</ItemGroup>
|
||||
<Target Name="GenerateEmbeddedResourcesHash" BeforeTargets="CoreCompile">
|
||||
<PropertyGroup>
|
||||
<HashFileDir>$(IntermediateOutputPath)Generated</HashFileDir>
|
||||
<HashFilePath>$(HashFileDir)\EmbeddedResourcesHash.cs</HashFilePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Create the output directory -->
|
||||
<MakeDir Directories="$(HashFileDir)" />
|
||||
|
||||
<!-- Run a script to calculate hash and generate code file -->
|
||||
<Exec Command="powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$(MSBuildThisFileDirectory)GenerateEmbeddedResourcesHash.ps1" -ResourcesDir "$(MSBuildThisFileDirectory)Resources" -OutputFile "$(HashFilePath)"" />
|
||||
|
||||
<!-- Include the generated file in compilation -->
|
||||
<ItemGroup>
|
||||
<Compile Include="$(HashFilePath)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\GitVersion.cs">
|
||||
|
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using QuickLook.Common.Helpers;
|
||||
|
||||
namespace QuickLook.Plugin.MarkdownViewer
|
||||
{
|
||||
internal class ResourceManager
|
||||
{
|
||||
private readonly string _resourcePath;
|
||||
private readonly string _resourcePrefix;
|
||||
private readonly string _versionFilePath;
|
||||
private readonly string _noUpdateFilePath;
|
||||
private readonly string _embeddedHash;
|
||||
|
||||
public ResourceManager(string resourcePath, string resourcePrefix)
|
||||
{
|
||||
_resourcePath = resourcePath;
|
||||
_resourcePrefix = resourcePrefix;
|
||||
_versionFilePath = Path.Combine(_resourcePath, ".version");
|
||||
_noUpdateFilePath = Path.Combine(_resourcePath, ".noupdate");
|
||||
_embeddedHash = GetEmbeddedResourcesHash();
|
||||
}
|
||||
|
||||
public void InitializeResources()
|
||||
{
|
||||
// Extract resources for the first time
|
||||
if (!Directory.Exists(_resourcePath))
|
||||
{
|
||||
ExtractResources();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if updates are disabled
|
||||
if (File.Exists(_noUpdateFilePath))
|
||||
return;
|
||||
|
||||
// Check if resources need updating by comparing hashes
|
||||
var versionInfo = ReadVersionFile();
|
||||
|
||||
if (versionInfo == null)
|
||||
{
|
||||
// No version file exists, create it and extract resources
|
||||
ExtractResources();
|
||||
return;
|
||||
}
|
||||
|
||||
// If embedded hash matches stored hash, no update needed
|
||||
if (_embeddedHash == versionInfo.EmbeddedHash) return;
|
||||
|
||||
// Calculate current directory hash
|
||||
var currentDirectoryHash = CalculateDirectoryHash(_resourcePath);
|
||||
|
||||
// If current directory matches the stored extracted hash, user hasn't modified files
|
||||
if (currentDirectoryHash == versionInfo.ExtractedHash)
|
||||
{
|
||||
// Safe to update
|
||||
ExtractResources();
|
||||
return;
|
||||
}
|
||||
|
||||
// User has modified files, ask for permission to update
|
||||
var result = MessageBox.Show(
|
||||
"The MarkdownViewer resources have been updated. Would you like to update to the newest version?\n\n" +
|
||||
"Note: Your current resources appear to have been modified. Updating will overwrite your modifications.",
|
||||
"MarkdownViewer Update Available",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
ExtractResources();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the embedded hash in the version file to prevent any more prompts for this version
|
||||
UpdateVersionFileEmbeddedHash();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractResources()
|
||||
{
|
||||
// Delete and recreate directory to ensure clean state
|
||||
if (Directory.Exists(_resourcePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(_resourcePath, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Failed to delete directory {_resourcePath}: {ex.Message}");
|
||||
// If we can't delete the directory, we'll try to continue with existing one
|
||||
}
|
||||
}
|
||||
Directory.CreateDirectory(_resourcePath);
|
||||
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var resourceNames = assembly.GetManifestResourceNames();
|
||||
|
||||
foreach (var resourceName in resourceNames)
|
||||
{
|
||||
if (!resourceName.StartsWith(_resourcePrefix)) continue;
|
||||
|
||||
var relativePath = resourceName.Substring(_resourcePrefix.Length);
|
||||
if (relativePath.Equals("resources", StringComparison.OrdinalIgnoreCase)) continue; // Skip 'resources' binary file
|
||||
|
||||
var targetPath = Path.Combine(_resourcePath, relativePath.Replace('/', Path.DirectorySeparatorChar));
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
var directory = Path.GetDirectoryName(targetPath);
|
||||
if (directory != null)
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// Extract the resource
|
||||
using (var stream = assembly.GetManifestResourceStream(resourceName))
|
||||
using (var fileStream = File.Create(targetPath))
|
||||
{
|
||||
if (stream == null) continue;
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate version file after extracting all resources
|
||||
GenerateVersionFile();
|
||||
|
||||
// Verify that md2html.html was extracted
|
||||
var htmlPath = Path.Combine(_resourcePath, "md2html.html");
|
||||
if (!File.Exists(htmlPath))
|
||||
{
|
||||
throw new FileNotFoundException($"Required template file md2html.html not found in resources. Available resources: {string.Join(", ", resourceNames)}");
|
||||
}
|
||||
}
|
||||
|
||||
private class VersionInfo
|
||||
{
|
||||
public string EmbeddedHash { get; set; }
|
||||
public string ExtractedHash { get; set; }
|
||||
|
||||
public VersionInfo(string embeddedHash, string extractedHash)
|
||||
{
|
||||
EmbeddedHash = embeddedHash;
|
||||
ExtractedHash = extractedHash;
|
||||
}
|
||||
}
|
||||
|
||||
private static string CalculateDirectoryHash(string directory)
|
||||
{
|
||||
var files = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories)
|
||||
.Where(f => !f.EndsWith(".version"))
|
||||
.OrderBy(f => f)
|
||||
.ToList();
|
||||
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
var combinedBytes = new List<byte>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
var relativePath = file.Substring(directory.Length + 1);
|
||||
var pathBytes = Encoding.UTF8.GetBytes(relativePath.ToLowerInvariant());
|
||||
combinedBytes.AddRange(pathBytes);
|
||||
|
||||
var contentBytes = File.ReadAllBytes(file);
|
||||
combinedBytes.AddRange(contentBytes);
|
||||
}
|
||||
|
||||
var hash = sha256.ComputeHash(combinedBytes.ToArray());
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEmbeddedResourcesHash()
|
||||
{
|
||||
try
|
||||
{
|
||||
return EmbeddedResourcesHash.Hash;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.WriteLine("QuickLook.Plugin.MarkdownViewer: Embedded resources hash file not found.");
|
||||
return CalculateEmbeddedResourcesHash();
|
||||
}
|
||||
}
|
||||
|
||||
private string CalculateEmbeddedResourcesHash()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
var combinedBytes = new List<byte>();
|
||||
var resourceNames = assembly.GetManifestResourceNames()
|
||||
.Where(name => name.StartsWith(_resourcePrefix) &&
|
||||
!name.EndsWith(".version"))
|
||||
.OrderBy(name => name)
|
||||
.ToList();
|
||||
|
||||
foreach (var resourceName in resourceNames)
|
||||
{
|
||||
var nameBytes = Encoding.UTF8.GetBytes(resourceName.ToLowerInvariant());
|
||||
combinedBytes.AddRange(nameBytes);
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(resourceName))
|
||||
{
|
||||
if (stream == null) continue;
|
||||
var buffer = new byte[stream.Length];
|
||||
stream.Read(buffer, 0, buffer.Length);
|
||||
combinedBytes.AddRange(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
var hash = sha256.ComputeHash(combinedBytes.ToArray());
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateVersionFile()
|
||||
{
|
||||
var extractedHash = CalculateDirectoryHash(_resourcePath);
|
||||
var newVersionContent = $"{_embeddedHash}{Environment.NewLine}{extractedHash}";
|
||||
File.WriteAllText(_versionFilePath, newVersionContent);
|
||||
}
|
||||
|
||||
private void UpdateVersionFileEmbeddedHash()
|
||||
{
|
||||
var versionInfo = ReadVersionFile() ?? throw new InvalidOperationException("Cannot update version file: no existing version file found");
|
||||
|
||||
var newVersionContent = $"{_embeddedHash}{Environment.NewLine}{versionInfo.ExtractedHash}";
|
||||
File.WriteAllText(_versionFilePath, newVersionContent);
|
||||
}
|
||||
|
||||
private VersionInfo? ReadVersionFile()
|
||||
{
|
||||
if (!File.Exists(_versionFilePath)) return null;
|
||||
|
||||
var lines = File.ReadAllLines(_versionFilePath);
|
||||
if (lines.Length < 2) return null;
|
||||
|
||||
return new VersionInfo(lines[0], lines[1]);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
1207
QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Resources/highlight.js/highlight.min.js
vendored
Normal file
1207
QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Resources/highlight.js/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||
Theme: GitHub Dark
|
||||
Description: Dark theme as seen on github.com
|
||||
Author: github.com
|
||||
Maintainer: @Hirse
|
||||
Updated: 2021-05-15
|
||||
|
||||
Outdated base version: https://github.com/primer/github-syntax-dark
|
||||
Current colors taken from GitHub's CSS
|
||||
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}
|
10
QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Resources/highlight.js/styles/github.min.css
vendored
Normal file
10
QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Resources/highlight.js/styles/github.min.css
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||
Theme: GitHub
|
||||
Description: Light theme as seen on github.com
|
||||
Author: github.com
|
||||
Maintainer: @Hirse
|
||||
Updated: 2021-05-15
|
||||
|
||||
Outdated base version: https://github.com/primer/github-syntax-light
|
||||
Current colors taken from GitHub's CSS
|
||||
*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}
|
2
QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Resources/js/markdown-it.min.js
vendored
Normal file
2
QuickLook.Plugin/QuickLook.Plugin.MarkdownViewer/Resources/js/markdown-it.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user