From 7e9e7613bac34962c126ddaeaec899956e1d8a6c Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 4 Oct 2026 20:03:01 +0100 Subject: [PATCH 1/3] Fix markdown draft preview sync in PostIt blog editor --- .../Controls/MarkdownEditorControl.axaml | 51 +++++++ .../Controls/MarkdownEditorControl.axaml.cs | 63 +++++++++ .../PostIt/ViewModels/Blogs/BlogsViewModel.cs | 52 ++++++- src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml | 133 +++++++----------- 4 files changed, 209 insertions(+), 90 deletions(-) create mode 100644 src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml create mode 100644 src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs diff --git a/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml new file mode 100644 index 000000000..7c639043d --- /dev/null +++ b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + diff --git a/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs new file mode 100644 index 000000000..2538f7168 --- /dev/null +++ b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs @@ -0,0 +1,63 @@ +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; + } + + 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; + } + } + } +} diff --git a/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs b/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs index 6fe6eccc9..9de63c24c 100644 --- a/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs @@ -39,18 +39,56 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel /// Editor buffer for the post body. Same pattern as /// . - public string DraftArticle + public string? DraftArticle { - get => DraftArticleDocument.Text; - set { + get => DraftArticleDocument?.Text ?? null; + set + { + var currentText = DraftArticleDocument?.Text; + if (string.Equals(currentText, value, StringComparison.Ordinal)) + { + OnPropertyChanged(nameof(DraftArticle)); + return; + } + OnPropertyChanging(nameof(DraftArticle)); - DraftArticleDocument = new TextDocument(value); - this.OnPropertyChanged(nameof(DraftArticle)); + DraftArticleDocument = value is not null ? new TextDocument(value) : null; + OnPropertyChanged(nameof(DraftArticle)); } } - [ObservableProperty] - public partial TextDocument DraftArticleDocument {get; set;} + private TextDocument? _draftArticleDocument; + + public TextDocument? DraftArticleDocument + { + get => _draftArticleDocument; + set + { + if (ReferenceEquals(_draftArticleDocument, value)) + { + return; + } + + if (_draftArticleDocument is not null) + { + _draftArticleDocument.TextChanged -= DraftArticleDocument_TextChanged; + } + + _draftArticleDocument = value; + + if (_draftArticleDocument is not null) + { + _draftArticleDocument.TextChanged += DraftArticleDocument_TextChanged; + } + + OnPropertyChanged(nameof(DraftArticleDocument)); + } + } + + private void DraftArticleDocument_TextChanged(object? sender, EventArgs e) + { + OnPropertyChanged(nameof(DraftArticle)); + } /// Editor buffer for the post's publication state. /// Reflects the server-side IsPublished flag (the diff --git a/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml b/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml index 90397f09d..f8534a094 100644 --- a/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml +++ b/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml @@ -17,10 +17,7 @@ - - + - + @@ -137,91 +134,61 @@ - + + + + + + + + + + + + + + - - + IsPreviewVisible="{Binding IsMarkdownViewerVisible, Mode=TwoWay}" + MarkdownText="{Binding DraftArticle, Mode=TwoWay}" + Document="{Binding DraftArticleDocument, Mode=TwoWay}" /> - - - - - - - - - - - + VerticalAlignment="Stretch"> + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - + - From bba5fe093c031e0ca41a2f3b57aca6cc2b5d9b12 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 4 Oct 2026 20:12:34 +0100 Subject: [PATCH 2/3] + scroll --- src/PostIt/PostIt.Tests/MD/le-chant-1.md | 34 ++++++++++++ .../Controls/MarkdownEditorControl.axaml | 40 ++++++++------ src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml | 12 ++++- .../PostIt/Views/Blogs/BlogsPage.axaml.cs | 53 ------------------- src/Yavsc.Org/wwwroot/css/site.css | 5 +- src/Yavsc.Org/wwwroot/css/site.scss | 5 +- 6 files changed, 74 insertions(+), 75 deletions(-) create mode 100644 src/PostIt/PostIt.Tests/MD/le-chant-1.md diff --git a/src/PostIt/PostIt.Tests/MD/le-chant-1.md b/src/PostIt/PostIt.Tests/MD/le-chant-1.md new file mode 100644 index 000000000..f4a7506fe --- /dev/null +++ b/src/PostIt/PostIt.Tests/MD/le-chant-1.md @@ -0,0 +1,34 @@ + + 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/Controls/MarkdownEditorControl.axaml b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml index 7c639043d..9e8df6dd1 100644 --- a/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml +++ b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml @@ -7,26 +7,35 @@ x:Name="Root" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> - + - + + VerticalScrollBarVisibility="Auto" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + + + Padding="8" + MinHeight="260"> - + + - + @@ -191,4 +198,5 @@ + diff --git a/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml.cs b/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml.cs index 629727870..b40021276 100644 --- a/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml.cs +++ b/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml.cs @@ -4,69 +4,16 @@ using Avalonia.Controls; using Avalonia.Platform.Storage; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; -using Avalonia.Controls.Platform; namespace PostIt.Views.Blogs; public partial class BlogsPage : ContentPage { - private IInputPane? _inputPane; - public BlogsPage() { InitializeComponent(); } - protected override void OnLoaded(RoutedEventArgs e) - { - base.OnLoaded(e); - - // Récupère l'accès au TopLevel de la fenêtre/application - var topLevel = TopLevel.GetTopLevel(this); - if (topLevel != null) - { - _inputPane = topLevel!.InputPane; - if (_inputPane != null) - { - // S'abonne aux changements d'état du clavier virtuel - _inputPane.StateChanged += OnInputPaneStateChanged; - - // Applique la configuration initiale si le clavier est déjà présent - UpdateLayoutForKeyboard(_inputPane.OccludedRect.Height); - } - } - } - - protected override void OnUnloaded(RoutedEventArgs e) - { - if (_inputPane != null) - { - _inputPane.StateChanged -= OnInputPaneStateChanged; - } - base.OnUnloaded(e); - } - - - private void UpdateLayoutForKeyboard(double keyboardHeight) - { - double visibleHeight = this.Bounds.Height - keyboardHeight; - if (keyboardHeight > 0) - { - DraftArticleTextEditor.MaxHeight = visibleHeight; - } - else - { - DraftArticleTextEditor.MaxHeight = double.PositiveInfinity; - } - } - - private void OnInputPaneStateChanged(object? sender, InputPaneStateEventArgs e) - { - // e.EndRect contient le rectangle final du clavier virtuel - // e.EndRect.Height vous donne sa hauteur exacte en pixels indépendants du périphérique (pixels logiques) - UpdateLayoutForKeyboard(e.EndRect.Height); - } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); diff --git a/src/Yavsc.Org/wwwroot/css/site.css b/src/Yavsc.Org/wwwroot/css/site.css index bafc2d571..2e4720ff3 100644 --- a/src/Yavsc.Org/wwwroot/css/site.css +++ b/src/Yavsc.Org/wwwroot/css/site.css @@ -1,4 +1 @@ -.user-icon { - max-width: 1.5em; - max-height: 1.5em; -} +.user-icon{max-width:1.5em;max-height:1.5em}.card{padding:.5em} diff --git a/src/Yavsc.Org/wwwroot/css/site.scss b/src/Yavsc.Org/wwwroot/css/site.scss index 1bf7a4d34..546aac220 100644 --- a/src/Yavsc.Org/wwwroot/css/site.scss +++ b/src/Yavsc.Org/wwwroot/css/site.scss @@ -1,4 +1,7 @@ -user-icon { +.user-icon { max-width: 1.5em; max-height: 1.5em; } +.card { + padding: .5em; +} From 1897a6807df4648efbe279d46f3587d409f26c58 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 4 Oct 2026 20:18:43 +0100 Subject: [PATCH 3/3] Fix long-form markdown editor scrolling and focus --- .../Controls/MarkdownEditorControl.axaml.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs index 2538f7168..0431b726e 100644 --- a/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs +++ b/src/PostIt/PostIt/Controls/MarkdownEditorControl.axaml.cs @@ -38,6 +38,8 @@ public partial class MarkdownEditorControl : UserControl { InitializeComponent(); PropertyChanged += OnPropertyChanged; + SizeChanged += OnSizeChanged; + UpdateEditorLimits(); } private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) @@ -60,4 +62,27 @@ public partial class MarkdownEditorControl : UserControl } } } + + 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; + } + } }