Merge pull request 'develop' (#12) from AkkomaGang/akkoma-fe:develop into develop

Reviewed-on: #12
This commit is contained in:
qbism 2023-08-05 03:02:36 +00:00
commit 5eeb7066f2
16 changed files with 127 additions and 45 deletions

View file

@ -7,7 +7,7 @@ import {
faStickyNote,
faSmileBeam
} from '@fortawesome/free-solid-svg-icons'
import { trim, escapeRegExp, startCase } from 'lodash'
import { trim, escapeRegExp, startCase, debounce } from 'lodash'
library.add(
faBoxOpen,
@ -42,6 +42,9 @@ const EmojiPicker = {
EmojiGrid
},
methods: {
debouncedSearch: debounce(function (e) {
this.keyword = e.target.value
}, 500),
onStickerUploaded (e) {
this.$emit('sticker-uploaded', e)
},
@ -85,17 +88,6 @@ const EmojiPicker = {
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
},
stickersAvailable () {
if (this.$store.state.instance.stickers) {
return this.$store.state.instance.stickers.length > 0
}
return 0
},
filteredEmoji () {
return this.filterByKeyword(
this.$store.state.instance.customEmoji || []
)
},
emojis () {
const recentEmojis = this.$store.getters.recentEmojis
const standardEmojis = this.$store.state.instance.emoji || []

View file

@ -44,11 +44,10 @@
>
<div class="emoji-search">
<input
v-model="keyword"
type="text"
class="form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
@input="debouncedSearch"
>
</div>
<EmojiGrid

View file

@ -136,18 +136,26 @@ const ExtraButtons = {
},
doRedraftStatus () {
this.$store.dispatch('fetchStatusSource', { id: this.status.id })
.then(data => this.$store.dispatch('openPostStatusModal', {
isRedraft: true,
statusId: this.status.id,
subject: data.spoiler_text,
statusText: data.text,
statusIsSensitive: this.status.nsfw,
statusPoll: this.status.poll,
statusFiles: [...this.status.attachments],
statusScope: this.status.visibility,
statusLanguage: this.status.language,
statusContentType: data.content_type
}))
.then(data => {
let repliedUserId = this.status.in_reply_to_user_id;
let repliedUser = this.status.attentions.filter(user =>
user.id === repliedUserId);
this.$store.dispatch('openPostStatusModal', {
isRedraft: true,
attentions: this.status.attentions,
statusId: this.status.id,
subject: data.spoiler_text,
statusText: data.text,
statusIsSensitive: this.status.nsfw,
statusPoll: this.status.poll,
statusFiles: [...this.status.attachments],
statusScope: this.status.visibility,
statusLanguage: this.status.language,
statusContentType: data.content_type,
replyTo: this.status.in_reply_to_status_id,
repliedUser: repliedUser
})
})
this.doDeleteStatus()
},
showRedraftStatusConfirmDialog () {

View file

@ -55,6 +55,9 @@
.interactive {
.svg-inline--fa {
@media (prefers-reduced-motion: reduce) {
animation: unset;
}
animation-duration: 0.6s;
}

View file

@ -1,9 +1,13 @@
<template>
<div class="list">
<div
class="list"
role="list"
>
<div
v-for="item in items"
:key="getKey(item)"
class="list-item"
role="listitem"
>
<slot
name="item"

View file

@ -157,6 +157,9 @@
box-shadow: var(--panelShadow);
transition-property: transform;
transition-duration: 0.25s;
@media (prefers-reduced-motion: reduce) {
transition: unset;
}
transform: translateX(0);
z-index: 1001;
-webkit-overflow-scrolling: touch;

View file

@ -74,6 +74,9 @@
.interactive {
.svg-inline--fa {
@media (prefers-reduced-motion: reduce) {
animation: unset;
}
animation-duration: 0.6s;
}

View file

@ -10,17 +10,20 @@ import SelectableList from 'src/components/selectable_list/selectable_list.vue'
import ProgressButton from 'src/components/progress_button/progress_button.vue'
import withSubscription from 'src/components/../hocs/with_subscription/with_subscription'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import withLoadMore from 'src/components/../hocs/with_load_more/with_load_more'
const BlockList = withSubscription({
const BlockList = withLoadMore({
fetch: (props, $store) => $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({

View file

@ -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;

View file

@ -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',

View file

@ -174,12 +174,12 @@
>
@{{ status.user.screen_name_ui }}
</router-link>
<img
<StillImage
v-if="!!(status.user && status.user.favicon)"
class="status-favicon"
:src="status.user.favicon"
:title="faviconAlt(status)"
>
/>
</span>
</div>

View file

@ -17,6 +17,9 @@
.emoji:hover {
transform: scale(1.4);
@media (prefers-reduced-motion: reduce) {
transition: unset;
}
transition: 0.05s;
}

View file

@ -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

View file

@ -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;
}

View file

@ -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
})

View file

@ -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))
}