diff --git a/CHANGELOG.md b/CHANGELOG.md index faea475..7c3b811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added + +- Render custom emojis in display names. + ### Changed - Use `/@username` routes by default. diff --git a/src/api/emojis.ts b/src/api/emojis.ts new file mode 100644 index 0000000..fb71c32 --- /dev/null +++ b/src/api/emojis.ts @@ -0,0 +1,17 @@ +export interface CustomEmoji { + shortcode: string, + url: string, +} + +export function replaceShortcodes(text: string, emojis: CustomEmoji[]): string { + return text.replace(/:([\w.]+):/g, (match, shortcode) => { + const emoji = emojis.find((emoji) => { + return emoji.shortcode === shortcode + }) + if (emoji) { + return `:${emoji.shortcode}:` + } else { + return match + } + }) +} diff --git a/src/api/posts.ts b/src/api/posts.ts index 699bf2a..ef4f33d 100644 --- a/src/api/posts.ts +++ b/src/api/posts.ts @@ -1,5 +1,6 @@ import { BACKEND_URL } from "@/constants" import { PAGE_SIZE, http } from "./common" +import { CustomEmoji } from "./emojis" import { defaultProfile, Profile } from "./users" export interface Attachment { @@ -52,11 +53,6 @@ export interface Tag { url: string; } -export interface CustomEmoji { - shortcode: string, - url: string, -} - export interface Post { id: string; uri: string; diff --git a/src/api/users.ts b/src/api/users.ts index 04c8aa7..7438ba9 100644 --- a/src/api/users.ts +++ b/src/api/users.ts @@ -3,6 +3,7 @@ import { RouteLocationRaw } from "vue-router" import { BACKEND_URL } from "@/constants" import { createDidFromEthereumAddress } from "@/utils/did" import { PAGE_SIZE, http } from "./common" +import { CustomEmoji } from "./emojis" export const EXTRA_FIELD_COUNT_MAX = 10 @@ -48,6 +49,7 @@ export interface Profile { identity_proofs: ProfileField[]; payment_options: ProfilePaymentOption[]; fields: ProfileField[]; + emojis: CustomEmoji[], followers_count: number; following_count: number; @@ -69,6 +71,7 @@ export function defaultProfile(): Profile { identity_proofs: [], payment_options: [], fields: [], + emojis: [], followers_count: 0, following_count: 0, subscribers_count: 0, diff --git a/src/components/Post.vue b/src/components/Post.vue index e1fe4cc..d750818 100644 --- a/src/components/Post.vue +++ b/src/components/Post.vue @@ -15,12 +15,12 @@ - {{ author.getDisplayName() }} +
@{{ getActorAddress(post.account) }} @@ -80,9 +80,8 @@ >
- - {{ getQuoteAuthorDisplayName(linkedPost) }} - + + @{{ getActorAddress(linkedPost.account) }} @@ -270,6 +269,7 @@ import CryptoAddress from "@/components/CryptoAddress.vue" import PostAttachment from "@/components/PostAttachment.vue" import PostContent from "@/components/PostContent.vue" import PostEditor from "@/components/PostEditor.vue" +import ProfileDisplayName from "@/components/ProfileDisplayName.vue" import VisibilityIcon from "@/components/VisibilityIcon.vue" import { useInstanceInfo } from "@/store/instance" import { useCurrentUser } from "@/store/user" @@ -351,9 +351,8 @@ function getReplyMentions(): Mention[] { return props.post.mentions } -function getQuoteAuthorDisplayName(post: Post): string | null { - const profile = new ProfileWrapper(post.account) - return profile.getDisplayName() +function getQuoteAuthor(post: Post): ProfileWrapper { + return new ProfileWrapper(post.account) } function canReply(): boolean { @@ -607,7 +606,7 @@ async function onMintToken() { } } - .display-name { + .display-name-link { color: $text-color; font-weight: bold; overflow: hidden; diff --git a/src/components/PostContent.vue b/src/components/PostContent.vue index 022c20d..a2edb91 100644 --- a/src/components/PostContent.vue +++ b/src/components/PostContent.vue @@ -7,6 +7,7 @@ import { onMounted } from "vue" import { $, $ref } from "vue/macros" import { useRouter } from "vue-router" +import { replaceShortcodes } from "@/api/emojis" import { Post } from "@/api/posts" import { useCurrentUser } from "@/store/user" import { addGreentext } from "@/utils/greentext" @@ -72,16 +73,7 @@ function configureInlineLinks() { function getContent(): string { let content = addGreentext(props.post.content) // Replace emoji shortcodes - content = content.replace(/:([\w.]+):/g, (match, shortcode) => { - const emoji = props.post.emojis.find((emoji) => { - return emoji.shortcode === shortcode - }) - if (emoji) { - return `:${emoji.shortcode}:` - } else { - return match - } - }) + content = replaceShortcodes(content, props.post.emojis) return content } @@ -92,6 +84,8 @@ function getContent(): string { @import "../styles/mixins"; .post-content { + @include ugc-emoji; + color: $text-color; line-height: 1.5; padding: $block-inner-padding; @@ -168,10 +162,6 @@ function getContent(): string { } :deep(.emoji) { - height: 24px; - vertical-align: text-bottom; - width: 24px; - &:hover { height: 48px; transition: 100ms linear; diff --git a/src/components/PostOrRepost.vue b/src/components/PostOrRepost.vue index 7294ae8..e034e92 100644 --- a/src/components/PostOrRepost.vue +++ b/src/components/PostOrRepost.vue @@ -5,8 +5,9 @@ - {{ author.getDisplayName() }} + reposted
@@ -32,6 +33,7 @@ import { $, $computed } from "vue/macros" import type { Post as PostObject } from "@/api/posts" import { ProfileWrapper } from "@/api/users" import Post from "@/components/Post.vue" +import ProfileDisplayName from "@/components/ProfileDisplayName.vue" import { useInstanceInfo } from "@/store/instance" /* eslint-disable-next-line no-undef */ diff --git a/src/components/ProfileCard.vue b/src/components/ProfileCard.vue index acae80e..63cafd0 100644 --- a/src/components/ProfileCard.vue +++ b/src/components/ProfileCard.vue @@ -7,7 +7,7 @@
-
{{ profile.getDisplayName() }}
+
@{{ getActorAddress(profile) }}
@@ -27,6 +27,7 @@ import { $computed } from "vue/macros" import { Profile, ProfileWrapper } from "@/api/users" import Avatar from "@/components/Avatar.vue" +import ProfileDisplayName from "@/components/ProfileDisplayName.vue" import { useInstanceInfo } from "@/store/instance" /* eslint-disable-next-line no-undef */ diff --git a/src/components/ProfileDisplayName.vue b/src/components/ProfileDisplayName.vue new file mode 100644 index 0000000..8343b08 --- /dev/null +++ b/src/components/ProfileDisplayName.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/components/ProfileListItem.vue b/src/components/ProfileListItem.vue index 7410ea7..0100c71 100644 --- a/src/components/ProfileListItem.vue +++ b/src/components/ProfileListItem.vue @@ -2,7 +2,7 @@
-
{{ profile.getDisplayName() }}
+
@{{ getActorAddress(profile) }}
@@ -13,6 +13,7 @@ import { $computed } from "vue/macros" import { Profile, ProfileWrapper } from "@/api/users" import Avatar from "@/components/Avatar.vue" +import ProfileDisplayName from "@/components/ProfileDisplayName.vue" import { useInstanceInfo } from "@/store/instance" const { getActorAddress } = useInstanceInfo() diff --git a/src/components/SubscriptionEthereum.vue b/src/components/SubscriptionEthereum.vue index e353793..ce9a33b 100644 --- a/src/components/SubscriptionEthereum.vue +++ b/src/components/SubscriptionEthereum.vue @@ -7,7 +7,7 @@ :to="{ name: 'profile-by-acct', params: { acct: sender.acct }}" > -
{{ sender.getDisplayName() }}
+
{{ walletAddress ? walletAddress.toLowerCase() : '?' }}
@@ -18,7 +18,7 @@ :to="{ name: 'profile-by-acct', params: { acct: recipient.acct }}" > -
{{ recipient.getDisplayName() }}
+
{{ recipientEthereumAddress }}
@@ -125,6 +125,7 @@ import { } from "@/api/subscriptions-ethereum" import Avatar from "@/components/Avatar.vue" import Loader from "@/components/Loader.vue" +import ProfileDisplayName from "@/components/ProfileDisplayName.vue" import { useWallet } from "@/composables/wallet" import { useInstanceInfo } from "@/store/instance" import { useCurrentUser } from "@/store/user" diff --git a/src/components/SubscriptionMonero.vue b/src/components/SubscriptionMonero.vue index dcef7e0..c15bd71 100644 --- a/src/components/SubscriptionMonero.vue +++ b/src/components/SubscriptionMonero.vue @@ -7,7 +7,7 @@ :to="{ name: 'profile-by-acct', params: { acct: sender.acct } }" > -
{{ sender.getDisplayName() }}
+
@@ -17,7 +17,7 @@ :to="{ name: 'profile-by-acct', params: { acct: recipient.acct }}" > -
{{ recipient.getDisplayName() }}
+
@@ -131,6 +131,7 @@ import { defaultProfile, Profile, ProfilePaymentOption, ProfileWrapper } from "@ import Avatar from "@/components/Avatar.vue" import Loader from "@/components/Loader.vue" import QrCode from "@/components/QrCode.vue" +import ProfileDisplayName from "@/components/ProfileDisplayName.vue" import { useCurrentUser } from "@/store/user" import { formatDate } from "@/utils/dates" import { createMoneroPaymentUri } from "@/utils/monero" diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index be59fce..b64f6a8 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -158,3 +158,11 @@ color: $error-color; } } + +@mixin ugc-emoji { + :deep(.emoji) { + height: 24px; + vertical-align: text-bottom; + width: 24px; + } +} diff --git a/src/views/NotificationList.vue b/src/views/NotificationList.vue index 799f2dd..d4a89a5 100644 --- a/src/views/NotificationList.vue +++ b/src/views/NotificationList.vue @@ -20,8 +20,10 @@ - {{ getSenderName(notification) }} + + followed you replied to your post @@ -47,7 +49,8 @@
-
{{ getSenderName(notification) }}
+ +
@{{ getActorAddress(notification.account) }}
{{ humanizeDate(notification.created_at) }}
@@ -73,6 +76,7 @@ import { getNotifications, Notification } from "@/api/notifications" import { ProfileWrapper } from "@/api/users" import Avatar from "@/components/Avatar.vue" import Post from "@/components/Post.vue" +import ProfileDisplayName from "@/components/ProfileDisplayName.vue" import SidebarLayout from "@/components/SidebarLayout.vue" import { useInstanceInfo } from "@/store/instance" import { useNotifications } from "@/store/notifications" @@ -94,13 +98,13 @@ onMounted(async () => { } }) -function getSenderName(notification: Notification): string { - const sender = new ProfileWrapper(notification.account) - return sender.getDisplayName() +function getSender(notification: Notification): ProfileWrapper { + return new ProfileWrapper(notification.account) } function getSenderInfo(notification: Notification): string { - return `${getSenderName(notification)} (${getActorAddress(notification.account)})` + const senderName = getSender(notification).getDisplayName() + return `${senderName} (${getActorAddress(notification.account)})` } function onPostDeleted(notificationIndex: number) { diff --git a/src/views/Profile.vue b/src/views/Profile.vue index 9ac3179..3b7a153 100644 --- a/src/views/Profile.vue +++ b/src/views/Profile.vue @@ -105,7 +105,8 @@
-
{{ profile.getDisplayName() }}
+ +
@{{ actorAddress }}
@@ -280,6 +281,7 @@ import { import Avatar from "@/components/Avatar.vue" import Loader from "@/components/Loader.vue" import PostList from "@/components/PostList.vue" +import ProfileDisplayName from "@/components/ProfileDisplayName.vue" import ProfileListItem from "@/components/ProfileListItem.vue" import SidebarLayout from "@/components/SidebarLayout.vue" import { useEthereumAddressVerification } from "@/composables/ethereum-address-verification" diff --git a/tests/unit/profile-wrapper.spec.ts b/tests/unit/profile-wrapper.spec.ts new file mode 100644 index 0000000..01c4169 --- /dev/null +++ b/tests/unit/profile-wrapper.spec.ts @@ -0,0 +1,15 @@ +import { expect } from "chai" +import { defaultProfile, ProfileWrapper } from "@/api/users" + +describe("ProfileWrapper", () => { + it("Replace invisible characters", () => { + const hidden = "​" + expect(hidden.length).to.equal(1) + const profile = new ProfileWrapper({ + ...defaultProfile(), + username: "test", + display_name: hidden, + }) + expect(profile.getDisplayName()).to.equal("test") + }) +})