diff --git a/Iceshrimp.Frontend/Components/ModerationMenu.razor b/Iceshrimp.Frontend/Components/ModerationMenu.razor index f3165fb2..0232f995 100644 --- a/Iceshrimp.Frontend/Components/ModerationMenu.razor +++ b/Iceshrimp.Frontend/Components/ModerationMenu.razor @@ -27,6 +27,12 @@ @Loc["Overview"] + + + @code { diff --git a/Iceshrimp.Frontend/Core/ControllerModels/EmojiControllerModel.cs b/Iceshrimp.Frontend/Core/ControllerModels/EmojiControllerModel.cs index 61ae4591..0916dbd5 100644 --- a/Iceshrimp.Frontend/Core/ControllerModels/EmojiControllerModel.cs +++ b/Iceshrimp.Frontend/Core/ControllerModels/EmojiControllerModel.cs @@ -9,6 +9,9 @@ internal class EmojiControllerModel(ApiClient api) public Task> GetAllEmojiAsync() => api.CallAsync>(HttpMethod.Get, "/emoji"); + public Task> GetRemoteEmojiAsync() => + api.CallAsync>(HttpMethod.Get, "/emoji/remote"); + public Task UploadEmojiAsync(IBrowserFile file) => api.CallAsync(HttpMethod.Post, "/emoji", data: file); diff --git a/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor new file mode 100644 index 00000000..7efbd6b6 --- /dev/null +++ b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor @@ -0,0 +1,120 @@ +@page "/mod/emojis" +@using Iceshrimp.Frontend.Core.Services +@using Iceshrimp.Frontend.Localization +@using Microsoft.AspNetCore.Authorization +@using Microsoft.Extensions.Localization +@using Microsoft.AspNetCore.Components.Sections +@using Iceshrimp.Assets.PhosphorIcons +@using Iceshrimp.Frontend.Core.Miscellaneous +@using Iceshrimp.Shared.Schemas.Web +@using Iceshrimp.Frontend.Components +@attribute [Authorize(Roles = "moderator")] +@layout ModerationLayout +@inject ApiService Api; +@inject IStringLocalizer Loc; + + + + @Loc["Custom Emojis"] + + +@if (State is State.Loaded) +{ +
+ + @foreach (var category in Categories) + { + @category.Key +
+ @foreach (var emoji in category.Value) + { +
+ +
+ @emoji.Name + + @foreach (var alias in emoji.Aliases) + { + @alias + } + + + @if (Source == "remote") + { + + } + @if (emoji.Sensitive) + { + + } + @if (!string.IsNullOrWhiteSpace(emoji.License)) + { + + } + +
+
+ } +
+ } +
+} +@if (State is State.Empty) +{ +
+ This instance has no emojis +
+} + +@code { + private List Emojis { get; set; } = []; + private Dictionary> Categories { get; set; } = new(); + private string EmojiFilter { get; set; } = ""; + private string HostFilter { get; set; } = ""; + private string Source { get; set; } = "local"; + private State State { get; set; } + + private void FilterEmojis() + { + Categories = Emojis + .Where(p => p.Name.Contains(EmojiFilter.Trim()) || p.Aliases.Count(a => a.Contains(EmojiFilter.Trim())) != 0) + .OrderBy(p => p.Name) + .ThenBy(p => p.Id) + .GroupBy(p => p.Category) + .Where(p => Source == "local" || (Source == "remote" && (p.Key?.Contains(HostFilter) ?? false))) + .OrderBy(p => string.IsNullOrEmpty(p.Key)) + .ThenBy(p => p.Key) + .ToDictionary(p => p.Key ?? "Other", p => p.ToList()); + } + + private async Task GetEmojis() + { + State = State.Loading; + + Emojis = Source == "remote" ? await Api.Emoji.GetRemoteEmojiAsync() : await Api.Emoji.GetAllEmojiAsync(); + if (Emojis.Count == 0) + { + State = State.Empty; + } + else + { + FilterEmojis(); + State = State.Loaded; + } + } + + protected override async Task OnInitializedAsync() + { + await GetEmojis(); + } +} diff --git a/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.css b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.css new file mode 100644 index 00000000..b90f71bf --- /dev/null +++ b/Iceshrimp.Frontend/Pages/Moderation/CustomEmojis.razor.css @@ -0,0 +1,74 @@ +.emoji-search { + display: flex; + margin-top: 1rem; +} + +.search { + display: inline-block; + width: 100%; +} + +.search-from { + display: inline-block; +} + +.category-name { + display: inline-block; + margin: 1rem 0 0.5rem; + font-weight: bold; +} + +.emoji-list { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.5rem; + width: 100%; +} + +.emoji-entry { + display: flex; + gap: 0.5rem; + padding: 0.5rem; + background-color: var(--foreground-color); + border-radius: 0.5rem; +} + +.emoji-details { + display: flex; + flex-direction: column; + text-wrap: wrap; + word-break: break-all; + word-wrap: break-word; +} + +.emoji-name::before, +.emoji-name::after { + display: inline; + content: ":"; + opacity: 0.7; +} + +.emoji-alias { + opacity: 0.7; + font-size: 0.8em; +} + +.emoji-alias::before, +.emoji-alias::after { + display: inline; + content: ":"; +} + +::deep { + .labels .ph { + display: inline-block; + line-height: 1; + color: var(--notice-color); + } +} + +@media (max-width: 1000px) { + .emoji-list { + grid-template-columns: 1fr; + } +} \ No newline at end of file