diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/boot/after_store.js | 1 | ||||
| -rw-r--r-- | src/components/media_modal/media_modal.js | 2 | ||||
| -rw-r--r-- | src/components/notification/notification.js | 6 | ||||
| -rw-r--r-- | src/components/notification/notification.vue | 6 | ||||
| -rw-r--r-- | src/components/notifications/notifications.scss | 6 | ||||
| -rw-r--r-- | src/components/react_button/react_button.js | 2 | ||||
| -rw-r--r-- | src/components/settings_modal/tabs/filtering_tab.js | 20 | ||||
| -rw-r--r-- | src/components/settings_modal/tabs/helpers/shared_computed_object.js | 10 | ||||
| -rw-r--r-- | src/components/status/status.js | 55 | ||||
| -rw-r--r-- | src/components/status/status.vue | 72 | ||||
| -rw-r--r-- | src/components/status_content/status_content.js | 4 | ||||
| -rw-r--r-- | src/components/still-image/still-image.vue | 11 | ||||
| -rw-r--r-- | src/i18n/en.json | 4 | ||||
| -rw-r--r-- | src/modules/statuses.js | 15 | ||||
| -rw-r--r-- | src/services/api/api.service.js | 4 | ||||
| -rw-r--r-- | src/services/entity_normalizer/entity_normalizer.service.js | 2 | ||||
| -rw-r--r-- | src/services/status_parser/status_parser.js | 11 |
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 |
