Browse Source

feat: restore release notes on home page

pull/2942/head
Jason Dove 3 weeks ago
parent
commit
d6588d6f4a
No known key found for this signature in database
  1. 1
      CHANGELOG.md
  2. 7
      ErsatzTV.Core/Interfaces/GitHub/IGitHubApiClient.cs
  3. 34
      ErsatzTV.Infrastructure/GitHub/GitHubApiClient.cs
  4. 14
      ErsatzTV.Infrastructure/GitHub/IGitHubApi.cs
  5. 6
      ErsatzTV.Infrastructure/GitHub/Models/GitHubTag.cs
  6. 1
      ErsatzTV/ErsatzTV.csproj
  7. 82
      ErsatzTV/Pages/Index.razor
  8. 1
      ErsatzTV/Shared/MarkdownView.razor
  9. 69
      ErsatzTV/Shared/MarkdownView.razor.cs
  10. 3
      ErsatzTV/Startup.cs

1
CHANGELOG.md

@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- Upgrade Intel driver in docker containers to support latest Battlemage devices (e.g. B70)
- Restore release notes section on home page, since bugs continue to be fixed
## [26.5.1] - 2026-05-08
### Fixed

7
ErsatzTV.Core/Interfaces/GitHub/IGitHubApiClient.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
namespace ErsatzTV.Core.Interfaces.GitHub;
public interface IGitHubApiClient
{
Task<Either<BaseError, string>> GetLatestReleaseNotes(CancellationToken cancellationToken);
Task<Either<BaseError, string>> GetReleaseNotes(string tag, CancellationToken cancellationToken);
}

34
ErsatzTV.Infrastructure/GitHub/GitHubApiClient.cs

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.GitHub;
using Refit;
namespace ErsatzTV.Infrastructure.GitHub;
public class GitHubApiClient : IGitHubApiClient
{
public async Task<Either<BaseError, string>> GetLatestReleaseNotes(CancellationToken cancellationToken)
{
try
{
IGitHubApi service = RestService.For<IGitHubApi>("https://api.github.com");
return await service.GetReleases(cancellationToken).Map(releases => releases.Head().Body);
}
catch (Exception ex)
{
return BaseError.New(ex.ToString());
}
}
public async Task<Either<BaseError, string>> GetReleaseNotes(string tag, CancellationToken cancellationToken)
{
try
{
IGitHubApi service = RestService.For<IGitHubApi>("https://api.github.com");
return await service.GetTag(tag, cancellationToken).Map(t => t.Body);
}
catch (Exception ex)
{
return BaseError.New(ex.ToString());
}
}
}

14
ErsatzTV.Infrastructure/GitHub/IGitHubApi.cs

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
using ErsatzTV.Infrastructure.GitHub.Models;
using Refit;
namespace ErsatzTV.Infrastructure.GitHub;
[Headers("Accept: application/vnd.github.v3+json", "User-Agent: ErsatzTV/ErsatzTV")]
public interface IGitHubApi
{
[Get("/repos/ErsatzTV/legacy/releases")]
Task<List<GitHubTag>> GetReleases(CancellationToken cancellationToken);
[Get("/repos/ErsatzTV/legacy/releases/tags/{tag}")]
Task<GitHubTag> GetTag(string tag, CancellationToken cancellationToken);
}

6
ErsatzTV.Infrastructure/GitHub/Models/GitHubTag.cs

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
namespace ErsatzTV.Infrastructure.GitHub.Models;
public class GitHubTag
{
public string Body { get; set; }
}

1
ErsatzTV/ErsatzTV.csproj

@ -38,6 +38,7 @@ @@ -38,6 +38,7 @@
<PackageReference Include="Heron.MudCalendar" Version="4.0.0" />
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
<PackageReference Include="Markdig" Version="1.3.2" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.9" />

82
ErsatzTV/Pages/Index.razor

