aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/chat_panel/chat_panel.vue4
-rw-r--r--src/components/emoji_reactions/emoji_reactions.js4
-rw-r--r--src/components/emoji_reactions/emoji_reactions.vue65
-rw-r--r--src/components/poll/poll_form.js1
-rw-r--r--src/components/post_status_form/post_status_form.js95
-rw-r--r--src/components/react_button/react_button.js5
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.vue34
-rw-r--r--src/components/status/status.js30
-rw-r--r--src/components/status/status.vue59
-rw-r--r--src/components/user_card/user_card.vue1
-rw-r--r--src/components/user_list_popover/user_list_popover.js18
-rw-r--r--src/components/user_list_popover/user_list_popover.vue71
12 files changed, 230 insertions, 157 deletions
diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue
index 12968cfb..ca529b5a 100644
--- a/src/components/chat_panel/chat_panel.vue
+++ b/src/components/chat_panel/chat_panel.vue
@@ -10,7 +10,7 @@
@click.stop.prevent="togglePanel"
>
<div class="title">
- <span>{{ $t('chat.title') }}</span>
+ <span>{{ $t('shoutbox.title') }}</span>
<i
v-if="floating"
class="icon-cancel"
@@ -64,7 +64,7 @@
>
<div class="title">
<i class="icon-comment-empty" />
- {{ $t('chat.title') }}
+ {{ $t('shoutbox.title') }}
</div>
</div>
</div>
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index ae7f53be..bb11b840 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -1,5 +1,5 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
-import Popover from '../popover/popover.vue'
+import UserListPopover from '../user_list_popover/user_list_popover.vue'
const EMOJI_REACTION_COUNT_CUTOFF = 12
@@ -7,7 +7,7 @@ const EmojiReactions = {
name: 'EmojiReactions',
components: {
UserAvatar,
- Popover
+ UserListPopover
},
props: ['status'],
data: () => ({
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index bac4c605..2f14b5b2 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,44 +1,11 @@
<template>
<div class="emoji-reactions">
- <Popover
+ <UserListPopover
v-for="(reaction) in emojiReactions"
:key="reaction.name"
- trigger="hover"
- placement="top"
- :offset="{ y: 5 }"
+ :users="accountsForEmoji[reaction.name]"
>
- <div
- slot="content"
- class="reacted-users"
- >
- <div v-if="accountsForEmoji[reaction.name].length">
- <div
- v-for="(account) in accountsForEmoji[reaction.name]"
- :key="account.id"
- class="reacted-user"
- >
- <UserAvatar
- :user="account"
- class="avatar-small"
- :compact="true"
- />
- <div class="reacted-user-names">
- <!-- eslint-disable vue/no-v-html -->
- <span
- class="reacted-user-name"
- v-html="account.name_html"
- />
- <!-- eslint-enable vue/no-v-html -->
- <span class="reacted-user-screen-name">{{ account.screen_name }}</span>
- </div>
- </div>
- </div>
- <div v-else>
- <i class="icon-spin4 animate-spin" />
- </div>
- </div>
<button
- slot="trigger"
class="emoji-reaction btn btn-default"
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
@click="emojiOnClick(reaction.name, $event)"
@@ -47,7 +14,7 @@
<span class="reaction-emoji">{{ reaction.name }}</span>
<span>{{ reaction.count }}</span>
</button>
- </Popover>
+ </UserListPopover>
<a
v-if="tooManyReactions"
class="emoji-reaction-expand faint"
@@ -69,32 +36,6 @@
flex-wrap: wrap;
}
-.reacted-users {
- padding: 0.5em;
-}
-
-.reacted-user {
- padding: 0.25em;
- display: flex;
- flex-direction: row;
-
- .reacted-user-names {
- display: flex;
- flex-direction: column;
- margin-left: 0.5em;
- min-width: 5em;
-
- img {
- width: 1em;
- height: 1em;
- }
- }
-
- .reacted-user-screen-name {
- font-size: 9px;
- }
-}
-
.emoji-reaction {
padding: 0 0.5em;
margin-right: 0.5em;
diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js
index c0c1ccf7..df93f038 100644
--- a/src/components/poll/poll_form.js
+++ b/src/components/poll/poll_form.js
@@ -75,6 +75,7 @@ export default {
deleteOption (index, event) {
if (this.options.length > 2) {
this.options.splice(index, 1)
+ this.updatePollToParent()
}
},
convertExpiryToUnit (unit, amount) {
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 1c0accac..e7094bec 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -27,6 +27,11 @@ const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
return mentions.length > 0 ? mentions.join(' ') + ' ' : ''
}
+// Converts a string with px to a number like '2px' -> 2
+const pxStringToNumber = (str) => {
+ return Number(str.substring(0, str.length - 2))
+}
+
const PostStatusForm = {
props: [
'replyTo',
@@ -61,6 +66,7 @@ const PostStatusForm = {
StatusContent
},
mounted () {
+ this.updateIdempotencyKey()
this.resize(this.$refs.textarea)
if (this.replyTo) {
@@ -111,7 +117,8 @@ const PostStatusForm = {
dropStopTimeout: null,
preview: null,
previewLoading: false,
- emojiInputShown: false
+ emojiInputShown: false,
+ idempotencyKey: ''
}
},
computed: {
@@ -206,14 +213,43 @@ const PostStatusForm = {
})
},
watch: {
- 'newStatus.contentType': function () {
- this.autoPreview()
- },
- 'newStatus.spoilerText': function () {
- this.autoPreview()
+ 'newStatus': {
+ deep: true,
+ handler () {
+ this.statusChanged()
+ }
}
},
methods: {
+ statusChanged () {
+ this.autoPreview()
+ this.updateIdempotencyKey()
+ },
+ clearStatus () {
+ const newStatus = this.newStatus
+ this.newStatus = {
+ status: '',
+ spoilerText: '',
+ files: [],
+ visibility: newStatus.visibility,
+ contentType: newStatus.contentType,
+ poll: {},
+ mediaDescriptions: {}
+ }
+ this.pollFormVisible = false
+ this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
+ this.clearPollForm()
+ if (this.preserveFocus) {
+ this.$nextTick(() => {
+ this.$refs.textarea.focus()
+ })
+ }
+ let el = this.$el.querySelector('textarea')
+ el.style.height = 'auto'
+ el.style.height = undefined
+ this.error = null
+ if (this.preview) this.previewStatus()
+ },
async postStatus (event, newStatus, opts = {}) {
if (this.posting) { return }
if (this.disableSubmit) { return }
@@ -253,36 +289,16 @@ const PostStatusForm = {
store: this.$store,
inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType,
- poll
+ poll,
+ idempotencyKey: this.idempotencyKey
}
const postHandler = this.postHandler ? this.postHandler : statusPoster.postStatus
postHandler(postingOptions).then((data) => {
if (!data.error) {
- this.newStatus = {
- status: '',
- spoilerText: '',
- files: [],
- visibility: newStatus.visibility,
- contentType: newStatus.contentType,
- poll: {},
- mediaDescriptions: {}
- }
- this.pollFormVisible = false
- this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
- this.clearPollForm()
+ this.clearStatus()
this.$emit('posted', data)
- if (this.preserveFocus) {
- this.$nextTick(() => {
- this.$refs.textarea.focus()
- })
- }
- let el = this.$el.querySelector('textarea')
- el.style.height = 'auto'
- el.style.height = undefined
- this.error = null
- if (this.preview) this.previewStatus()
} else {
this.error = data.error
}
@@ -399,7 +415,6 @@ const PostStatusForm = {
}
},
onEmojiInputInput (e) {
- this.autoPreview()
this.$nextTick(() => {
this.resize(this.$refs['textarea'])
})
@@ -423,7 +438,7 @@ const PostStatusForm = {
* scroll is different for `Window` and `Element`s
*/
const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom']
- const bottomBottomPadding = Number(bottomBottomPaddingStr.substring(0, bottomBottomPaddingStr.length - 2))
+ const bottomBottomPadding = pxStringToNumber(bottomBottomPaddingStr)
const scrollerRef = this.$el.closest('.sidebar-scroller') ||
this.$el.closest('.post-form-modal-view') ||
@@ -432,10 +447,12 @@ const PostStatusForm = {
// Getting info about padding we have to account for, removing 'px' part
const topPaddingStr = window.getComputedStyle(target)['padding-top']
const bottomPaddingStr = window.getComputedStyle(target)['padding-bottom']
- const topPadding = Number(topPaddingStr.substring(0, topPaddingStr.length - 2))
- const bottomPadding = Number(bottomPaddingStr.substring(0, bottomPaddingStr.length - 2))
+ const topPadding = pxStringToNumber(topPaddingStr)
+ const bottomPadding = pxStringToNumber(bottomPaddingStr)
const vertPadding = topPadding + bottomPadding
+ const oldHeight = pxStringToNumber(target.style.height)
+
/* Explanation:
*
* https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
@@ -464,8 +481,13 @@ const PostStatusForm = {
// BEGIN content size update
target.style.height = 'auto'
- const heightWithoutPadding = target.scrollHeight - vertPadding
- const newHeight = this.maxHeight ? Math.min(heightWithoutPadding, this.maxHeight) : heightWithoutPadding
+ const heightWithoutPadding = Math.floor(target.scrollHeight - vertPadding)
+ let newHeight = this.maxHeight ? Math.min(heightWithoutPadding, this.maxHeight) : heightWithoutPadding
+ // This is a bit of a hack to combat target.scrollHeight being different on every other input
+ // on some browsers for whatever reason. Don't change the height if difference is 1px or less.
+ if (Math.abs(newHeight - oldHeight) <= 1) {
+ newHeight = oldHeight
+ }
target.style.height = `${newHeight}px`
this.$emit('resize', newHeight)
// END content size update
@@ -530,6 +552,9 @@ const PostStatusForm = {
},
handleEmojiInputShow (value) {
this.emojiInputShown = value
+ },
+ updateIdempotencyKey () {
+ this.idempotencyKey = Date.now().toString()
}
}
}
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index f0931446..abcf0455 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -28,7 +28,10 @@ const ReactButton = {
},
emojis () {
if (this.filterWord !== '') {
- return this.$store.state.instance.emoji.filter(emoji => emoji.displayText.includes(this.filterWord))
+ const filterWordLowercase = this.filterWord.toLowerCase()
+ return this.$store.state.instance.emoji.filter(emoji =>
+ emoji.displayText.toLowerCase().includes(filterWordLowercase)
+ )
}
return this.$store.state.instance.emoji || []
},
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
index b7a3cb37..86eed3f5 100644
--- a/src/components/settings_modal/tabs/notifications_tab.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -2,38 +2,18 @@
<div :label="$t('settings.notifications')">
<div class="setting-item">
<h2>{{ $t('settings.notification_setting_filters') }}</h2>
- <div class="select-multiple">
- <span class="label">{{ $t('settings.notification_setting') }}</span>
- <ul class="option-list">
- <li>
- <Checkbox v-model="notificationSettings.follows">
- {{ $t('settings.notification_setting_follows') }}
- </Checkbox>
- </li>
- <li>
- <Checkbox v-model="notificationSettings.followers">
- {{ $t('settings.notification_setting_followers') }}
- </Checkbox>
- </li>
- <li>
- <Checkbox v-model="notificationSettings.non_follows">
- {{ $t('settings.notification_setting_non_follows') }}
- </Checkbox>
- </li>
- <li>
- <Checkbox v-model="notificationSettings.non_followers">
- {{ $t('settings.notification_setting_non_followers') }}
- </Checkbox>
- </li>
- </ul>
- </div>
+ <p>
+ <Checkbox v-model="notificationSettings.block_from_strangers">
+ {{ $t('settings.notification_setting_block_from_strangers') }}
+ </Checkbox>
+ </p>
</div>
<div class="setting-item">
<h2>{{ $t('settings.notification_setting_privacy') }}</h2>
<p>
- <Checkbox v-model="notificationSettings.privacy_option">
- {{ $t('settings.notification_setting_privacy_option') }}
+ <Checkbox v-model="notificationSettings.hide_notification_contents">
+ {{ $t('settings.notification_setting_hide_notification_contents') }}
</Checkbox>
</p>
</div>
diff --git a/src/components/status/status.js b/src/components/status/status.js
index ad0b72a9..d263da68 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -9,6 +9,7 @@ import AvatarList from '../avatar_list/avatar_list.vue'
import Timeago from '../timeago/timeago.vue'
import StatusContent from '../status_content/status_content.vue'
import StatusPopover from '../status_popover/status_popover.vue'
+import UserListPopover from '../user_list_popover/user_list_popover.vue'
import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
@@ -18,6 +19,21 @@ import { mapGetters, mapState } from 'vuex'
const Status = {
name: 'Status',
+ components: {
+ FavoriteButton,
+ ReactButton,
+ RetweetButton,
+ ExtraButtons,
+ PostStatusForm,
+ UserCard,
+ UserAvatar,
+ AvatarList,
+ Timeago,
+ StatusPopover,
+ UserListPopover,
+ EmojiReactions,
+ StatusContent
+ },
props: [
'statusoid',
'expandable',
@@ -197,20 +213,6 @@ const Status = {
currentUser: state => state.users.currentUser
})
},
- components: {
- FavoriteButton,
- ReactButton,
- RetweetButton,
- ExtraButtons,
- PostStatusForm,
- UserCard,
- UserAvatar,
- AvatarList,
- Timeago,
- StatusPopover,
- EmojiReactions,
- StatusContent
- },
methods: {
visibilityIcon (visibility) {
switch (visibility) {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index f6b5dd6f..e1e56ec9 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -72,7 +72,10 @@
:user="statusoid.user"
/>
<div class="media-body faint">
- <span class="user-name">
+ <span
+ class="user-name"
+ :title="retweeter"
+ >
<router-link
v-if="retweeterHtml"
:to="retweeterProfileLink"
@@ -129,20 +132,28 @@
<h4
v-if="status.user.name_html"
class="user-name"
+ :title="status.user.name"
v-html="status.user.name_html"
/>
<h4
v-else
class="user-name"
+ :title="status.user.name"
>
{{ status.user.name }}
</h4>
<router-link
class="account-name"
+ :title="status.user.screen_name"
:to="userProfileLink"
>
{{ status.user.screen_name }}
</router-link>
+ <img
+ class="status-favicon"
+ v-if="!!(status.user && status.user.favicon)"
+ :src="status.user.favicon"
+ >
</div>
<span class="heading-right">
@@ -222,7 +233,10 @@
>
<span class="reply-to-text">{{ $t('status.reply_to') }}</span>
</span>
- <router-link :to="replyProfileLink">
+ <router-link
+ :title="replyToName"
+ :to="replyProfileLink"
+ >
{{ replyToName }}
</router-link>
<span
@@ -265,24 +279,30 @@
class="favs-repeated-users"
>
<div class="stats">
- <div
+ <UserListPopover
v-if="statusFromGlobalRepository.rebloggedBy && statusFromGlobalRepository.rebloggedBy.length > 0"
- class="stat-count"
+ :users="statusFromGlobalRepository.rebloggedBy"
>
- <a class="stat-title">{{ $t('status.repeats') }}</a>
- <div class="stat-number">
- {{ statusFromGlobalRepository.rebloggedBy.length }}
+ <div class="stat-count">
+ <a class="stat-title">{{ $t('status.repeats') }}</a>
+ <div class="stat-number">
+ {{ statusFromGlobalRepository.rebloggedBy.length }}
+ </div>
</div>
- </div>
- <div
+ </UserListPopover>
+ <UserListPopover
v-if="statusFromGlobalRepository.favoritedBy && statusFromGlobalRepository.favoritedBy.length > 0"
- class="stat-count"
+ :users="statusFromGlobalRepository.favoritedBy"
>
- <a class="stat-title">{{ $t('status.favorites') }}</a>
- <div class="stat-number">
- {{ statusFromGlobalRepository.favoritedBy.length }}
+ <div
+ class="stat-count"
+ >
+ <a class="stat-title">{{ $t('status.favorites') }}</a>
+ <div class="stat-number">
+ {{ statusFromGlobalRepository.favoritedBy.length }}
+ </div>
</div>
- </div>
+ </UserListPopover>
<div class="avatar-row">
<AvatarList :users="combinedFavsAndRepeatsUsers" />
</div>
@@ -428,6 +448,12 @@ $status-margin: 0.75em;
}
}
+ .status-favicon {
+ height: 18px;
+ width: 18px;
+ margin-right: 0.4em;
+ }
+
.media-heading {
padding: 0;
vertical-align: bottom;
@@ -722,6 +748,11 @@ $status-margin: 0.75em;
.stat-count {
margin-right: $status-margin;
+ user-select: none;
+
+ &:hover .stat-title {
+ text-decoration: underline;
+ }
.stat-title {
color: var(--faint, $fallback--faint);
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 9529d7f6..75db5db1 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -66,6 +66,7 @@
<div class="bottom-line">
<router-link
class="user-screen-name"
+ :title="user.screen_name"
:to="userProfileLink(user)"
>
@{{ user.screen_name }}
diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js
new file mode 100644
index 00000000..b60f2c4c
--- /dev/null
+++ b/src/components/user_list_popover/user_list_popover.js
@@ -0,0 +1,18 @@
+
+const UserListPopover = {
+ name: 'UserListPopover',
+ props: [
+ 'users'
+ ],
+ components: {
+ Popover: () => import('../popover/popover.vue'),
+ UserAvatar: () => import('../user_avatar/user_avatar.vue')
+ },
+ computed: {
+ usersCapped () {
+ return this.users.slice(0, 16)
+ }
+ }
+}
+
+export default UserListPopover
diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue
new file mode 100644
index 00000000..185c73ca
--- /dev/null
+++ b/src/components/user_list_popover/user_list_popover.vue
@@ -0,0 +1,71 @@
+<template>
+ <Popover
+ trigger="hover"
+ placement="top"
+ :offset="{ y: 5 }"
+ >
+ <template slot="trigger">
+ <slot />
+ </template>
+ <div
+ slot="content"
+ class="user-list-popover"
+ >
+ <div v-if="users.length">
+ <div
+ v-for="(user) in usersCapped"
+ :key="user.id"
+ class="user-list-row"
+ >
+ <UserAvatar
+ :user="user"
+ class="avatar-small"
+ :compact="true"
+ />
+ <div class="user-list-names">
+ <!-- eslint-disable vue/no-v-html -->
+ <span v-html="user.name_html" />
+ <!-- eslint-enable vue/no-v-html -->
+ <span class="user-list-screen-name">{{ user.screen_name }}</span>
+ </div>
+ </div>
+ </div>
+ <div v-else>
+ <i class="icon-spin4 animate-spin" />
+ </div>
+ </div>
+ </Popover>
+</template>
+
+<script src="./user_list_popover.js" ></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.user-list-popover {
+ padding: 0.5em;
+
+ .user-list-row {
+ padding: 0.25em;
+ display: flex;
+ flex-direction: row;
+
+ .user-list-names {
+ display: flex;
+ flex-direction: column;
+ margin-left: 0.5em;
+ min-width: 5em;
+
+ img {
+ width: 1em;
+ height: 1em;
+ }
+ }
+
+ .user-list-screen-name {
+ font-size: 9px;
+ }
+ }
+}
+
+</style>