Merge branch 'better-still-emoji' into shigusegubu

* better-still-emoji:
  change defaults
  bump limit to a saner one
  moved mentions into a separate component - MentionLine, added collapsing of mentions when there's too many of 'em
  fix empty spaces again
  configurable mentions placement
  moving mentions into separate row
  moved transparent button styles into button itself
This commit is contained in:
Henry Jameson 2021-06-08 16:34:12 +03:00
commit 59d8a3bd92
16 changed files with 289 additions and 56 deletions

View file

@ -88,6 +88,10 @@ a {
font-family: sans-serif; font-family: sans-serif;
font-family: var(--interfaceFont, sans-serif); font-family: var(--interfaceFont, sans-serif);
&.-sublime {
background: transparent;
}
i[class*=icon-], i[class*=icon-],
.svg-inline--fa { .svg-inline--fa {
color: $fallback--text; color: $fallback--text;

View file

@ -13,9 +13,10 @@ const MentionLink = {
required: true, required: true,
type: String type: String
}, },
origattrs: { firstMention: {
required: true, required: false,
type: Object type: Boolean,
default: false
} }
}, },
methods: { methods: {
@ -50,6 +51,12 @@ const MentionLink = {
highlightClass () { highlightClass () {
if (this.highlight) return highlightClass(this.user) if (this.highlight) return highlightClass(this.user)
}, },
oldPlace () {
return !this.mergedConfig.mentionsOwnLine
},
oldStyle () {
return !this.mergedConfig.mentionsNewStyle
},
style () { style () {
if (this.highlight) { if (this.highlight) {
const { const {
@ -61,6 +68,17 @@ const MentionLink = {
return rest return rest
} }
}, },
classnames () {
return [
{
'-you': this.isYou,
'-highlighted': this.highlight,
'-firstMention': this.firstMention,
'-oldStyle': this.oldStyle
},
this.highlightType
]
},
...mapGetters(['mergedConfig']), ...mapGetters(['mergedConfig']),
...mapState({ ...mapState({
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser

View file

@ -11,7 +11,7 @@
} }
.original { .original {
opacity: 0.5; margin-right: 0.25em;
} }
.full { .full {
@ -33,23 +33,31 @@
& .short, & .short,
& .full { & .full {
&::before { &::before {
color: var(--faint);
content: '@'; content: '@';
} }
} }
.new:not(.-highlighted) {
.short {
background: none;
}
}
.new { .new {
&, margin-right: 0.25em;
&.-highlighted {
&.-firstMention {
display: none;
}
&.-you {
& .shortName,
& .full {
font-weight: 600;
}
}
&:not(.-oldStyle) {
.short { .short {
line-height: 1.5; line-height: 1.5;
font-size: inherit;
&::before { &::before {
color: var(--faint);
display: inline-block; display: inline-block;
height: 50%; height: 50%;
line-height: 1; line-height: 1;
@ -111,15 +119,6 @@
} }
} }
.new {
&.-you {
& .shortName,
& .full {
font-weight: 600;
}
}
}
&:hover .new .full { &:hover .new .full {
opacity: 1; opacity: 1;
pointer-events: initial; pointer-events: initial;

View file

@ -1,5 +1,8 @@
<template> <template>
<span class="MentionLink"> <span
class="MentionLink"
:class="{ '-oldPlace': oldPlace }"
>
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<a <a
v-if="!user" v-if="!user"
@ -12,15 +15,22 @@
v-if="user" v-if="user"
class="new" class="new"
:style="style" :style="style"
:class="[{ '-you': isYou, '-highlighted': highlight }, highlightType]" :class="classnames"
> >
<button <button
class="short" class="short"
:class="highlight ? 'button-default' : 'button-default' " :class="[{ '-sublime': !highlight }, oldStyle ? 'button-unstyled' : 'button-default']"
@click.prevent="onClick" @click.prevent="onClick"
> >
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<span class="shortName"><span class="userName" v-html="userName" /></span><span class="you" v-if="isYou">{{ $t('status.you')}}</span> <span class="shortName"><span
class="userName"
v-html="userName"
/></span>
<span
v-if="isYou"
class="you"
>{{ $t('status.you') }}</span>
<!-- eslint-enable vue/no-v-html --> <!-- eslint-enable vue/no-v-html -->
</button> </button>
<span <span
@ -29,7 +39,10 @@
:class="[highlightType]" :class="[highlightType]"
> >
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<span class="userNameFull" v-html="userNameFull" /> <span
class="userNameFull"
v-html="userNameFull"
/>
<!-- eslint-enable vue/no-v-html --> <!-- eslint-enable vue/no-v-html -->
</span> </span>
</span> </span>

View file

@ -0,0 +1,51 @@
import MentionLink from 'src/components/mention_link/mention_link.vue'
import { mapGetters } from 'vuex'
const MentionsLine = {
name: 'MentionsLine',
props: {
attentions: {
required: true,
type: Object
}
},
data: () => ({ expanded: false }),
components: {
MentionLink
},
computed: {
oldStyle () {
return !this.mergedConfig.mentionsNewStyle
},
limit () {
return 6
},
mentions () {
return this.attentions.slice(0, this.limit)
},
extraMentions () {
return this.attentions.slice(this.limit)
},
manyMentions () {
return this.extraMentions.length > 0
},
buttonClasses () {
return [
this.oldStyle
? 'button-unstyled'
: 'button-default -sublime',
this.oldStyle
? '-oldStyle'
: '-newStyle'
]
},
...mapGetters(['mergedConfig'])
},
methods: {
toggleShowMore () {
this.expanded = !this.expanded
}
}
}
export default MentionsLine

View file

@ -0,0 +1,15 @@
.MentionsLine {
.showMoreLess {
&.-newStyle {
line-height: 1.5;
font-size: inherit;
display: inline-block;
padding-top: 0;
padding-bottom: 0;
}
&.-oldStyle {
color: var(--link);
}
}
}

View file

@ -0,0 +1,45 @@
<template>
<span class="MentionsLine">
<MentionLink
v-for="mention in mentions"
:key="mention.statusnet_profile_url"
class="mention-link"
:content="mention.statusnet_profile_url"
:url="mention.statusnet_profile_url"
:first-mention="false"
/><span
v-if="manyMentions"
class="extraMentions"
>
<span
v-if="expanded"
class="fullExtraMentions"
>
<MentionLink
v-for="mention in extraMentions"
:key="mention.statusnet_profile_url"
class="mention-link"
:content="mention.statusnet_profile_url"
:url="mention.statusnet_profile_url"
:first-mention="false"
/>
</span><button
v-if="!expanded"
class="showMoreLess"
:class="buttonClasses"
@click="toggleShowMore"
>
{{ $t('status.plus_more', { number: extraMentions.length }) }}
</button><button
v-if="expanded"
class="showMoreLess"
:class="buttonClasses"
@click="toggleShowMore"
>
{{ $t('general.show_less') }}
</button>
</span>
</span>
</template>
<script src="./mentions_line.js" ></script>
<style lang="scss" src="./mentions_line.scss" />

View file

@ -33,21 +33,31 @@ export default Vue.component('RichContent', {
class="img" class="img"
/> />
} }
const renderMention = (attrs, children) => { const renderMention = (attrs, children, encounteredText) => {
return <MentionLink return <MentionLink
url={attrs.href} url={attrs.href}
content={flattenDeep(children).join('')} content={flattenDeep(children).join('')}
origattrs={attrs} firstMention={!encounteredText}
/> />
} }
let encounteredText = false
// Processor to use with mini_html_converter // Processor to use with mini_html_converter
const processItem = (item) => { const processItem = (item) => {
// Handle text noes - just add emoji // Handle text noes - just add emoji
if (typeof item === 'string') { if (typeof item === 'string') {
const emptyText = item.trim() === ''
if (emptyText) {
return encounteredText ? item : item.trim()
}
let unescapedItem = unescape(item)
if (!encounteredText) {
unescapedItem = unescapedItem.trimStart()
encounteredText = true
}
if (item.includes(':')) { if (item.includes(':')) {
return processTextForEmoji( return processTextForEmoji(
unescape(item), unescapedItem,
this.emoji, this.emoji,
({ shortcode, url }) => { ({ shortcode, url }) => {
return <StillImage return <StillImage
@ -59,7 +69,7 @@ export default Vue.component('RichContent', {
} }
) )
} else { } else {
return unescape(item) return unescapedItem
} }
} }
// Handle tag nodes // Handle tag nodes
@ -73,7 +83,7 @@ export default Vue.component('RichContent', {
if (!this.handleLinks) break if (!this.handleLinks) break
const attrs = getAttrs(opener) const attrs = getAttrs(opener)
if (attrs['class'] && attrs['class'].includes('mention')) { if (attrs['class'] && attrs['class'].includes('mention')) {
return renderMention(attrs, children) return renderMention(attrs, children, encounteredText)
} }
} }
// Render tag as is // Render tag as is

View file

@ -26,6 +26,16 @@
{{ $t('settings.stop_gifs') }} {{ $t('settings.stop_gifs') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li>
<BooleanSetting path="mentionsOwnLine">
{{ $t('settings.mentions_new_place') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="mentionsNewStyle">
{{ $t('settings.mentions_new_style') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting path="streaming"> <BooleanSetting path="streaming">
{{ $t('settings.streaming') }} {{ $t('settings.streaming') }}

View file

@ -13,6 +13,8 @@ import RichContent from 'src/components/rich_content/rich_content.jsx'
import StatusPopover from '../status_popover/status_popover.vue' import StatusPopover from '../status_popover/status_popover.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue' import UserListPopover from '../user_list_popover/user_list_popover.vue'
import EmojiReactions from '../emoji_reactions/emoji_reactions.vue' import EmojiReactions from '../emoji_reactions/emoji_reactions.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' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { muteWordHits } from '../../services/status_parser/status_parser.js' import { muteWordHits } from '../../services/status_parser/status_parser.js'
@ -33,7 +35,8 @@ import {
faStar, faStar,
faEyeSlash, faEyeSlash,
faEye, faEye,
faThumbtack faThumbtack,
faAt,
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
library.add( library.add(
@ -50,7 +53,8 @@ library.add(
faEllipsisH, faEllipsisH,
faEyeSlash, faEyeSlash,
faEye, faEye,
faThumbtack faThumbtack,
faAt
) )
const Status = { const Status = {
@ -70,7 +74,9 @@ const Status = {
UserListPopover, UserListPopover,
EmojiReactions, EmojiReactions,
StatusContent, StatusContent,
RichContent RichContent,
MentionLink,
MentionsLine
}, },
props: [ props: [
'statusoid', 'statusoid',
@ -133,9 +139,7 @@ const Status = {
return this.generateUserProfileLink(this.status.user.id, this.status.user.screen_name) return this.generateUserProfileLink(this.status.user.id, this.status.user.screen_name)
}, },
replyProfileLink () { replyProfileLink () {
if (this.isReply) { return this.$store.getters.findUser(this.status.in_reply_to_user_id).statusnet_profile_url
return this.generateUserProfileLink(this.status.in_reply_to_user_id, this.replyToName)
}
}, },
retweet () { return !!this.statusoid.retweeted_status }, retweet () { return !!this.statusoid.retweeted_status },
retweeterUser () { return this.statusoid.user }, retweeterUser () { return this.statusoid.user },
@ -159,6 +163,18 @@ const Status = {
muteWordHits () { muteWordHits () {
return muteWordHits(this.status, this.muteWords) return muteWordHits(this.status, this.muteWords)
}, },
mentionsOwnLine () {
return this.mergedConfig.mentionsOwnLine
},
mentions () {
return this.statusoid.attentions.filter(attn => {
return attn.screen_name !== this.replyToName &&
attn.screen_name !== this.statusoid.user.screen_name
})
},
hasMentions () {
return this.mentions.length > 0
},
muted () { muted () {
if (this.statusoid.user.id === this.currentUser.id) return false if (this.statusoid.user.id === this.currentUser.id) return false
const reasonsToMute = this.userIsMuted || const reasonsToMute = this.userIsMuted ||

View file

@ -155,7 +155,8 @@ $status-margin: 0.75em;
margin-right: 0.2em; margin-right: 0.2em;
} }
.heading-reply-row { & .heading-mentions-row,
& .heading-reply-row {
position: relative; position: relative;
align-content: baseline; align-content: baseline;
font-size: 12px; font-size: 12px;

View file

@ -221,7 +221,6 @@
</button> </button>
</span> </span>
</div> </div>
<div class="heading-reply-row"> <div class="heading-reply-row">
<div <div
v-if="isReply" v-if="isReply"
@ -258,13 +257,13 @@
> >
<span class="reply-to-text">{{ $t('status.reply_to') }}</span> <span class="reply-to-text">{{ $t('status.reply_to') }}</span>
</span> </span>
<router-link
class="reply-to-link" <MentionLink
:title="replyToName" class="mention-link"
:to="replyProfileLink" :content="replyToName"
> :url="replyProfileLink"
{{ replyToName }} :first-mention="false"
</router-link> />
<span <span
v-if="replies && replies.length" v-if="replies && replies.length"
class="faint replies-separator" class="faint replies-separator"
@ -291,6 +290,35 @@
</StatusPopover> </StatusPopover>
</div> </div>
</div> </div>
<div
v-if="hasMentions && mentionsOwnLine"
class="heading-mentions-row"
>
<div
class="mentions"
>
<span
class="button-unstyled reply-to"
:aria-label="$t('tool_tip.reply')"
@click.prevent="gotoOriginal(status.in_reply_to_status_id)"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="at"
/>
<span
class="faint-link reply-to-text"
>
{{ $t('status.mentions') }}
</span>
</span>
<MentionsLine
:attentions="mentions"
class="mentions-line"
/>
</div>
</div>
</div> </div>
<StatusContent <StatusContent

View file

@ -1,5 +1,6 @@
import fileType from 'src/services/file_type/file_type.service' import fileType from 'src/services/file_type/file_type.service'
import RichContent from 'src/components/rich_content/rich_content.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js' import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
import { extractTagFromUrl } from 'src/services/matcher/matcher.service.js' import { extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
@ -104,10 +105,17 @@ const StatusContent = {
attachmentTypes () { attachmentTypes () {
return this.status.attachments.map(file => fileType.fileType(file.mimetype)) return this.status.attachments.map(file => fileType.fileType(file.mimetype))
}, },
mentionsOwnLine () {
return this.mergedConfig.mentionsOwnLine
},
mentions () {
return this.status.attentions
},
...mapGetters(['mergedConfig']) ...mapGetters(['mergedConfig'])
}, },
components: { components: {
RichContent RichContent,
MentionsLine
}, },
mounted () { mounted () {
this.status.attentions && this.status.attentions.forEach(attn => { this.status.attentions && this.status.attentions.forEach(attn => {

View file

@ -39,15 +39,24 @@
> >
{{ $t("general.show_more") }} {{ $t("general.show_more") }}
</button> </button>
<RichContent <span
v-if="!hideSubjectStatus && !(singleLine && status.summary_html)" v-if="!hideSubjectStatus && !(singleLine && status.summary_html)"
:class="{ '-single-line': singleLine }" >
class="text media-body" <MentionsLine
:html="postBodyHtml" v-if="!mentionsOwnLine"
:emoji="status.emojis" :attentions="status.attentions"
:handle-links="true" class="mentions-line"
@click.prevent="linkClicked" />
/> <RichContent
:class="{ '-single-line': singleLine }"
class="text media-body"
:html="postBodyHtml"
:emoji="status.emojis"
:handle-links="true"
@click.prevent="linkClicked"
/>
</span>
<button <button
v-if="hideSubjectStatus" v-if="hideSubjectStatus"
class="button-unstyled -link cw-status-hider" class="button-unstyled -link cw-status-hider"

View file

@ -260,6 +260,8 @@
"setting_changed": "Setting is different from default", "setting_changed": "Setting is different from default",
"enter_current_password_to_confirm": "Enter your current password to confirm your identity", "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
"post_look_feel": "Posts Look & Feel", "post_look_feel": "Posts Look & Feel",
"mentions_old_style": "Old style mentions",
"mentions_old_place": "Leave mentions inside post",
"mfa": { "mfa": {
"otp": "OTP", "otp": "OTP",
"setup_otp": "Setup OTP", "setup_otp": "Setup OTP",
@ -703,6 +705,7 @@
"unbookmark": "Unbookmark", "unbookmark": "Unbookmark",
"delete_confirm": "Do you really want to delete this status?", "delete_confirm": "Do you really want to delete this status?",
"reply_to": "Reply to", "reply_to": "Reply to",
"mentions": "Mentions",
"replies_list": "Replies:", "replies_list": "Replies:",
"mute_conversation": "Mute conversation", "mute_conversation": "Mute conversation",
"unmute_conversation": "Unmute conversation", "unmute_conversation": "Unmute conversation",
@ -718,7 +721,8 @@
"status_deleted": "This post was deleted", "status_deleted": "This post was deleted",
"nsfw": "NSFW", "nsfw": "NSFW",
"expand": "Expand", "expand": "Expand",
"you": "(You)" "you": "(You)",
"plus_more": "+{number} more"
}, },
"user_card": { "user_card": {
"approve": "Approve", "approve": "Approve",

View file

@ -56,6 +56,8 @@ export const defaultState = {
interfaceLanguage: browserLocale, interfaceLanguage: browserLocale,
hideScopeNotice: false, hideScopeNotice: false,
useStreamingApi: false, useStreamingApi: false,
mentionsOwnLine: false,
mentionsNewStyle: false,
sidebarRight: undefined, // instance default sidebarRight: undefined, // instance default
scopeCopy: undefined, // instance default scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default subjectLineBehavior: undefined, // instance default