Logout: - It navigated to "authenticatio/logout", a misspelled route. - The correct template route, authentication/logout, would throw anyway. Its RemoteAuthenticatorView casts the AuthenticationStateProvider to the OIDC template's service, and the app registers TokenAuthStateProvider instead. - It now signs out through TokenAuthStateProvider.LogoutAsync, as the code commented out in the page intended, and returns home. LoginDisplay's sign-out goes through it as well. - This also drops the obsolete SignOutSessionStateManager, the build's last warning. The IndexedDB schema never declared the ClientLogs store that IStorage.AddLog writes to, so every client log write failed inside its try/catch. Version 2 adds the store, keyed by the Id AddLog assigns. DInputBase.NameAttribute renders no name instead of name="" when the form maps no field names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
164 lines
4.1 KiB
C#
164 lines
4.1 KiB
C#
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
using System.Web;
|
|
|
|
using Blazored.LocalStorage;
|
|
|
|
using decePubClient.Models;
|
|
using decePubClient.Models.Types;
|
|
using decePubClient.Resources;
|
|
using decePubClient.Services;
|
|
|
|
using DnetIndexedDb;
|
|
using DnetIndexedDb.Fluent;
|
|
using DnetIndexedDb.Models;
|
|
|
|
using Markdig;
|
|
|
|
using Microsoft.AspNetCore.Components;
|
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
|
using Microsoft.Extensions.Localization;
|
|
|
|
namespace decePubClient.Extensions;
|
|
|
|
public static class GenericExtensions
|
|
{
|
|
public static IServiceCollection AddIndexedDb(this IServiceCollection services)
|
|
{
|
|
return services.AddIndexedDbDatabase<IndexedDb>(options =>
|
|
{
|
|
var model = new IndexedDbDatabaseModel()
|
|
.WithName("data")
|
|
.WithVersion(2)
|
|
.WithModelId(1);
|
|
|
|
model.AddStore(nameof(Message))
|
|
.WithKey(nameof(Message.MessageId))
|
|
.AddUniqueIndex(nameof(Message.MessageId))
|
|
.AddIndex(nameof(Message.RootMessageId))
|
|
.AddIndex(nameof(Message.User))
|
|
.AddIndex(nameof(Message.MessageType))
|
|
.AddIndex(nameof(Message.Title))
|
|
.AddIndex(nameof(Message.Content))
|
|
.AddIndex(nameof(Message.CreatedAt))
|
|
.AddIndex(nameof(Message.Medias));
|
|
|
|
// IStorage.AddLog numbers entries itself (max key + 1), so the key is Id rather than generated. Version 2
|
|
// added this store; before it, every log write failed inside AddLog's try/catch.
|
|
model.AddStore(nameof(ClientLogs))
|
|
.WithKey(nameof(ClientLogs.Id))
|
|
.AddUniqueIndex(nameof(ClientLogs.Id))
|
|
.AddIndex(nameof(ClientLogs.TimeStamp));
|
|
|
|
options.UseDatabase(model);
|
|
});
|
|
}
|
|
|
|
public static string GetPassedTime(this DateTime dateTime, IStringLocalizer localizer)
|
|
{
|
|
var timeframe = DateTime.Now - dateTime.ToLocalTime();
|
|
switch ((int)timeframe.TotalHours)
|
|
{
|
|
case >= 24:
|
|
return string.Format(localizer["{0}d"], (int)timeframe.TotalDays);
|
|
case >= 1 and < 24:
|
|
return string.Format(localizer["{0}h"], (int)timeframe.TotalHours);
|
|
case 0:
|
|
return string.Format(localizer["{0}m"], (int)timeframe.TotalMinutes);
|
|
default:
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public static string GetFileSize(this long size, IStringLocalizer<AllStrings> localizer)
|
|
{
|
|
switch (size)
|
|
{
|
|
case >= 1_048_576:
|
|
return string.Format(localizer["{0:N0}MB"], size / 1_048_576);
|
|
case < 1_048_576:
|
|
return string.Format(localizer["{0}KB"], size / 1_024);
|
|
}
|
|
}
|
|
|
|
public static string ParseContent(this string content, ContentType contentType)
|
|
{
|
|
switch (contentType)
|
|
{
|
|
case ContentType.PlainText:
|
|
return content;
|
|
case ContentType.HTML:
|
|
return ((MarkupString)content).Value;
|
|
case ContentType.Markdown:
|
|
return Markdown.ToHtml(content);
|
|
default:
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public static string GetMessageTypeIcon(this MessageType messageType)
|
|
{
|
|
switch (messageType)
|
|
{
|
|
case MessageType.Public:
|
|
return "ion-md-globe";
|
|
case MessageType.Unlisted:
|
|
return "ion-md-unlock";
|
|
case MessageType.FollowersOnly:
|
|
return "ion-md-lock";
|
|
case MessageType.Direct:
|
|
return "ion-md-paper-plane";
|
|
default:
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public static string GetTimelineTypeIcon(this TimelineType timelineType)
|
|
{
|
|
switch (timelineType)
|
|
{
|
|
case TimelineType.Home:
|
|
return "ion-md-home";
|
|
case TimelineType.Local:
|
|
return "ion-md-people";
|
|
case TimelineType.Federation:
|
|
return "ion-md-globe";
|
|
default:
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public static string GetContentTypeIcon(this ContentType contentType)
|
|
{
|
|
switch (contentType)
|
|
{
|
|
case ContentType.PlainText:
|
|
return "ion-md-quote";
|
|
case ContentType.HTML:
|
|
return "ion-logo-html5";
|
|
case ContentType.Markdown:
|
|
return "ion-logo-markdown";
|
|
default:
|
|
return default;
|
|
}
|
|
}
|
|
|
|
public static string GetMediaTypeIcon(this MediaType mediaType)
|
|
{
|
|
switch (mediaType)
|
|
{
|
|
case MediaType.Images:
|
|
return "ion-md-images";
|
|
case MediaType.Video:
|
|
return "ion-md-videocam";
|
|
case MediaType.Documents:
|
|
return "ion-md-document";
|
|
case MediaType.Audio:
|
|
return "ion-md-volume-high";
|
|
default:
|
|
return default;
|
|
}
|
|
}
|
|
} |