mirror of
https://github.com/Stone-Red-Code/Markdowser.git
synced 2026-09-04 00:56:13 +02:00
Add URL history to tabs
This commit is contained in:
@@ -30,7 +30,7 @@ internal class ChangeThemeCommand : ICommand
|
||||
ThemeVariant theme = app.ActualThemeVariant;
|
||||
app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark;
|
||||
Settings.Current.DarkMode = app.RequestedThemeVariant == ThemeVariant.Dark;
|
||||
GlobalState.ReloadContent(this);
|
||||
GlobalState.InvokeThemeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,6 @@ internal class HomeCommand : ICommand
|
||||
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
GlobalState.Url = Settings.Current.HomeUrl ?? string.Empty;
|
||||
GlobalState.CurrentTabState.Url = Settings.Current.HomeUrl ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,11 @@ public class HyperlinkCommand : ICommand
|
||||
|
||||
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
{
|
||||
GlobalState.Url = url;
|
||||
GlobalState.CurrentTabState.Url = url;
|
||||
}
|
||||
else if (Uri.IsWellFormedUriString(url, UriKind.Relative) || url.StartsWith('/'))
|
||||
{
|
||||
GlobalState.Url = new Uri(new Uri(GlobalState.Url), url).ToString();
|
||||
GlobalState.CurrentTabState.Url = new Uri(new Uri(GlobalState.CurrentTabState.Url), url).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using Markdowser.ViewModels;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Markdowser.Models;
|
||||
|
||||
public class TabState
|
||||
{
|
||||
public event EventHandler? UrlChanged;
|
||||
|
||||
private string url = string.Empty;
|
||||
|
||||
public Stack<string> BackHistory { get; } = new();
|
||||
|
||||
public Stack<string> ForwardHistory { get; } = new();
|
||||
|
||||
public string Url
|
||||
{
|
||||
get => url;
|
||||
set
|
||||
{
|
||||
if (url != value)
|
||||
{
|
||||
BackHistory.Push(url);
|
||||
ForwardHistory.Clear();
|
||||
}
|
||||
|
||||
url = value;
|
||||
UrlChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public ContentViewModelBase? Content { get; set; }
|
||||
|
||||
public void SetUrl(string url, object? sender = null)
|
||||
{
|
||||
this.url = url;
|
||||
UrlChanged?.Invoke(sender ?? this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,16 @@
|
||||
using Avalonia.Controls;
|
||||
using CuteUtils.Logging;
|
||||
|
||||
using CuteUtils.Logging;
|
||||
using Markdowser.Models;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Markdowser.Utilities;
|
||||
|
||||
[SuppressMessage("Major Code Smell", "S4220:Events should have proper arguments", Justification = "<Pending>")]
|
||||
internal static class GlobalState
|
||||
{
|
||||
public static event EventHandler<string>? UrlChanged;
|
||||
public static event EventHandler? ThemeChanged;
|
||||
|
||||
public static event EventHandler? ContentReload;
|
||||
|
||||
private static string url = string.Empty;
|
||||
public static TabState CurrentTabState { get; internal set; } = new();
|
||||
|
||||
public static Logger Logger { get; } = new()
|
||||
{
|
||||
@@ -54,36 +48,8 @@ internal static class GlobalState
|
||||
}
|
||||
};
|
||||
|
||||
public static Stack<string> BackHistory { get; } = new();
|
||||
|
||||
public static Stack<string> ForwardHistory { get; } = new();
|
||||
|
||||
public static string Url
|
||||
public static void InvokeThemeChanged()
|
||||
{
|
||||
get => url;
|
||||
set
|
||||
{
|
||||
if (url != value)
|
||||
{
|
||||
BackHistory.Push(url);
|
||||
ForwardHistory.Clear();
|
||||
}
|
||||
|
||||
url = value;
|
||||
UrlChanged?.Invoke(null, url);
|
||||
}
|
||||
}
|
||||
|
||||
public static ObservableCollection<TabItem> Tabs { get; internal set; } = [new TabItem() { Header = "New Tab" }];
|
||||
|
||||
internal static void SetUrl(object sender, string url)
|
||||
{
|
||||
GlobalState.url = url;
|
||||
UrlChanged?.Invoke(sender, url);
|
||||
}
|
||||
|
||||
internal static void ReloadContent(object sender)
|
||||
{
|
||||
ContentReload?.Invoke(sender, EventArgs.Empty);
|
||||
ThemeChanged?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class HttpPathResolver : IPathResolver
|
||||
|
||||
if (!Uri.IsWellFormedUriString(relativeOrAbsolutePath, UriKind.Absolute))
|
||||
{
|
||||
relativeOrAbsolutePath = new Uri(new Uri(GlobalState.Url), relativeOrAbsolutePath).ToString();
|
||||
relativeOrAbsolutePath = new Uri(new Uri(GlobalState.CurrentTabState.Url), relativeOrAbsolutePath).ToString();
|
||||
}
|
||||
|
||||
GlobalState.Logger.LogDebug($"Resolving image: {relativeOrAbsolutePath}");
|
||||
|
||||
@@ -27,32 +27,50 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
private readonly ContentProcessorManager contentProcessorManager = new();
|
||||
private readonly CacheService cacheService = new CacheService();
|
||||
|
||||
private ContentViewModelBase content;
|
||||
private TabItem currentTab = null!;
|
||||
private WindowState windowState;
|
||||
private bool showSidePanel;
|
||||
private bool isBusy;
|
||||
private int progress;
|
||||
public ObservableCollection<TabItem> Tabs => GlobalState.Tabs;
|
||||
public ObservableCollection<TabItem> Tabs { get; } = [new TabItem() { Header = "New Tab" }];
|
||||
|
||||
public TabItem CurrentTab
|
||||
{
|
||||
get => currentTab;
|
||||
set
|
||||
{
|
||||
if (currentTab is not null)
|
||||
{
|
||||
currentTab.Tag = Url;
|
||||
}
|
||||
CurrentTabState.UrlChanged -= UrlChanged;
|
||||
|
||||
_ = this.RaiseAndSetIfChanged(ref currentTab!, value);
|
||||
this.RaisePropertyChanged(nameof(CurrentTabState));
|
||||
this.RaisePropertyChanged(nameof(Url));
|
||||
this.RaisePropertyChanged(nameof(BackEnabled));
|
||||
this.RaisePropertyChanged(nameof(ForwardEnabled));
|
||||
|
||||
Url = value?.Tag?.ToString() ?? string.Empty;
|
||||
GlobalState.CurrentTabState = CurrentTabState;
|
||||
CurrentTabState.UrlChanged += UrlChanged;
|
||||
|
||||
FetchUrl(true);
|
||||
}
|
||||
}
|
||||
|
||||
public TabState CurrentTabState
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentTab is null)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
|
||||
return Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
CurrentTab.Tag ??= new TabState();
|
||||
return (TabState)CurrentTab.Tag;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public WindowState WindowState
|
||||
{
|
||||
get => windowState;
|
||||
@@ -61,14 +79,18 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
|
||||
public ContentViewModelBase Content
|
||||
{
|
||||
get => content;
|
||||
set => this.RaiseAndSetIfChanged(ref content, value);
|
||||
get => CurrentTabState.Content!;
|
||||
set
|
||||
{
|
||||
CurrentTabState.Content = value;
|
||||
this.RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string Url
|
||||
{
|
||||
get => GlobalState.Url;
|
||||
set => GlobalState.SetUrl(this, value);
|
||||
get => CurrentTabState.Url;
|
||||
set => CurrentTabState.SetUrl(value, this);
|
||||
}
|
||||
|
||||
public bool ShowSidePanel
|
||||
@@ -97,18 +119,18 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
public RawHtmlViewModel RawHtmlViewModel => new RawHtmlViewModel(new());
|
||||
public RawMarkdownViewModel RawMarkdownViewModel => new RawMarkdownViewModel(() => new());
|
||||
public bool CloseTabEnabled => Tabs.Count > 1;
|
||||
public bool BackEnabled => GlobalState.BackHistory.Count > 0;
|
||||
public bool ForwardEnabled => GlobalState.ForwardHistory.Count > 0;
|
||||
public bool BackEnabled => CurrentTabState.BackHistory.Count > 0;
|
||||
public bool ForwardEnabled => CurrentTabState.ForwardHistory.Count > 0;
|
||||
public bool ProgressIndeterminate => Progress == 0;
|
||||
public ICommand Browse => ReactiveCommand.Create(() => FetchUrl(true));
|
||||
public ICommand Reload => ReactiveCommand.Create(() => FetchUrl(false));
|
||||
|
||||
public ICommand Back => ReactiveCommand.Create(() =>
|
||||
{
|
||||
if (GlobalState.BackHistory.Count > 0)
|
||||
if (CurrentTabState.BackHistory.Count > 0)
|
||||
{
|
||||
GlobalState.ForwardHistory.Push(GlobalState.Url);
|
||||
Url = GlobalState.BackHistory.Pop();
|
||||
CurrentTabState.ForwardHistory.Push(CurrentTabState.Url);
|
||||
Url = CurrentTabState.BackHistory.Pop();
|
||||
FetchUrl();
|
||||
|
||||
this.RaisePropertyChanged(nameof(BackEnabled));
|
||||
@@ -118,10 +140,10 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
|
||||
public ICommand Forward => ReactiveCommand.Create(() =>
|
||||
{
|
||||
if (GlobalState.ForwardHistory.Count > 0)
|
||||
if (CurrentTabState.ForwardHistory.Count > 0)
|
||||
{
|
||||
GlobalState.BackHistory.Push(GlobalState.Url);
|
||||
Url = GlobalState.ForwardHistory.Pop();
|
||||
CurrentTabState.BackHistory.Push(CurrentTabState.Url);
|
||||
Url = CurrentTabState.ForwardHistory.Pop();
|
||||
FetchUrl();
|
||||
|
||||
this.RaisePropertyChanged(nameof(ForwardEnabled));
|
||||
@@ -178,30 +200,14 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
content = DefaultContent;
|
||||
Tabs[0].Tag = new TabState() { Content = DefaultContent };
|
||||
|
||||
contentProcessorManager.RegisterProcessor(new HtmlProcessor());
|
||||
contentProcessorManager.RegisterProcessor(new CommonImageProcessor());
|
||||
|
||||
GlobalState.UrlChanged += (sender, url) =>
|
||||
GlobalState.ThemeChanged += (s, e) =>
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(ForwardEnabled));
|
||||
this.RaisePropertyChanged(nameof(BackEnabled));
|
||||
this.RaisePropertyChanged(nameof(Url));
|
||||
|
||||
if (sender == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FetchUrl();
|
||||
};
|
||||
|
||||
GlobalState.ContentReload += (sender, _) =>
|
||||
{
|
||||
// Update icon when dark mode changes
|
||||
this.RaisePropertyChanged(nameof(Icon));
|
||||
this.RaisePropertyChanged(nameof(Content));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -272,10 +278,10 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync(Url);
|
||||
|
||||
string? newUrl = httpResponseMessage.RequestMessage?.RequestUri?.ToString();
|
||||
string oldUrl = Url;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -294,6 +300,12 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
Content = await contentProcessorManager.ProcessContent(httpResponseMessage, new Progress<ProcessingProgress>(p => Progress = p.Percentage));
|
||||
|
||||
if (oldUrl != Url)
|
||||
{
|
||||
cacheService.Set(oldUrl, Content);
|
||||
}
|
||||
|
||||
cacheService.Set(Url, Content);
|
||||
}
|
||||
|
||||
@@ -340,4 +352,18 @@ public partial class MainWindowViewModel : ViewModelBase
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void UrlChanged(object? sender, EventArgs e)
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(ForwardEnabled));
|
||||
this.RaisePropertyChanged(nameof(BackEnabled));
|
||||
this.RaisePropertyChanged(nameof(Url));
|
||||
|
||||
if (sender == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FetchUrl();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user