diff options
Diffstat (limited to 'src/components/mention_link')
| -rw-r--r-- | src/components/mention_link/mention_link.js | 154 | ||||
| -rw-r--r-- | src/components/mention_link/mention_link.scss | 116 | ||||
| -rw-r--r-- | src/components/mention_link/mention_link.vue | 75 |
3 files changed, 345 insertions, 0 deletions
diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js new file mode 100644 index 00000000..6515bd11 --- /dev/null +++ b/src/components/mention_link/mention_link.js @@ -0,0 +1,154 @@ +import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' +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 { + faAt +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faAt +) + +const MentionLink = { + name: 'MentionLink', + components: { + UserAvatar, + UnicodeDomainIndicator, + UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue')) + }, + props: { + url: { + required: true, + type: String + }, + content: { + required: true, + type: String + }, + userId: { + required: false, + type: String + }, + userScreenName: { + required: false, + type: String + } + }, + data () { + return { + hasSelection: false + } + }, + methods: { + onClick () { + if (this.shouldShowTooltip) return + const link = generateProfileLink( + this.userId || this.user.id, + this.userScreenName || this.user.screen_name + ) + this.$router.push(link) + }, + handleSelection () { + this.hasSelection = document.getSelection().containsNode(this.$refs.full, true) + } + }, + mounted () { + document.addEventListener('selectionchange', this.handleSelection) + }, + unmounted () { + document.removeEventListener('selectionchange', this.handleSelection) + }, + computed: { + user () { + return this.url && this.$store && this.$store.getters.findUserByUrl(this.url) + }, + isYou () { + // FIXME why user !== currentUser??? + return this.user && this.user.id === this.currentUser.id + }, + userName () { + return this.user && this.userNameFullUi.split('@')[0] + }, + serverName () { + // XXX assumed that domain does not contain @ + return this.user && (this.userNameFullUi.split('@')[1] || this.$store.getters.instanceDomain) + }, + userNameFull () { + return this.user && this.user.screen_name + }, + userNameFullUi () { + return this.user && this.user.screen_name_ui + }, + highlight () { + return this.user && this.mergedConfig.highlight[this.user.screen_name] + }, + highlightType () { + return this.highlight && ('-' + this.highlight.type) + }, + highlightClass () { + if (this.highlight) return highlightClass(this.user) + }, + style () { + if (this.highlight) { + const { + backgroundColor, + backgroundPosition, + backgroundImage, + ...rest + } = highlightStyle(this.highlight) + return rest + } + }, + classnames () { + return [ + { + '-you': this.isYou && this.shouldBoldenYou, + '-highlighted': this.highlight, + '-has-selection': this.hasSelection + }, + this.highlightType + ] + }, + useAtIcon () { + return this.mergedConfig.useAtIcon + }, + isRemote () { + return this.userName !== this.userNameFull + }, + shouldShowFullUserName () { + const conf = this.mergedConfig.mentionLinkDisplay + if (conf === 'short') { + return false + } else if (conf === 'full') { + return true + } else { // full_for_remote + return this.isRemote + } + }, + shouldShowTooltip () { + return this.mergedConfig.mentionLinkShowTooltip + }, + shouldShowAvatar () { + return this.mergedConfig.mentionLinkShowAvatar + }, + shouldShowYous () { + return this.mergedConfig.mentionLinkShowYous + }, + shouldBoldenYou () { + return this.mergedConfig.mentionLinkBoldenYou + }, + shouldFadeDomain () { + return this.mergedConfig.mentionLinkFadeDomain + }, + ...mapGetters(['mergedConfig']), + ...mapState({ + currentUser: state => state.users.currentUser + }) + } +} + +export default MentionLink diff --git a/src/components/mention_link/mention_link.scss b/src/components/mention_link/mention_link.scss new file mode 100644 index 00000000..8b2af926 --- /dev/null +++ b/src/components/mention_link/mention_link.scss @@ -0,0 +1,116 @@ +@import '../../_variables.scss'; + +.MentionLink { + position: relative; + white-space: normal; + display: inline; + color: var(--link); + word-break: normal; + + & .new, + & .original { + display: inline; + border-radius: 2px; + } + + .mention-avatar { + border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); + width: 1.5em; + height: 1.5em; + vertical-align: middle; + user-select: none; + margin-right: 0.2em; + } + + .full { + position: absolute; + display: inline-block; + pointer-events: none; + opacity: 0; + top: 100%; + left: 0; + height: 100%; + word-wrap: normal; + white-space: nowrap; + transition: opacity 0.2s ease; + z-index: 1; + margin-top: 0.25em; + padding: 0.5em; + user-select: all; + } + + & .short.-with-tooltip, + & .you { + user-select: none; + } + + & .short, + & .full { + white-space: nowrap; + } + + .shortName { + white-space: normal; + } + + .new { + &.-you { + .shortName { + font-weight: 600; + } + } + &.-has-selection { + color: var(--alertNeutralText, $fallback--text); + background-color: var(--alertNeutral, $fallback--fg); + } + + .at { + color: var(--link); + opacity: 0.8; + display: inline-block; + line-height: 1; + padding: 0 0.1em; + vertical-align: -25%; + margin: 0; + } + + &.-striped { + & .shortName { + background-image: + repeating-linear-gradient( + 135deg, + var(--____highlight-tintColor), + var(--____highlight-tintColor) 5px, + var(--____highlight-tintColor2) 5px, + var(--____highlight-tintColor2) 10px + ); + } + } + + &.-solid { + .shortName { + background-image: linear-gradient(var(--____highlight-tintColor2), var(--____highlight-tintColor2)); + } + } + + &.-side { + .shortName { + box-shadow: 0 -5px 3px -4px inset var(--____highlight-solidColor); + } + } + } + + .full { + pointer-events: none; + } + + .serverName.-faded { + color: var(--faintLink, $fallback--link); + } +} + +.mention-link-popover { + max-width: 70ch; + max-height: 20rem; + overflow: hidden; +} diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue new file mode 100644 index 00000000..869a3257 --- /dev/null +++ b/src/components/mention_link/mention_link.vue @@ -0,0 +1,75 @@ +<template> + <span + class="MentionLink" + > + <!-- eslint-disable vue/no-v-html --> + <a + v-if="!user" + :href="url" + class="original" + target="_blank" + v-html="content" + /><!-- eslint-enable vue/no-v-html --> + <UserPopover + v-else + :user-id="user.id" + :disabled="!shouldShowTooltip" + > + <span + v-if="user" + class="new" + :style="style" + :class="classnames" + > + <a + class="short button-unstyled" + :class="{ '-with-tooltip': shouldShowTooltip }" + :href="url" + @click.prevent="onClick" + > + <!-- eslint-disable vue/no-v-html --> + <UserAvatar + v-if="shouldShowAvatar" + class="mention-avatar" + :user="user" + /><span + class="shortName" + ><FAIcon + v-if="useAtIcon" + size="sm" + icon="at" + class="at" + />{{ !useAtIcon ? '@' : '' }}<span + class="userName" + v-html="userName" + /><span + v-if="shouldShowFullUserName" + class="serverName" + :class="{ '-faded': shouldFadeDomain }" + v-html="'@' + serverName" + /><UnicodeDomainIndicator + v-if="shouldShowFullUserName" + :user="user" + /> + </span> + <span + v-if="isYou && shouldShowYous" + :class="{ '-you': shouldBoldenYou }" + > {{ ' ' + $t('status.you') }}</span> + <!-- eslint-enable vue/no-v-html --> + </a><span + ref="full" + class="full" + > + <!-- eslint-disable vue/no-v-html --> + @<span v-html="userName" /><span v-html="'@' + serverName" /> + <!-- eslint-enable vue/no-v-html --> + </span> + </span> + </UserPopover> + </span> +</template> + +<script src="./mention_link.js" /> + +<style lang="scss" src="./mention_link.scss" /> |
