diff --git a/YKanBan/ViewModels/Board/CardViewModel.cs b/YKanBan/ViewModels/Board/CardViewModel.cs
index 53ec052..f94c923 100644
--- a/YKanBan/ViewModels/Board/CardViewModel.cs
+++ b/YKanBan/ViewModels/Board/CardViewModel.cs
@@ -16,6 +16,11 @@ public sealed partial class CardViewModel : ViewModelBase {
///
private const int PreviewLength = 200;
+ ///
+ /// Maximum number of explicit content lines shown in the card preview.
+ ///
+ private const int PreviewMaxLines = 10;
+
private readonly BoardViewModel _board;
///
@@ -70,12 +75,29 @@ public sealed partial class CardViewModel : ViewModelBase {
public bool HasContent => Model.Content.Length > 0;
///
- /// Gets the content preview, collapsed to one line and truncated.
+ /// Gets the content preview, truncated to at most 200 characters and
+ /// 10 explicit lines, with an ellipsis when anything was cut.
///
+ // YYC MARK: the preview is truncated here instead of in the view because
+ // both a character cap and an explicit-line cap are needed, and because
+ // Avalonia 11.3.x renders no ellipsis when MaxLines clips wrapped text
+ // (fixed upstream in 12.1.2, PR #22017, never backported). Revisit after
+ // upgrading Avalonia.
public string PreviewContent {
get {
- string singleLine = Model.Content.Replace("\r", " ").Replace("\n", " ");
- return singleLine.Length <= PreviewLength ? singleLine : singleLine[..PreviewLength] + "…";
+ string content = Model.Content;
+
+ // Cut at whichever boundary comes first: 200 characters or the 10th line break.
+ int limit = content.Length;
+ int breaks = 0;
+ for (int i = 0; i < content.Length; i++) {
+ if (i == PreviewLength || (content[i] == '\n' && ++breaks == PreviewMaxLines)) {
+ limit = i;
+ break;
+ }
+ }
+
+ return limit < content.Length ? content[..limit] + "…" : content;
}
}