Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
ebdf51f468 |
|||
|
cd7351a783 |
|||
|
95e8f251d2 |
|||
|
e7721bc3ab |
10 changed files with 308 additions and 74 deletions
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -1,10 +1,33 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.8-rc24] - unstable
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* [PostIt] Reconnection automatique aux salons de discussion au reconnect
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* [PostIt] La liste des billets et l'éditeur Markdown se partagent
|
||||||
|
l'espace disponible et défilent séparément ; un séparateur permet
|
||||||
|
de redimensionner les deux zones.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* [PostIt] Défilement des articles longs en édition et en aperçu :
|
||||||
|
suppression de la limite de hauteur qui masquait le contenu Markdown
|
||||||
|
et du défilement imbriqué autour d'AvaloniaEdit. Six tests headless
|
||||||
|
couvrent les barres automatiques, le redimensionnement et le
|
||||||
|
défilement indépendant de la liste.
|
||||||
|
|
||||||
## [1.0.8-rc23] - unstable
|
## [1.0.8-rc23] - unstable
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
* [PostIt] Badge « Publié » à côté du titre des billets publiés dans la
|
||||||
|
liste, mis à jour lors de la publication ou du retour en brouillon.
|
||||||
* [PostIt] Gestion native du compte inspirée de `/Manage` : modification
|
* [PostIt] Gestion native du compte inspirée de `/Manage` : modification
|
||||||
du nom d'utilisateur, du nom complet et de l'adresse, téléversement
|
du nom d'utilisateur, du nom complet et de l'adresse, téléversement
|
||||||
d'avatar, confirmation e-mail, préférence de courriel mensuel,
|
d'avatar, confirmation e-mail, préférence de courriel mensuel,
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,20 @@ le DI est construit. Ordre, dans cet ordre :
|
||||||
> à cette table, *ne pas* ajouter de registration pour
|
> à cette table, *ne pas* ajouter de registration pour
|
||||||
> `Settings` ailleurs que la ligne `AddSingleton(settings)`.
|
> `Settings` ailleurs que la ligne `AddSingleton(settings)`.
|
||||||
|
|
||||||
|
## Défilement de la page des billets
|
||||||
|
|
||||||
|
La page `BlogsPage` utilise une grille à hauteur contrainte : l'en-tête
|
||||||
|
et les commandes restent visibles, tandis que la liste des billets et
|
||||||
|
l'éditeur se partagent l'espace disponible. Un `GridSplitter` permet
|
||||||
|
de modifier cette répartition. La liste et l'article défilent séparément.
|
||||||
|
|
||||||
|
Dans `MarkdownEditorControl`, AvaloniaEdit gère son propre défilement ;
|
||||||
|
l'aperçu Markdown est placé dans un `ScrollViewer`. Ne pas limiter
|
||||||
|
la hauteur du `MarkdownViewer` lui-même : son contenu doit pouvoir
|
||||||
|
dépasser le viewport pour que les barres automatiques s'activent.
|
||||||
|
Ne pas placer cette grille dans un `StackPanel` vertical ou un
|
||||||
|
`ScrollViewer` de page, qui lui donnerait une hauteur de mesure illimitée.
|
||||||
|
|
||||||
## Navigation
|
## Navigation
|
||||||
|
|
||||||
Le host de navigation est un `NavigationPage x:Name="NavRoot"`
|
Le host de navigation est un `NavigationPage x:Name="NavRoot"`
|
||||||
|
|
|
||||||
143
src/PostIt/PostIt.Tests/MarkdownEditorScrollTests.cs
Normal file
143
src/PostIt/PostIt.Tests/MarkdownEditorScrollTests.cs
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.LogicalTree;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using AvaloniaEdit;
|
||||||
|
using PostIt.Controls;
|
||||||
|
using PostIt.Views.Blogs;
|
||||||
|
|
||||||
|
namespace PostIt.Tests;
|
||||||
|
|
||||||
|
public class MarkdownEditorScrollTests
|
||||||
|
{
|
||||||
|
private static readonly string LongArticle =
|
||||||
|
string.Join("\n\n", Enumerable.Range(1, 100).Select(i => $"Paragraph {i}: article content."));
|
||||||
|
|
||||||
|
[AvaloniaTheory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void Long_article_scrolls_to_the_end_and_adapts_to_resizing(bool preview)
|
||||||
|
{
|
||||||
|
var editor = new MarkdownEditorControl
|
||||||
|
{
|
||||||
|
MarkdownText = LongArticle,
|
||||||
|
IsPreviewVisible = preview
|
||||||
|
};
|
||||||
|
var window = new Window { Width = 600, Height = 400, Content = editor };
|
||||||
|
try
|
||||||
|
{
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.UpdateLayout();
|
||||||
|
|
||||||
|
var scroller = FindArticleScroller(editor, preview);
|
||||||
|
AssertScrollable(scroller);
|
||||||
|
var initialViewport = scroller.Viewport.Height;
|
||||||
|
|
||||||
|
window.Height = 300;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.UpdateLayout();
|
||||||
|
|
||||||
|
Assert.True(scroller.Viewport.Height < initialViewport);
|
||||||
|
AssertScrollable(scroller);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseWindow(window, editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaTheory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void Blog_page_bounds_the_article_viewport(bool preview)
|
||||||
|
{
|
||||||
|
var page = new BlogsPage();
|
||||||
|
var editor = page.GetLogicalDescendants().OfType<MarkdownEditorControl>().Single();
|
||||||
|
editor.MarkdownText = LongArticle;
|
||||||
|
editor.IsPreviewVisible = preview;
|
||||||
|
var posts = page.FindControl<ListBox>("PostsListBox")!;
|
||||||
|
posts.ItemsSource = Enumerable.Range(1, 50)
|
||||||
|
.Select(i => new Yavsc.Blogspot.BlogPostDto { Title = $"Post {i}" }).ToArray();
|
||||||
|
var navigation = new NavigationPage();
|
||||||
|
navigation.PushAsync(page).GetAwaiter().GetResult();
|
||||||
|
var window = new Window { Width = 800, Height = 700, Content = navigation };
|
||||||
|
try
|
||||||
|
{
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.UpdateLayout();
|
||||||
|
|
||||||
|
var scroller = FindArticleScroller(editor, preview);
|
||||||
|
Assert.InRange(scroller.Viewport.Height, 1, window.ClientSize.Height);
|
||||||
|
AssertScrollable(scroller);
|
||||||
|
AssertScrollable(posts.GetVisualDescendants().OfType<ScrollViewer>().Single());
|
||||||
|
var editorBottom = editor.TranslatePoint(new Point(0, editor.Bounds.Height), window);
|
||||||
|
Assert.NotNull(editorBottom);
|
||||||
|
Assert.InRange(editorBottom.Value.Y, 1, window.ClientSize.Height);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseWindow(window, editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaTheory]
|
||||||
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void Short_article_needs_no_vertical_scrollbar(bool preview)
|
||||||
|
{
|
||||||
|
var editor = new MarkdownEditorControl
|
||||||
|
{
|
||||||
|
MarkdownText = "Short article",
|
||||||
|
IsPreviewVisible = preview
|
||||||
|
};
|
||||||
|
var window = new Window { Width = 600, Height = 400, Content = editor };
|
||||||
|
try
|
||||||
|
{
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.UpdateLayout();
|
||||||
|
|
||||||
|
var scroller = FindArticleScroller(editor, preview);
|
||||||
|
Assert.True(scroller.Extent.Height <= scroller.Viewport.Height);
|
||||||
|
Assert.DoesNotContain(scroller.GetVisualDescendants().OfType<ScrollBar>(),
|
||||||
|
bar => bar.Orientation == Avalonia.Layout.Orientation.Vertical && bar.IsVisible);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseWindow(window, editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CloseWindow(Window window, MarkdownEditorControl editor)
|
||||||
|
{
|
||||||
|
// The package's shared renderer caches the visual; detach it between headless applications.
|
||||||
|
editor.FindControl<MarkdownViewer.Core.Controls.MarkdownViewer>("PreviewViewer")!.Content = null;
|
||||||
|
window.UpdateLayout();
|
||||||
|
window.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ScrollViewer FindArticleScroller(MarkdownEditorControl editor, bool preview)
|
||||||
|
{
|
||||||
|
if (!preview)
|
||||||
|
return editor.GetVisualDescendants().OfType<TextEditor>().Single()
|
||||||
|
.GetVisualDescendants().OfType<ScrollViewer>().Single();
|
||||||
|
|
||||||
|
return editor.FindControl<ScrollViewer>("PreviewScrollViewer")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssertScrollable(ScrollViewer scroller)
|
||||||
|
{
|
||||||
|
Assert.True(scroller.Extent.Height > scroller.Viewport.Height,
|
||||||
|
$"Expected overflow, but extent={scroller.Extent.Height}, viewport={scroller.Viewport.Height}.");
|
||||||
|
Assert.Contains(scroller.GetVisualDescendants().OfType<ScrollBar>(),
|
||||||
|
bar => bar.Orientation == Avalonia.Layout.Orientation.Vertical && bar.IsVisible);
|
||||||
|
scroller.ScrollToEnd();
|
||||||
|
Assert.True(scroller.Offset.Y > 0);
|
||||||
|
Assert.Equal(scroller.Extent.Height - scroller.Viewport.Height, scroller.Offset.Y, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,47 @@
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
using Yavsc.Blogspot;
|
using Yavsc.Blogspot;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
using PostIt.Services;
|
using PostIt.Services;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
|
using PostIt.Views.Blogs;
|
||||||
|
|
||||||
namespace PostIt.Tests;
|
namespace PostIt.Tests;
|
||||||
|
|
||||||
public class PostItViewModelTests
|
public class PostItViewModelTests
|
||||||
{
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Published_badge_tracks_post_publication_state()
|
||||||
|
{
|
||||||
|
var post = new BlogPostDto { Id = 42, Title = "Test post", IsPublished = true };
|
||||||
|
var page = new BlogsPage();
|
||||||
|
var row = page.PostsListBox.ItemTemplate!.Build(post)!;
|
||||||
|
row.DataContext = post;
|
||||||
|
var window = new Window { Content = row };
|
||||||
|
try
|
||||||
|
{
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var badge = Assert.Single(row.GetVisualDescendants().OfType<Border>(),
|
||||||
|
border => border.Name == "PublishedBadge");
|
||||||
|
|
||||||
|
Assert.True(badge.IsVisible);
|
||||||
|
|
||||||
|
post.IsPublished = false;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.False(badge.IsVisible);
|
||||||
|
|
||||||
|
post.IsPublished = true;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(badge.IsVisible);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
window.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SearchCommand_filters_posts_by_title_article_or_author()
|
public void SearchCommand_filters_posts_by_title_article_or_author()
|
||||||
|
|
@ -61,11 +96,22 @@ public class PostItViewModelTests
|
||||||
var viewModel = new BlogsViewModel(blog);
|
var viewModel = new BlogsViewModel(blog);
|
||||||
|
|
||||||
viewModel.SelectedPost = new BlogPostDto { Id = 42, IsPublished = false };
|
viewModel.SelectedPost = new BlogPostDto { Id = 42, IsPublished = false };
|
||||||
|
var changes = new List<string?>();
|
||||||
|
viewModel.SelectedPost.PropertyChanged += (_, args) => changes.Add(args.PropertyName);
|
||||||
|
|
||||||
await viewModel.SetPublishStateAsync(true);
|
await viewModel.SetPublishStateAsync(true);
|
||||||
|
|
||||||
Assert.True(api.LastPublishValue);
|
Assert.True(api.LastPublishValue);
|
||||||
Assert.True(viewModel.DraftIsPublished);
|
Assert.True(viewModel.DraftIsPublished);
|
||||||
|
Assert.True(viewModel.SelectedPost.IsPublished);
|
||||||
|
Assert.Equal(new[] { nameof(BlogPostDto.IsPublished) }, changes);
|
||||||
|
|
||||||
|
await viewModel.SetPublishStateAsync(false);
|
||||||
|
|
||||||
|
Assert.False(api.LastPublishValue);
|
||||||
|
Assert.False(viewModel.DraftIsPublished);
|
||||||
|
Assert.False(viewModel.SelectedPost.IsPublished);
|
||||||
|
Assert.Equal(new[] { nameof(BlogPostDto.IsPublished), nameof(BlogPostDto.IsPublished) }, changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Test fake that always throws if the API is invoked.</summary>
|
/// <summary>Test fake that always throws if the API is invoked.</summary>
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,13 @@
|
||||||
x:Name="Root"
|
x:Name="Root"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="260">
|
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
<Border
|
<Border
|
||||||
BorderBrush="LightGray"
|
BorderBrush="LightGray"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="5"
|
CornerRadius="5"
|
||||||
IsVisible="{Binding !IsPreviewVisible, ElementName=Root}"
|
IsVisible="{Binding !IsPreviewVisible, ElementName=Root}"
|
||||||
Padding="0"
|
Padding="0">
|
||||||
MinHeight="260">
|
|
||||||
<ScrollViewer
|
|
||||||
BringIntoViewOnFocusChange="False"
|
|
||||||
HorizontalScrollBarVisibility="Disabled"
|
|
||||||
VerticalScrollBarVisibility="Auto"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch">
|
|
||||||
<AvaloniaEdit:TextEditor
|
<AvaloniaEdit:TextEditor
|
||||||
x:Name="SourceEditor"
|
x:Name="SourceEditor"
|
||||||
Document="{Binding Document, ElementName=Root, Mode=TwoWay}"
|
Document="{Binding Document, ElementName=Root, Mode=TwoWay}"
|
||||||
|
|
@ -35,7 +28,6 @@
|
||||||
VerticalScrollBarVisibility="Auto"
|
VerticalScrollBarVisibility="Auto"
|
||||||
TextInputOptions.Multiline="True"
|
TextInputOptions.Multiline="True"
|
||||||
TextInputOptions.ReturnKeyType="Default" />
|
TextInputOptions.ReturnKeyType="Default" />
|
||||||
</ScrollViewer>
|
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Border
|
<Border
|
||||||
|
|
@ -43,18 +35,21 @@
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="5"
|
CornerRadius="5"
|
||||||
IsVisible="{Binding IsPreviewVisible, ElementName=Root}"
|
IsVisible="{Binding IsPreviewVisible, ElementName=Root}"
|
||||||
Padding="8"
|
Padding="8">
|
||||||
MinHeight="260">
|
|
||||||
<ScrollViewer
|
<ScrollViewer
|
||||||
|
x:Name="PreviewScrollViewer"
|
||||||
BringIntoViewOnFocusChange="False"
|
BringIntoViewOnFocusChange="False"
|
||||||
HorizontalScrollBarVisibility="Disabled"
|
|
||||||
VerticalScrollBarVisibility="Auto"
|
VerticalScrollBarVisibility="Auto"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch"
|
||||||
|
HorizontalScrollBarVisibility="Auto">
|
||||||
<md:MarkdownViewer
|
<md:MarkdownViewer
|
||||||
x:Name="PreviewViewer"
|
x:Name="PreviewViewer"
|
||||||
MarkdownText="{Binding MarkdownText, ElementName=Root}"
|
HorizontalContentAlignment="Stretch"
|
||||||
ClipToBounds="False" />
|
VerticalAlignment="Top"
|
||||||
|
ClipToBounds="False"
|
||||||
|
UseLayoutRounding="True"
|
||||||
|
MarkdownText="{Binding MarkdownText, ElementName=Root}" />
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,6 @@ public partial class MarkdownEditorControl : UserControl
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
PropertyChanged += OnPropertyChanged;
|
PropertyChanged += OnPropertyChanged;
|
||||||
SizeChanged += OnSizeChanged;
|
|
||||||
UpdateEditorLimits();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||||
|
|
@ -62,27 +60,4 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using Yavsc.Blogspot;
|
using Yavsc.Blogspot;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
using Yavsc.Abstract.Files;
|
using Yavsc.Abstract.Files;
|
||||||
using PostIt.Helpers;
|
|
||||||
using AvaloniaEdit.Document;
|
using AvaloniaEdit.Document;
|
||||||
using PostIt.Models;
|
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ namespace PostIt.ViewModels.Chat
|
||||||
});
|
});
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
};
|
};
|
||||||
_connection.Reconnected += _ =>
|
_connection.Reconnected += async _ =>
|
||||||
{
|
{
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
|
|
@ -254,7 +254,11 @@ namespace PostIt.ViewModels.Chat
|
||||||
Status = new StatusNotice("Reconnecté au Hub SignalR", StatusSeverity.Info,
|
Status = new StatusNotice("Reconnecté au Hub SignalR", StatusSeverity.Info,
|
||||||
new RelayCheckCommand("Déconnecter", async () => await DisconnectAsync()));
|
new RelayCheckCommand("Déconnecter", async () => await DisconnectAsync()));
|
||||||
});
|
});
|
||||||
return Task.CompletedTask;
|
// La reconnexion crée une nouvelle ConnectionId côté
|
||||||
|
// serveur : l'appartenance aux salons est perdue, il
|
||||||
|
// faut rejoindre à nouveau le salon courant.
|
||||||
|
if (!string.IsNullOrWhiteSpace(CurrentRoom))
|
||||||
|
await JoinRoomAsync();
|
||||||
};
|
};
|
||||||
_connection.Closed += error =>
|
_connection.Closed += error =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,11 @@
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<vm:BlogsViewModel />
|
<vm:BlogsViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
<ScrollViewer
|
<Grid
|
||||||
|
RowDefinitions="Auto,Auto,*"
|
||||||
Margin="5,5,10,5"
|
Margin="5,5,10,5"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch">
|
||||||
HorizontalScrollBarVisibility="Disabled"
|
|
||||||
VerticalScrollBarVisibility="Auto"
|
|
||||||
BringIntoViewOnFocusChange="False">
|
|
||||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
|
||||||
|
|
||||||
<WrapPanel x:Name="BlogsHeaderPanel"
|
<WrapPanel x:Name="BlogsHeaderPanel"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
|
|
@ -39,6 +36,7 @@
|
||||||
Text="{Binding SearchText, Mode=TwoWay}" />
|
Text="{Binding SearchText, Mode=TwoWay}" />
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
<CommandBar x:Name="BlogsCommandBar"
|
<CommandBar x:Name="BlogsCommandBar"
|
||||||
|
Grid.Row="1"
|
||||||
HorizontalAlignment="Right" IsDynamicOverflowEnabled="True">
|
HorizontalAlignment="Right" IsDynamicOverflowEnabled="True">
|
||||||
<CommandBar.PrimaryCommands>
|
<CommandBar.PrimaryCommands>
|
||||||
<CommandBarButton Command="{Binding RefreshAsync}" Label="Refresh">
|
<CommandBarButton Command="{Binding RefreshAsync}" Label="Refresh">
|
||||||
|
|
@ -107,16 +105,14 @@
|
||||||
</CommandBar.PrimaryCommands>
|
</CommandBar.PrimaryCommands>
|
||||||
</CommandBar>
|
</CommandBar>
|
||||||
|
|
||||||
<Grid Margin="12" RowSpacing="12" x:Name="MainGrid" MinHeight="500">
|
<Grid Grid.Row="2" Margin="12" RowSpacing="12" x:Name="MainGrid">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="4" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="2*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="1"
|
Grid.Row="0"
|
||||||
Padding="8"
|
Padding="8"
|
||||||
BorderBrush="Gray"
|
BorderBrush="Gray"
|
||||||
BorderThickness="1">
|
BorderThickness="1">
|
||||||
|
|
@ -126,7 +122,27 @@
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="models:BlogPostDto">
|
<DataTemplate x:DataType="models:BlogPostDto">
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock FontWeight="SemiBold" Text="{Binding Title}" />
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{Binding Title}" />
|
||||||
|
<Border
|
||||||
|
x:Name="PublishedBadge"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Padding="6,2"
|
||||||
|
Background="#F2F7F2"
|
||||||
|
BorderBrush="#2F6D2F"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="6"
|
||||||
|
IsVisible="{Binding IsPublished}">
|
||||||
|
<TextBlock
|
||||||
|
FontSize="10"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="#2F6D2F"
|
||||||
|
Text="Publié" />
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
Foreground="Gray"
|
Foreground="Gray"
|
||||||
|
|
@ -141,6 +157,11 @@
|
||||||
</ListBox>
|
</ListBox>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<GridSplitter
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
ResizeDirection="Rows" />
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
|
|
@ -151,6 +172,7 @@
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
|
|
@ -197,6 +219,5 @@
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</Grid>
|
||||||
</ScrollViewer>
|
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
#nullable enable annotations
|
#nullable enable annotations
|
||||||
|
|
||||||
using Yavsc.Abstract.Identity.Security;
|
using Yavsc.Abstract.Identity.Security;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Yavsc.Blogspot;
|
namespace Yavsc.Blogspot;
|
||||||
|
|
||||||
public class BlogPostDto : IBlogPost
|
public class BlogPostDto : IBlogPost, INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
private bool _isPublished;
|
||||||
|
|
||||||
public string AuthorId { get; set; }
|
public string AuthorId { get; set; }
|
||||||
|
|
||||||
public BlogPostAuthorDto? Author { get; set; }
|
public BlogPostAuthorDto? Author { get; set; }
|
||||||
|
|
@ -30,7 +34,18 @@ public class BlogPostDto : IBlogPost
|
||||||
/// follow-up request. Toggled via
|
/// follow-up request. Toggled via
|
||||||
/// <c>PUT /api/BlogApi/{id}/publish</c>.
|
/// <c>PUT /api/BlogApi/{id}/publish</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPublished { get; set; }
|
public bool IsPublished
|
||||||
|
{
|
||||||
|
get => _isPublished;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isPublished == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isPublished = value;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsPublished)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual bool AuthorizeCircle(long circleId)
|
public virtual bool AuthorizeCircle(long circleId)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue