Articles fold in a feed, with their cover, excerpt and a Read button
An Article or a Page with its title, or a titled post longer than a few paragraphs (WriteFreely sends its posts as Notes with a name), shows its cover, its excerpt and a Read button in a feed, and its whole text on demand; the post opened in its thread shows whole. A feed of Ghost, WordPress and WriteFreely long reads stays a feed. tests/e2e ArticleTests checks it with a WriteFreely blog; the render matrix still passes (70). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
2f29872745
commit
5a7e1804a9
5 files changed
+112
-1
No files matched your search
@@ -133,7 +133,25 @@
|
||||
<p class="text-sm md:text-base font-bold break-words">@title</p>
|
||||
}
|
||||
|
||||
@if (status.SpoilerText is { Length: > 0 } spoiler)
|
||||
@if (IsFoldedArticle(status))
|
||||
{
|
||||
@* an article (WordPress, Ghost, WriteFreely, Lemmy's titled posts) shows its cover and excerpt in a feed, and
|
||||
its whole text on demand: a feed of long reads stays a feed *@
|
||||
<div class="flex flex-col gap-2" data-article>
|
||||
@if (status.Privapub.Cover is { Length: > 0 } cover)
|
||||
{
|
||||
<img alt="" loading="lazy" class="w-full max-h-56 object-cover rounded-lg" src="@cover" />
|
||||
}
|
||||
@if (ArticleExcerpt(status) is { Length: > 0 } excerpt)
|
||||
{
|
||||
<p class="text-sm opacity-80 break-words line-clamp-4">@excerpt</p>
|
||||
}
|
||||
<DButton Size="DSize.Sm" class="self-start" OnClick="() => isArticleOpen = true" data-article-open>
|
||||
<i aria-hidden="true" class="ion-md-book"></i><span>@Localizer["Read article"]</span>
|
||||
</DButton>
|
||||
</div>
|
||||
}
|
||||
else if (status.SpoilerText is { Length: > 0 } spoiler)
|
||||
{
|
||||
<DCollapse @bind-IsOpen="isSpoilerOpen" Icon="DCollapseIcon.Arrow" class="rounded-lg neo-xs" HeaderClass="text-sm py-2 min-h-0"
|
||||
ContentClass="text-sm">
|
||||
@@ -391,6 +409,7 @@
|
||||
bool isOptionsOpen;
|
||||
bool isSpoilerOpen;
|
||||
bool showFiltered;
|
||||
bool isArticleOpen;
|
||||
bool isReadingProvenance;
|
||||
bool isReadingServer;
|
||||
readonly HashSet<string> revealedMedia = [];
|
||||
@@ -709,6 +728,26 @@
|
||||
|
||||
HashSet<int> pollChoices = [];
|
||||
|
||||
// folded until asked for, in a feed: an article with its title (an Article or a Page, or a long titled Note, as
|
||||
// WriteFreely sends its posts), not the post opened in its thread, nor one behind a content warning (the warning
|
||||
// folds it already)
|
||||
bool IsFoldedArticle(Status status) => !isArticleOpen && !Highlighted && status.SpoilerText is not { Length: > 0 }
|
||||
&& status.Privapub is { Title.Length: > 0 } extra
|
||||
&& (extra.ObjectType is "Article" or "Page" || (status.Content?.Length ?? 0) > LongRead);
|
||||
|
||||
// how much markup a titled post holds before a feed folds it
|
||||
const int LongRead = 600;
|
||||
|
||||
// the article's own summary, or the start of its text
|
||||
static string ArticleExcerpt(Status status)
|
||||
{
|
||||
if (status.Privapub?.Excerpt is { Length: > 0 } excerpt)
|
||||
return excerpt;
|
||||
var text = System.Text.RegularExpressions.Regex.Replace(status.Content ?? "", "<[^>]+>", " ");
|
||||
text = System.Net.WebUtility.HtmlDecode(System.Text.RegularExpressions.Regex.Replace(text, @"\s+", " ")).Trim();
|
||||
return text.Length <= 280 ? text : text[..280].TrimEnd() + "…";
|
||||
}
|
||||
|
||||
// a profile shows the persona's own public and unlisted posts it pins, as Mastodon allows
|
||||
bool CanPin(Status status) => !Compact && !Post.Located && status.Reblog is null && status.Visibility is ("public" or "unlisted");
|
||||
bool CanVote(Poll poll) => !poll.Expired && poll.Voted != true && !IsOwn && !Compact && !Post.Located;
|
||||
|
||||
Reference in new issue
Block a user