@ -1,8 +1,13 @@ @@ -1,8 +1,13 @@
@page "/"
@page "/"
@page "/system/health"
@using ErsatzTV.Application.Health
@using ErsatzTV.Core.Health
@using System.Reflection
@using ErsatzTV.Core.Interfaces.GitHub
@using Microsoft.Extensions.Caching.Memory
@implements IDisposable
@inject IGitHubApiClient GitHubApiClient
@inject IMemoryCache MemoryCache
@inject IMediator Mediator
@inject SystemStartup SystemStartup;
@ -124,12 +129,24 @@ @@ -124,12 +129,24 @@
</MudTd>
</RowTemplate>
</MudTable>
<MudCard Class="mt-6">
<MudCardHeader>
<MudText Class="mt-6">Full changelog is available on <MudLink Href="https://github.com/ErsatzTV/legacy/blob/main/CHANGELOG.md" Target="_blank">GitHub</MudLink></MudText>
</MudCardHeader>
<MudCardContent Class="release-notes mud-typography mud-typography-body1">
<MarkdownView Content="@_releaseNotes"/>
</MudCardContent>
</MudCard>
}
</MudContainer>
</div>
</MudForm>
@code {
private CancellationTokenSource _cts;
private string _releaseNotes;
protected override void OnInitialized()
{
SystemStartup.OnDatabaseReady += OnStartupProgress;
@ -140,6 +157,9 @@ @@ -140,6 +157,9 @@
{
SystemStartup.OnDatabaseReady -= OnStartupProgress;
SystemStartup.OnSearchIndexReady -= OnStartupProgress;
_cts?.Cancel();
_cts?.Dispose();
}
private async void OnStartupProgress(object sender, EventArgs e)
@ -154,6 +174,66 @@ @@ -154,6 +174,66 @@
}
}
protected override async Task OnParametersSetAsync()
{
_cts?.Cancel();
_cts?.Dispose();
_cts = new CancellationTokenSource();
var token = _cts.Token;
try
{
if (MemoryCache.TryGetValue("Index.ReleaseNotesHtml", out string releaseNotesHtml))
{
_releaseNotes = releaseNotesHtml;
}
else
{
var assembly = Assembly.GetEntryAssembly();
if (assembly != null)
{
string version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
if (version != null)
{
Either<BaseError, string> maybeNotes;
if (version != "develop")
{
string gitHubVersion = version.Split("-").Head();
if (!gitHubVersion.StartsWith("v"))
{
gitHubVersion = $"v{gitHubVersion}";
}
maybeNotes = await GitHubApiClient.GetReleaseNotes(gitHubVersion, token);
foreach (string notes in maybeNotes.RightToSeq())
{
_releaseNotes = notes;
}
}
else
{
maybeNotes = await GitHubApiClient.GetLatestReleaseNotes(token);
foreach (string notes in maybeNotes.RightToSeq())
{
_releaseNotes = notes;
}
}
}
}
if (_releaseNotes != null)
{
MemoryCache.Set("Index.ReleaseNotesHtml", _releaseNotes);
}
}
}
catch (Exception)
{
// ignore
}
}
private async Task<TableData<HealthCheckResult>> ServerReload(TableState state, CancellationToken cancellationToken)
{
List<HealthCheckResult> healthCheckResults = await Mediator.Send(new GetAllHealthCheckResults(), cancellationToken);

1
ErsatzTV/Shared/MarkdownView.razor

@ -0,0 +1 @@ @@ -0,0 +1 @@
@HtmlContent

69
ErsatzTV/Shared/MarkdownView.razor.cs

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
using Ganss.Xss;
using Markdig;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace ErsatzTV.Shared;
public partial class MarkdownView
{
private MarkupString? _markupContent;
[Inject]
public IHtmlSanitizer HtmlSanitizer { get; set; }
[Inject]
public IJSRuntime JsRuntime { get; set; }
[Parameter]
public string Content { get; set; }
public MarkupString? HtmlContent
{
get
{
if (string.IsNullOrWhiteSpace(Content))
{
return null;
}
return _markupContent ?? (_markupContent = ConvertStringToMarkupString(Content)).Value;
}
}
private MarkupString ConvertStringToMarkupString(string value)
{
if (!string.IsNullOrWhiteSpace(value))
{
// Convert markdown string to HTML
string html = Markdown.ToHtml(
value,
new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.UseSoftlineBreakAsHardlineBreak()
.Build());
// Sanitize HTML before rendering
string sanitizedHtml = HtmlSanitizer.Sanitize(html);
// Return sanitized HTML as a MarkupString that Blazor can render
return new MarkupString(sanitizedHtml);
}
return new MarkupString();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
try
{
await JsRuntime.InvokeVoidAsync("styleMarkdown");
}
catch (Exception)
{
// ignored
}
await base.OnAfterRenderAsync(true);
}
}

3
ErsatzTV/Startup.cs

@ -21,6 +21,7 @@ using ErsatzTV.Core.Images; @@ -21,6 +21,7 @@ using ErsatzTV.Core.Images;
using ErsatzTV.Core.Interfaces.Database;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.GitHub;
using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Locking;
@ -55,6 +56,7 @@ using ErsatzTV.Infrastructure.Data.Repositories; @@ -55,6 +56,7 @@ using ErsatzTV.Infrastructure.Data.Repositories;
using ErsatzTV.Infrastructure.Database;
using ErsatzTV.Infrastructure.Emby;
using ErsatzTV.Infrastructure.FFmpeg;
using ErsatzTV.Infrastructure.GitHub;
using ErsatzTV.Infrastructure.Health;
using ErsatzTV.Infrastructure.Health.Checks;
using ErsatzTV.Infrastructure.Images;
@ -833,6 +835,7 @@ public class Startup @@ -833,6 +835,7 @@ public class Startup
services.AddScoped<ISongVideoGenerator, SongVideoGenerator>();
services.AddScoped<IMusicVideoCreditsGenerator, MusicVideoCreditsGenerator>();
services.AddScoped<IGitHubApiClient, GitHubApiClient>();
services.AddScoped<IHtmlSanitizer, HtmlSanitizer>(_ =>
{
var sanitizer = new HtmlSanitizer();

Loading…
Cancel
Save