update with develop
This commit is contained in:
commit
db9471cd3e
|
@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Private mode support
|
- Private mode support
|
||||||
- Support for 'Move' type notifications
|
- Support for 'Move' type notifications
|
||||||
- Pleroma AMOLED dark theme
|
- Pleroma AMOLED dark theme
|
||||||
|
- User level domain mutes, under User Settings -> Mutes
|
||||||
|
- Emoji reactions for statuses
|
||||||
|
- MRF keyword policy disclosure
|
||||||
### Changed
|
### Changed
|
||||||
- Captcha now resets on failed registrations
|
- Captcha now resets on failed registrations
|
||||||
- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
|
- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
|
||||||
|
@ -17,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Single notifications left unread when hitting read on another device/tab
|
- Single notifications left unread when hitting read on another device/tab
|
||||||
- Registration fixed
|
- Registration fixed
|
||||||
- Deactivation of remote accounts from frontend
|
- Deactivation of remote accounts from frontend
|
||||||
|
- Fixed NSFW unhiding not working with videos when using one-click unhiding/displaying
|
||||||
|
|
||||||
## [1.1.7 and earlier] - 2019-12-14
|
## [1.1.7 and earlier] - 2019-12-14
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"chromatism": "^3.0.0",
|
"chromatism": "^3.0.0",
|
||||||
"cropperjs": "^1.4.3",
|
"cropperjs": "^1.4.3",
|
||||||
"diff": "^3.0.1",
|
"diff": "^3.0.1",
|
||||||
|
"escape-html": "^1.0.3",
|
||||||
"karma-mocha-reporter": "^2.2.1",
|
"karma-mocha-reporter": "^2.2.1",
|
||||||
"localforage": "^1.5.0",
|
"localforage": "^1.5.0",
|
||||||
"object-path": "^0.11.3",
|
"object-path": "^0.11.3",
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||||
"@babel/preset-env": "^7.7.6",
|
"@babel/preset-env": "^7.7.6",
|
||||||
"@babel/register": "^7.7.4",
|
"@babel/register": "^7.7.4",
|
||||||
|
"@ungap/event-target": "^0.1.0",
|
||||||
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
|
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
|
||||||
"@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
|
"@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
|
||||||
"@vue/test-utils": "^1.0.0-beta.26",
|
"@vue/test-utils": "^1.0.0-beta.26",
|
||||||
|
@ -56,6 +58,7 @@
|
||||||
"connect-history-api-fallback": "^1.1.0",
|
"connect-history-api-fallback": "^1.1.0",
|
||||||
"cross-spawn": "^4.0.2",
|
"cross-spawn": "^4.0.2",
|
||||||
"css-loader": "^0.28.0",
|
"css-loader": "^0.28.0",
|
||||||
|
"custom-event-polyfill": "^1.0.7",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-config-standard": "^12.0.0",
|
"eslint-config-standard": "^12.0.0",
|
||||||
"eslint-friendly-formatter": "^2.0.5",
|
"eslint-friendly-formatter": "^2.0.5",
|
||||||
|
|
|
@ -75,7 +75,7 @@ button {
|
||||||
border-radius: $fallback--btnRadius;
|
border-radius: $fallback--btnRadius;
|
||||||
border-radius: var(--btnRadius, $fallback--btnRadius);
|
border-radius: var(--btnRadius, $fallback--btnRadius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
|
box-shadow: $fallback--buttonShadow;
|
||||||
box-shadow: var(--buttonShadow);
|
box-shadow: var(--buttonShadow);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
|
|
|
@ -27,3 +27,5 @@ $fallback--tooltipRadius: 5px;
|
||||||
$fallback--avatarRadius: 4px;
|
$fallback--avatarRadius: 4px;
|
||||||
$fallback--avatarAltRadius: 10px;
|
$fallback--avatarAltRadius: 10px;
|
||||||
$fallback--attachmentRadius: 10px;
|
$fallback--attachmentRadius: 10px;
|
||||||
|
|
||||||
|
$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
|
||||||
|
|
|
@ -185,12 +185,9 @@ const getAppSecret = async ({ store }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolveStaffAccounts = async ({ store, accounts }) => {
|
const resolveStaffAccounts = ({ store, accounts }) => {
|
||||||
const backendInteractor = store.state.api.backendInteractor
|
const nicknames = accounts.map(uri => uri.split('/').pop())
|
||||||
let nicknames = accounts.map(uri => uri.split('/').pop())
|
nicknames.map(nickname => store.dispatch('fetchUser', nickname))
|
||||||
.map(id => backendInteractor.fetchUser({ id }))
|
|
||||||
nicknames = await Promise.all(nicknames)
|
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
|
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +233,7 @@ const getNodeInfo = async ({ store }) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const accounts = metadata.staffAccounts
|
const accounts = metadata.staffAccounts
|
||||||
await resolveStaffAccounts({ store, accounts })
|
resolveStaffAccounts({ store, accounts })
|
||||||
} else {
|
} else {
|
||||||
throw (res)
|
throw (res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import StillImage from '../still-image/still-image.vue'
|
||||||
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
||||||
import nsfwImage from '../../assets/nsfw.png'
|
import nsfwImage from '../../assets/nsfw.png'
|
||||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
const Attachment = {
|
const Attachment = {
|
||||||
props: [
|
props: [
|
||||||
|
@ -49,7 +50,8 @@ const Attachment = {
|
||||||
},
|
},
|
||||||
fullwidth () {
|
fullwidth () {
|
||||||
return this.type === 'html' || this.type === 'audio'
|
return this.type === 'html' || this.type === 'audio'
|
||||||
}
|
},
|
||||||
|
...mapGetters(['mergedConfig'])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
linkClicked ({ target }) {
|
linkClicked ({ target }) {
|
||||||
|
@ -58,7 +60,7 @@ const Attachment = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openModal (event) {
|
openModal (event) {
|
||||||
const modalTypes = this.$store.getters.mergedConfig.playVideosInModal
|
const modalTypes = this.mergedConfig.playVideosInModal
|
||||||
? ['image', 'video']
|
? ['image', 'video']
|
||||||
: ['image']
|
: ['image']
|
||||||
if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) ||
|
if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) ||
|
||||||
|
@ -71,7 +73,10 @@ const Attachment = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggleHidden (event) {
|
toggleHidden (event) {
|
||||||
if (this.$store.getters.mergedConfig.useOneClickNsfw && !this.showHidden) {
|
if (
|
||||||
|
(this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
|
||||||
|
(this.type !== 'video' || this.mergedConfig.playVideosInModal)
|
||||||
|
) {
|
||||||
this.openModal(event)
|
this.openModal(event)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,7 @@ const conversation = {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
this.highlight = id
|
this.highlight = id
|
||||||
this.$store.dispatch('fetchFavsAndRepeats', id)
|
this.$store.dispatch('fetchFavsAndRepeats', id)
|
||||||
|
this.$store.dispatch('fetchEmojiReactionsBy', id)
|
||||||
},
|
},
|
||||||
getHighlight () {
|
getHighlight () {
|
||||||
return this.isExpanded ? this.highlight : null
|
return this.isExpanded ? this.highlight : null
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import ProgressButton from '../progress_button/progress_button.vue'
|
||||||
|
|
||||||
|
const DomainMuteCard = {
|
||||||
|
props: ['domain'],
|
||||||
|
components: {
|
||||||
|
ProgressButton
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
unmuteDomain () {
|
||||||
|
return this.$store.dispatch('unmuteDomain', this.domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DomainMuteCard
|
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<div class="domain-mute-card">
|
||||||
|
<div class="domain-mute-card-domain">
|
||||||
|
{{ domain }}
|
||||||
|
</div>
|
||||||
|
<ProgressButton
|
||||||
|
:click="unmuteDomain"
|
||||||
|
class="btn btn-default"
|
||||||
|
>
|
||||||
|
{{ $t('domain_mute_card.unmute') }}
|
||||||
|
<template slot="progress">
|
||||||
|
{{ $t('domain_mute_card.unmute_progress') }}
|
||||||
|
</template>
|
||||||
|
</ProgressButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./domain_mute_card.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.domain-mute-card {
|
||||||
|
flex: 1 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.6em 1em 0.6em 0;
|
||||||
|
|
||||||
|
&-domain {
|
||||||
|
margin-right: 1em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,72 @@
|
||||||
|
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||||
|
|
||||||
|
const EMOJI_REACTION_COUNT_CUTOFF = 12
|
||||||
|
|
||||||
|
const EmojiReactions = {
|
||||||
|
name: 'EmojiReactions',
|
||||||
|
components: {
|
||||||
|
UserAvatar
|
||||||
|
},
|
||||||
|
props: ['status'],
|
||||||
|
data: () => ({
|
||||||
|
showAll: false,
|
||||||
|
popperOptions: {
|
||||||
|
modifiers: {
|
||||||
|
preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
tooManyReactions () {
|
||||||
|
return this.status.emoji_reactions.length > EMOJI_REACTION_COUNT_CUTOFF
|
||||||
|
},
|
||||||
|
emojiReactions () {
|
||||||
|
return this.showAll
|
||||||
|
? this.status.emoji_reactions
|
||||||
|
: this.status.emoji_reactions.slice(0, EMOJI_REACTION_COUNT_CUTOFF)
|
||||||
|
},
|
||||||
|
showMoreString () {
|
||||||
|
return `+${this.status.emoji_reactions.length - EMOJI_REACTION_COUNT_CUTOFF}`
|
||||||
|
},
|
||||||
|
accountsForEmoji () {
|
||||||
|
return this.status.emoji_reactions.reduce((acc, reaction) => {
|
||||||
|
acc[reaction.name] = reaction.accounts || []
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
loggedIn () {
|
||||||
|
return !!this.$store.state.users.currentUser
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleShowAll () {
|
||||||
|
this.showAll = !this.showAll
|
||||||
|
},
|
||||||
|
reactedWith (emoji) {
|
||||||
|
return this.status.emoji_reactions.find(r => r.name === emoji).me
|
||||||
|
},
|
||||||
|
fetchEmojiReactionsByIfMissing () {
|
||||||
|
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
|
||||||
|
if (hasNoAccounts) {
|
||||||
|
this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reactWith (emoji) {
|
||||||
|
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
|
||||||
|
},
|
||||||
|
unreact (emoji) {
|
||||||
|
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
||||||
|
},
|
||||||
|
emojiOnClick (emoji, event) {
|
||||||
|
if (!this.loggedIn) return
|
||||||
|
|
||||||
|
if (this.reactedWith(emoji)) {
|
||||||
|
this.unreact(emoji)
|
||||||
|
} else {
|
||||||
|
this.reactWith(emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmojiReactions
|
|
@ -0,0 +1,136 @@
|
||||||
|
<template>
|
||||||
|
<div class="emoji-reactions">
|
||||||
|
<v-popover
|
||||||
|
v-for="(reaction) in emojiReactions"
|
||||||
|
:key="reaction.name"
|
||||||
|
:popper-options="popperOptions"
|
||||||
|
trigger="hover"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div
|
||||||
|
slot="popover"
|
||||||
|
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">
|
||||||
|
<span class="reacted-user-name" v-html="account.name_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
|
||||||
|
class="emoji-reaction btn btn-default"
|
||||||
|
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
|
||||||
|
@click="emojiOnClick(reaction.name, $event)"
|
||||||
|
@mouseenter="fetchEmojiReactionsByIfMissing()"
|
||||||
|
>
|
||||||
|
<span class="reaction-emoji">{{ reaction.name }}</span>
|
||||||
|
<span>{{ reaction.count }}</span>
|
||||||
|
</button>
|
||||||
|
</v-popover>
|
||||||
|
<a
|
||||||
|
v-if="tooManyReactions"
|
||||||
|
@click="toggleShowAll"
|
||||||
|
class="emoji-reaction-expand faint"
|
||||||
|
href='javascript:void(0)'
|
||||||
|
>
|
||||||
|
{{ showAll ? $t('general.show_less') : showMoreString }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./emoji_reactions.js" ></script>
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.emoji-reactions {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
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;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reacted-user-screen-name {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-reaction {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
.reaction-emoji {
|
||||||
|
width: 1.25em;
|
||||||
|
margin-right: 0.25em;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.not-clickable {
|
||||||
|
cursor: default;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: $fallback--buttonShadow;
|
||||||
|
box-shadow: var(--buttonShadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-reaction-expand {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picked-reaction {
|
||||||
|
border: 1px solid var(--link, $fallback--link);
|
||||||
|
margin-left: -1px; // offset the border, can't use inset shadows either
|
||||||
|
margin-right: calc(0.5em - 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -10,6 +10,7 @@ const tabModeDict = {
|
||||||
const Interactions = {
|
const Interactions = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
|
||||||
filterMode: tabModeDict['mentions']
|
filterMode: tabModeDict['mentions']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
:label="$t('interactions.follows')"
|
:label="$t('interactions.follows')"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
|
v-if="!allowFollowingMove"
|
||||||
key="moves"
|
key="moves"
|
||||||
:label="$t('interactions.moves')"
|
:label="$t('interactions.moves')"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,7 +11,10 @@ const MRFTransparencyPanel = {
|
||||||
rejectInstances: state => get(state, 'instance.federationPolicy.mrf_simple.reject', []),
|
rejectInstances: state => get(state, 'instance.federationPolicy.mrf_simple.reject', []),
|
||||||
ftlRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []),
|
ftlRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []),
|
||||||
mediaNsfwInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []),
|
mediaNsfwInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []),
|
||||||
mediaRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_removal', [])
|
mediaRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_removal', []),
|
||||||
|
keywordsFtlRemoval: state => get(state, 'instance.federationPolicy.mrf_keyword.federated_timeline_removal', []),
|
||||||
|
keywordsReject: state => get(state, 'instance.federationPolicy.mrf_keyword.reject', []),
|
||||||
|
keywordsReplace: state => get(state, 'instance.federationPolicy.mrf_keyword.replace', [])
|
||||||
}),
|
}),
|
||||||
hasInstanceSpecificPolicies () {
|
hasInstanceSpecificPolicies () {
|
||||||
return this.quarantineInstances.length ||
|
return this.quarantineInstances.length ||
|
||||||
|
@ -20,6 +23,11 @@ const MRFTransparencyPanel = {
|
||||||
this.ftlRemovalInstances.length ||
|
this.ftlRemovalInstances.length ||
|
||||||
this.mediaNsfwInstances.length ||
|
this.mediaNsfwInstances.length ||
|
||||||
this.mediaRemovalInstances.length
|
this.mediaRemovalInstances.length
|
||||||
|
},
|
||||||
|
hasKeywordPolicies () {
|
||||||
|
return this.keywordsFtlRemoval.length ||
|
||||||
|
this.keywordsReject.length ||
|
||||||
|
this.keywordsReplace.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,49 @@
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2 v-if="hasKeywordPolicies">
|
||||||
|
{{ $t("about.mrf.keyword.keyword_policies") }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div v-if="keywordsFtlRemoval.length">
|
||||||
|
<h4>{{ $t("about.mrf.keyword.ftl_removal") }}</h4>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="keyword in keywordsFtlRemoval"
|
||||||
|
:key="keyword"
|
||||||
|
v-text="keyword"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="keywordsReject.length">
|
||||||
|
<h4>{{ $t("about.mrf.keyword.reject") }}</h4>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="keyword in keywordsReject"
|
||||||
|
:key="keyword"
|
||||||
|
v-text="keyword"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="keywordsReplace.length">
|
||||||
|
<h4>{{ $t("about.mrf.keyword.replace") }}</h4>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="keyword in keywordsReplace"
|
||||||
|
:key="keyword"
|
||||||
|
>
|
||||||
|
{{ keyword.pattern }}
|
||||||
|
{{ $t("about.mrf.keyword.is_replaced_by") }}
|
||||||
|
{{ keyword.replacement }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { mapState } from 'vuex'
|
||||||
const NavPanel = {
|
const NavPanel = {
|
||||||
created () {
|
created () {
|
||||||
if (this.currentUser && this.currentUser.locked) {
|
if (this.currentUser && this.currentUser.locked) {
|
||||||
this.$store.dispatch('startFetchingFollowRequest')
|
this.$store.dispatch('startFetchingFollowRequests')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: mapState({
|
computed: mapState({
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
|
<i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="federating && !privateMode">
|
<li v-if="federating && (currentUser || !privateMode)">
|
||||||
<router-link :to="{ name: 'public-external-timeline' }">
|
<router-link :to="{ name: 'public-external-timeline' }">
|
||||||
<i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
|
<i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
@ -78,6 +78,13 @@
|
||||||
<i class="fa icon-arrow-curved lit" />
|
<i class="fa icon-arrow-curved lit" />
|
||||||
<small>{{ $t('notifications.migrated_to') }}</small>
|
<small>{{ $t('notifications.migrated_to') }}</small>
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="notification.type === 'pleroma:emoji_reaction'">
|
||||||
|
<small>
|
||||||
|
<i18n path="notifications.reacted_with">
|
||||||
|
<span class="emoji-reaction-emoji">{{ notification.emoji }}</span>
|
||||||
|
</i18n>
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="notification.type === 'follow' || notification.type === 'move'"
|
v-if="notification.type === 'follow' || notification.type === 'move'"
|
||||||
|
|
|
@ -94,6 +94,10 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emoji-reaction-emoji {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.notification-details {
|
.notification-details {
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
|
||||||
|
const ReactButton = {
|
||||||
|
props: ['status', 'loggedIn'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
showTooltip: false,
|
||||||
|
filterWord: '',
|
||||||
|
popperOptions: {
|
||||||
|
modifiers: {
|
||||||
|
preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openReactionSelect () {
|
||||||
|
this.showTooltip = true
|
||||||
|
this.filterWord = ''
|
||||||
|
},
|
||||||
|
closeReactionSelect () {
|
||||||
|
this.showTooltip = false
|
||||||
|
},
|
||||||
|
addReaction (event, emoji) {
|
||||||
|
const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji)
|
||||||
|
if (existingReaction && existingReaction.me) {
|
||||||
|
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
||||||
|
} else {
|
||||||
|
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
|
||||||
|
}
|
||||||
|
this.closeReactionSelect()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
commonEmojis () {
|
||||||
|
return ['❤️', '😠', '👀', '😂', '🔥']
|
||||||
|
},
|
||||||
|
emojis () {
|
||||||
|
if (this.filterWord !== '') {
|
||||||
|
return this.$store.state.instance.emoji.filter(emoji => emoji.displayText.includes(this.filterWord))
|
||||||
|
}
|
||||||
|
return this.$store.state.instance.emoji || []
|
||||||
|
},
|
||||||
|
...mapGetters(['mergedConfig'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReactButton
|
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<v-popover
|
||||||
|
:popper-options="popperOptions"
|
||||||
|
:open="showTooltip"
|
||||||
|
trigger="manual"
|
||||||
|
placement="top"
|
||||||
|
class="react-button-popover"
|
||||||
|
@hide="closeReactionSelect"
|
||||||
|
>
|
||||||
|
<div slot="popover">
|
||||||
|
<div class="reaction-picker-filter">
|
||||||
|
<input
|
||||||
|
v-model="filterWord"
|
||||||
|
:placeholder="$t('emoji.search_emoji')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="reaction-picker">
|
||||||
|
<span
|
||||||
|
v-for="emoji in commonEmojis"
|
||||||
|
:key="emoji"
|
||||||
|
class="emoji-button"
|
||||||
|
@click="addReaction($event, emoji)"
|
||||||
|
>
|
||||||
|
{{ emoji }}
|
||||||
|
</span>
|
||||||
|
<div class="reaction-picker-divider" />
|
||||||
|
<span
|
||||||
|
v-for="(emoji, key) in emojis"
|
||||||
|
:key="key"
|
||||||
|
class="emoji-button"
|
||||||
|
@click="addReaction($event, emoji.replacement)"
|
||||||
|
>
|
||||||
|
{{ emoji.replacement }}
|
||||||
|
</span>
|
||||||
|
<div class="reaction-bottom-fader" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="loggedIn"
|
||||||
|
@click.prevent="openReactionSelect"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="icon-smile button-icon add-reaction-button"
|
||||||
|
:title="$t('tool_tip.add_reaction')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./react_button.js" ></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.reaction-picker-filter {
|
||||||
|
padding: 0.5em;
|
||||||
|
display: flex;
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reaction-picker-divider {
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.5em;
|
||||||
|
background-color: var(--border, $fallback--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reaction-picker {
|
||||||
|
width: 10em;
|
||||||
|
height: 9em;
|
||||||
|
font-size: 1.5em;
|
||||||
|
overflow-y: scroll;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
align-content: flex-start;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
|
||||||
|
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
|
||||||
|
linear-gradient(to top, white, white);
|
||||||
|
transition: mask-size 150ms;
|
||||||
|
mask-size: 100% 20px, 100% 20px, auto;
|
||||||
|
// Autoprefixed seem to ignore this one, and also syntax is different
|
||||||
|
-webkit-mask-composite: xor;
|
||||||
|
mask-composite: exclude;
|
||||||
|
|
||||||
|
.emoji-button {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
flex-basis: 20%;
|
||||||
|
line-height: 1.5em;
|
||||||
|
align-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-reaction-button {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--text, $fallback--text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -97,6 +97,11 @@
|
||||||
{{ $t('settings.virtual_scrolling') }}
|
{{ $t('settings.virtual_scrolling') }}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="emojiReactionsOnTimeline">
|
||||||
|
{{ $t('settings.emoji_reactions_on_timeline') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -333,6 +338,11 @@
|
||||||
{{ $t('settings.notification_visibility_moves') }}
|
{{ $t('settings.notification_visibility_moves') }}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<Checkbox v-model="notificationVisibility.emojiReactions">
|
||||||
|
{{ $t('settings.notification_visibility_emoji_reactions') }}
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -12,7 +12,7 @@ const SideDrawer = {
|
||||||
this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer)
|
this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer)
|
||||||
|
|
||||||
if (this.currentUser && this.currentUser.locked) {
|
if (this.currentUser && this.currentUser.locked) {
|
||||||
this.$store.dispatch('startFetchingFollowRequest')
|
this.$store.dispatch('startFetchingFollowRequests')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { UserCard },
|
components: { UserCard },
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="federating && !privateMode"
|
v-if="federating && (currentUser || !privateMode)"
|
||||||
@click="toggleDrawer"
|
@click="toggleDrawer"
|
||||||
>
|
>
|
||||||
<router-link to="/main/all">
|
<router-link to="/main/all">
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import map from 'lodash/map'
|
||||||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||||
|
|
||||||
const StaffPanel = {
|
const StaffPanel = {
|
||||||
|
@ -6,7 +7,7 @@ const StaffPanel = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
staffAccounts () {
|
staffAccounts () {
|
||||||
return this.$store.state.instance.staffAccounts
|
return map(this.$store.state.instance.staffAccounts, nickname => this.$store.getters.findUser(nickname)).filter(_ => _)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Attachment from '../attachment/attachment.vue'
|
import Attachment from '../attachment/attachment.vue'
|
||||||
import FavoriteButton from '../favorite_button/favorite_button.vue'
|
import FavoriteButton from '../favorite_button/favorite_button.vue'
|
||||||
|
import ReactButton from '../react_button/react_button.vue'
|
||||||
import RetweetButton from '../retweet_button/retweet_button.vue'
|
import RetweetButton from '../retweet_button/retweet_button.vue'
|
||||||
import Poll from '../poll/poll.vue'
|
import Poll from '../poll/poll.vue'
|
||||||
import ExtraButtons from '../extra_buttons/extra_buttons.vue'
|
import ExtraButtons from '../extra_buttons/extra_buttons.vue'
|
||||||
|
@ -11,6 +12,7 @@ import LinkPreview from '../link-preview/link-preview.vue'
|
||||||
import AvatarList from '../avatar_list/avatar_list.vue'
|
import AvatarList from '../avatar_list/avatar_list.vue'
|
||||||
import Timeago from '../timeago/timeago.vue'
|
import Timeago from '../timeago/timeago.vue'
|
||||||
import StatusPopover from '../status_popover/status_popover.vue'
|
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 generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
import fileType from 'src/services/file_type/file_type.service'
|
import fileType from 'src/services/file_type/file_type.service'
|
||||||
import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
|
import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
|
||||||
|
@ -255,6 +257,16 @@ const Status = {
|
||||||
file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
|
file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
hasImageAttachments () {
|
||||||
|
return this.status.attachments.some(
|
||||||
|
file => fileType.fileType(file.mimetype) === 'image'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
hasVideoAttachments () {
|
||||||
|
return this.status.attachments.some(
|
||||||
|
file => fileType.fileType(file.mimetype) === 'video'
|
||||||
|
)
|
||||||
|
},
|
||||||
maxThumbnails () {
|
maxThumbnails () {
|
||||||
return this.mergedConfig.maxThumbnails
|
return this.mergedConfig.maxThumbnails
|
||||||
},
|
},
|
||||||
|
@ -320,6 +332,7 @@ const Status = {
|
||||||
components: {
|
components: {
|
||||||
Attachment,
|
Attachment,
|
||||||
FavoriteButton,
|
FavoriteButton,
|
||||||
|
ReactButton,
|
||||||
RetweetButton,
|
RetweetButton,
|
||||||
ExtraButtons,
|
ExtraButtons,
|
||||||
PostStatusForm,
|
PostStatusForm,
|
||||||
|
@ -330,7 +343,8 @@ const Status = {
|
||||||
LinkPreview,
|
LinkPreview,
|
||||||
AvatarList,
|
AvatarList,
|
||||||
Timeago,
|
Timeago,
|
||||||
StatusPopover
|
StatusPopover,
|
||||||
|
EmojiReactions
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
visibilityIcon (visibility) {
|
visibilityIcon (visibility) {
|
||||||
|
|
|
@ -277,7 +277,21 @@
|
||||||
href="#"
|
href="#"
|
||||||
class="cw-status-hider"
|
class="cw-status-hider"
|
||||||
@click.prevent="toggleShowMore"
|
@click.prevent="toggleShowMore"
|
||||||
>{{ $t("general.show_more") }}</a>
|
>
|
||||||
|
{{ $t("general.show_more") }}
|
||||||
|
<span
|
||||||
|
v-if="hasImageAttachments"
|
||||||
|
class="icon-picture"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="hasVideoAttachments"
|
||||||
|
class="icon-video"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="status.card"
|
||||||
|
class="icon-link"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
v-if="showingMore"
|
v-if="showingMore"
|
||||||
href="#"
|
href="#"
|
||||||
|
@ -354,6 +368,11 @@
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
|
<EmojiReactions
|
||||||
|
v-if="(mergedConfig.emojiReactionsOnTimeline || isFocused) && (!noHeading && !isPreview)"
|
||||||
|
:status="status"
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="!noHeading && !isPreview"
|
v-if="!noHeading && !isPreview"
|
||||||
class="status-actions media-body"
|
class="status-actions media-body"
|
||||||
|
@ -382,6 +401,10 @@
|
||||||
:logged-in="loggedIn"
|
:logged-in="loggedIn"
|
||||||
:status="status"
|
:status="status"
|
||||||
/>
|
/>
|
||||||
|
<ReactButton
|
||||||
|
:logged-in="loggedIn"
|
||||||
|
:status="status"
|
||||||
|
/>
|
||||||
<extra-buttons
|
<extra-buttons
|
||||||
:status="status"
|
:status="status"
|
||||||
@onError="showError"
|
@onError="showError"
|
||||||
|
|
|
@ -9,6 +9,7 @@ import ScopeSelector from '../scope_selector/scope_selector.vue'
|
||||||
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
||||||
import BlockCard from '../block_card/block_card.vue'
|
import BlockCard from '../block_card/block_card.vue'
|
||||||
import MuteCard from '../mute_card/mute_card.vue'
|
import MuteCard from '../mute_card/mute_card.vue'
|
||||||
|
import DomainMuteCard from '../domain_mute_card/domain_mute_card.vue'
|
||||||
import SelectableList from '../selectable_list/selectable_list.vue'
|
import SelectableList from '../selectable_list/selectable_list.vue'
|
||||||
import ProgressButton from '../progress_button/progress_button.vue'
|
import ProgressButton from '../progress_button/progress_button.vue'
|
||||||
import EmojiInput from '../emoji_input/emoji_input.vue'
|
import EmojiInput from '../emoji_input/emoji_input.vue'
|
||||||
|
@ -32,6 +33,12 @@ const MuteList = withSubscription({
|
||||||
childPropName: 'items'
|
childPropName: 'items'
|
||||||
})(SelectableList)
|
})(SelectableList)
|
||||||
|
|
||||||
|
const DomainMuteList = withSubscription({
|
||||||
|
fetch: (props, $store) => $store.dispatch('fetchDomainMutes'),
|
||||||
|
select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []),
|
||||||
|
childPropName: 'items'
|
||||||
|
})(SelectableList)
|
||||||
|
|
||||||
const UserSettings = {
|
const UserSettings = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
@ -48,6 +55,7 @@ const UserSettings = {
|
||||||
showRole: this.$store.state.users.currentUser.show_role,
|
showRole: this.$store.state.users.currentUser.show_role,
|
||||||
role: this.$store.state.users.currentUser.role,
|
role: this.$store.state.users.currentUser.role,
|
||||||
discoverable: this.$store.state.users.currentUser.discoverable,
|
discoverable: this.$store.state.users.currentUser.discoverable,
|
||||||
|
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
|
||||||
pickAvatarBtnVisible: true,
|
pickAvatarBtnVisible: true,
|
||||||
bannerUploading: false,
|
bannerUploading: false,
|
||||||
backgroundUploading: false,
|
backgroundUploading: false,
|
||||||
|
@ -67,7 +75,8 @@ const UserSettings = {
|
||||||
changedPassword: false,
|
changedPassword: false,
|
||||||
changePasswordError: false,
|
changePasswordError: false,
|
||||||
activeTab: 'profile',
|
activeTab: 'profile',
|
||||||
notificationSettings: this.$store.state.users.currentUser.notification_settings
|
notificationSettings: this.$store.state.users.currentUser.notification_settings,
|
||||||
|
newDomainToMute: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
@ -80,10 +89,12 @@ const UserSettings = {
|
||||||
ImageCropper,
|
ImageCropper,
|
||||||
BlockList,
|
BlockList,
|
||||||
MuteList,
|
MuteList,
|
||||||
|
DomainMuteList,
|
||||||
EmojiInput,
|
EmojiInput,
|
||||||
Autosuggest,
|
Autosuggest,
|
||||||
BlockCard,
|
BlockCard,
|
||||||
MuteCard,
|
MuteCard,
|
||||||
|
DomainMuteCard,
|
||||||
ProgressButton,
|
ProgressButton,
|
||||||
Importer,
|
Importer,
|
||||||
Exporter,
|
Exporter,
|
||||||
|
@ -152,6 +163,7 @@ const UserSettings = {
|
||||||
hide_follows: this.hideFollows,
|
hide_follows: this.hideFollows,
|
||||||
hide_followers: this.hideFollowers,
|
hide_followers: this.hideFollowers,
|
||||||
discoverable: this.discoverable,
|
discoverable: this.discoverable,
|
||||||
|
allow_following_move: this.allowFollowingMove,
|
||||||
hide_follows_count: this.hideFollowsCount,
|
hide_follows_count: this.hideFollowsCount,
|
||||||
hide_followers_count: this.hideFollowersCount,
|
hide_followers_count: this.hideFollowersCount,
|
||||||
show_role: this.showRole
|
show_role: this.showRole
|
||||||
|
@ -297,7 +309,7 @@ const UserSettings = {
|
||||||
newPassword: this.changePasswordInputs[1],
|
newPassword: this.changePasswordInputs[1],
|
||||||
newPasswordConfirmation: this.changePasswordInputs[2]
|
newPasswordConfirmation: this.changePasswordInputs[2]
|
||||||
}
|
}
|
||||||
this.$store.state.api.backendInteractor.changePassword({ params })
|
this.$store.state.api.backendInteractor.changePassword(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 'success') {
|
if (res.status === 'success') {
|
||||||
this.changedPassword = true
|
this.changedPassword = true
|
||||||
|
@ -314,7 +326,7 @@ const UserSettings = {
|
||||||
email: this.newEmail,
|
email: this.newEmail,
|
||||||
password: this.changeEmailPassword
|
password: this.changeEmailPassword
|
||||||
}
|
}
|
||||||
this.$store.state.api.backendInteractor.changeEmail({ params })
|
this.$store.state.api.backendInteractor.changeEmail(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 'success') {
|
if (res.status === 'success') {
|
||||||
this.changedEmail = true
|
this.changedEmail = true
|
||||||
|
@ -365,6 +377,13 @@ const UserSettings = {
|
||||||
unmuteUsers (ids) {
|
unmuteUsers (ids) {
|
||||||
return this.$store.dispatch('unmuteUsers', ids)
|
return this.$store.dispatch('unmuteUsers', ids)
|
||||||
},
|
},
|
||||||
|
unmuteDomains (domains) {
|
||||||
|
return this.$store.dispatch('unmuteDomains', domains)
|
||||||
|
},
|
||||||
|
muteDomain () {
|
||||||
|
return this.$store.dispatch('muteDomain', this.newDomainToMute)
|
||||||
|
.then(() => { this.newDomainToMute = '' })
|
||||||
|
},
|
||||||
identity (value) {
|
identity (value) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,9 +90,7 @@
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<Checkbox
|
<Checkbox v-model="hideFollowers">
|
||||||
v-model="hideFollowers"
|
|
||||||
>
|
|
||||||
{{ $t('settings.hide_followers_description') }}
|
{{ $t('settings.hide_followers_description') }}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</p>
|
</p>
|
||||||
|
@ -104,6 +102,11 @@
|
||||||
{{ $t('settings.hide_followers_count_description') }}
|
{{ $t('settings.hide_followers_count_description') }}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<Checkbox v-model="allowFollowingMove">
|
||||||
|
{{ $t('settings.allow_following_move') }}
|
||||||
|
</Checkbox>
|
||||||
|
</p>
|
||||||
<p v-if="role === 'admin' || role === 'moderator'">
|
<p v-if="role === 'admin' || role === 'moderator'">
|
||||||
<Checkbox v-model="showRole">
|
<Checkbox v-model="showRole">
|
||||||
<template v-if="role === 'admin'">
|
<template v-if="role === 'admin'">
|
||||||
|
@ -509,6 +512,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :label="$t('settings.mutes_tab')">
|
<div :label="$t('settings.mutes_tab')">
|
||||||
|
<tab-switcher>
|
||||||
|
<div label="Users">
|
||||||
<div class="profile-edit-usersearch-wrapper">
|
<div class="profile-edit-usersearch-wrapper">
|
||||||
<Autosuggest
|
<Autosuggest
|
||||||
:filter="filterUnMutedUsers"
|
:filter="filterUnMutedUsers"
|
||||||
|
@ -563,6 +568,59 @@
|
||||||
</template>
|
</template>
|
||||||
</MuteList>
|
</MuteList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div :label="$t('settings.domain_mutes')">
|
||||||
|
<div class="profile-edit-domain-mute-form">
|
||||||
|
<input
|
||||||
|
v-model="newDomainToMute"
|
||||||
|
:placeholder="$t('settings.type_domains_to_mute')"
|
||||||
|
type="text"
|
||||||
|
@keyup.enter="muteDomain"
|
||||||
|
>
|
||||||
|
<ProgressButton
|
||||||
|
class="btn btn-default"
|
||||||
|
:click="muteDomain"
|
||||||
|
>
|
||||||
|
{{ $t('domain_mute_card.mute') }}
|
||||||
|
<template slot="progress">
|
||||||
|
{{ $t('domain_mute_card.mute_progress') }}
|
||||||
|
</template>
|
||||||
|
</ProgressButton>
|
||||||
|
</div>
|
||||||
|
<DomainMuteList
|
||||||
|
:refresh="true"
|
||||||
|
:get-key="identity"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
slot="header"
|
||||||
|
slot-scope="{selected}"
|
||||||
|
>
|
||||||
|
<div class="profile-edit-bulk-actions">
|
||||||
|
<ProgressButton
|
||||||
|
v-if="selected.length > 0"
|
||||||
|
class="btn btn-default"
|
||||||
|
:click="() => unmuteDomains(selected)"
|
||||||
|
>
|
||||||
|
{{ $t('domain_mute_card.unmute') }}
|
||||||
|
<template slot="progress">
|
||||||
|
{{ $t('domain_mute_card.unmute_progress') }}
|
||||||
|
</template>
|
||||||
|
</ProgressButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
slot="item"
|
||||||
|
slot-scope="{item}"
|
||||||
|
>
|
||||||
|
<DomainMuteCard :domain="item" />
|
||||||
|
</template>
|
||||||
|
<template slot="empty">
|
||||||
|
{{ $t('settings.no_mutes') }}
|
||||||
|
</template>
|
||||||
|
</DomainMuteList>
|
||||||
|
</div>
|
||||||
|
</tab-switcher>
|
||||||
|
</div>
|
||||||
</tab-switcher>
|
</tab-switcher>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -639,6 +697,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-domain-mute-form {
|
||||||
|
padding: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-top: 1em;
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.setting-subitem {
|
.setting-subitem {
|
||||||
margin-left: 1.75em;
|
margin-left: 1.75em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,26 @@
|
||||||
"mrf_policy_simple_media_removal": "Media Removal",
|
"mrf_policy_simple_media_removal": "Media Removal",
|
||||||
"mrf_policy_simple_media_removal_desc": "This instance removes media from posts on the following instances:",
|
"mrf_policy_simple_media_removal_desc": "This instance removes media from posts on the following instances:",
|
||||||
"mrf_policy_simple_media_nsfw": "Media Force-set As Sensitive",
|
"mrf_policy_simple_media_nsfw": "Media Force-set As Sensitive",
|
||||||
"mrf_policy_simple_media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
|
"mrf_policy_simple_media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:",
|
||||||
|
"mrf": {
|
||||||
|
"keyword": {
|
||||||
|
"keyword_policies": "Keyword Policies",
|
||||||
|
"ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
|
||||||
|
"reject": "Reject",
|
||||||
|
"replace": "Replace",
|
||||||
|
"is_replaced_by": "→"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Chat"
|
"title": "Chat"
|
||||||
},
|
},
|
||||||
|
"domain_mute_card": {
|
||||||
|
"mute": "Mute",
|
||||||
|
"mute_progress": "Muting...",
|
||||||
|
"unmute": "Unmute",
|
||||||
|
"unmute_progress": "Unmuting..."
|
||||||
|
},
|
||||||
"exporter": {
|
"exporter": {
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
"processing": "Processing, you'll soon be asked to download your file"
|
"processing": "Processing, you'll soon be asked to download your file"
|
||||||
|
@ -111,7 +126,8 @@
|
||||||
"read": "Read!",
|
"read": "Read!",
|
||||||
"repeated_you": "repeated your status",
|
"repeated_you": "repeated your status",
|
||||||
"no_more_notifications": "No more notifications",
|
"no_more_notifications": "No more notifications",
|
||||||
"migrated_to": "migrated to"
|
"migrated_to": "migrated to",
|
||||||
|
"reacted_with": "reacted with {0}"
|
||||||
},
|
},
|
||||||
"polls": {
|
"polls": {
|
||||||
"add_poll": "Add Poll",
|
"add_poll": "Add Poll",
|
||||||
|
@ -226,6 +242,7 @@
|
||||||
"desc": "To enable two-factor authentication, enter the code from your two-factor app:"
|
"desc": "To enable two-factor authentication, enter the code from your two-factor app:"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"allow_following_move": "Allow auto-follow when following account moves",
|
||||||
"attachmentRadius": "Attachments",
|
"attachmentRadius": "Attachments",
|
||||||
"attachments": "Attachments",
|
"attachments": "Attachments",
|
||||||
"autoload": "Enable automatic loading when scrolled to the bottom",
|
"autoload": "Enable automatic loading when scrolled to the bottom",
|
||||||
|
@ -264,8 +281,10 @@
|
||||||
"delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
|
"delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
|
||||||
"delete_account_instructions": "Type your password in the input below to confirm account deletion.",
|
"delete_account_instructions": "Type your password in the input below to confirm account deletion.",
|
||||||
"discoverable": "Allow discovery of this account in search results and other services",
|
"discoverable": "Allow discovery of this account in search results and other services",
|
||||||
|
"domain_mutes": "Domains",
|
||||||
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
|
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
|
||||||
"pad_emoji": "Pad emoji with spaces when adding from picker",
|
"pad_emoji": "Pad emoji with spaces when adding from picker",
|
||||||
|
"emoji_reactions_on_timeline": "Show emoji reactions on timeline",
|
||||||
"export_theme": "Save preset",
|
"export_theme": "Save preset",
|
||||||
"filtering": "Filtering",
|
"filtering": "Filtering",
|
||||||
"filtering_explanation": "All statuses containing these words will be muted, one per line",
|
"filtering_explanation": "All statuses containing these words will be muted, one per line",
|
||||||
|
@ -314,6 +333,7 @@
|
||||||
"notification_visibility_mentions": "Mentions",
|
"notification_visibility_mentions": "Mentions",
|
||||||
"notification_visibility_repeats": "Repeats",
|
"notification_visibility_repeats": "Repeats",
|
||||||
"notification_visibility_moves": "User Migrates",
|
"notification_visibility_moves": "User Migrates",
|
||||||
|
"notification_visibility_emoji_reactions": "Reactions",
|
||||||
"no_rich_text_description": "Strip rich text formatting from all posts",
|
"no_rich_text_description": "Strip rich text formatting from all posts",
|
||||||
"no_blocks": "No blocks",
|
"no_blocks": "No blocks",
|
||||||
"no_mutes": "No mutes",
|
"no_mutes": "No mutes",
|
||||||
|
@ -361,6 +381,7 @@
|
||||||
"post_status_content_type": "Post status content type",
|
"post_status_content_type": "Post status content type",
|
||||||
"stop_gifs": "Play-on-hover GIFs",
|
"stop_gifs": "Play-on-hover GIFs",
|
||||||
"streaming": "Enable automatic streaming of new posts when scrolled to the top",
|
"streaming": "Enable automatic streaming of new posts when scrolled to the top",
|
||||||
|
"user_mutes": "Users",
|
||||||
"useStreamingApi": "Receive posts and notifications real-time",
|
"useStreamingApi": "Receive posts and notifications real-time",
|
||||||
"useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
|
"useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
|
||||||
"text": "Text",
|
"text": "Text",
|
||||||
|
@ -369,6 +390,7 @@
|
||||||
"theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
|
"theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
|
||||||
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
|
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
|
||||||
"tooltipRadius": "Tooltips/alerts",
|
"tooltipRadius": "Tooltips/alerts",
|
||||||
|
"type_domains_to_mute": "Type in domains to mute",
|
||||||
"upload_a_photo": "Upload a photo",
|
"upload_a_photo": "Upload a photo",
|
||||||
"user_settings": "User Settings",
|
"user_settings": "User Settings",
|
||||||
"values": {
|
"values": {
|
||||||
|
@ -640,6 +662,7 @@
|
||||||
"repeat": "Repeat",
|
"repeat": "Repeat",
|
||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
"favorite": "Favorite",
|
"favorite": "Favorite",
|
||||||
|
"add_reaction": "Add Reaction",
|
||||||
"user_settings": "User Settings"
|
"user_settings": "User Settings"
|
||||||
},
|
},
|
||||||
"upload":{
|
"upload":{
|
||||||
|
|
|
@ -53,7 +53,8 @@
|
||||||
"notifications": "Ilmoitukset",
|
"notifications": "Ilmoitukset",
|
||||||
"read": "Lue!",
|
"read": "Lue!",
|
||||||
"repeated_you": "toisti viestisi",
|
"repeated_you": "toisti viestisi",
|
||||||
"no_more_notifications": "Ei enempää ilmoituksia"
|
"no_more_notifications": "Ei enempää ilmoituksia",
|
||||||
|
"reacted_with": "lisäsi reaktion {0}"
|
||||||
},
|
},
|
||||||
"polls": {
|
"polls": {
|
||||||
"add_poll": "Lisää äänestys",
|
"add_poll": "Lisää äänestys",
|
||||||
|
@ -140,6 +141,7 @@
|
||||||
"delete_account_description": "Poista tilisi ja viestisi pysyvästi.",
|
"delete_account_description": "Poista tilisi ja viestisi pysyvästi.",
|
||||||
"delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.",
|
"delete_account_error": "Virhe poistaessa tiliäsi. Jos virhe jatkuu, ota yhteyttä palvelimesi ylläpitoon.",
|
||||||
"delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.",
|
"delete_account_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.",
|
||||||
|
"emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla",
|
||||||
"export_theme": "Tallenna teema",
|
"export_theme": "Tallenna teema",
|
||||||
"filtering": "Suodatus",
|
"filtering": "Suodatus",
|
||||||
"filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.",
|
"filtering_explanation": "Kaikki viestit, jotka sisältävät näitä sanoja, suodatetaan. Yksi sana per rivi.",
|
||||||
|
@ -183,6 +185,7 @@
|
||||||
"notification_visibility_likes": "Tykkäykset",
|
"notification_visibility_likes": "Tykkäykset",
|
||||||
"notification_visibility_mentions": "Maininnat",
|
"notification_visibility_mentions": "Maininnat",
|
||||||
"notification_visibility_repeats": "Toistot",
|
"notification_visibility_repeats": "Toistot",
|
||||||
|
"notification_visibility_emoji_reactions": "Reaktiot",
|
||||||
"no_rich_text_description": "Älä näytä tekstin muotoilua.",
|
"no_rich_text_description": "Älä näytä tekstin muotoilua.",
|
||||||
"hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
|
"hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
|
||||||
"nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
|
"nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import EventTargetPolyfill from '@ungap/event-target'
|
||||||
|
|
||||||
|
try {
|
||||||
|
/* eslint-disable no-new */
|
||||||
|
new EventTarget()
|
||||||
|
/* eslint-enable no-new */
|
||||||
|
} catch (e) {
|
||||||
|
window.EventTarget = EventTargetPolyfill
|
||||||
|
}
|
|
@ -2,6 +2,9 @@ import Vue from 'vue'
|
||||||
import VueRouter from 'vue-router'
|
import VueRouter from 'vue-router'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
|
import 'custom-event-polyfill'
|
||||||
|
import './lib/event_target_polyfill.js'
|
||||||
|
|
||||||
import interfaceModule from './modules/interface.js'
|
import interfaceModule from './modules/interface.js'
|
||||||
import instanceModule from './modules/instance.js'
|
import instanceModule from './modules/instance.js'
|
||||||
import statusesModule from './modules/statuses.js'
|
import statusesModule from './modules/statuses.js'
|
||||||
|
|
|
@ -146,6 +146,7 @@ const api = {
|
||||||
startFetchingFollowRequests (store) {
|
startFetchingFollowRequests (store) {
|
||||||
if (store.state.fetchers['followRequests']) return
|
if (store.state.fetchers['followRequests']) return
|
||||||
const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
|
const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
|
||||||
|
|
||||||
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
|
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
|
||||||
},
|
},
|
||||||
stopFetchingFollowRequests (store) {
|
stopFetchingFollowRequests (store) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ export const defaultState = {
|
||||||
autoLoad: true,
|
autoLoad: true,
|
||||||
streaming: false,
|
streaming: false,
|
||||||
hoverPreview: true,
|
hoverPreview: true,
|
||||||
|
emojiReactionsOnTimeline: true,
|
||||||
autohideFloatingPostButton: false,
|
autohideFloatingPostButton: false,
|
||||||
pauseOnUnfocused: true,
|
pauseOnUnfocused: true,
|
||||||
stopGifs: false,
|
stopGifs: false,
|
||||||
|
@ -29,7 +30,8 @@ export const defaultState = {
|
||||||
mentions: true,
|
mentions: true,
|
||||||
likes: true,
|
likes: true,
|
||||||
repeats: true,
|
repeats: true,
|
||||||
moves: true
|
moves: true,
|
||||||
|
emojiReactions: false
|
||||||
},
|
},
|
||||||
webPushNotifications: false,
|
webPushNotifications: false,
|
||||||
muteWords: [],
|
muteWords: [],
|
||||||
|
|
|
@ -1,4 +1,17 @@
|
||||||
import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash'
|
import {
|
||||||
|
remove,
|
||||||
|
slice,
|
||||||
|
each,
|
||||||
|
findIndex,
|
||||||
|
find,
|
||||||
|
maxBy,
|
||||||
|
minBy,
|
||||||
|
merge,
|
||||||
|
first,
|
||||||
|
last,
|
||||||
|
isArray,
|
||||||
|
omitBy
|
||||||
|
} from 'lodash'
|
||||||
import { set } from 'vue'
|
import { set } from 'vue'
|
||||||
import apiService from '../services/api/api.service.js'
|
import apiService from '../services/api/api.service.js'
|
||||||
// import parse from '../services/status_parser/status_parser.js'
|
// import parse from '../services/status_parser/status_parser.js'
|
||||||
|
@ -68,7 +81,8 @@ const visibleNotificationTypes = (rootState) => {
|
||||||
rootState.config.notificationVisibility.mentions && 'mention',
|
rootState.config.notificationVisibility.mentions && 'mention',
|
||||||
rootState.config.notificationVisibility.repeats && 'repeat',
|
rootState.config.notificationVisibility.repeats && 'repeat',
|
||||||
rootState.config.notificationVisibility.follows && 'follow',
|
rootState.config.notificationVisibility.follows && 'follow',
|
||||||
rootState.config.notificationVisibility.moves && 'move'
|
rootState.config.notificationVisibility.moves && 'move',
|
||||||
|
rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reactions'
|
||||||
].filter(_ => _)
|
].filter(_ => _)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,6 +326,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
||||||
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
|
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (notification.type === 'pleroma:emoji_reaction') {
|
||||||
|
dispatch('fetchEmojiReactionsBy', notification.status.id)
|
||||||
|
}
|
||||||
|
|
||||||
// Only add a new notification if we don't have one for the same action
|
// Only add a new notification if we don't have one for the same action
|
||||||
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
|
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
|
||||||
state.notifications.maxId = notification.id > state.notifications.maxId
|
state.notifications.maxId = notification.id > state.notifications.maxId
|
||||||
|
@ -345,7 +363,9 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i18nString) {
|
if (notification.type === 'pleroma:emoji_reaction') {
|
||||||
|
notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji])
|
||||||
|
} else if (i18nString) {
|
||||||
notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
|
notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
|
||||||
} else {
|
} else {
|
||||||
notifObj.body = notification.status.text
|
notifObj.body = notification.status.text
|
||||||
|
@ -358,10 +378,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) {
|
if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) {
|
||||||
let notification = new window.Notification(title, notifObj)
|
let desktopNotification = new window.Notification(title, notifObj)
|
||||||
// Chrome is known for not closing notifications automatically
|
// Chrome is known for not closing notifications automatically
|
||||||
// according to MDN, anyway.
|
// according to MDN, anyway.
|
||||||
setTimeout(notification.close.bind(notification), 5000)
|
setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (notification.seen) {
|
} else if (notification.seen) {
|
||||||
|
@ -518,6 +538,53 @@ export const mutations = {
|
||||||
newStatus.fave_num = newStatus.favoritedBy.length
|
newStatus.fave_num = newStatus.favoritedBy.length
|
||||||
newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id)
|
newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id)
|
||||||
},
|
},
|
||||||
|
addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) {
|
||||||
|
const status = state.allStatusesObject[id]
|
||||||
|
set(status, 'emoji_reactions', emojiReactions)
|
||||||
|
},
|
||||||
|
addOwnReaction (state, { id, emoji, currentUser }) {
|
||||||
|
const status = state.allStatusesObject[id]
|
||||||
|
const reactionIndex = findIndex(status.emoji_reactions, { name: emoji })
|
||||||
|
const reaction = status.emoji_reactions[reactionIndex] || { name: emoji, count: 0, accounts: [] }
|
||||||
|
|
||||||
|
const newReaction = {
|
||||||
|
...reaction,
|
||||||
|
count: reaction.count + 1,
|
||||||
|
me: true,
|
||||||
|
accounts: [
|
||||||
|
...reaction.accounts,
|
||||||
|
currentUser
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update count of existing reaction if it exists, otherwise append at the end
|
||||||
|
if (reactionIndex >= 0) {
|
||||||
|
set(status.emoji_reactions, reactionIndex, newReaction)
|
||||||
|
} else {
|
||||||
|
set(status, 'emoji_reactions', [...status.emoji_reactions, newReaction])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeOwnReaction (state, { id, emoji, currentUser }) {
|
||||||
|
const status = state.allStatusesObject[id]
|
||||||
|
const reactionIndex = findIndex(status.emoji_reactions, { name: emoji })
|
||||||
|
if (reactionIndex < 0) return
|
||||||
|
|
||||||
|
const reaction = status.emoji_reactions[reactionIndex]
|
||||||
|
const accounts = reaction.accounts || []
|
||||||
|
|
||||||
|
const newReaction = {
|
||||||
|
...reaction,
|
||||||
|
count: reaction.count - 1,
|
||||||
|
me: false,
|
||||||
|
accounts: accounts.filter(acc => acc.id !== currentUser.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newReaction.count > 0) {
|
||||||
|
set(status.emoji_reactions, reactionIndex, newReaction)
|
||||||
|
} else {
|
||||||
|
set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.name !== emoji))
|
||||||
|
}
|
||||||
|
},
|
||||||
updateStatusWithPoll (state, { id, poll }) {
|
updateStatusWithPoll (state, { id, poll }) {
|
||||||
const status = state.allStatusesObject[id]
|
const status = state.allStatusesObject[id]
|
||||||
status.poll = poll
|
status.poll = poll
|
||||||
|
@ -622,6 +689,35 @@ const statuses = {
|
||||||
commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
|
commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
reactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
|
||||||
|
const currentUser = rootState.users.currentUser
|
||||||
|
if (!currentUser) return
|
||||||
|
|
||||||
|
commit('addOwnReaction', { id, emoji, currentUser })
|
||||||
|
rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(
|
||||||
|
ok => {
|
||||||
|
dispatch('fetchEmojiReactionsBy', id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
unreactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
|
||||||
|
const currentUser = rootState.users.currentUser
|
||||||
|
if (!currentUser) return
|
||||||
|
|
||||||
|
commit('removeOwnReaction', { id, emoji, currentUser })
|
||||||
|
rootState.api.backendInteractor.unreactWithEmoji({ id, emoji }).then(
|
||||||
|
ok => {
|
||||||
|
dispatch('fetchEmojiReactionsBy', id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
fetchEmojiReactionsBy ({ rootState, commit }, id) {
|
||||||
|
rootState.api.backendInteractor.fetchEmojiReactions({ id }).then(
|
||||||
|
emojiReactions => {
|
||||||
|
commit('addEmojiReactionsBy', { id, emojiReactions, currentUser: rootState.users.currentUser })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
fetchFavs ({ rootState, commit }, id) {
|
fetchFavs ({ rootState, commit }, id) {
|
||||||
rootState.api.backendInteractor.fetchFavoritedByUsers({ id })
|
rootState.api.backendInteractor.fetchFavoritedByUsers({ id })
|
||||||
.then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
|
.then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
|
||||||
|
|
|
@ -72,6 +72,16 @@ const showReblogs = (store, userId) => {
|
||||||
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const muteDomain = (store, domain) => {
|
||||||
|
return store.rootState.api.backendInteractor.muteDomain({ domain })
|
||||||
|
.then(() => store.commit('addDomainMute', domain))
|
||||||
|
}
|
||||||
|
|
||||||
|
const unmuteDomain = (store, domain) => {
|
||||||
|
return store.rootState.api.backendInteractor.unmuteDomain({ domain })
|
||||||
|
.then(() => store.commit('removeDomainMute', domain))
|
||||||
|
}
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
setMuted (state, { user: { id }, muted }) {
|
setMuted (state, { user: { id }, muted }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
|
@ -177,6 +187,20 @@ export const mutations = {
|
||||||
state.currentUser.muteIds.push(muteId)
|
state.currentUser.muteIds.push(muteId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
saveDomainMutes (state, domainMutes) {
|
||||||
|
state.currentUser.domainMutes = domainMutes
|
||||||
|
},
|
||||||
|
addDomainMute (state, domain) {
|
||||||
|
if (state.currentUser.domainMutes.indexOf(domain) === -1) {
|
||||||
|
state.currentUser.domainMutes.push(domain)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeDomainMute (state, domain) {
|
||||||
|
const index = state.currentUser.domainMutes.indexOf(domain)
|
||||||
|
if (index !== -1) {
|
||||||
|
state.currentUser.domainMutes.splice(index, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
setPinnedToUser (state, status) {
|
setPinnedToUser (state, status) {
|
||||||
const user = state.usersObject[status.user.id]
|
const user = state.usersObject[status.user.id]
|
||||||
const index = user.pinnedStatusIds.indexOf(status.id)
|
const index = user.pinnedStatusIds.indexOf(status.id)
|
||||||
|
@ -297,6 +321,25 @@ const users = {
|
||||||
unmuteUsers (store, ids = []) {
|
unmuteUsers (store, ids = []) {
|
||||||
return Promise.all(ids.map(id => unmuteUser(store, id)))
|
return Promise.all(ids.map(id => unmuteUser(store, id)))
|
||||||
},
|
},
|
||||||
|
fetchDomainMutes (store) {
|
||||||
|
return store.rootState.api.backendInteractor.fetchDomainMutes()
|
||||||
|
.then((domainMutes) => {
|
||||||
|
store.commit('saveDomainMutes', domainMutes)
|
||||||
|
return domainMutes
|
||||||
|
})
|
||||||
|
},
|
||||||
|
muteDomain (store, domain) {
|
||||||
|
return muteDomain(store, domain)
|
||||||
|
},
|
||||||
|
unmuteDomain (store, domain) {
|
||||||
|
return unmuteDomain(store, domain)
|
||||||
|
},
|
||||||
|
muteDomains (store, domains = []) {
|
||||||
|
return Promise.all(domains.map(domain => muteDomain(store, domain)))
|
||||||
|
},
|
||||||
|
unmuteDomains (store, domain = []) {
|
||||||
|
return Promise.all(domain.map(domain => unmuteDomain(store, domain)))
|
||||||
|
},
|
||||||
fetchFriends ({ rootState, commit }, id) {
|
fetchFriends ({ rootState, commit }, id) {
|
||||||
const user = rootState.users.usersObject[id]
|
const user = rootState.users.usersObject[id]
|
||||||
const maxId = last(user.friendIds)
|
const maxId = last(user.friendIds)
|
||||||
|
@ -460,6 +503,7 @@ const users = {
|
||||||
user.credentials = accessToken
|
user.credentials = accessToken
|
||||||
user.blockIds = []
|
user.blockIds = []
|
||||||
user.muteIds = []
|
user.muteIds = []
|
||||||
|
user.domainMutes = []
|
||||||
commit('setCurrentUser', user)
|
commit('setCurrentUser', user)
|
||||||
commit('addNewUsers', [user])
|
commit('addNewUsers', [user])
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,11 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
|
||||||
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
|
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
|
||||||
const MASTODON_SEARCH_2 = `/api/v2/search`
|
const MASTODON_SEARCH_2 = `/api/v2/search`
|
||||||
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
|
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
|
||||||
|
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
|
||||||
const MASTODON_STREAMING = '/api/v1/streaming'
|
const MASTODON_STREAMING = '/api/v1/streaming'
|
||||||
|
const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions`
|
||||||
|
const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
|
||||||
|
const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
|
||||||
|
|
||||||
const oldfetch = window.fetch
|
const oldfetch = window.fetch
|
||||||
|
|
||||||
|
@ -491,7 +495,8 @@ const fetchTimeline = ({
|
||||||
until = false,
|
until = false,
|
||||||
userId = false,
|
userId = false,
|
||||||
tag = false,
|
tag = false,
|
||||||
withMuted = false
|
withMuted = false,
|
||||||
|
withMove = false
|
||||||
}) => {
|
}) => {
|
||||||
const timelineUrls = {
|
const timelineUrls = {
|
||||||
public: MASTODON_PUBLIC_TIMELINE,
|
public: MASTODON_PUBLIC_TIMELINE,
|
||||||
|
@ -531,6 +536,9 @@ const fetchTimeline = ({
|
||||||
if (timeline === 'public' || timeline === 'publicAndExternal') {
|
if (timeline === 'public' || timeline === 'publicAndExternal') {
|
||||||
params.push(['only_media', false])
|
params.push(['only_media', false])
|
||||||
}
|
}
|
||||||
|
if (timeline === 'notifications') {
|
||||||
|
params.push(['with_move', withMove])
|
||||||
|
}
|
||||||
|
|
||||||
params.push(['count', 20])
|
params.push(['count', 20])
|
||||||
params.push(['with_muted', withMuted])
|
params.push(['with_muted', withMuted])
|
||||||
|
@ -880,6 +888,30 @@ const fetchRebloggedByUsers = ({ id }) => {
|
||||||
return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
|
return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchEmojiReactions = ({ id, credentials }) => {
|
||||||
|
return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id), credentials })
|
||||||
|
.then((reactions) => reactions.map(r => {
|
||||||
|
r.accounts = r.accounts.map(parseUser)
|
||||||
|
return r
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const reactWithEmoji = ({ id, emoji, credentials }) => {
|
||||||
|
return promisedRequest({
|
||||||
|
url: PLEROMA_EMOJI_REACT_URL(id, emoji),
|
||||||
|
method: 'PUT',
|
||||||
|
credentials
|
||||||
|
}).then(parseStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
const unreactWithEmoji = ({ id, emoji, credentials }) => {
|
||||||
|
return promisedRequest({
|
||||||
|
url: PLEROMA_EMOJI_UNREACT_URL(id, emoji),
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials
|
||||||
|
}).then(parseStatus)
|
||||||
|
}
|
||||||
|
|
||||||
const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
|
const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
|
||||||
return promisedRequest({
|
return promisedRequest({
|
||||||
url: MASTODON_REPORT_USER_URL,
|
url: MASTODON_REPORT_USER_URL,
|
||||||
|
@ -948,6 +980,28 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchDomainMutes = ({ credentials }) => {
|
||||||
|
return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
|
||||||
|
}
|
||||||
|
|
||||||
|
const muteDomain = ({ domain, credentials }) => {
|
||||||
|
return promisedRequest({
|
||||||
|
url: MASTODON_DOMAIN_BLOCKS_URL,
|
||||||
|
method: 'POST',
|
||||||
|
payload: { domain },
|
||||||
|
credentials
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const unmuteDomain = ({ domain, credentials }) => {
|
||||||
|
return promisedRequest({
|
||||||
|
url: MASTODON_DOMAIN_BLOCKS_URL,
|
||||||
|
method: 'DELETE',
|
||||||
|
payload: { domain },
|
||||||
|
credentials
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
|
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
|
||||||
return Object.entries({
|
return Object.entries({
|
||||||
...(credentials
|
...(credentials
|
||||||
|
@ -1107,10 +1161,16 @@ const apiService = {
|
||||||
fetchPoll,
|
fetchPoll,
|
||||||
fetchFavoritedByUsers,
|
fetchFavoritedByUsers,
|
||||||
fetchRebloggedByUsers,
|
fetchRebloggedByUsers,
|
||||||
|
fetchEmojiReactions,
|
||||||
|
reactWithEmoji,
|
||||||
|
unreactWithEmoji,
|
||||||
reportUser,
|
reportUser,
|
||||||
updateNotificationSettings,
|
updateNotificationSettings,
|
||||||
search2,
|
search2,
|
||||||
searchUsers
|
searchUsers,
|
||||||
|
fetchDomainMutes,
|
||||||
|
muteDomain,
|
||||||
|
unmuteDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
export default apiService
|
export default apiService
|
||||||
|
|
|
@ -16,7 +16,7 @@ const backendInteractorService = credentials => ({
|
||||||
return notificationsFetcher.fetchAndUpdate({ store, credentials })
|
return notificationsFetcher.fetchAndUpdate({ store, credentials })
|
||||||
},
|
},
|
||||||
|
|
||||||
startFetchingFollowRequest ({ store }) {
|
startFetchingFollowRequests ({ store }) {
|
||||||
return followRequestFetcher.startFetching({ store, credentials })
|
return followRequestFetcher.startFetching({ store, credentials })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import escape from 'escape-html'
|
||||||
|
|
||||||
const qvitterStatusType = (status) => {
|
const qvitterStatusType = (status) => {
|
||||||
if (status.is_post_verb) {
|
if (status.is_post_verb) {
|
||||||
return 'status'
|
return 'status'
|
||||||
|
@ -41,7 +43,7 @@ export const parseUser = (data) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
output.name = data.display_name
|
output.name = data.display_name
|
||||||
output.name_html = addEmojis(data.display_name, data.emojis)
|
output.name_html = addEmojis(escape(data.display_name), data.emojis)
|
||||||
|
|
||||||
output.description = data.note
|
output.description = data.note
|
||||||
output.description_html = addEmojis(data.note, data.emojis)
|
output.description_html = addEmojis(data.note, data.emojis)
|
||||||
|
@ -81,6 +83,8 @@ export const parseUser = (data) => {
|
||||||
output.subscribed = relationship.subscribing
|
output.subscribed = relationship.subscribing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output.allow_following_move = data.pleroma.allow_following_move
|
||||||
|
|
||||||
output.hide_follows = data.pleroma.hide_follows
|
output.hide_follows = data.pleroma.hide_follows
|
||||||
output.hide_followers = data.pleroma.hide_followers
|
output.hide_followers = data.pleroma.hide_followers
|
||||||
output.hide_follows_count = data.pleroma.hide_follows_count
|
output.hide_follows_count = data.pleroma.hide_follows_count
|
||||||
|
@ -242,6 +246,7 @@ export const parseStatus = (data) => {
|
||||||
output.is_local = pleroma.local
|
output.is_local = pleroma.local
|
||||||
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
|
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
|
||||||
output.thread_muted = pleroma.thread_muted
|
output.thread_muted = pleroma.thread_muted
|
||||||
|
output.emoji_reactions = pleroma.emoji_reactions
|
||||||
} else {
|
} else {
|
||||||
output.text = data.content
|
output.text = data.content
|
||||||
output.summary = data.spoiler_text
|
output.summary = data.spoiler_text
|
||||||
|
@ -255,7 +260,7 @@ export const parseStatus = (data) => {
|
||||||
output.retweeted_status = parseStatus(data.reblog)
|
output.retweeted_status = parseStatus(data.reblog)
|
||||||
}
|
}
|
||||||
|
|
||||||
output.summary_html = addEmojis(data.spoiler_text, data.emojis)
|
output.summary_html = addEmojis(escape(data.spoiler_text), data.emojis)
|
||||||
output.external_url = data.url
|
output.external_url = data.url
|
||||||
output.poll = data.poll
|
output.poll = data.poll
|
||||||
output.pinned = data.pinned
|
output.pinned = data.pinned
|
||||||
|
@ -349,6 +354,7 @@ export const parseNotification = (data) => {
|
||||||
? null
|
? null
|
||||||
: parseUser(data.target)
|
: parseUser(data.target)
|
||||||
output.from_profile = parseUser(data.account)
|
output.from_profile = parseUser(data.account)
|
||||||
|
output.emoji = data.emoji
|
||||||
} else {
|
} else {
|
||||||
const parsedNotice = parseStatus(data.notice)
|
const parsedNotice = parseStatus(data.notice)
|
||||||
output.type = data.ntype
|
output.type = data.ntype
|
||||||
|
|
|
@ -7,7 +7,8 @@ export const visibleTypes = store => ([
|
||||||
store.state.config.notificationVisibility.mentions && 'mention',
|
store.state.config.notificationVisibility.mentions && 'mention',
|
||||||
store.state.config.notificationVisibility.repeats && 'repeat',
|
store.state.config.notificationVisibility.repeats && 'repeat',
|
||||||
store.state.config.notificationVisibility.follows && 'follow',
|
store.state.config.notificationVisibility.follows && 'follow',
|
||||||
store.state.config.notificationVisibility.moves && 'move'
|
store.state.config.notificationVisibility.moves && 'move',
|
||||||
|
store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction'
|
||||||
].filter(_ => _))
|
].filter(_ => _))
|
||||||
|
|
||||||
const sortById = (a, b) => {
|
const sortById = (a, b) => {
|
||||||
|
|
|
@ -11,9 +11,12 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
|
||||||
const rootState = store.rootState || store.state
|
const rootState = store.rootState || store.state
|
||||||
const timelineData = rootState.statuses.notifications
|
const timelineData = rootState.statuses.notifications
|
||||||
const hideMutedPosts = getters.mergedConfig.hideMutedPosts
|
const hideMutedPosts = getters.mergedConfig.hideMutedPosts
|
||||||
|
const allowFollowingMove = rootState.users.currentUser.allow_following_move
|
||||||
|
|
||||||
args['withMuted'] = !hideMutedPosts
|
args['withMuted'] = !hideMutedPosts
|
||||||
|
|
||||||
|
args['withMove'] = !allowFollowingMove
|
||||||
|
|
||||||
args['timeline'] = 'notifications'
|
args['timeline'] = 'notifications'
|
||||||
if (older) {
|
if (older) {
|
||||||
if (timelineData.minId !== Number.POSITIVE_INFINITY) {
|
if (timelineData.minId !== Number.POSITIVE_INFINITY) {
|
||||||
|
|
|
@ -339,6 +339,12 @@
|
||||||
"css": "arrow-curved",
|
"css": "arrow-curved",
|
||||||
"code": 59426,
|
"code": 59426,
|
||||||
"src": "iconic"
|
"src": "iconic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1",
|
||||||
|
"css": "link",
|
||||||
|
"code": 59427,
|
||||||
|
"src": "fontawesome"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -241,6 +241,54 @@ describe('Statuses module', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('emojiReactions', () => {
|
||||||
|
it('increments count in existing reaction', () => {
|
||||||
|
const state = defaultState()
|
||||||
|
const status = makeMockStatus({ id: '1' })
|
||||||
|
status.emoji_reactions = [ { name: '😂', count: 1, accounts: [] } ]
|
||||||
|
|
||||||
|
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
|
||||||
|
mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(2)
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true)
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds a new reaction', () => {
|
||||||
|
const state = defaultState()
|
||||||
|
const status = makeMockStatus({ id: '1' })
|
||||||
|
status.emoji_reactions = []
|
||||||
|
|
||||||
|
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
|
||||||
|
mutations.addOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1)
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(true)
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions[0].accounts[0].id).to.eql('me')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('decreases count in existing reaction', () => {
|
||||||
|
const state = defaultState()
|
||||||
|
const status = makeMockStatus({ id: '1' })
|
||||||
|
status.emoji_reactions = [ { name: '😂', count: 2, accounts: [{ id: 'me' }] } ]
|
||||||
|
|
||||||
|
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
|
||||||
|
mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1)
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions[0].me).to.eql(false)
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions[0].accounts).to.eql([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes a reaction', () => {
|
||||||
|
const state = defaultState()
|
||||||
|
const status = makeMockStatus({ id: '1' })
|
||||||
|
status.emoji_reactions = [{ name: '😂', count: 1, accounts: [{ id: 'me' }] }]
|
||||||
|
|
||||||
|
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
|
||||||
|
mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
|
||||||
|
expect(state.allStatusesObject['1'].emoji_reactions.length).to.eql(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('showNewStatuses', () => {
|
describe('showNewStatuses', () => {
|
||||||
it('resets the minId to the min of the visible statuses when adding new to visible statuses', () => {
|
it('resets the minId to the min of the visible statuses when adding new to visible statuses', () => {
|
||||||
const state = defaultState()
|
const state = defaultState()
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -710,6 +710,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
qrcode "^1.3.0"
|
qrcode "^1.3.0"
|
||||||
|
|
||||||
|
"@ungap/event-target@^0.1.0":
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@ungap/event-target/-/event-target-0.1.0.tgz#88d527d40de86c4b0c99a060ca241d755999915b"
|
||||||
|
integrity sha512-W2oyj0Fe1w/XhPZjkI3oUcDUAmu5P4qsdT2/2S8aMhtAWM/CE/jYWtji0pKNPDfxLI75fa5gWSEmnynKMNP/oA==
|
||||||
|
|
||||||
"@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
|
"@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
|
resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
|
||||||
|
@ -2281,6 +2286,11 @@ currently-unhandled@^0.4.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
array-find-index "^1.0.1"
|
array-find-index "^1.0.1"
|
||||||
|
|
||||||
|
custom-event-polyfill@^1.0.7:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
|
||||||
|
integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
|
||||||
|
|
||||||
custom-event@~1.0.0:
|
custom-event@~1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
|
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
|
||||||
|
@ -2747,9 +2757,10 @@ es6-promisify@^5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-promise "^4.0.3"
|
es6-promise "^4.0.3"
|
||||||
|
|
||||||
escape-html@~1.0.3:
|
escape-html@^1.0.3, escape-html@~1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||||
|
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||||
|
|
||||||
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
|
|
Loading…
Reference in New Issue