commit: 0a79a747730bb4a10eb2544412dab68a10602240
parent e812d5ea3c97fdac0ed6b23817ae144cf96bb300
Author: Tusooa Zhu <tusooa@kazv.moe>
Date: Mon, 29 Aug 2022 18:46:41 -0400
Use dedicated indicator for non-ascii domain names
Diffstat:
22 files changed, 151 insertions(+), 49 deletions(-)
diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js
@@ -1,5 +1,6 @@
import UserPopover from '../user_popover/user_popover.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
+import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -10,7 +11,8 @@ const BasicUserCard = {
components: {
UserPopover,
UserAvatar,
- RichContent
+ RichContent,
+ UserLink
},
methods: {
userProfileLink (user) {
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
@@ -30,12 +30,10 @@
/>
</div>
<div>
- <router-link
+ <user-link
class="basic-user-card-screen-name"
- :to="userProfileLink(user)"
- >
- @{{ user.screen_name_ui }}
- </router-link>
+ :user="user"
+ />
</div>
<slot />
</div>
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
@@ -1,5 +1,6 @@
import Completion from '../../services/completion/completion.js'
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
import { take } from 'lodash'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
@@ -120,7 +121,8 @@ const EmojiInput = {
}
},
components: {
- EmojiPicker
+ EmojiPicker,
+ UnicodeDomainIndicator
},
computed: {
padEmoji () {
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
@@ -50,7 +50,21 @@
<span v-else>{{ suggestion.replacement }}</span>
</span>
<div class="label">
- <span class="displayText">{{ suggestion.displayText }}</span>
+ <span
+ v-if="suggestion.user"
+ class="displayText"
+ >
+ {{ suggestion.displayText }}<UnicodeDomainIndicator
+ :user="suggestion.user"
+ :at="false"
+ />
+ </span>
+ <span
+ v-if="!suggestion.user"
+ class="displayText"
+ >
+ {{ suggestion.displayText }}
+ </span>
<span class="detailText">{{ suggestion.detailText }}</span>
</div>
</div>
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
@@ -116,11 +116,12 @@ export const suggestUsers = ({ dispatch, state }) => {
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */
- }).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
- displayText: screen_name_ui,
- detailText: name,
- imageUrl: profile_image_url_original,
- replacement: '@' + screen_name + ' '
+ }).map((user) => ({
+ user,
+ displayText: user.screen_name_ui,
+ detailText: user.name,
+ imageUrl: user.profile_image_url_original,
+ replacement: '@' + user.screen_name + ' '
}))
/* eslint-enable camelcase */
diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js
@@ -2,6 +2,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p
import { mapGetters, mapState } from 'vuex'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import UserAvatar from '../user_avatar/user_avatar.vue'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
import { defineAsyncComponent } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -16,6 +17,7 @@ const MentionLink = {
name: 'MentionLink',
components: {
UserAvatar,
+ UnicodeDomainIndicator,
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
props: {
diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue
@@ -47,6 +47,9 @@
class="serverName"
:class="{ '-faded': shouldFadeDomain }"
v-html="'@' + serverName"
+ /><UnicodeDomainIndicator
+ v-if="shouldShowFullUserName"
+ :user="user"
/>
</span>
<span
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
@@ -5,6 +5,7 @@ import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue'
import Report from '../report/report.vue'
+import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import UserPopover from '../user_popover/user_popover.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
@@ -50,7 +51,8 @@ const Notification = {
Status,
Report,
RichContent,
- UserPopover
+ UserPopover,
+ UserLink
},
methods: {
toggleUserExpanded () {
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
@@ -11,9 +11,10 @@
class="Notification container -muted"
>
<small>
- <router-link :to="userProfileLink">
- {{ notification.from_profile.screen_name_ui }}
- </router-link>
+ <user-link
+ :user="notification.from_profile"
+ :at="false"
+ />
</small>
<button
class="button-unstyled unmute"
@@ -174,12 +175,10 @@
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
class="follow-text"
>
- <router-link
- :to="userProfileLink"
+ <user-link
class="follow-name"
- >
- @{{ notification.from_profile.screen_name_ui }}
- </router-link>
+ :user="notification.from_profile"
+ />
<div
v-if="notification.type === 'follow_request'"
style="white-space: nowrap;"
@@ -210,9 +209,9 @@
v-else-if="notification.type === 'move'"
class="move-text"
>
- <router-link :to="targetUserProfileLink">
- @{{ notification.target.screen_name_ui }}
- </router-link>
+ <user-link
+ :user="notification.target"
+ />
</div>
<Report
v-else-if="notification.type === 'pleroma:report'"
diff --git a/src/components/status/status.js b/src/components/status/status.js
@@ -13,6 +13,7 @@ import StatusPopover from '../status_popover/status_popover.vue'
import UserPopover from '../user_popover/user_popover.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue'
import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
+import UserLink from '../user_link/user_link.vue'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import MentionLink from 'src/components/mention_link/mention_link.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -115,7 +116,8 @@ const Status = {
RichContent,
MentionLink,
MentionsLine,
- UserPopover
+ UserPopover,
+ UserLink
},
props: [
'statusoid',
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
@@ -25,9 +25,10 @@
class="fa-scale-110 fa-old-padding repeat-icon"
icon="retweet"
/>
- <router-link :to="userProfileLink">
- {{ status.user.screen_name_ui }}
- </router-link>
+ <user-link
+ :user="status.user"
+ :at="false"
+ />
</small>
<small
v-if="showReasonMutedThread"
@@ -164,13 +165,12 @@
>
{{ status.user.name }}
</h4>
- <router-link
+ <user-link
class="account-name"
:title="status.user.screen_name_ui"
- :to="userProfileLink"
- >
- {{ status.user.screen_name_ui }}
- </router-link>
+ :user="status.user"
+ :at="false"
+ />
<img
v-if="!!(status.user && status.user.favicon)"
class="status-favicon"
diff --git a/src/components/unicode_domain_indicator/unicode_domain_indicator.vue b/src/components/unicode_domain_indicator/unicode_domain_indicator.vue
@@ -0,0 +1,26 @@
+<template>
+ <FAIcon
+ v-if="user && user.screen_name_ui_contains_non_ascii"
+ icon="code"
+ :title="$t('unicode_domain_indicator.tooltip')"
+ />
+</template>
+
+<script>
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faCode
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faCode
+)
+
+const UnicodeDomainIndicator = {
+ props: {
+ user: Object
+ }
+}
+
+export default UnicodeDomainIndicator
+</script>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
@@ -5,6 +5,7 @@ import FollowButton from '../follow_button/follow_button.vue'
import ModerationTools from '../moderation_tools/moderation_tools.vue'
import AccountActions from '../account_actions/account_actions.vue'
import Select from '../select/select.vue'
+import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters } from 'vuex'
@@ -134,7 +135,8 @@ export default {
ProgressButton,
FollowButton,
Select,
- RichContent
+ RichContent,
+ UserLink
},
methods: {
muteUser () {
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
@@ -106,13 +106,10 @@
</button>
</div>
<div class="bottom-line">
- <router-link
+ <user-link
class="user-screen-name"
- :title="user.screen_name_ui"
- :to="userProfileLink(user)"
- >
- @{{ user.screen_name_ui }}
- </router-link>
+ :user="user"
+ />
<template v-if="!hideBio">
<span
v-if="user.deactivated"
diff --git a/src/components/user_link/user_link.vue b/src/components/user_link/user_link.vue
@@ -0,0 +1,38 @@
+<template>
+ <router-link
+ :title="user.screen_name_ui"
+ :to="userProfileLink(user)"
+ >
+ {{ at ? '@' : '' }}{{ user.screen_name_ui }}<UnicodeDomainIndicator
+ :user="user"
+ />
+ </router-link>
+</template>
+
+<script>
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
+import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+
+const UserLink = {
+ props: {
+ user: Object,
+ at: {
+ type: Boolean,
+ default: true
+ }
+ },
+ components: {
+ UnicodeDomainIndicator
+ },
+ methods: {
+ userProfileLink (user) {
+ return generateProfileLink(
+ user.id, user.screen_name,
+ this.$store.state.instance.restrictedNicknames
+ )
+ }
+ }
+}
+
+export default UserLink
+</script>
diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js
@@ -1,5 +1,6 @@
import { defineAsyncComponent } from 'vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
@@ -15,6 +16,7 @@ const UserListPopover = {
],
components: {
RichContent,
+ UnicodeDomainIndicator,
Popover: defineAsyncComponent(() => import('../popover/popover.vue')),
UserAvatar: defineAsyncComponent(() => import('../user_avatar/user_avatar.vue'))
},
diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue
@@ -29,7 +29,7 @@
:emoji="user.emoji"
/>
<!-- eslint-enable vue/no-v-html -->
- <span class="user-list-screen-name">{{ user.screen_name_ui }}</span>
+ <span class="user-list-screen-name">{{ user.screen_name_ui }}</span><UnicodeDomainIndicator :user="user" />
</div>
</div>
</template>
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -2,13 +2,15 @@ import Status from '../status/status.vue'
import List from '../list/list.vue'
import Checkbox from '../checkbox/checkbox.vue'
import Modal from '../modal/modal.vue'
+import UserLink from '../user_link/user_link.vue'
const UserReportingModal = {
components: {
Status,
List,
Checkbox,
- Modal
+ Modal,
+ UserLink
},
data () {
return {
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -5,9 +5,13 @@
>
<div class="user-reporting-panel panel">
<div class="panel-heading">
- <div class="title">
- {{ $t('user_reporting.title', [user.screen_name_ui]) }}
- </div>
+ <i18n-t
+ tag="div"
+ keypath="user_reporting.title"
+ class="title"
+ >
+ <UserLink :user="user" />
+ </i18n-t>
</div>
<div class="panel-body">
<div class="user-reporting-panel-left">
diff --git a/src/i18n/en.json b/src/i18n/en.json
@@ -1006,5 +1006,8 @@
"update_changelog": "For more details on what's changed, see {theFullChangelog}.",
"update_changelog_here": "the full changelog",
"art_by": "Art by {linkToArtist}"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "This domain contains non-ascii characters."
}
}
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -214,12 +214,14 @@ export const parseUser = (data) => {
output.screen_name_ui = output.screen_name
if (output.screen_name && output.screen_name.includes('@')) {
const parts = output.screen_name.split('@')
- let unicodeDomain = punycode.toUnicode(parts[1])
+ const unicodeDomain = punycode.toUnicode(parts[1])
if (unicodeDomain !== parts[1]) {
// Add some identifier so users can potentially spot spoofing attempts:
// lain.com and xn--lin-6cd.com would appear identical otherwise.
- unicodeDomain = '🌏' + unicodeDomain
+ output.screen_name_ui_contains_non_ascii = true
output.screen_name_ui = [parts[0], unicodeDomain].join('@')
+ } else {
+ output.screen_name_ui_contains_non_ascii = false
}
}
diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -269,7 +269,8 @@ describe('API Entities normalizer', () => {
it('converts IDN to unicode and marks it as internatonal', () => {
const user = makeMockUserMasto({ acct: 'lain@xn--lin-6cd.com' })
- expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@🌏lаin.com')
+ expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@lаin.com')
+ expect(parseUser(user)).to.have.property('screen_name_ui_contains_non_ascii').that.equal(true)
})
})