$store.dispatch('fetchBlocks'),
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
- childPropName: 'items'
+ childPropName: 'items',
+ destroy: () => {}
})(SelectableList)
-const MuteList = withSubscription({
+const MuteList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
- childPropName: 'items'
+ childPropName: 'items',
+ destroy: () => {}
})(SelectableList)
const DomainMuteList = withSubscription({
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 86943e27..d031a21e 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -268,6 +268,10 @@
.side-drawer {
overflow-x: hidden;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
+ @media (prefers-reduced-motion: reduce) {
+ transition-timing-function: unset;
+ transition: unset;
+ }
transition: 0.35s;
transition-property: transform;
margin: 0 0 0 -100px;
diff --git a/src/components/status/status.js b/src/components/status/status.js
index a35b5084..9bb3364f 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -20,6 +20,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { muteWordHits } from '../../services/status_parser/status_parser.js'
import { unescape, uniqBy } from 'lodash'
+import StillImage from '../still-image/still-image.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -117,7 +118,8 @@ const Status = {
RichContent,
MentionLink,
MentionsLine,
- QuoteButton
+ QuoteButton,
+ StillImage
},
props: [
'statusoid',
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index c9ce4a8a..a54782e3 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -174,12 +174,12 @@
>
@{{ status.user.screen_name_ui }}
-
+ />
diff --git a/src/components/status_body/status_body.scss b/src/components/status_body/status_body.scss
index d618f65e..434cb482 100644
--- a/src/components/status_body/status_body.scss
+++ b/src/components/status_body/status_body.scss
@@ -17,6 +17,9 @@
.emoji:hover {
transform: scale(1.4);
+ @media (prefers-reduced-motion: reduce) {
+ transition: unset;
+ }
transition: 0.05s;
}
diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js
index 81da126a..9c335d6c 100644
--- a/src/components/still-image/still-image.js
+++ b/src/components/still-image/still-image.js
@@ -39,12 +39,25 @@ const StillImage = {
this.imageLoadError && this.imageLoadError()
},
detectAnimation (image) {
+ // If there are no file extensions, the mimetype isn't set, and no mediaproxy is available, we can't figure out
+ // the mimetype of the image.
+ const hasFileExtension = this.src.split('/').pop().includes('.') // TODO: Better check?
+ const mediaProxyAvailable = this.$store.state.instance.mediaProxyAvailable
+ if (!hasFileExtension && this.mimetype === undefined && !mediaProxyAvailable) {
+ // It's a bit aggressive to assume all images we can't find the mimetype of is animated, but necessary for
+ // people in need of reduced motion accessibility. As such, we'll consider those images animated if the user
+ // agent is set to prefer reduced motion. Otherwise, it'll just be used as an early exit.
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches)
+ this.isAnimated = true
+ return
+ }
+
if (this.mimetype === 'image/gif' || this.src.endsWith('.gif')) {
this.isAnimated = true
return
}
// harmless CORS errors without-- clean console with
- if (!this.$store.state.instance.mediaProxyAvailable) return
+ if (!mediaProxyAvailable) return
// Animated JPEGs?
if (!(this.src.endsWith('.webp') || this.src.endsWith('.png'))) return
// Browser Cache should ensure image doesn't get loaded twice if cache exists
diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue
index c036c64b..ce60b105 100644
--- a/src/components/timeline_menu/timeline_menu.vue
+++ b/src/components/timeline_menu/timeline_menu.vue
@@ -62,6 +62,9 @@
border-top-right-radius: 0;
border-top-left-radius: 0;
transform: translateY(-100%);
+ @media (prefers-reduced-motion: reduce) {
+ transition: unset;
+ }
transition: transform 100ms;
}
@@ -89,6 +92,9 @@
svg {
margin-left: 0.6em;
+ @media (prefers-reduced-motion: reduce) {
+ transition: unset;
+ }
transition: transform 100ms;
}
diff --git a/src/modules/users.js b/src/modules/users.js
index 9d81f9bc..6968ce1e 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -199,21 +199,28 @@ export const mutations = {
})
},
saveBlockIds (state, blockIds) {
- state.currentUser.blockIds = blockIds
+ console.log("ADDING BLOCK IDS", blockIds);
+ state.currentUser.blockIds = uniq(concat(state.currentUser.blockIds || [], blockIds))
},
addBlockId (state, blockId) {
if (state.currentUser.blockIds.indexOf(blockId) === -1) {
state.currentUser.blockIds.push(blockId)
}
},
+ setBlockIdsMaxId (state, blockIdsMaxId) {
+ state.currentUser.blockIdsMaxId = blockIdsMaxId
+ },
saveMuteIds (state, muteIds) {
- state.currentUser.muteIds = muteIds
+ state.currentUser.muteIds = uniq(concat(state.currentUser.muteIds || [], muteIds))
},
addMuteId (state, muteId) {
if (state.currentUser.muteIds.indexOf(muteId) === -1) {
state.currentUser.muteIds.push(muteId)
}
},
+ setMuteIdsMaxId (state, muteIdsMaxId) {
+ state.currentUser.muteIdsMaxId = muteIdsMaxId
+ },
updateMascot (state, mascotUrl) {
state.currentUser.mascot = mascotUrl
},
@@ -330,10 +337,21 @@ const users = {
.then((relationships) => store.commit('updateUserRelationship', relationships))
}
},
- fetchBlocks (store) {
- return store.rootState.api.backendInteractor.fetchBlocks()
+ fetchBlocks (store, args) {
+ const { reset } = args || {}
+
+ const maxId = store.state.currentUser.blockIdsMaxId
+ return store.rootState.api.backendInteractor.fetchBlocks({ maxId })
.then((blocks) => {
store.commit('saveBlockIds', map(blocks, 'id'))
+ if (reset) {
+ store.commit('saveBlockIds', map(blocks, 'id'))
+ } else {
+ map(blocks, 'id').map(id => store.commit('addBlockId', id))
+ }
+ if (blocks.length) {
+ store.commit('setBlockIdsMaxId', last(blocks).id)
+ }
store.commit('addNewUsers', blocks)
return blocks
})
@@ -353,10 +371,22 @@ const users = {
unblockUsers (store, ids = []) {
return Promise.all(ids.map(id => unblockUser(store, id)))
},
- fetchMutes (store) {
- return store.rootState.api.backendInteractor.fetchMutes()
+ fetchMutes (store, args) {
+ const { reset } = args || {}
+
+ const maxId = store.state.currentUser.muteIdsMaxId
+ return store.rootState.api.backendInteractor.fetchMutes({ maxId })
.then((mutes) => {
store.commit('saveMuteIds', map(mutes, 'id'))
+ if (reset) {
+ store.commit('saveMuteIds', map(mutes, 'id'))
+ } else {
+ map(mutes, 'id').map(id => store.commit('addMuteId', id))
+ }
+ if (mutes.length) {
+ store.commit('setMuteIdsMaxId', last(mutes).id)
+ }
+
store.commit('addNewUsers', mutes)
return mutes
})
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 947e9da9..de21ef3b 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1166,8 +1166,13 @@ const generateMfaBackupCodes = ({ credentials }) => {
}).then((data) => data.json())
}
-const fetchMutes = ({ credentials }) => {
- return promisedRequest({ url: MASTODON_USER_MUTES_URL, credentials })
+const fetchMutes = ({ maxId, credentials }) => {
+ const query = new URLSearchParams({ with_relationships: true })
+ if (maxId) {
+ query.append('max_id', maxId)
+ }
+
+ return promisedRequest({ url: `${MASTODON_USER_MUTES_URL}?${query.toString()}`, credentials })
.then((users) => users.map(parseUser))
}
@@ -1213,8 +1218,12 @@ const unsubscribeUser = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_UNSUBSCRIBE_USER(id), credentials, method: 'POST' })
}
-const fetchBlocks = ({ credentials }) => {
- return promisedRequest({ url: MASTODON_USER_BLOCKS_URL, credentials })
+const fetchBlocks = ({ maxId, credentials }) => {
+ const query = new URLSearchParams({ with_relationships: true })
+ if (maxId) {
+ query.append('max_id', maxId)
+ }
+ return promisedRequest({ url: `${MASTODON_USER_BLOCKS_URL}?${query.toString()}`, credentials })
.then((users) => users.map(parseUser))
}