diff options
| -rw-r--r-- | src/components/desktop_nav/desktop_nav.vue | 1 | ||||
| -rw-r--r-- | src/components/follow_card/follow_card.vue | 1 | ||||
| -rw-r--r-- | src/components/mention_link/mention_link.js | 33 | ||||
| -rw-r--r-- | src/components/mention_link/mention_link.scss | 28 | ||||
| -rw-r--r-- | src/components/mention_link/mention_link.vue | 33 | ||||
| -rw-r--r-- | src/components/settings_modal/tabs/filtering_tab.vue | 2 | ||||
| -rw-r--r-- | src/components/settings_modal/tabs/general_tab.js | 5 | ||||
| -rw-r--r-- | src/components/settings_modal/tabs/general_tab.vue | 35 | ||||
| -rw-r--r-- | src/components/timeline/timeline.js | 18 | ||||
| -rw-r--r-- | src/components/timeline/timeline.vue | 2 | ||||
| -rw-r--r-- | src/i18n/en.json | 8 | ||||
| -rw-r--r-- | src/modules/config.js | 8 | ||||
| -rw-r--r-- | src/modules/instance.js | 8 | ||||
| -rw-r--r-- | test/unit/specs/components/timeline.spec.js | 27 |
14 files changed, 152 insertions, 57 deletions
diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue index 762aa610..304baf9d 100644 --- a/src/components/desktop_nav/desktop_nav.vue +++ b/src/components/desktop_nav/desktop_nav.vue @@ -52,6 +52,7 @@ href="/pleroma/admin/#/login-pleroma" class="nav-icon" target="_blank" + @click.stop > <FAIcon fixed-width diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue index b503783f..895a8fa3 100644 --- a/src/components/follow_card/follow_card.vue +++ b/src/components/follow_card/follow_card.vue @@ -20,6 +20,7 @@ :relationship="relationship" :label-following="$t('user_card.follow_unfollow')" class="follow-card-follow-button" + :user="user" /> </template> </div> diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 65c62baa..5209907d 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -1,6 +1,7 @@ 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 { library } from '@fortawesome/fontawesome-svg-core' import { faAt @@ -12,6 +13,9 @@ library.add( const MentionLink = { name: 'MentionLink', + components: { + UserAvatar + }, props: { url: { required: true, @@ -50,6 +54,10 @@ const MentionLink = { 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 }, @@ -85,6 +93,31 @@ const MentionLink = { 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 && this.mergedConfig.mentionLinkDisplay === 'short' && this.isRemote + }, + shouldShowAvatar () { + return this.mergedConfig.mentionLinkShowAvatar + }, + shouldFadeDomain () { + return this.mergedConfig.mentionLinkFadeDomain + }, ...mapGetters(['mergedConfig']), ...mapState({ currentUser: state => state.users.currentUser diff --git a/src/components/mention_link/mention_link.scss b/src/components/mention_link/mention_link.scss index ec2689f8..03306dcc 100644 --- a/src/components/mention_link/mention_link.scss +++ b/src/components/mention_link/mention_link.scss @@ -1,3 +1,5 @@ +@import '../../_variables.scss'; + .MentionLink { position: relative; white-space: normal; @@ -10,6 +12,15 @@ 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; @@ -27,7 +38,8 @@ user-select: all; } - .short { + .short.-with-tooltip, + .you { user-select: none; } @@ -56,7 +68,7 @@ } &.-striped { - & .userName, + & .shortName, & .full { background-image: repeating-linear-gradient( @@ -70,14 +82,14 @@ } &.-solid { - & .userName, + & .shortName, & .full { background-image: linear-gradient(var(--____highlight-tintColor2), var(--____highlight-tintColor2)); } } &.-side { - & .userName, + & .shortName, & .userNameFull { box-shadow: 0 -5px 3px -4px inset var(--____highlight-solidColor); } @@ -88,4 +100,12 @@ opacity: 1; pointer-events: initial; } + + .serverName.-faded { + color: var(--faintLink, $fallback--link); + } + + .full .-faded { + color: var(--faint, $fallback--faint); + } } diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue index a22b486c..ac20eb5a 100644 --- a/src/components/mention_link/mention_link.vue +++ b/src/components/mention_link/mention_link.vue @@ -19,17 +19,30 @@ > <a class="short button-unstyled" + :class="{ '-with-tooltip': shouldShowTooltip }" :href="url" @click.prevent="onClick" > <!-- eslint-disable vue/no-v-html --> - <FAIcon + <UserAvatar + v-if="shouldShowAvatar" + class="mention-avatar" + :user="user" + /><span + class="shortName" + ><FAIcon + v-if="useAtIcon" size="sm" icon="at" class="at" - /><span class="shortName"><span + />{{ !useAtIcon ? '@' : '' }}<span class="userName" v-html="userName" + /><span + v-if="shouldShowFullUserName" + class="serverName" + :class="{ '-faded': shouldFadeDomain }" + v-html="'@' + serverName" /></span> <span v-if="isYou" @@ -38,14 +51,24 @@ <!-- eslint-enable vue/no-v-html --> </a> <span - v-if="userName !== userNameFull" + v-if="shouldShowTooltip" class="full popover-default" :class="[highlightType]" > <span class="userNameFull" - v-text="'@' + userNameFull" - /> + > + <!-- eslint-disable vue/no-v-html --> + @<span + class="userName" + v-html="userName" + /><span + class="serverName" + :class="{ '-faded': shouldFadeDomain }" + v-html="'@' + serverName" + /> + <!-- eslint-enable vue/no-v-html --> + </span> </span> </span> </span> diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index 5d3c614a..50ee20e0 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -31,7 +31,7 @@ <BooleanSetting :disabled="hideFilteredStatuses" path="hideMutedPosts" - > + > {{ $t('settings.hide_muted_posts') }} </BooleanSetting> </li> diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index eeda61bf..952c328d 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -20,6 +20,11 @@ const GeneralTab = { value: mode, label: this.$t(`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`) })), + mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(mode => ({ + key: mode, + value: mode, + label: this.$t(`settings.mention_link_display_${mode}`) + })), loopSilentAvailable: // Firefox Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index bd3ee84e..44b1ac92 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -147,6 +147,41 @@ {{ $t('settings.greentext') }} </BooleanSetting> </li> + <li> + <ChoiceSetting + id="mentionLinkDisplay" + path="mentionLinkDisplay" + :options="mentionLinkDisplayOptions" + > + {{ $t('settings.mention_link_display') }} + </ChoiceSetting> + </li> + <ul + class="setting-list suboptions" + > + <li + v-if="mentionLinkDisplay === 'short'" + > + <BooleanSetting path="mentionLinkShowTooltip"> + {{ $t('settings.mention_link_show_tooltip') }} + </BooleanSetting> + </li> + <li> + <BooleanSetting path="useAtIcon"> + {{ $t('settings.use_at_icon') }} + </BooleanSetting> + </li> + <li> + <BooleanSetting path="mentionLinkShowAvatar"> + {{ $t('settings.mention_link_show_avatar') }} + </BooleanSetting> + </li> + <li> + <BooleanSetting path="mentionLinkFadeDomain"> + {{ $t('settings.mention_link_fade_domain') }} + </BooleanSetting> + </li> + </ul> </ul> </div> diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 44f749c3..04f0e7d6 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -12,19 +12,6 @@ library.add( faCog ) -export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => { - const ids = [] - if (pinnedStatusIds && pinnedStatusIds.length > 0) { - for (let status of statuses) { - if (!pinnedStatusIds.includes(status.id)) { - break - } - ids.push(status.id) - } - } - return ids -} - const Timeline = { props: [ 'timeline', @@ -77,11 +64,6 @@ const Timeline = { } }, // id map of statuses which need to be hidden in the main list due to pinning logic - excludedStatusIdsObject () { - const ids = getExcludedStatusIdsByPinning(this.timeline.visibleStatuses, this.pinnedStatusIds) - // Convert id array to object - return keyBy(ids) - }, pinnedStatusIdsObject () { return keyBy(this.pinnedStatusIds) }, diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 767428f0..ff16208d 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -37,7 +37,7 @@ </template> <template v-for="status in timeline.visibleStatuses"> <conversation - v-if="!excludedStatusIdsObject[status.id]" + v-if="timelineName !== 'user' || (status.id >= timeline.minId && status.id <= timeline.maxId)" :key="status.id" class="status-fadein" :status-id="status.id" diff --git a/src/i18n/en.json b/src/i18n/en.json index ffa15b49..84d1d314 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -485,6 +485,14 @@ "true": "yes" }, "virtual_scrolling": "Optimize timeline rendering", + "use_at_icon": "Display @ symbol as an icon instead of text", + "mention_link_display": "Display mention links", + "mention_link_display_short": "always as short names (e.g. @foo)", + "mention_link_display_full_for_remote": "as full names only for remote users (e.g. @foo@example.org)", + "mention_link_display_full": "always as full names (e.g. @foo@example.org)", + "mention_link_show_tooltip": "Show full user names as tooltip for remote users", + "mention_link_show_avatar": "Show user avatar beside the link", + "mention_link_fade_domain": "Fade domains (e.g. @example.org in @foo@example.org)", "fun": "Fun", "greentext": "Meme arrows", "notifications": "Notifications", diff --git a/src/modules/config.js b/src/modules/config.js index c79302b5..9f2d4ef3 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -11,7 +11,8 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0] */ export const multiChoiceProperties = [ 'postContentType', - 'subjectLineBehavior' + 'subjectLineBehavior', + 'mentionLinkDisplay' // short | full_for_remote | full ] export const defaultState = { @@ -70,6 +71,11 @@ export const defaultState = { useOneClickNsfw: false, useContainFit: false, greentext: undefined, // instance default + useAtIcon: undefined, // instance default + mentionLinkDisplay: undefined, // instance default + mentionLinkShowTooltip: undefined, // instance default + mentionLinkShowAvatar: undefined, // instance default + mentionLinkFadeDomain: undefined, // instance default hidePostStats: undefined, // instance default hideUserStats: undefined, // instance default virtualScrolling: undefined, // instance default diff --git a/src/modules/instance.js b/src/modules/instance.js index 60038f08..d686f258 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -20,6 +20,11 @@ const defaultState = { background: '/static/aurora_borealis.jpg', collapseMessageWithSubject: false, greentext: false, + useAtIcon: false, + mentionLinkDisplay: 'short', + mentionLinkShowTooltip: true, + mentionLinkShowAvatar: false, + mentionLinkFadeDomain: true, hideFilteredStatuses: false, // bad name: actually hides posts of muted USERS hideMutedPosts: false, @@ -100,6 +105,9 @@ const instance = { return instanceDefaultProperties .map(key => [key, state[key]]) .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) + }, + instanceDomain (state) { + return new URL(state.server).hostname } }, actions: { diff --git a/test/unit/specs/components/timeline.spec.js b/test/unit/specs/components/timeline.spec.js deleted file mode 100644 index 0c8674a8..00000000 --- a/test/unit/specs/components/timeline.spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import { getExcludedStatusIdsByPinning } from 'src/components/timeline/timeline.js' - -describe('Timeline', () => { - describe('getExcludedStatusIdsByPinning', () => { - const mockStatuses = (ids) => ids.map(id => ({ id })) - - it('should return only members of both pinnedStatusIds and ids of the given statuses', () => { - const statusIds = [1, 2, 3, 4] - const statuses = mockStatuses(statusIds) - const pinnedStatusIds = [1, 3, 5] - const result = getExcludedStatusIdsByPinning(statuses, pinnedStatusIds) - result.forEach(item => { - expect(item).to.be.oneOf(statusIds) - expect(item).to.be.oneOf(pinnedStatusIds) - }) - }) - - it('should return ids of pinned statuses not posted before any unpinned status', () => { - const pinnedStatusIdSet1 = ['PINNED1', 'PINNED2'] - const pinnedStatusIdSet2 = ['PINNED3', 'PINNED4'] - const pinnedStatusIds = [...pinnedStatusIdSet1, ...pinnedStatusIdSet2] - const statusIds = [...pinnedStatusIdSet1, 'UNPINNED1', ...pinnedStatusIdSet2] - const statuses = mockStatuses(statusIds) - expect(getExcludedStatusIdsByPinning(statuses, pinnedStatusIds)).to.eql(pinnedStatusIdSet1) - }) - }) -}) |
