diff --git a/.gitignore b/.gitignore index 426d76d..4f9edbb 100644 --- a/.gitignore +++ b/.gitignore @@ -396,3 +396,143 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Rider ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# AWS User-specific + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr diff --git a/src/.idea/.idea.Markdowser/.idea/.name b/src/.idea/.idea.Markdowser/.idea/.name new file mode 100644 index 0000000..3442041 --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/.name @@ -0,0 +1 @@ +Markdowser \ No newline at end of file diff --git a/src/.idea/.idea.Markdowser/.idea/indexLayout.xml b/src/.idea/.idea.Markdowser/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Markdowser/.idea/projectSettingsUpdater.xml b/src/.idea/.idea.Markdowser/.idea/projectSettingsUpdater.xml new file mode 100644 index 0000000..4bb9f4d --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/projectSettingsUpdater.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Markdowser/.idea/vcs.xml b/src/.idea/.idea.Markdowser/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Markdowser/.idea/workspace.xml b/src/.idea/.idea.Markdowser/.idea/workspace.xml new file mode 100644 index 0000000..760e254 --- /dev/null +++ b/src/.idea/.idea.Markdowser/.idea/workspace.xml @@ -0,0 +1,80 @@ + + + + Markdowser/Markdowser.csproj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Markdowser/Utilities/CacheService.cs b/src/Markdowser/Utilities/CacheService.cs new file mode 100644 index 0000000..69f255d --- /dev/null +++ b/src/Markdowser/Utilities/CacheService.cs @@ -0,0 +1,27 @@ +using Markdowser.ViewModels; + +using System.Collections.Generic; + +namespace Markdowser.Utilities; + +public class CacheService +{ + private readonly Dictionary cache = []; + + public ContentViewModelBase? Get(string url) + { + if (cache.TryGetValue(url, out ContentViewModelBase? value)) + { + return value; + } + else + { + return null; + } + } + + public void Set(string url, ContentViewModelBase content) + { + cache[url] = content; + } +} \ No newline at end of file diff --git a/src/Markdowser/ViewModels/MainWindowViewModel.cs b/src/Markdowser/ViewModels/MainWindowViewModel.cs index 437a0d0..00a53e6 100644 --- a/src/Markdowser/ViewModels/MainWindowViewModel.cs +++ b/src/Markdowser/ViewModels/MainWindowViewModel.cs @@ -25,6 +25,7 @@ public partial class MainWindowViewModel : ViewModelBase { private readonly HttpClient httpClient = new(new HttpClientHandler() { AllowAutoRedirect = true }); private readonly ContentProcessorManager contentProcessorManager = new(); + private readonly CacheService cacheService = new CacheService(); private ContentViewModelBase content; private TabItem currentTab = null!; @@ -48,7 +49,7 @@ public partial class MainWindowViewModel : ViewModelBase Url = value?.Tag?.ToString() ?? string.Empty; - FetchUrl(); + FetchUrl(true); } } @@ -99,7 +100,8 @@ public partial class MainWindowViewModel : ViewModelBase public bool BackEnabled => GlobalState.BackHistory.Count > 0; public bool ForwardEnabled => GlobalState.ForwardHistory.Count > 0; public bool ProgressIndeterminate => Progress == 0; - public ICommand Browse => ReactiveCommand.Create(FetchUrl); + public ICommand Browse => ReactiveCommand.Create(() => FetchUrl(true)); + public ICommand Reload => ReactiveCommand.Create(() => FetchUrl(false)); public ICommand Back => ReactiveCommand.Create(() => { @@ -208,7 +210,7 @@ public partial class MainWindowViewModel : ViewModelBase return Uri.TryCreate(uriString, UriKind.Absolute, out uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps); } - private void FetchUrl() + private void FetchUrl(bool useCache = true) { if (IsBusy) { @@ -260,32 +262,41 @@ public partial class MainWindowViewModel : ViewModelBase try { - HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url); - - string? newUrl = httpResponseMessage.RequestMessage?.RequestUri?.ToString(); - - if (newUrl is not null && newUrl != Url) + ContentViewModelBase? cachedContent = cacheService.Get(Url); + if (cachedContent is not null && useCache) { - string oldUrl = Url; - Dispatcher.UIThread.Post(() => WindowNotificationManager.Show(new Notification("Redirected", $"Redirected from\n{oldUrl}\nto\n{newUrl}", NotificationType.Warning))); - Url = newUrl; + Content = cachedContent; } - - if (!httpResponseMessage.IsSuccessStatusCode) + else { - IsBusy = false; + HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url); - StringBuilder errorMessage = new(); + string? newUrl = httpResponseMessage.RequestMessage?.RequestUri?.ToString(); - _ = errorMessage.AppendLine($"# {(int)httpResponseMessage.StatusCode} {httpResponseMessage.StatusCode}"); - _ = errorMessage.AppendLine($"Failed to fetch URL: {httpResponseMessage.ReasonPhrase}"); + if (newUrl is not null && newUrl != Url) + { + string oldUrl = Url; + Dispatcher.UIThread.Post(() => WindowNotificationManager.Show(new Notification("Redirected", $"Redirected from\n{oldUrl}\nto\n{newUrl}", NotificationType.Warning))); + Url = newUrl; + } - Content = new MarkdownContentViewModel("Error", errorMessage.ToString()); - return; + if (!httpResponseMessage.IsSuccessStatusCode) + { + IsBusy = false; + + StringBuilder errorMessage = new(); + + _ = errorMessage.AppendLine($"# {(int)httpResponseMessage.StatusCode} {httpResponseMessage.StatusCode}"); + _ = errorMessage.AppendLine($"Failed to fetch URL: {httpResponseMessage.ReasonPhrase}"); + + Content = new MarkdownContentViewModel("Error", errorMessage.ToString()); + return; + } + + Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress(p => Progress = p.Percentage)); + cacheService.Set(Url, Content); } - Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress(p => Progress = p.Percentage)); - Dispatcher.UIThread.Post(() => CurrentTab.Header = Content.Title); Logger.LogInfo($"Fetched URL: {Url}"); diff --git a/src/Markdowser/Views/Content/CommonImageContentView.axaml b/src/Markdowser/Views/Content/CommonImageContentView.axaml index 1426a1d..34c13f8 100644 --- a/src/Markdowser/Views/Content/CommonImageContentView.axaml +++ b/src/Markdowser/Views/Content/CommonImageContentView.axaml @@ -8,7 +8,8 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:md="https://github.com/whistyun/Markdown.Avalonia" xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia" - x:DataType="vm:Content.CommonImageContentViewModel" + xmlns:content="clr-namespace:Markdowser.ViewModels.Content" + x:DataType="content:CommonImageContentViewModel" x:Class="Markdowser.Views.Content.CommonImageContentView"> diff --git a/src/Markdowser/Views/Content/MarkdownContentView.axaml b/src/Markdowser/Views/Content/MarkdownContentView.axaml index 8b01caa..3dfdc1d 100644 --- a/src/Markdowser/Views/Content/MarkdownContentView.axaml +++ b/src/Markdowser/Views/Content/MarkdownContentView.axaml @@ -8,7 +8,8 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:md="https://github.com/whistyun/Markdown.Avalonia" xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia" - x:DataType="vm:Content.MarkdownContentViewModel" + xmlns:content="clr-namespace:Markdowser.ViewModels.Content" + x:DataType="content:MarkdownContentViewModel" x:Class="Markdowser.Views.Content.MarkdownContentView"> diff --git a/src/Markdowser/Views/MainWindow.axaml b/src/Markdowser/Views/MainWindow.axaml index a3dc6ae..c0ddd1f 100644 --- a/src/Markdowser/Views/MainWindow.axaml +++ b/src/Markdowser/Views/MainWindow.axaml @@ -31,8 +31,8 @@ - - + + @@ -104,7 +104,7 @@ -