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