aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/boot/after_store.js1
-rw-r--r--src/components/media_modal/media_modal.js2
-rw-r--r--src/components/notification/notification.js6
-rw-r--r--src/components/notification/notification.vue6
-rw-r--r--src/components/notifications/notifications.scss6
-rw-r--r--src/components/react_button/react_button.js2
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.js20
-rw-r--r--src/components/settings_modal/tabs/helpers/shared_computed_object.js10
-rw-r--r--src/components/status/status.js55
-rw-r--r--src/components/status/status.vue72
-rw-r--r--src/components/status_content/status_content.js4
-rw-r--r--src/components/still-image/still-image.vue11
-rw-r--r--src/i18n/en.json4
-rw-r--r--src/modules/statuses.js15
-rw-r--r--src/services/api/api.service.js4
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js2
-rw-r--r--src/services/status_parser/status_parser.js11
17 files changed, 177 insertions, 54 deletions
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index abdba305..0db03547 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -110,6 +110,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename')
+ copyInstanceOption('sidebarRight')
return store.dispatch('setTheme', config['theme'])
}
diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index abb18c7d..24764e80 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -84,10 +84,12 @@ const MediaModal = {
}
},
mounted () {
+ window.addEventListener('popstate', this.hide)
document.addEventListener('keyup', this.handleKeyupEvent)
document.addEventListener('keydown', this.handleKeydownEvent)
},
destroyed () {
+ window.removeEventListener('popstate', this.hide)
document.removeEventListener('keyup', this.handleKeyupEvent)
document.removeEventListener('keydown', this.handleKeydownEvent)
}
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 1cf4c9bc..5aa40e98 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -1,3 +1,4 @@
+import StatusContent from '../status_content/status_content.vue'
import Status from '../status/status.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue'
@@ -16,10 +17,11 @@ const Notification = {
},
props: [ 'notification' ],
components: {
- Status,
+ StatusContent,
UserAvatar,
UserCard,
- Timeago
+ Timeago,
+ Status
},
methods: {
toggleUserExpanded () {
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 0e46a2a7..044ac871 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -157,11 +157,9 @@
</router-link>
</div>
<template v-else>
- <status
+ <status-content
class="faint"
- :compact="true"
- :statusoid="notification.action"
- :no-heading="true"
+ :status="notification.action"
/>
</template>
</div>
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 9efcfcf8..b675af5a 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -36,6 +36,8 @@
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
+ word-wrap: break-word;
+ word-break: break-word;
&:hover .animated.avatar {
canvas {
@@ -46,10 +48,6 @@
}
}
- .muted {
- padding: .25em .6em;
- }
-
.non-mention {
display: flex;
flex: 1;
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index abc3bf07..f0931446 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -24,7 +24,7 @@ const ReactButton = {
},
computed: {
commonEmojis () {
- return ['โค๏ธ', '๐Ÿ˜ ', '๐Ÿ‘€', '๐Ÿ˜‚', '๐Ÿ”ฅ']
+ return ['๐Ÿ‘', '๐Ÿ˜ ', '๐Ÿ‘€', '๐Ÿ˜‚', '๐Ÿ”ฅ']
},
emojis () {
if (this.filterWord !== '') {
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
index ec330667..dd7ecaf7 100644
--- a/src/components/settings_modal/tabs/filtering_tab.js
+++ b/src/components/settings_modal/tabs/filtering_tab.js
@@ -1,13 +1,31 @@
+import { filter, trim } from 'lodash'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import SharedComputedObject from './helpers/shared_computed_object.js'
const FilteringTab = {
+ data () {
+ return {
+ muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n')
+ }
+ },
components: {
Checkbox
},
computed: {
- ...SharedComputedObject()
+ ...SharedComputedObject(),
+ muteWordsString: {
+ get () {
+ return this.muteWordsStringLocal
+ },
+ set (value) {
+ this.muteWordsStringLocal = value
+ this.$store.dispatch('setOption', {
+ name: 'muteWords',
+ value: filter(value.split('\n'), (word) => trim(word).length > 0)
+ })
+ }
+ }
},
// Updating nested properties
watch: {
diff --git a/src/components/settings_modal/tabs/helpers/shared_computed_object.js b/src/components/settings_modal/tabs/helpers/shared_computed_object.js
index b6a18e9c..86703697 100644
--- a/src/components/settings_modal/tabs/helpers/shared_computed_object.js
+++ b/src/components/settings_modal/tabs/helpers/shared_computed_object.js
@@ -1,4 +1,3 @@
-import { filter, trim } from 'lodash'
import {
instanceDefaultProperties,
multiChoiceProperties,
@@ -38,15 +37,6 @@ const SharedComputedObject = () => ({
}])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Special cases (need to transform values or perform actions first)
- muteWordsString: {
- get () { return this.$store.getters.mergedConfig.muteWords.join('\n') },
- set (value) {
- this.$store.dispatch('setOption', {
- name: 'muteWords',
- value: filter(value.split('\n'), (word) => trim(word).length > 0)
- })
- }
- },
useStreamingApi: {
get () { return this.$store.getters.mergedConfig.useStreamingApi },
set (value) {
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 9cd9d61c..73382521 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -12,7 +12,8 @@ import StatusPopover from '../status_popover/status_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'
-import { filter, unescape, uniqBy } from 'lodash'
+import { muteWordHits } from '../../services/status_parser/status_parser.js'
+import { unescape, uniqBy } from 'lodash'
import { mapGetters, mapState } from 'vuex'
const Status = {
@@ -44,6 +45,12 @@ const Status = {
muteWords () {
return this.mergedConfig.muteWords
},
+ showReasonMutedThread () {
+ return (
+ this.status.thread_muted ||
+ (this.status.reblog && this.status.reblog.thread_muted)
+ ) && !this.inConversation
+ },
repeaterClass () {
const user = this.statusoid.user
return highlightClass(user)
@@ -93,20 +100,42 @@ const Status = {
return !!this.currentUser
},
muteWordHits () {
- const statusText = this.status.text.toLowerCase()
- const statusSummary = this.status.summary.toLowerCase()
- const hits = filter(this.muteWords, (muteWord) => {
- return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase())
- })
-
- return hits
+ return muteWordHits(this.status, this.muteWords)
},
muted () {
- const relationship = this.$store.getters.relationship(this.status.user.id)
- return !this.unmuted && (
- (!(this.inProfile && this.status.user.id === this.profileUserId) && relationship.muting) ||
- (!this.inConversation && this.status.thread_muted) ||
- this.muteWordHits.length > 0)
+ const { status } = this
+ const { reblog } = status
+ const relationship = this.$store.getters.relationship(status.user.id)
+ const relationshipReblog = reblog && this.$store.getters.relationship(reblog.user.id)
+ const reasonsToMute = (
+ // Post is muted according to BE
+ status.muted ||
+ // Reprรถรถt of a muted post according to BE
+ (reblog && reblog.muted) ||
+ // Muted user
+ relationship.muting ||
+ // Muted user of a reprรถรถt
+ (relationshipReblog && relationshipReblog.muting) ||
+ // Thread is muted
+ status.thread_muted ||
+ // Wordfiltered
+ this.muteWordHits.length > 0
+ )
+ const excusesNotToMute = (
+ (
+ this.inProfile && (
+ // Don't mute user's posts on user timeline (except reblogs)
+ (!reblog && status.user.id === this.profileUserId) ||
+ // Same as above but also allow self-reblogs
+ (reblog && reblog.user.id === this.profileUserId)
+ )
+ ) ||
+ // Don't mute statuses in muted conversation when said conversation is opened
+ (this.inConversation && status.thread_muted)
+ // No excuses if post has muted words
+ ) && !this.muteWordHits.length > 0
+
+ return !this.unmuted && !excusesNotToMute && reasonsToMute
},
hideFilteredStatuses () {
return this.mergedConfig.hideFilteredStatuses
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index e4c7545b..336f912a 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -17,12 +17,33 @@
</div>
<template v-if="muted && !isPreview">
<div class="media status container muted">
- <small>
+ <small class="username">
+ <i
+ v-if="muted && retweet"
+ class="button-icon icon-retweet"
+ />
<router-link :to="userProfileLink">
{{ status.user.screen_name }}
</router-link>
</small>
- <small class="muteWords">{{ muteWordHits.join(', ') }}</small>
+ <small
+ v-if="showReasonMutedThread"
+ class="mute-thread"
+ >
+ {{ $t('status.thread_muted') }}
+ </small>
+ <small
+ v-if="showReasonMutedThread && muteWordHits.length > 0"
+ class="mute-thread"
+ >
+ {{ $t('status.thread_muted_and_words') }}
+ </small>
+ <small
+ class="mute-words"
+ :title="muteWordHits.join(', ')"
+ >
+ {{ muteWordHits.join(', ') }}
+ </small>
<a
href="#"
class="unmute"
@@ -637,19 +658,48 @@ $status-margin: 0.75em;
}
.muted {
- padding: 0.25em 0.5em;
- button {
- margin-left: auto;
+ padding: .25em .6em;
+ height: 1.2em;
+ line-height: 1.2em;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ display: flex;
+ flex-wrap: nowrap;
+
+ .username, .mute-thread, .mute-words {
+ word-wrap: normal;
+ word-break: normal;
+ white-space: nowrap;
}
- .muteWords {
- margin-left: 10px;
+ .username, .mute-words {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+
+ .username {
+ flex: 0 1 auto;
+ margin-right: .2em;
+ }
+
+ .mute-thread {
+ flex: 0 0 auto;
}
-}
-a.unmute {
- display: block;
- margin-left: auto;
+ .mute-words {
+ flex: 1 0 5em;
+ margin-left: .2em;
+ &::before {
+ content: ' '
+ }
+ }
+
+ .unmute {
+ flex: 0 0 auto;
+ margin-left: auto;
+ display: block;
+ margin-left: auto;
+ }
}
.reply-body {
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
index ccc01b6f..c0a71e8f 100644
--- a/src/components/status_content/status_content.js
+++ b/src/components/status_content/status_content.js
@@ -176,8 +176,8 @@ const StatusContent = {
}
}
if (target.rel.match(/(?:^|\s)tag(?:$|\s)/) || target.className.match(/hashtag/)) {
- // Extract tag name from link url
- const tag = extractTagFromUrl(target.href)
+ // Extract tag name from dataset or link url
+ const tag = target.dataset.tag || extractTagFromUrl(target.href)
if (tag) {
const link = this.generateTagLink(tag)
this.$router.push(link)
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index 4137bd59..08af26f6 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -23,12 +23,21 @@
<style lang="scss">
@import '../../_variables.scss';
+.contain-fit {
+ .still-image {
+ img {
+ height: 100%;
+ }
+ }
+}
+
.still-image {
position: relative;
line-height: 0;
overflow: hidden;
width: 100%;
height: 100%;
+ display: flex;
&:hover canvas {
display: none;
@@ -36,8 +45,8 @@
img {
width: 100%;
- height: 100%;
object-fit: contain;
+ align-self: center;
}
&.animated {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 062af2c7..fddead69 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -627,7 +627,9 @@
"mute_conversation": "Mute conversation",
"unmute_conversation": "Unmute conversation",
"status_unavailable": "Status unavailable",
- "copy_link": "Copy link to status"
+ "copy_link": "Copy link to status",
+ "thread_muted": "Thread muted",
+ "thread_muted_and_words": ", has words:"
},
"user_card": {
"approve": "Approve",
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index cd8c1dba..9a2e0df1 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -15,7 +15,7 @@ import {
import { set } from 'vue'
import { isStatusNotification } from '../services/notification_utils/notification_utils.js'
import apiService from '../services/api/api.service.js'
-// import parse from '../services/status_parser/status_parser.js'
+import { muteWordHits } from '../services/status_parser/status_parser.js'
const emptyTl = (userId = 0) => ({
statuses: [],
@@ -381,7 +381,18 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
notifObj.image = status.attachments[0].url
}
- if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) {
+ const reasonsToMuteNotif = (
+ notification.seen ||
+ state.notifications.desktopNotificationSilence ||
+ !visibleNotificationTypes.includes(notification.type) ||
+ (
+ notification.type === 'mention' && status && (
+ status.muted ||
+ muteWordHits(status, rootGetters.mergedConfig.muteWords).length === 0
+ )
+ )
+ )
+ if (!reasonsToMuteNotif) {
let desktopNotification = new window.Notification(title, notifObj)
// Chrome is known for not closing notifications automatically
// according to MDN, anyway.
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7f82d2fa..9c7530a2 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -538,9 +538,11 @@ const fetchTimeline = ({
if (timeline === 'public' || timeline === 'publicAndExternal') {
params.push(['only_media', false])
}
+ if (timeline !== 'favorites') {
+ params.push(['with_muted', withMuted])
+ }
params.push(['limit', 20])
- params.push(['with_muted', withMuted])
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}`
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 6dac7c15..c7ed65a4 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -210,7 +210,7 @@ export const addEmojis = (string, emojis) => {
const regexSafeShortCode = emoji.shortcode.replace(matchOperatorsRegex, '\\$&')
return acc.replace(
new RegExp(`:${regexSafeShortCode}:`, 'g'),
- `<img src='${emoji.url}' alt='${emoji.shortcode}' title='${emoji.shortcode}' class='emoji' />`
+ `<img src='${emoji.url}' alt=':${emoji.shortcode}:' title=':${emoji.shortcode}:' class='emoji' />`
)
}, string)
}
diff --git a/src/services/status_parser/status_parser.js b/src/services/status_parser/status_parser.js
index 900cd56e..3d517e3c 100644
--- a/src/services/status_parser/status_parser.js
+++ b/src/services/status_parser/status_parser.js
@@ -1,3 +1,4 @@
+import { filter } from 'lodash'
import sanitize from 'sanitize-html'
export const removeAttachmentLinks = (html) => {
@@ -12,4 +13,14 @@ export const parse = (html) => {
return removeAttachmentLinks(html)
}
+export const muteWordHits = (status, muteWords) => {
+ const statusText = status.text.toLowerCase()
+ const statusSummary = status.summary.toLowerCase()
+ const hits = filter(muteWords, (muteWord) => {
+ return statusText.includes(muteWord.toLowerCase()) || statusSummary.includes(muteWord.toLowerCase())
+ })
+
+ return hits
+}
+
export default parse