diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 408212098..e1e936204 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -27,7 +27,7 @@ jobs: build: runs-on: docker container: - image: pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v4 + image: pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v2 steps: - name: Clone yavsc run: | diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index de61bd43d..7d0670a17 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -52,7 +52,7 @@ jobs: release: runs-on: docker container: - image: pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v4 + image: pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v2 steps: - name: Clone du repo au tag demandé env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f77b68af..e36d75dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,88 +1,7 @@ # Changelog -## [1.0.8-rc22] - unstable -### Fixed - -User experience login into PostIt - -## [1.0.8-rc21] - unstable - -### Fixed - -User experience editing the blog article. - -## [1.0.8-rc20] - unstable - -Chat SignalR fonctionnel de bout en bout (connexion, salons multiples, -envoi/réception de messages), enregistreur audio et localisation dans -PostIt, intégration Live.Avalonia démarrée, et reverse-proxy nginx -apprêté pour WebSocket. - -### Added - -* [PostIt] Interface de chat : page `ChatPage` (connexion au hub, - saisie du nom de salon, liste des messages et des membres présents), - entrée depuis la page d'accueil. -* [PostIt] Envoi de messages dans un salon joint : DTO `ChatMessage` - (Name/Room/Message) pour l'événement `ReceiveMessage`, saisie avec - envoi sur `Enter`, commande inactive tant qu'aucune salle n'est en - focus (le hub refuse l'envoi hors salon, `LabNoJoinNoSend`). -* [PostIt] Modèle multi-salles côté client : `JoinedRooms`, - `SelectedRoom` (salle en focus), messages et utilisateurs indexés par - salle (`FocusedMessages`/`FocusedUsers`), ComboBox de choix de la - salle active ; un `Join` sur une salle déjà jointe ne fait que - changer le focus. -* [PostIt] Enregistreur audio (`AudioRecorder`, interface - `IPCMProcessor`) et ressources de localisation - (`Resources.{resx,en-EN,pt-PT}`). -* [PostIt][WIP] Début d'intégration Live.Avalonia (preview XAML à - chaud ; encore KO). -* [contrib] Bloc `location /chathub` dans `template.vhost` : upgrade - WebSocket (`Upgrade`/`Connection`), `proxy_read_timeout 3600s` et - `proxy_buffering off` — sans quoi la connexion SignalR tombait au - premier silence ou l'upgrade échouait derrière nginx. - -### Changed - -* [PostIt] `StatusNotice` déplacé de `Models` vers `ViewModels` et - enrichi d'une commande d'action optionnelle (`CheckCommand` + - `CheckCommandLabel`, interface `IWithLabel`) : le bandeau de statut - propose désormais l'action contextuelle (Connecter / Déconnecter / - Reconnecter / Vérifier la connexion). -* [PostIt] `ChatViewModel` suit le cycle de vie complet de la - connexion : handlers `Reconnecting` / `Reconnected` / `Closed` de - `WithAutomaticReconnect`, pour refléter l'état réel au lieu d'un - `IsConnected` optimiste. -* [Yavsc.Api] `AddSignalR().AddJsonProtocol(IncludeFields = true)` : - `ChatRoomInfo` n'expose que des champs publics, que System.Text.Json - ne sérialise pas par défaut — le client recevait des objets vides - (`Name` null, `Users` vide) et la liste des salons joints n'était - jamais alimentée. -* [Yavsc.Server] `IConnexionManager.OnConnected` prend le `userName` - en paramètre (résolu depuis les claims dans `ChatHub`) au lieu de le - relire depuis un dictionnaire jamais alimenté. -* [PostIt.Android] `network_security_config.xml` déplacé sous - `Resources/xml/` (chemin canonique Android). - -### Fixed - -* [Yavsc.Server] `KeyNotFoundException` dans - `HubConnectionManager.OnConnected` à chaque connexion SignalR : - `ChatUserNames` (et `ChatCxIds`) n'étaient jamais alimentés — relique - d'un `SetUserName` disparu lors d'une refactorisation. L'exception - dans `OnConnectedAsync` faisait fermer la connexion par le hub, et - PostIt affichait « Déconnecté (The server closed the connection…) » - immédiatement après la connexion. -* [PostIt] URL du hub SignalR : retrait du préfixe `/api/v1` de - `ApiUrl` avant d'apposer `Constants.ChatHubPath` (le hub est mappé à - la racine du service, pas sous le préfixe versionné). -* [PostIt] La `StatusBar` de la page chat référençait `Grid.Row="4"` - sur une grille à 3 rangées (rangée hors limites, bandeau invisible). - -## [1.0.8-rc19] - unstable - -[PostIt] Blogs UX +## [1.0.8-rc19] - Blogs UX ### Fixed diff --git a/README.md b/README.md index 1cee99bbb..fefbdd48d 100644 --- a/README.md +++ b/README.md @@ -232,12 +232,6 @@ L'impact de l'usage d'un nouveau nom d’environnement d'execution, à l'heure d * ~/Views/Home/Index.cshtml * ~/Views/Home/About.cshtml -# 👥 Crédits & Co-autoraClaudt - -Ce projet a été développé de manière hybride : -- **Conception, architecture et revue de code :** Paul Schneider -- **Assistance au code et optimisation :** IA générative (Gemini, Google, Claude (Anthropic), https://qoder.com/ (ex-Windsurf)) - # Remerciements Photo de Dewang Guptasur Unsplash diff --git a/contrib/Makefile b/contrib/Makefile index 5ccf773a1..deb55bcc6 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -14,7 +14,7 @@ reinstall: copy-binaries @echo "💯 backend started" reinstall-prod: - make reinstall APP_INSTALL_PREFIX= APP_INSTALL_ENV=production + make reinstall APP_INSTALL_ENV= APP_INSTALL_PREFIX= APP_INSTALL_ENV=production generated/$(APP_INSTALL_PREFIX)yavscOrg.service: generated/$(APP_INSTALL_PREFIX)yavscBlogs.service: diff --git a/contrib/template.vhost b/contrib/template.vhost index b4c9c139e..ea2c2cb6d 100644 --- a/contrib/template.vhost +++ b/contrib/template.vhost @@ -57,25 +57,5 @@ server { proxy_read_timeout 100s; proxy_buffering off; } - - # SignalR (ChatHub) : WebSocket exige Upgrade/Connection et un timeout - # long, sinon la connexion tombe au premier silence ou l'upgrade échoue. - # Connection est fixé à "upgrade" (pas de map $connection_upgrade, qui - # exigerait une directive map {} globale hors de ce vhost) : SignalR - # négocie toujours un upgrade sur ce chemin, et la négociation initiale - # (POST /negotiate, sans en-tête Upgrade) n'est pas affectée par cet - # en-tête côté Kestrel. - location /chathub { - proxy_pass $BACKEND_SCHEME://$HTTP_HOST:$BACKEND_PORT; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_read_timeout 3600s; - proxy_buffering off; - } include server-status.conf; -} +} \ No newline at end of file diff --git a/src/PostIt/Directory.Packages.props b/src/PostIt/Directory.Packages.props index a353e4b3a..42d09cd55 100644 --- a/src/PostIt/Directory.Packages.props +++ b/src/PostIt/Directory.Packages.props @@ -4,7 +4,7 @@ true - 12.1.3 + 12.1.2 @@ -13,21 +13,16 @@ - + - - - - + + - - - @@ -36,4 +31,4 @@ - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml b/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml index 256555df1..8793aae8a 100644 --- a/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml +++ b/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml @@ -1,10 +1,6 @@  - + diff --git a/src/PostIt/PostIt.Android/Resources/xml/network_security_config.xml b/src/PostIt/PostIt.Android/Resources/xml/network_security_config.xml deleted file mode 100644 index 6d2c97559..000000000 --- a/src/PostIt/PostIt.Android/Resources/xml/network_security_config.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - 10.0.2.2 - - diff --git a/src/PostIt/PostIt.Desktop/Program.cs b/src/PostIt/PostIt.Desktop/Program.cs index 1c5aa0c27..a2b8cd66f 100644 --- a/src/PostIt/PostIt.Desktop/Program.cs +++ b/src/PostIt/PostIt.Desktop/Program.cs @@ -1,7 +1,6 @@ using System; using Avalonia; using PostIt.Services; -using PostIt.CLIOptions; namespace PostIt.Desktop; diff --git a/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs b/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs index 31c82387d..17efd391b 100644 --- a/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs @@ -1,5 +1,4 @@ using System.Net.Http; -using PostIt.Models; using PostIt.ViewModels; using Yavsc; using Yavsc.Api.Client; diff --git a/src/PostIt/PostIt.Tests/MD/le-chant-1.md b/src/PostIt/PostIt.Tests/MD/le-chant-1.md deleted file mode 100644 index f4a7506fe..000000000 --- a/src/PostIt/PostIt.Tests/MD/le-chant-1.md +++ /dev/null @@ -1,34 +0,0 @@ - - Loin vers l'infini s'étendent - Les grands prés marécageux . - Pas un seul oiseau ne chante - Sur les arbres secs et creux ! - -*(Refrain)* - - Ô terre de détresse, - Où nous devons sans cesse, - Piocher, piocher . - - Dans ce camp morne et sauvage - Entouré de murs de fer, - Il nous semble vivre en cage - Au milieu d'un grand désert . - - Bruit des pas et bruit des armes, - Sentinelles jours et nuits, - Et du sang, et des cris, des larmes, - La mort pour celui qui fuit . - - Mais un jour dans notre vie, - Le printemps refleurira . - Liberté, liberté chérie - Je dirai : « Tu es à moi ! » - -*(Dernier refrain)* - - Ô terre d'allégresse, - Où nous pourrons sans cesse, - Aimer, aimer ! - -[audio : Mario Hacquard - Youtube](https://www.youtube.com/watch?v=ogw_mgk1pFs) diff --git a/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs b/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs index 3516e93d6..cc08304d5 100644 --- a/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs +++ b/src/PostIt/PostIt.Tests/SessionStatusBannerTests.cs @@ -101,7 +101,7 @@ public class SessionStatusBannerTests { MainWindow window = new MainWindow(); window.Show(); - var banner = new SessionStatusBanner(); + var banner = window.MainView.SessionBanner; var status = new SessionStatusViewModel(); banner.DataContext = status; return banner; diff --git a/src/PostIt/PostIt.Tests/StatusBarTests.cs b/src/PostIt/PostIt.Tests/StatusBarTests.cs index 15889e0b0..4053dbaf3 100644 --- a/src/PostIt/PostIt.Tests/StatusBarTests.cs +++ b/src/PostIt/PostIt.Tests/StatusBarTests.cs @@ -2,7 +2,6 @@ using Avalonia.Controls; using Avalonia.Headless.XUnit; using Avalonia.VisualTree; using PostIt.Controls; -using PostIt.Models; using PostIt.ViewModels; namespace PostIt.Tests; @@ -22,9 +21,7 @@ public class StatusBarTests [AvaloniaFact] public void Status_message_is_rendered_by_a_SelectableTextBlock() { - var bar = new StatusBar { - DataContext = - StatusNotice.Error("Échec de la suppression : boom") }; + var bar = new StatusBar { DataContext = StatusNotice.Error("Échec de la suppression : boom") }; var window = new Window { Content = bar }; window.Show(); @@ -35,4 +32,4 @@ public class StatusBarTests Assert.NotNull(message); Assert.Equal("Échec de la suppression : boom", message!.Text); } -} +} \ No newline at end of file diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index fca8e0313..9a04ffd03 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -6,26 +6,18 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Styling; -using Avalonia.Threading; using Microsoft.Extensions.DependencyInjection; using PostIt.Services; using PostIt.ViewModels; using PostIt.Views; using PostIt.Helpers; -using System.Globalization; -using Live.Avalonia; -using System.Diagnostics; -using System.Linq; -using ReactiveUI; -using System.Reactive; namespace PostIt; -public partial class App : Application, ILiveView, IVMPusherApp +public partial class App : Application { - public static bool LiveMode { get; private set; } private int _bootStarted; - public static string? CliConfigFileSpecification { get; private set; } + public string? CliConfigFileSpecification {  get; private set; } /// /// DI container the platform entry points hand to ViewModels so @@ -44,132 +36,36 @@ public partial class App : Application, ILiveView, IVMPusherApp public override void Initialize() { AvaloniaXamlLoader.Load(this); - -#if DEBUG - this.AttachDeveloperTools(); -#endif } public override void OnFrameworkInitializationCompleted() { if (TryHandOffCustomSchemeUrl()) return; - this.ServiceProvider = new ServiceCollection().BuildPostItServices(); + this.ServiceProvider = new ServiceCollection().BuildServices(); var settings = ServiceProvider.GetRequiredService(); - if (!LiveMode) + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - // Debugging requires pdb loading etc, so we disable live reloading - // during a test run with an attached debugger. - - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - InitializeClassicDesktopLifetime(settings, desktop); - } - else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime) - { - InitializeActivityLifetime(settings, singleViewFactoryApplicationLifetime); - } - else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) - { - InitializeSingleViewLifetime(settings, singleViewPlatform); - } + InitializeClassicDesktopLifetime(settings, desktop); } - else + else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime) { - // Here, we create a new LiveViewHost, located in the 'Live.Avalonia' - // namespace, and pass an ILiveView implementation to it. The ILiveView - // implementation should have a parameterless constructor! Next, we - // start listening for any changes in the source files. And then, we - // show the LiveViewHost window. Simple enough, huh? - var window = new LiveViewHost(this, Console.WriteLine); - var view = ServiceProvider!.GetRequiredService(); - - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - InitializeClassicDesktopLifetime(window, view, settings, desktop); - } - else if (ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime) - { - InitializeActivityLifetime(settings, singleViewFactoryApplicationLifetime); - } - else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) - { - InitializeSingleViewLifetime(settings, singleViewPlatform); - } - - window.StartWatchingSourceFilesForHotReloading(); - Console.WriteLine("LiveViewHost started for hot reloading."); - window.Show(); + InitializeActivityLifetime(settings, singleViewFactoryApplicationLifetime); + } + else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) + { + InitializeSingleViewLifetime(settings, singleViewPlatform); } - - -#if DEBUG - // Here we subscribe to ReactiveUI default exception handler to avoid app - // termination in case if we do something wrong in our view models. See: - // https://www.reactiveui.net/docs/handbook/default-exception-handler/ - // - // In case if you are using another MV* framework, please refer to its - // documentation explaining global exception handling. - Console.WriteLine("Debug mode: ReactiveUI default exception handler is set."); - RxApp.DefaultExceptionHandler = Observer.Create(Console.WriteLine); -#endif base.OnFrameworkInitializationCompleted(); } - private bool IsProduction() - { -#if DEBUG - return false; -#else - return true; -#endif - } - - public async Task PushPageAsync(ViewModelBase viewModel) - { - var template = DataTemplates.FirstOrDefault(t => t.Match(viewModel)); - if (template is null) - { - throw new InvalidOperationException($"No IDataTemplate found for {viewModel.GetType().Name}."); - } - - Control? view = template.Build(viewModel) ; - if (view is null) - { - throw new InvalidOperationException( - $"Template for {viewModel.GetType().Name} returned ."); - } - - var page = view as Page; - if (page is null) - { - // NavigationPage expects Page instances. Wrap any fallback control - // (e.g. ViewLocator error TextBlock) into a ContentPage so it can render. - page = new ContentPage { Content = view }; - } - - page.DataContext = viewModel; - - MainView? mainView = (Current as App).View; - - // Avoid stacking the same singleton page twice (e.g. SettingsPage). - var stack = mainView.NavRoot.NavigationStack; - if (stack.Count > 0 && ReferenceEquals(stack[stack.Count - 1], page)) - { - return page; - } - - await mainView.NavRoot.PushAsync(page); - return page; - } - public void InitializeSingleViewLifetime(Settings settings, ISingleViewApplicationLifetime singleViewPlatform) { singleViewPlatform.MainView = View = ServiceProvider!.GetRequiredService(); ConfigureRootView(View); - ApplyStettings(settings); + ApplyDarkMode(settings); } public void InitializeActivityLifetime(Settings settings, IActivityApplicationLifetime singleViewFactoryApplicationLifetime) @@ -178,7 +74,7 @@ public partial class App : Application, ILiveView, IVMPusherApp () => { this.ConfigureRootView(ServiceProvider!.GetRequiredService()); - ApplyStettings(settings); + ApplyDarkMode(settings); return View!; }; } @@ -186,35 +82,37 @@ public partial class App : Application, ILiveView, IVMPusherApp public void InitializeClassicDesktopLifetime(Settings settings, IClassicDesktopStyleApplicationLifetime desktop) { var window = ServiceProvider!.GetRequiredService(); - var mainView = (MainView)CreateView(window); - InitializeClassicDesktopLifetime(window, mainView, settings, desktop); - } - - public void InitializeClassicDesktopLifetime(Window window, MainView mainView, Settings settings, IClassicDesktopStyleApplicationLifetime desktop) - { - this.ConfigureRootView(mainView); - ApplyStettings(settings); desktop.MainWindow = window; - window.Content = mainView; - window.Show(); + this.ConfigureRootView(window.MainView); + + ApplyDarkMode(settings); } public void ConfigureRootView(MainView rootView) +{ + // Déclencher le Boot une seule fois lors du chargement du contrôle à l'écran. + rootView.AttachedToVisualTree += async (_, _) => await BootOnceAsync(); + + var sessionStatus = ServiceProvider!.GetRequiredService(); + sessionStatus.LogoutCompleted += () => { - rootView.AttachedToVisualTree += (_, _) => - Dispatcher.UIThread.Post(() => _ = BootOnceAsync()); + // Remplacer Window.NavRoot par rootView.NavRoot + rootView.NavRoot.PopToRootAsync(); + }; - var sessionStatus = ServiceProvider!.GetRequiredService(); - sessionStatus.LogoutCompleted += () => + rootView.SessionBanner.DataContext = sessionStatus; + this.View = rootView; +} + + private async Task BootOnceAsync() + { + if (Interlocked.Exchange(ref _bootStarted, 1) == 1) { - // Remplacer Window.NavRoot par rootView.NavRoot - rootView.NavRoot.PopToRootAsync(); - }; - if (rootView.DataContext==null) - rootView.DataContext = ServiceProvider!.GetRequiredService(); + return; + } - rootView.SessionBanner.DataContext = sessionStatus; - this.View = rootView; + var api = ServiceProvider!.GetRequiredService(); + await BootAsync(this.ServiceProvider!, api); } /// @@ -228,24 +126,10 @@ public partial class App : Application, ILiveView, IVMPusherApp View = mainView ?? throw new ArgumentNullException(nameof(mainView)); } - private static void ApplyStettings(Settings settings) + private static void ApplyDarkMode(Settings settings) { - if (!string.IsNullOrWhiteSpace(settings.SelectedLanguage)) - Yavsc.Lang.Resources.Culture = new CultureInfo(settings.SelectedLanguage); - Current!.RequestedThemeVariant = - settings.DarkMode ? ThemeVariant.Dark : ThemeVariant.Light; - } - - - private async Task BootOnceAsync() - { - if (Interlocked.Exchange(ref _bootStarted, 1) == 1) - { - return; - } - - var api = ServiceProvider!.GetRequiredService(); - await BootAsync(this.ServiceProvider!, api); + Application.Current!.RequestedThemeVariant = + settings.DarkMode ? ThemeVariant.Dark : ThemeVariant.Light; } /// @@ -263,9 +147,9 @@ public partial class App : Application, ILiveView, IVMPusherApp var refreshed = await api.TrySilentLoginAsync().ConfigureAwait(true); var sessionStatus = provider.GetRequiredService(); sessionStatus.Refresh(); - var homePageVm = provider.GetRequiredService(); + var homePage = provider.GetRequiredService(); var app = (App)Current!; - await app.PushPageAsync(homePageVm); + await app.PushPageAsync(homePage); } /// @@ -321,31 +205,8 @@ public partial class App : Application, ILiveView, IVMPusherApp await View!.NavRoot.PopAsync(); } - internal static void UseConfigFileWhenLoading(string configFile) + internal void UseConfigFileWhenLoading(string configFile) { - CliConfigFileSpecification = configFile; - } - - internal static void EnableLiveMode() - { - LiveMode = true; - } - - public object CreateView(Window window) - { - if (this.ServiceProvider == null) - { - var serviceCollection = new ServiceCollection(); - this.ServiceProvider = serviceCollection.BuildPostItServices(); - } - if (window.DataContext == null) - window.DataContext = ServiceProvider!.GetRequiredService(); - - // The AppView class will inherit the DataContext - // of the window. The AppView class can be a - // UserControl, a Grid, a TextBlock, whatever. - this.View = ServiceProvider!.GetRequiredService(); - this.ConfigureRootView(this.View); - return this.View; + this.CliConfigFileSpecification = configFile; } } diff --git a/src/PostIt/PostIt/CLIOptions/CLIOptions.cs b/src/PostIt/PostIt/CLIOptions/CLIOptions.cs index 643102b38..c0bea85ff 100644 --- a/src/PostIt/PostIt/CLIOptions/CLIOptions.cs +++ b/src/PostIt/PostIt/CLIOptions/CLIOptions.cs @@ -4,21 +4,15 @@ using Mono.Options; using PostIt; using PostIt.ViewModels; -namespace PostIt.CLIOptions; - public static class CLIOptionHandler { public static void HandleArgs(string[] args) { bool showHelp = false; string configFile = null; - bool liveMode = false; var options = new OptionSet { { "c|configuration=", "Configuration file to load", (string v) => configFile = v }, - { "l|live", "Enable live mode", v => liveMode = true }, - { "v|version", "Show the application version", v => Console.WriteLine("PostIt version " - + typeof(App).Assembly.GetName().Version?.ToString()) }, { "h|help", "Show this message and exit", v => showHelp = true } }; @@ -32,11 +26,8 @@ public static class CLIOptionHandler } if (configFile != null) { - App.UseConfigFileWhenLoading(configFile); - } - if (liveMode) - { - App.EnableLiveMode(); + var app = App.Current as App; + app.UseConfigFileWhenLoading(configFile); } } } diff --git a/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml deleted file mode 100644 index 9e8df6dd1..000000000 --- a/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs deleted file mode 100644 index 0431b726e..000000000 --- a/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using Avalonia; -using Avalonia.Controls; -using AvaloniaEdit.Document; - -namespace PostIt.Controls; - -public partial class MarkdownEditorControl : UserControl -{ - public static readonly StyledProperty MarkdownTextProperty = - AvaloniaProperty.Register(nameof(MarkdownText)); - - public static readonly StyledProperty DocumentProperty = - AvaloniaProperty.Register(nameof(Document)); - - public static readonly StyledProperty IsPreviewVisibleProperty = - AvaloniaProperty.Register(nameof(IsPreviewVisible), false); - - public string? MarkdownText - { - get => GetValue(MarkdownTextProperty); - set => SetValue(MarkdownTextProperty, value); - } - - public TextDocument? Document - { - get => GetValue(DocumentProperty); - set => SetValue(DocumentProperty, value); - } - - public bool IsPreviewVisible - { - get => GetValue(IsPreviewVisibleProperty); - set => SetValue(IsPreviewVisibleProperty, value); - } - - public MarkdownEditorControl() - { - InitializeComponent(); - PropertyChanged += OnPropertyChanged; - SizeChanged += OnSizeChanged; - UpdateEditorLimits(); - } - - private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) - { - if (e.Property == MarkdownTextProperty) - { - var text = e.NewValue as string ?? string.Empty; - if (Document is null || !string.Equals(Document.Text, text, StringComparison.Ordinal)) - { - Document = new TextDocument(text); - } - } - else if (e.Property == DocumentProperty) - { - var newDocument = e.NewValue as TextDocument; - var text = newDocument?.Text ?? string.Empty; - if (!string.Equals(MarkdownText, text, StringComparison.Ordinal)) - { - MarkdownText = text; - } - } - } - - private void OnSizeChanged(object? sender, SizeChangedEventArgs e) - { - UpdateEditorLimits(); - } - - private void UpdateEditorLimits() - { - var visibleHeight = Bounds.Height > 0 ? Bounds.Height - 24 : 260; - visibleHeight = Math.Max(visibleHeight, 80); - - if (SourceEditor is not null) - { - SourceEditor.MaxHeight = visibleHeight; - SourceEditor.Height = double.NaN; - } - - if (PreviewViewer is not null) - { - PreviewViewer.MaxHeight = visibleHeight; - PreviewViewer.Height = double.NaN; - } - } -} diff --git a/src/PostIt/PostIt/Controls/StatusBar.axaml b/src/PostIt/PostIt/Controls/StatusBar.axaml index 755ff7d3e..2f46fee33 100644 --- a/src/PostIt/PostIt/Controls/StatusBar.axaml +++ b/src/PostIt/PostIt/Controls/StatusBar.axaml @@ -10,19 +10,16 @@ Padding="8"> + FontWeight="Bold" + Foreground="{Binding Foreground}" + VerticalAlignment="Top"/> + -