[backend/database] [frontend] [shared] Rename emoji aliases to tags (ISH-717)

This commit is contained in:
Laura Hausmann 2025-03-04 23:28:59 +01:00
parent 6391e5f185
commit 0442f676e1
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
13 changed files with 136 additions and 110 deletions

View file

@ -34,19 +34,19 @@ public class EmojiController(
public async Task<IEnumerable<EmojiResponse>> GetAllEmoji() public async Task<IEnumerable<EmojiResponse>> GetAllEmoji()
{ {
return await db.Emojis return await db.Emojis
.Where(p => p.Host == null) .Where(p => p.Host == null)
.Select(p => new EmojiResponse .Select(p => new EmojiResponse
{ {
Id = p.Id, Id = p.Id,
Name = p.Name, Name = p.Name,
Uri = p.Uri, Uri = p.Uri,
Aliases = p.Aliases, Tags = p.Tags,
Category = p.Category, Category = p.Category,
PublicUrl = p.GetAccessUrl(instance.Value), PublicUrl = p.GetAccessUrl(instance.Value),
License = p.License, License = p.License,
Sensitive = p.Sensitive Sensitive = p.Sensitive
}) })
.ToListAsync(); .ToListAsync();
} }
[HttpGet("remote")] [HttpGet("remote")]
@ -56,20 +56,20 @@ public class EmojiController(
public async Task<PaginationWrapper<List<EmojiResponse>>> GetRemoteEmoji(PaginationQuery pq) public async Task<PaginationWrapper<List<EmojiResponse>>> GetRemoteEmoji(PaginationQuery pq)
{ {
var res = await db.Emojis var res = await db.Emojis
.Where(p => p.Host != null) .Where(p => p.Host != null)
.Select(p => new EmojiResponse .Select(p => new EmojiResponse
{ {
Id = p.Id, Id = p.Id,
Name = p.Name, Name = p.Name,
Uri = p.Uri, Uri = p.Uri,
Aliases = p.Aliases, Tags = p.Tags,
Category = p.Host, Category = p.Host,
PublicUrl = p.GetAccessUrl(instance.Value), PublicUrl = p.GetAccessUrl(instance.Value),
License = p.License, License = p.License,
Sensitive = p.Sensitive Sensitive = p.Sensitive
}) })
.Paginate(pq, ControllerContext) .Paginate(pq, ControllerContext)
.ToListAsync(); .ToListAsync();
return HttpContext.CreatePaginationWrapper(pq, res); return HttpContext.CreatePaginationWrapper(pq, res);
} }
@ -81,20 +81,20 @@ public class EmojiController(
public async Task<PaginationWrapper<List<EmojiResponse>>> GetRemoteEmojiByHost(string host, PaginationQuery pq) public async Task<PaginationWrapper<List<EmojiResponse>>> GetRemoteEmojiByHost(string host, PaginationQuery pq)
{ {
var res = await db.Emojis var res = await db.Emojis
.Where(p => p.Host == host) .Where(p => p.Host == host)
.Select(p => new EmojiResponse .Select(p => new EmojiResponse
{ {
Id = p.Id, Id = p.Id,
Name = p.Name, Name = p.Name,
Uri = p.Uri, Uri = p.Uri,
Aliases = p.Aliases, Tags = p.Tags,
Category = p.Host, Category = p.Host,
PublicUrl = p.GetAccessUrl(instance.Value), PublicUrl = p.GetAccessUrl(instance.Value),
License = p.License, License = p.License,
Sensitive = p.Sensitive Sensitive = p.Sensitive
}) })
.Paginate(pq, ControllerContext) .Paginate(pq, ControllerContext)
.ToListAsync(); .ToListAsync();
return HttpContext.CreatePaginationWrapper(pq, res); return HttpContext.CreatePaginationWrapper(pq, res);
} }
@ -107,11 +107,11 @@ public class EmojiController(
{ {
pq.MinId ??= ""; pq.MinId ??= "";
var res = await db.Emojis.Where(p => p.Host != null) var res = await db.Emojis.Where(p => p.Host != null)
.Select(p => new EntityWrapper<string> { Entity = p.Host!, Id = p.Host! }) .Select(p => new EntityWrapper<string> { Entity = p.Host!, Id = p.Host! })
.Distinct() .Distinct()
.Paginate(pq, ControllerContext) .Paginate(pq, ControllerContext)
.ToListAsync() .ToListAsync()
.ContinueWithResult(p => p.NotNull()); .ContinueWithResult(p => p.NotNull());
return res; return res;
} }
@ -122,14 +122,14 @@ public class EmojiController(
public async Task<EmojiResponse> GetEmoji(string id) public async Task<EmojiResponse> GetEmoji(string id)
{ {
var emoji = await db.Emojis.FirstOrDefaultAsync(p => p.Id == id) var emoji = await db.Emojis.FirstOrDefaultAsync(p => p.Id == id)
?? throw GracefulException.NotFound("Emoji not found"); ?? throw GracefulException.NotFound("Emoji not found");
return new EmojiResponse return new EmojiResponse
{ {
Id = emoji.Id, Id = emoji.Id,
Name = emoji.Name, Name = emoji.Name,
Uri = emoji.Uri, Uri = emoji.Uri,
Aliases = emoji.Aliases, Tags = emoji.Tags,
Category = emoji.Category, Category = emoji.Category,
PublicUrl = emoji.GetAccessUrl(instance.Value), PublicUrl = emoji.GetAccessUrl(instance.Value),
License = emoji.License, License = emoji.License,
@ -151,7 +151,7 @@ public class EmojiController(
Id = emoji.Id, Id = emoji.Id,
Name = emoji.Name, Name = emoji.Name,
Uri = emoji.Uri, Uri = emoji.Uri,
Aliases = [], Tags = [],
Category = null, Category = null,
PublicUrl = emoji.GetAccessUrl(instance.Value), PublicUrl = emoji.GetAccessUrl(instance.Value),
License = null, License = null,
@ -177,7 +177,7 @@ public class EmojiController(
Id = cloned.Id, Id = cloned.Id,
Name = cloned.Name, Name = cloned.Name,
Uri = cloned.Uri, Uri = cloned.Uri,
Aliases = [], Tags = [],
Category = null, Category = null,
PublicUrl = cloned.GetAccessUrl(instance.Value), PublicUrl = cloned.GetAccessUrl(instance.Value),
License = null, License = null,
@ -203,16 +203,16 @@ public class EmojiController(
[ProducesErrors(HttpStatusCode.NotFound)] [ProducesErrors(HttpStatusCode.NotFound)]
public async Task<EmojiResponse> UpdateEmoji(string id, UpdateEmojiRequest request) public async Task<EmojiResponse> UpdateEmoji(string id, UpdateEmojiRequest request)
{ {
var emoji = await emojiSvc.UpdateLocalEmojiAsync(id, request.Name, request.Aliases, request.Category, var emoji = await emojiSvc.UpdateLocalEmojiAsync(id, request.Name, request.Tags, request.Category,
request.License, request.Sensitive) request.License, request.Sensitive)
?? throw GracefulException.NotFound("Emoji not found"); ?? throw GracefulException.NotFound("Emoji not found");
return new EmojiResponse return new EmojiResponse
{ {
Id = emoji.Id, Id = emoji.Id,
Name = emoji.Name, Name = emoji.Name,
Uri = emoji.Uri, Uri = emoji.Uri,
Aliases = emoji.Aliases, Tags = emoji.Tags,
Category = emoji.Category, Category = emoji.Category,
PublicUrl = emoji.GetAccessUrl(instance.Value), PublicUrl = emoji.GetAccessUrl(instance.Value),
License = emoji.License, License = emoji.License,

View file

@ -76,8 +76,8 @@ public class NoteRenderer(
var attachments = var attachments =
(data?.Attachments ?? await GetAttachmentsAsync([note])).Where(p => note.FileIds.Contains(p.Id)); (data?.Attachments ?? await GetAttachmentsAsync([note])).Where(p => note.FileIds.Contains(p.Id));
var reactions = (data?.Reactions ?? await GetReactionsAsync([note], user)).Where(p => p.NoteId == note.Id); var reactions = (data?.Reactions ?? await GetReactionsAsync([note], user)).Where(p => p.NoteId == note.Id);
var liked = data?.LikedNotes?.Contains(note.Id) ?? var liked = data?.LikedNotes?.Contains(note.Id)
await db.NoteLikes.AnyAsync(p => p.Note == note && p.User == user); ?? await db.NoteLikes.AnyAsync(p => p.Note == note && p.User == user);
var emoji = data?.Emoji?.Where(p => note.Emojis.Contains(p.Id)).ToList() ?? await GetEmojiAsync([note]); var emoji = data?.Emoji?.Where(p => note.Emojis.Contains(p.Id)).ToList() ?? await GetEmojiAsync([note]);
var poll = (data?.Polls ?? await GetPollsAsync([note], user)).FirstOrDefault(p => p.NoteId == note.Id); var poll = (data?.Polls ?? await GetPollsAsync([note], user)).FirstOrDefault(p => p.NoteId == note.Id);
@ -138,10 +138,12 @@ public class NoteRenderer(
.Select(p => new NoteReactionSchema .Select(p => new NoteReactionSchema
{ {
NoteId = p.First().NoteId, NoteId = p.First().NoteId,
Count = (int)counts[p.First().NoteId].GetValueOrDefault(p.First().Reaction, 1), Count =
Reacted = db.NoteReactions.Any(i => i.NoteId == p.First().NoteId && (int)counts[p.First().NoteId].GetValueOrDefault(p.First().Reaction, 1),
i.Reaction == p.First().Reaction && Reacted =
i.User == user), db.NoteReactions.Any(i => i.NoteId == p.First().NoteId
&& i.Reaction == p.First().Reaction
&& i.User == user),
Name = p.First().Reaction, Name = p.First().Reaction,
Url = null, Url = null,
Sensitive = false Sensitive = false
@ -194,7 +196,7 @@ public class NoteRenderer(
Id = p.Id, Id = p.Id,
Name = p.Name, Name = p.Name,
Uri = p.Uri, Uri = p.Uri,
Aliases = p.Aliases, Tags = p.Tags,
Category = p.Category, Category = p.Category,
PublicUrl = p.GetAccessUrl(config.Value), PublicUrl = p.GetAccessUrl(config.Value),
License = p.License, License = p.License,
@ -206,8 +208,8 @@ public class NoteRenderer(
private async Task<List<NotePollSchema>> GetPollsAsync(IEnumerable<Note> notes, User? user) private async Task<List<NotePollSchema>> GetPollsAsync(IEnumerable<Note> notes, User? user)
{ {
var polls = await db.Polls var polls = await db.Polls
.Where(p => notes.Contains(p.Note)) .Where(p => notes.Contains(p.Note))
.ToListAsync(); .ToListAsync();
var votes = user != null var votes = user != null
? await db.PollVotes ? await db.PollVotes

View file

@ -51,7 +51,10 @@ public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseConte
var bannerAlt = await GetBannerAltAsync([user]); var bannerAlt = await GetBannerAltAsync([user]);
var data = new UserRendererDto var data = new UserRendererDto
{ {
Emojis = emojis, InstanceData = instanceData, AvatarAlt = avatarAlt, BannerAlt = bannerAlt Emojis = emojis,
InstanceData = instanceData,
AvatarAlt = avatarAlt,
BannerAlt = bannerAlt
}; };
return Render(user, data); return Render(user, data);
@ -107,7 +110,7 @@ public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseConte
Id = p.Id, Id = p.Id,
Name = p.Name, Name = p.Name,
Uri = p.Uri, Uri = p.Uri,
Aliases = p.Aliases, Tags = p.Tags,
Category = p.Category, Category = p.Category,
PublicUrl = p.GetAccessUrl(config.Value), PublicUrl = p.GetAccessUrl(config.Value),
License = p.License, License = p.License,

View file

@ -19,7 +19,7 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "9.0.1") .HasAnnotation("ProductVersion", "9.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "antenna_src_enum", new[] { "home", "all", "users", "list", "group", "instances" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "antenna_src_enum", new[] { "home", "all", "users", "list", "group", "instances" });
@ -978,13 +978,6 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("id"); .HasColumnName("id");
b.PrimitiveCollection<List<string>>("Aliases")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("character varying(128)[]")
.HasColumnName("aliases")
.HasDefaultValueSql("'{}'::character varying[]");
b.Property<string>("Category") b.Property<string>("Category")
.HasMaxLength(128) .HasMaxLength(128)
.HasColumnType("character varying(128)") .HasColumnType("character varying(128)")
@ -1029,6 +1022,13 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
.HasColumnType("boolean") .HasColumnType("boolean")
.HasColumnName("sensitive"); .HasColumnName("sensitive");
b.PrimitiveCollection<List<string>>("Tags")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("character varying(128)[]")
.HasColumnName("tags")
.HasDefaultValueSql("'{}'::character varying[]");
b.Property<string>("Type") b.Property<string>("Type")
.HasMaxLength(64) .HasMaxLength(64)
.HasColumnType("character varying(64)") .HasColumnType("character varying(64)")

View file

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Infrastructure;
#nullable disable
namespace Iceshrimp.Backend.Core.Database.Migrations
{
/// <inheritdoc />
[DbContext(typeof(DatabaseContext))]
[Migration("20250304222123_RenameEmojiTagsColumn")]
public partial class RenameEmojiTagsColumn : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "aliases",
table: "emoji",
newName: "tags");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "tags",
table: "emoji",
newName: "aliases");
}
}
}

View file

@ -30,8 +30,8 @@ public class Emoji
[Column("type")] [StringLength(64)] public string? Type { get; set; } [Column("type")] [StringLength(64)] public string? Type { get; set; }
[Column("aliases", TypeName = "character varying(128)[]")] [Column("tags", TypeName = "character varying(128)[]")]
public List<string> Aliases { get; set; } = []; public List<string> Tags { get; set; } = [];
[Column("category")] [Column("category")]
[StringLength(128)] [StringLength(128)]
@ -75,7 +75,7 @@ public class Emoji
{ {
public void Configure(EntityTypeBuilder<Emoji> entity) public void Configure(EntityTypeBuilder<Emoji> entity)
{ {
entity.Property(e => e.Aliases).HasDefaultValueSql("'{}'::character varying[]"); entity.Property(e => e.Tags).HasDefaultValueSql("'{}'::character varying[]");
entity.Property(e => e.Height).HasComment("Image height"); entity.Property(e => e.Height).HasComment("Image height");
entity.Property(e => e.RawPublicUrl).HasDefaultValueSql("''::character varying"); entity.Property(e => e.RawPublicUrl).HasDefaultValueSql("''::character varying");
entity.Property(e => e.Width).HasComment("Image width"); entity.Property(e => e.Width).HasComment("Image width");

View file

@ -28,7 +28,7 @@ public partial class EmojiService(
}); });
public async Task<Emoji> CreateEmojiFromStreamAsync( public async Task<Emoji> CreateEmojiFromStreamAsync(
Stream input, string fileName, string mimeType, List<string>? aliases = null, Stream input, string fileName, string mimeType, List<string>? tags = null,
string? category = null string? category = null
) )
{ {
@ -51,7 +51,7 @@ public partial class EmojiService(
{ {
Id = id, Id = id,
Name = name, Name = name,
Aliases = aliases ?? [], Tags = tags ?? [],
Category = category, Category = category,
UpdatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow,
OriginalUrl = driveFile.Url, OriginalUrl = driveFile.Url,
@ -224,7 +224,7 @@ public partial class EmojiService(
} }
public async Task<Emoji?> UpdateLocalEmojiAsync( public async Task<Emoji?> UpdateLocalEmojiAsync(
string id, string? name, List<string>? aliases, string? category, string? license, bool? sensitive string id, string? name, List<string>? tags, string? category, string? license, bool? sensitive
) )
{ {
var emoji = await db.Emojis.FirstOrDefaultAsync(p => p.Id == id); var emoji = await db.Emojis.FirstOrDefaultAsync(p => p.Id == id);
@ -241,8 +241,8 @@ public partial class EmojiService(
emoji.Uri = emoji.GetPublicUri(config.Value); emoji.Uri = emoji.GetPublicUri(config.Value);
} }
if (aliases != null) if (tags != null)
emoji.Aliases = aliases.Select(p => p.Trim()).Where(p => !string.IsNullOrWhiteSpace(p)).ToList(); emoji.Tags = tags.Select(p => p.Trim()).Where(p => !string.IsNullOrWhiteSpace(p)).ToList();
// If category is provided but empty reset to null // If category is provided but empty reset to null
if (category != null) emoji.Category = string.IsNullOrEmpty(category) ? null : category; if (category != null) emoji.Category = string.IsNullOrEmpty(category) ? null : category;

View file

@ -13,9 +13,9 @@
<div class="emoji-details"> <div class="emoji-details">
<span class="emoji-name">@Emoji.Name</span> <span class="emoji-name">@Emoji.Name</span>
<span> <span>
@foreach (var alias in Emoji.Aliases) @foreach (var tag in Emoji.Tags)
{ {
<span class="emoji-alias">@alias</span> <span class="emoji-tag">@tag</span>
} }
</span> </span>
<span class="labels"> <span class="labels">
@ -58,8 +58,8 @@
</MenuElement> </MenuElement>
} }
<MenuElement Icon="Icons.TextAa" OnSelect="SetAliases"> <MenuElement Icon="Icons.TextAa" OnSelect="SetTags">
<Text>@Loc["Set aliases"]</Text> <Text>@Loc["Set tags"]</Text>
</MenuElement> </MenuElement>
<MenuElement Icon="Icons.Folder" OnSelect="SetCategory"> <MenuElement Icon="Icons.Folder" OnSelect="SetCategory">
<Text>@Loc["Set category"]</Text> <Text>@Loc["Set category"]</Text>
@ -143,19 +143,19 @@
private async Task MarkAsSensitive() => await MarkSensitive(true); private async Task MarkAsSensitive() => await MarkSensitive(true);
private async Task SetAliases() => private async Task SetTags() =>
await Global.PromptDialog?.Prompt(new EventCallback<string?>(this, SetAliasesCallback), Loc["Set aliases (separated by new line)"], "one\ntwo\nthree", string.Join("\n", Emoji.Aliases), true, true)!; await Global.PromptDialog?.Prompt(new EventCallback<string?>(this, SetTagsCallback), Loc["Set tags (separated by new line)"], "one\ntwo\nthree", string.Join("\n", Emoji.Tags), true, true)!;
private async Task SetAliasesCallback(string? aliases) private async Task SetTagsCallback(string? tags)
{ {
if (aliases == null) return; if (tags == null) return;
try try
{ {
var res = await Api.Emoji.UpdateEmojiAsync(Emoji.Id, new UpdateEmojiRequest { Aliases = string.IsNullOrWhiteSpace(aliases) ? [] : aliases.Replace(" ", "").Split("\n").ToList() }); var res = await Api.Emoji.UpdateEmojiAsync(Emoji.Id, new UpdateEmojiRequest { Tags = string.IsNullOrWhiteSpace(tags) ? [] : tags.Replace(" ", "").Split("\n").ToList() });
if (res != null) if (res != null)
{ {
Emoji.Aliases = res.Aliases; Emoji.Tags = res.Tags;
StateHasChanged(); StateHasChanged();
} }
} }

View file

@ -27,21 +27,11 @@
opacity: 0.7; opacity: 0.7;
} }
.emoji-alias { .emoji-tag {
opacity: 0.7; opacity: 0.7;
font-size: 0.8em; font-size: 0.8em;
} }
.emoji-alias::before {
display: inline;
content: ":";
}
.emoji-alias::after {
display: inline;
content: ": ";
}
::deep { ::deep {
.labels .ph { .labels .ph {
display: inline-block; display: inline-block;

View file

@ -71,7 +71,7 @@
private void FilterEmojis() private void FilterEmojis()
{ {
Categories = EmojiList Categories = EmojiList
.Where(p => p.Name.Contains(EmojiFilter.StripLeadingTrailingSpaces()) || p.Aliases.Count(a => a.Contains(EmojiFilter.StripLeadingTrailingSpaces())) != 0) .Where(p => p.Name.Contains(EmojiFilter.StripLeadingTrailingSpaces()) || p.Tags.Count(a => a.Contains(EmojiFilter.StripLeadingTrailingSpaces())) != 0)
.OrderBy(p => p.Name) .OrderBy(p => p.Name)
.ThenBy(p => p.Id) .ThenBy(p => p.Id)
.GroupBy(p => p.Category) .GroupBy(p => p.Category)

View file

@ -128,7 +128,7 @@
{ {
if (Source == "remote" && _displayType == DisplayType.All || Source == "local") if (Source == "remote" && _displayType == DisplayType.All || Source == "local")
{ {
DisplayedEmojis = StoredRemoteEmojis.Where(p => p.Name.Contains(EmojiFilter.Trim()) || p.Aliases.Count(a => a.Contains(EmojiFilter.Trim())) > 0).ToList(); DisplayedEmojis = StoredRemoteEmojis.Where(p => p.Name.Contains(EmojiFilter.Trim()) || p.Tags.Count(a => a.Contains(EmojiFilter.Trim())) > 0).ToList();
} }
if (Source == "remote" && _displayType == DisplayType.Categories) if (Source == "remote" && _displayType == DisplayType.Categories)
@ -139,7 +139,7 @@
if (Source == "local" && _displayType == DisplayType.Categories) if (Source == "local" && _displayType == DisplayType.Categories)
{ {
LocalEmojiCategories = StoredLocalEmojis LocalEmojiCategories = StoredLocalEmojis
.Where(p => p.Name.Contains(EmojiFilter.Trim()) || p.Aliases.Count(a => a.Contains(EmojiFilter.Trim())) != 0) .Where(p => p.Name.Contains(EmojiFilter.Trim()) || p.Tags.Count(a => a.Contains(EmojiFilter.Trim())) != 0)
.OrderBy(p => p.Name) .OrderBy(p => p.Name)
.ThenBy(p => p.Id) .ThenBy(p => p.Id)
.GroupBy(p => p.Category) .GroupBy(p => p.Category)

View file

@ -7,7 +7,7 @@ public class EmojiResponse : IIdentifiable
public required string Id { get; set; } public required string Id { get; set; }
public required string Name { get; set; } public required string Name { get; set; }
public required string? Uri { get; set; } public required string? Uri { get; set; }
public required List<string> Aliases { get; set; } public required List<string> Tags { get; set; }
public required string? Category { get; set; } public required string? Category { get; set; }
public required string PublicUrl { get; set; } public required string PublicUrl { get; set; }
public required string? License { get; set; } public required string? License { get; set; }

View file

@ -3,7 +3,7 @@ namespace Iceshrimp.Shared.Schemas.Web;
public class UpdateEmojiRequest public class UpdateEmojiRequest
{ {
public string? Name { get; set; } public string? Name { get; set; }
public List<string>? Aliases { get; set; } public List<string>? Tags { get; set; }
public string? Category { get; set; } public string? Category { get; set; }
public string? License { get; set; } public string? License { get; set; }
public bool? Sensitive { get; set; } public bool? Sensitive { get; set; }