From d0075026290c90d8406c7ac81413259a8ae58ec7 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Fri, 15 Nov 2019 08:39:21 +0200
Subject: [PATCH 01/30] add fetching for emoji reactions, draft design
---
src/components/conversation/conversation.js | 1 +
src/components/status/status.js | 6 ++++
src/components/status/status.vue | 28 +++++++++++++++++++
src/modules/statuses.js | 14 +++++++++-
src/services/api/api.service.js | 6 ++++
.../backend_interactor_service.js | 2 ++
6 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 72ee9c39..715804ff 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -149,6 +149,7 @@ const conversation = {
if (!id) return
this.highlight = id
this.$store.dispatch('fetchFavsAndRepeats', id)
+ this.$store.dispatch('fetchEmojiReactions', id)
},
getHighlight () {
return this.isExpanded ? this.highlight : null
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 4fbd5ac3..8268e615 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -278,6 +278,12 @@ const Status = {
hidePostStats () {
return this.mergedConfig.hidePostStats
},
+ emojiReactions () {
+ return {
+ '🤔': [{ 'id': 'xyz..' }, { 'id': 'zyx...' }],
+ '🐻': [{ 'id': 'abc...' }]
+ }
+ },
...mapGetters(['mergedConfig'])
},
components: {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 65778b2e..aae58a5e 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -354,6 +354,17 @@
+
+
+
+
currentUser.id === id)
},
+ addEmojiReactions (state, { id, emojiReactions, currentUser }) {
+ const status = state.allStatusesObject[id]
+ status.emojiReactions = emojiReactions
+ status.reactedWithEmoji = findKey(emojiReactions, { id: currentUser.id })
+ },
updateStatusWithPoll (state, { id, poll }) {
const status = state.allStatusesObject[id]
status.poll = poll
@@ -611,6 +616,13 @@ const statuses = {
commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
})
},
+ fetchEmojiReactions ({ rootState, commit }, id) {
+ rootState.api.backendInteractor.fetchEmojiReactions(id).then(
+ emojiReactions => {
+ commit('addEmojiReactions', { id, emojiReactions, currentUser: rootState.users.currentUser })
+ }
+ )
+ },
fetchFavs ({ rootState, commit }, id) {
rootState.api.backendInteractor.fetchFavoritedByUsers(id)
.then(favoritedByUsers => commit('addFavs', { id, favoritedByUsers, currentUser: rootState.users.currentUser }))
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8f5eb416..7ef4b74a 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -71,6 +71,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = `/api/v2/search`
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
+const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by`
const oldfetch = window.fetch
@@ -864,6 +865,10 @@ const fetchRebloggedByUsers = ({ id }) => {
return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
}
+const fetchEmojiReactions = ({ id }) => {
+ return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) })
+}
+
const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
return promisedRequest({
url: MASTODON_REPORT_USER_URL,
@@ -997,6 +1002,7 @@ const apiService = {
fetchPoll,
fetchFavoritedByUsers,
fetchRebloggedByUsers,
+ fetchEmojiReactions,
reportUser,
updateNotificationSettings,
search2,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index d6617276..52234fcc 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -143,6 +143,7 @@ const backendInteractorService = credentials => {
const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id })
const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id })
+ const fetchEmojiReactions = (id) => apiService.fetchEmojiReactions({ id })
const reportUser = (params) => apiService.reportUser({ credentials, ...params })
const favorite = (id) => apiService.favorite({ id, credentials })
@@ -210,6 +211,7 @@ const backendInteractorService = credentials => {
fetchPoll,
fetchFavoritedByUsers,
fetchRebloggedByUsers,
+ fetchEmojiReactions,
reportUser,
favorite,
unfavorite,
From de945ba3e9470b28dd010fb32f658b42053f19d3 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Fri, 15 Nov 2019 16:29:25 +0200
Subject: [PATCH 02/30] wip commit, add basic popover for emoji reaction select
---
src/components/react_button/react_button.js | 50 +++++++++++++
src/components/react_button/react_button.vue | 78 ++++++++++++++++++++
src/components/status/status.js | 2 +
src/components/status/status.vue | 10 ++-
src/i18n/en.json | 1 +
5 files changed, 138 insertions(+), 3 deletions(-)
create mode 100644 src/components/react_button/react_button.js
create mode 100644 src/components/react_button/react_button.vue
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
new file mode 100644
index 00000000..d1d15d93
--- /dev/null
+++ b/src/components/react_button/react_button.js
@@ -0,0 +1,50 @@
+import { mapGetters } from 'vuex'
+
+const ReactButton = {
+ props: ['status', 'loggedIn'],
+ data () {
+ return {
+ animated: false,
+ showTooltip: false,
+ popperOptions: {
+ modifiers: {
+ preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
+ }
+ }
+ }
+ },
+ methods: {
+ openReactionSelect () {
+ console.log('test')
+ this.showTooltip = true
+ },
+ closeReactionSelect () {
+ this.showTooltip = false
+ },
+ favorite () {
+ if (!this.status.favorited) {
+ this.$store.dispatch('favorite', { id: this.status.id })
+ } else {
+ this.$store.dispatch('unfavorite', { id: this.status.id })
+ }
+ this.animated = true
+ setTimeout(() => {
+ this.animated = false
+ }, 500)
+ }
+ },
+ computed: {
+ emojis () {
+ return this.$store.state.instance.emoji || []
+ },
+ classes () {
+ return {
+ 'icon-smile': true,
+ 'animate-spin': this.animated
+ }
+ },
+ ...mapGetters(['mergedConfig'])
+ }
+}
+
+export default ReactButton
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
new file mode 100644
index 00000000..93638770
--- /dev/null
+++ b/src/components/react_button/react_button.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+ {{ emoji.replacement }}
+
+
+
+
+
+
+ {{ status.fave_num }}
+
+
+
+
+
+
+
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 8268e615..8c6fc0cf 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -1,5 +1,6 @@
import Attachment from '../attachment/attachment.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 Poll from '../poll/poll.vue'
import ExtraButtons from '../extra_buttons/extra_buttons.vue'
@@ -289,6 +290,7 @@ const Status = {
components: {
Attachment,
FavoriteButton,
+ ReactButton,
RetweetButton,
ExtraButtons,
PostStatusForm,
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index aae58a5e..d455ccf6 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -356,12 +356,12 @@
@@ -393,6 +393,10 @@
:logged-in="loggedIn"
:status="status"
/>
+
Date: Sun, 15 Dec 2019 14:29:45 -0500
Subject: [PATCH 03/30] wire up staff accounts with correct store data
---
src/boot/after_store.js | 11 ++++-------
src/components/staff_panel/staff_panel.js | 3 ++-
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 228a0497..0bb1b2b4 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -185,12 +185,9 @@ const getAppSecret = async ({ store }) => {
})
}
-const resolveStaffAccounts = async ({ store, accounts }) => {
- const backendInteractor = store.state.api.backendInteractor
- let nicknames = accounts.map(uri => uri.split('/').pop())
- .map(id => backendInteractor.fetchUser({ id }))
- nicknames = await Promise.all(nicknames)
-
+const resolveStaffAccounts = ({ store, accounts }) => {
+ const nicknames = accounts.map(uri => uri.split('/').pop())
+ nicknames.map(nickname => store.dispatch('fetchUser', nickname))
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
}
@@ -236,7 +233,7 @@ const getNodeInfo = async ({ store }) => {
})
const accounts = metadata.staffAccounts
- await resolveStaffAccounts({ store, accounts })
+ resolveStaffAccounts({ store, accounts })
} else {
throw (res)
}
diff --git a/src/components/staff_panel/staff_panel.js b/src/components/staff_panel/staff_panel.js
index 93e950ad..4f98fff6 100644
--- a/src/components/staff_panel/staff_panel.js
+++ b/src/components/staff_panel/staff_panel.js
@@ -1,3 +1,4 @@
+import map from 'lodash/map'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const StaffPanel = {
@@ -6,7 +7,7 @@ const StaffPanel = {
},
computed: {
staffAccounts () {
- return this.$store.state.instance.staffAccounts
+ return map(this.$store.state.instance.staffAccounts, nickname => this.$store.getters.findUser(nickname)).filter(_ => _)
}
}
}
From 33abbed5a1e1d1cf99d21d481b2a22481d7533b2 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Mon, 13 Jan 2020 23:34:39 +0200
Subject: [PATCH 04/30] usable-but-buggy: picker, adding/removing reaction on
click, search, styles
---
src/components/react_button/react_button.js | 25 ++++----
src/components/react_button/react_button.vue | 37 +++++++++--
src/components/status/status.js | 21 +++++--
src/components/status/status.vue | 24 ++++++--
src/modules/statuses.js | 61 ++++++++++++++++++-
src/services/api/api.service.js | 22 +++++++
.../backend_interactor_service.js | 4 ++
7 files changed, 166 insertions(+), 28 deletions(-)
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index d1d15d93..76a49305 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -6,6 +6,7 @@ const ReactButton = {
return {
animated: false,
showTooltip: false,
+ filterWord: '',
popperOptions: {
modifiers: {
preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
@@ -14,27 +15,25 @@ const ReactButton = {
}
},
methods: {
- openReactionSelect () {
- console.log('test')
- this.showTooltip = true
+ toggleReactionSelect () {
+ this.showTooltip = !this.showTooltip
},
closeReactionSelect () {
this.showTooltip = false
},
- favorite () {
- if (!this.status.favorited) {
- this.$store.dispatch('favorite', { id: this.status.id })
- } else {
- this.$store.dispatch('unfavorite', { id: this.status.id })
- }
- this.animated = true
- setTimeout(() => {
- this.animated = false
- }, 500)
+ addReaction (event, emoji) {
+ 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 || []
},
classes () {
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index 93638770..f7015316 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -5,21 +5,37 @@
trigger="manual"
placement="top"
class="react-button-popover"
- @close-group="closeReactionSelect"
+ @hide="closeReactionSelect"
>
+
+
+
+
+ {{ emoji }}
+
+
{{ emoji.replacement }}
-
+
@import '../../_variables.scss';
+.reaction-picker-filter {
+ padding: 0.5em;
+}
+
+.reaction-picker-divider {
+ height: 1px;
+ width: 100%;
+ margin: 0.4em;
+ background-color: var(--border, $fallback--border);
+}
+
.reaction-picker {
width: 10em;
- height: 8em;
+ height: 9em;
font-size: 1.5em;
overflow-y: scroll;
display: flex;
flex-wrap: wrap;
padding: 0.5em;
- text-align:center;
+ 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,
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 8c6fc0cf..cc0c9e06 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -280,10 +280,7 @@ const Status = {
return this.mergedConfig.hidePostStats
},
emojiReactions () {
- return {
- '🤔': [{ 'id': 'xyz..' }, { 'id': 'zyx...' }],
- '🐻': [{ 'id': 'abc...' }]
- }
+ return this.status.emojiReactions
},
...mapGetters(['mergedConfig'])
},
@@ -385,6 +382,22 @@ const Status = {
setMedia () {
const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
return () => this.$store.dispatch('setMedia', attachments)
+ },
+ reactedWith (emoji) {
+ return this.status.reactedWithEmoji.includes(emoji)
+ },
+ 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.reactedWith(emoji)) {
+ this.unreact(emoji)
+ } else {
+ this.reactWith(emoji)
+ }
}
},
watch: {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index d455ccf6..503de98d 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -354,13 +354,15 @@
-
+
@@ -788,19 +790,33 @@ $status-margin: 0.75em;
.emoji-reactions {
display: flex;
- margin-top: 0.75em;
+ margin-top: 0.25em;
+ flex-wrap: wrap;
}
.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;
:first-child {
margin-right: 0.25em;
}
+ :last-child {
+ width: 1.5em;
+ }
+ &:focus {
+ outline: none;
+ }
+}
+
+.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);
}
.button-icon.icon-reply {
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index c285b452..fcb6d1f3 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -1,4 +1,20 @@
-import { remove, slice, each, findIndex, find, maxBy, minBy, merge, first, last, isArray, omitBy, findKey } from 'lodash'
+import {
+ remove,
+ slice,
+ each,
+ findIndex,
+ find,
+ maxBy,
+ minBy,
+ merge,
+ first,
+ last,
+ isArray,
+ omitBy,
+ flow,
+ filter,
+ keys
+} from 'lodash'
import { set } from 'vue'
import apiService from '../services/api/api.service.js'
// import parse from '../services/status_parser/status_parser.js'
@@ -512,8 +528,29 @@ export const mutations = {
},
addEmojiReactions (state, { id, emojiReactions, currentUser }) {
const status = state.allStatusesObject[id]
- status.emojiReactions = emojiReactions
- status.reactedWithEmoji = findKey(emojiReactions, { id: currentUser.id })
+ set(status, 'emojiReactions', emojiReactions)
+ const reactedWithEmoji = flow(keys, filter(reaction => find(reaction, { id: currentUser.id })))(emojiReactions)
+ set(status, 'reactedWithEmoji', reactedWithEmoji)
+ },
+ addOwnReaction (state, { id, emoji, currentUser }) {
+ const status = state.allStatusesObject[id]
+ status.emojiReactions = status.emojiReactions || {}
+ const listOfUsers = (status.emojiReactions && status.emojiReactions[emoji]) || []
+ const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
+ if (!hasSelfAlready) {
+ set(status.emojiReactions, emoji, listOfUsers.concat([{ id: currentUser.id }]))
+ set(status, 'reactedWithEmoji', emoji)
+ }
+ },
+ removeOwnReaction (state, { id, emoji, currentUser }) {
+ const status = state.allStatusesObject[id]
+ const listOfUsers = status.emojiReactions[emoji] || []
+ const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
+ if (hasSelfAlready) {
+ const newUsers = filter(listOfUsers, user => user.id !== currentUser.id)
+ set(status.emojiReactions, emoji, newUsers)
+ set(status, 'reactedWith', emoji)
+ }
},
updateStatusWithPoll (state, { id, poll }) {
const status = state.allStatusesObject[id]
@@ -616,6 +653,24 @@ const statuses = {
commit('addRepeats', { id, rebloggedByUsers, currentUser: rootState.users.currentUser })
})
},
+ reactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
+ const currentUser = rootState.users.currentUser
+ commit('addOwnReaction', { id, emoji, currentUser })
+ rootState.api.backendInteractor.reactWithEmoji(id, emoji).then(
+ status => {
+ dispatch('fetchEmojiReactions', id)
+ }
+ )
+ },
+ unreactWithEmoji ({ rootState, dispatch, commit }, { id, emoji }) {
+ const currentUser = rootState.users.currentUser
+ commit('removeOwnReaction', { id, emoji, currentUser })
+ rootState.api.backendInteractor.unreactWithEmoji(id, emoji).then(
+ status => {
+ dispatch('fetchEmojiReactions', id)
+ }
+ )
+ },
fetchEmojiReactions ({ rootState, commit }, id) {
rootState.api.backendInteractor.fetchEmojiReactions(id).then(
emojiReactions => {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7ef4b74a..2e96264a 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -72,6 +72,8 @@ const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = `/api/v2/search`
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by`
+const PLEROMA_EMOJI_REACT_URL = id => `/api/v1/pleroma/statuses/${id}/react_with_emoji`
+const PLEROMA_EMOJI_UNREACT_URL = id => `/api/v1/pleroma/statuses/${id}/unreact_with_emoji`
const oldfetch = window.fetch
@@ -869,6 +871,24 @@ const fetchEmojiReactions = ({ id }) => {
return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) })
}
+const reactWithEmoji = ({ id, emoji, credentials }) => {
+ return promisedRequest({
+ url: PLEROMA_EMOJI_REACT_URL(id),
+ method: 'POST',
+ credentials,
+ payload: { emoji }
+ }).then(status => parseStatus(status))
+}
+
+const unreactWithEmoji = ({ id, emoji, credentials }) => {
+ return promisedRequest({
+ url: PLEROMA_EMOJI_UNREACT_URL(id),
+ method: 'POST',
+ credentials,
+ payload: { emoji }
+ }).then(parseStatus)
+}
+
const reportUser = ({ credentials, userId, statusIds, comment, forward }) => {
return promisedRequest({
url: MASTODON_REPORT_USER_URL,
@@ -1003,6 +1023,8 @@ const apiService = {
fetchFavoritedByUsers,
fetchRebloggedByUsers,
fetchEmojiReactions,
+ reactWithEmoji,
+ unreactWithEmoji,
reportUser,
updateNotificationSettings,
search2,
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index 52234fcc..44233a24 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -144,6 +144,8 @@ const backendInteractorService = credentials => {
const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({ id })
const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({ id })
const fetchEmojiReactions = (id) => apiService.fetchEmojiReactions({ id })
+ const reactWithEmoji = (id, emoji) => apiService.reactWithEmoji({ id, emoji, credentials })
+ const unreactWithEmoji = (id, emoji) => apiService.unreactWithEmoji({ id, emoji, credentials })
const reportUser = (params) => apiService.reportUser({ credentials, ...params })
const favorite = (id) => apiService.favorite({ id, credentials })
@@ -212,6 +214,8 @@ const backendInteractorService = credentials => {
fetchFavoritedByUsers,
fetchRebloggedByUsers,
fetchEmojiReactions,
+ reactWithEmoji,
+ unreactWithEmoji,
reportUser,
favorite,
unfavorite,
From b10b92a876eb185a88e751d028e69063c9117298 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Tue, 14 Jan 2020 10:06:14 +0200
Subject: [PATCH 05/30] clean up code, fix prediction bug
---
.../emoji_reactions/emoji_reactions.js | 30 +++++++++++
.../emoji_reactions/emoji_reactions.vue | 51 +++++++++++++++++++
src/components/react_button/react_button.js | 5 +-
src/components/react_button/react_button.vue | 46 +++++++++--------
src/components/status/status.js | 23 ++-------
src/components/status/status.vue | 47 ++---------------
src/modules/statuses.js | 9 ++--
7 files changed, 122 insertions(+), 89 deletions(-)
create mode 100644 src/components/emoji_reactions/emoji_reactions.js
create mode 100644 src/components/emoji_reactions/emoji_reactions.vue
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
new file mode 100644
index 00000000..e81e6e25
--- /dev/null
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -0,0 +1,30 @@
+
+const EmojiReactions = {
+ name: 'EmojiReactions',
+ props: ['status'],
+ computed: {
+ emojiReactions () {
+ return this.status.emojiReactions
+ }
+ },
+ methods: {
+ reactedWith (emoji) {
+ return this.status.reactedWithEmoji.includes(emoji)
+ },
+ 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.reactedWith(emoji)) {
+ this.unreact(emoji)
+ } else {
+ this.reactWith(emoji)
+ }
+ }
+ }
+}
+
+export default EmojiReactions
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
new file mode 100644
index 00000000..d83f60b6
--- /dev/null
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index 76a49305..d1a179bc 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -15,8 +15,9 @@ const ReactButton = {
}
},
methods: {
- toggleReactionSelect () {
- this.showTooltip = !this.showTooltip
+ openReactionSelect () {
+ this.showTooltip = true
+ this.filterWord = ''
},
closeReactionSelect () {
this.showTooltip = false
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index f7015316..ae975dee 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -9,13 +9,16 @@
>
-
+
{{ emoji }}
@@ -24,7 +27,7 @@
{{ emoji.replacement }}
@@ -34,11 +37,11 @@
{{ status.fave_num }}
@@ -58,7 +61,7 @@
.reaction-picker-divider {
height: 1px;
width: 100%;
- margin: 0.4em;
+ margin: 0.5em;
background-color: var(--border, $fallback--border);
}
@@ -82,26 +85,27 @@
// Autoprefixed seem to ignore this one, and also syntax is different
-webkit-mask-composite: xor;
mask-composite: exclude;
-}
-.emoji-reaction-button {
- flex-basis: 20%;
- line-height: 1.5em;
- align-content: center;
-}
+ .emoji-button {
+ cursor: pointer;
-.fav-active {
- cursor: pointer;
- animation-duration: 0.6s;
+ flex-basis: 20%;
+ line-height: 1.5em;
+ align-content: center;
- &:hover {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ &:hover {
+ transform: scale(1.25);
+ }
}
}
-.favorite-button.icon-star {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+.add-reaction-button {
+ cursor: pointer;
+
+ &:hover {
+ color: $fallback--text;
+ color: var(--text, $fallback--text);
+ }
}
+
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 18617938..81b57667 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -12,6 +12,7 @@ import LinkPreview from '../link-preview/link-preview.vue'
import AvatarList from '../avatar_list/avatar_list.vue'
import Timeago from '../timeago/timeago.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 fileType from 'src/services/file_type/file_type.service'
import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
@@ -311,9 +312,6 @@ const Status = {
hidePostStats () {
return this.mergedConfig.hidePostStats
},
- emojiReactions () {
- return this.status.emojiReactions
- },
...mapGetters(['mergedConfig']),
...mapState({
betterShadow: state => state.interface.browserSupport.cssFilter,
@@ -334,7 +332,8 @@ const Status = {
LinkPreview,
AvatarList,
Timeago,
- StatusPopover
+ StatusPopover,
+ EmojiReactions
},
methods: {
visibilityIcon (visibility) {
@@ -418,22 +417,6 @@ const Status = {
setMedia () {
const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
return () => this.$store.dispatch('setMedia', attachments)
- },
- reactedWith (emoji) {
- return this.status.reactedWithEmoji.includes(emoji)
- },
- 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.reactedWith(emoji)) {
- this.unreact(emoji)
- } else {
- this.reactWith(emoji)
- }
}
},
watch: {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 4ea1b74b..87e8b5da 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -354,18 +354,10 @@
-
-
-
+
find(reaction, { id: currentUser.id })))(emojiReactions)
+ const reactedWithEmoji = flow(
+ keys,
+ filter(reaction => find(reaction, { id: currentUser.id }))
+ )(emojiReactions)
set(status, 'reactedWithEmoji', reactedWithEmoji)
},
addOwnReaction (state, { id, emoji, currentUser }) {
@@ -547,7 +550,7 @@ export const mutations = {
const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
if (!hasSelfAlready) {
set(status.emojiReactions, emoji, listOfUsers.concat([{ id: currentUser.id }]))
- set(status, 'reactedWithEmoji', emoji)
+ set(status, 'reactedWithEmoji', [...status.reactedWithEmoji, emoji])
}
},
removeOwnReaction (state, { id, emoji, currentUser }) {
@@ -557,7 +560,7 @@ export const mutations = {
if (hasSelfAlready) {
const newUsers = filter(listOfUsers, user => user.id !== currentUser.id)
set(status.emojiReactions, emoji, newUsers)
- set(status, 'reactedWith', emoji)
+ set(status, 'reactedWithEmoji', status.reactedWithEmoji.filter(e => e !== emoji))
}
},
updateStatusWithPoll (state, { id, poll }) {
From 7a013ac39392ef251c0789f27dd4660dcd30bd6d Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson
Date: Wed, 15 Jan 2020 20:22:54 +0000
Subject: [PATCH 06/30] Implement domain mutes v2
---
.../domain_mute_card/domain_mute_card.js | 15 ++
.../domain_mute_card/domain_mute_card.vue | 38 +++++
src/components/user_settings/user_settings.js | 19 ++-
.../user_settings/user_settings.vue | 161 +++++++++++++-----
src/i18n/en.json | 9 +
src/modules/users.js | 44 +++++
src/services/api/api.service.js | 28 ++-
7 files changed, 265 insertions(+), 49 deletions(-)
create mode 100644 src/components/domain_mute_card/domain_mute_card.js
create mode 100644 src/components/domain_mute_card/domain_mute_card.vue
diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js
new file mode 100644
index 00000000..c8e838ba
--- /dev/null
+++ b/src/components/domain_mute_card/domain_mute_card.js
@@ -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
diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue
new file mode 100644
index 00000000..567d81c5
--- /dev/null
+++ b/src/components/domain_mute_card/domain_mute_card.vue
@@ -0,0 +1,38 @@
+
+
+
+ {{ domain }}
+
+
+ {{ $t('domain_mute_card.unmute') }}
+
+ {{ $t('domain_mute_card.unmute_progress') }}
+
+
+
+
+
+
+
+
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index d5d671e4..1709b48f 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -9,6 +9,7 @@ import ScopeSelector from '../scope_selector/scope_selector.vue'
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
import BlockCard from '../block_card/block_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 ProgressButton from '../progress_button/progress_button.vue'
import EmojiInput from '../emoji_input/emoji_input.vue'
@@ -32,6 +33,12 @@ const MuteList = withSubscription({
childPropName: 'items'
})(SelectableList)
+const DomainMuteList = withSubscription({
+ fetch: (props, $store) => $store.dispatch('fetchDomainMutes'),
+ select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []),
+ childPropName: 'items'
+})(SelectableList)
+
const UserSettings = {
data () {
return {
@@ -67,7 +74,8 @@ const UserSettings = {
changedPassword: false,
changePasswordError: false,
activeTab: 'profile',
- notificationSettings: this.$store.state.users.currentUser.notification_settings
+ notificationSettings: this.$store.state.users.currentUser.notification_settings,
+ newDomainToMute: ''
}
},
created () {
@@ -80,10 +88,12 @@ const UserSettings = {
ImageCropper,
BlockList,
MuteList,
+ DomainMuteList,
EmojiInput,
Autosuggest,
BlockCard,
MuteCard,
+ DomainMuteCard,
ProgressButton,
Importer,
Exporter,
@@ -365,6 +375,13 @@ const UserSettings = {
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) {
return value
}
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 3f1982a6..2222c293 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -509,59 +509,114 @@
-
-
-
-
-
+
+
+
- {{ $t('user_card.mute') }}
-
- {{ $t('user_card.mute_progress') }}
-
-
-
+
+
+
+
- {{ $t('user_card.unmute') }}
+
+
+ {{ $t('user_card.mute') }}
+
+ {{ $t('user_card.mute_progress') }}
+
+
+
+ {{ $t('user_card.unmute') }}
+
+ {{ $t('user_card.unmute_progress') }}
+
+
+
+
+
+
+
+
+ {{ $t('settings.no_mutes') }}
+
+
+
+
+
+
-
-
-
-
-
- {{ $t('settings.no_mutes') }}
-
-
+
+
+
+
+ {{ $t('domain_mute_card.unmute') }}
+
+ {{ $t('domain_mute_card.unmute_progress') }}
+
+
+
+
+
+
+
+
+ {{ $t('settings.no_mutes') }}
+
+
+
+
@@ -639,6 +694,18 @@
}
}
+ &-domain-mute-form {
+ padding: 1em;
+ display: flex;
+ flex-direction: column;
+
+ button {
+ align-self: flex-end;
+ margin-top: 1em;
+ width: 10em;
+ }
+ }
+
.setting-subitem {
margin-left: 1.75em;
}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 75d66b9f..31f4ac24 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -21,6 +21,12 @@
"chat": {
"title": "Chat"
},
+ "domain_mute_card": {
+ "mute": "Mute",
+ "mute_progress": "Muting...",
+ "unmute": "Unmute",
+ "unmute_progress": "Unmuting..."
+ },
"exporter": {
"export": "Export",
"processing": "Processing, you'll soon be asked to download your file"
@@ -264,6 +270,7 @@
"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.",
"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.",
"pad_emoji": "Pad emoji with spaces when adding from picker",
"export_theme": "Save preset",
@@ -361,6 +368,7 @@
"post_status_content_type": "Post status content type",
"stop_gifs": "Play-on-hover GIFs",
"streaming": "Enable automatic streaming of new posts when scrolled to the top",
+ "user_mutes": "Users",
"useStreamingApi": "Receive posts and notifications real-time",
"useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
"text": "Text",
@@ -369,6 +377,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_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",
+ "type_domains_to_mute": "Type in domains to mute",
"upload_a_photo": "Upload a photo",
"user_settings": "User Settings",
"values": {
diff --git a/src/modules/users.js b/src/modules/users.js
index b9ed0efa..ce3e595d 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -72,6 +72,16 @@ const showReblogs = (store, userId) => {
.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 = {
setMuted (state, { user: { id }, muted }) {
const user = state.usersObject[id]
@@ -177,6 +187,20 @@ export const mutations = {
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) {
const user = state.usersObject[status.user.id]
const index = user.pinnedStatusIds.indexOf(status.id)
@@ -297,6 +321,25 @@ const users = {
unmuteUsers (store, ids = []) {
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) {
const user = rootState.users.usersObject[id]
const maxId = last(user.friendIds)
@@ -460,6 +503,7 @@ const users = {
user.credentials = accessToken
user.blockIds = []
user.muteIds = []
+ user.domainMutes = []
commit('setCurrentUser', user)
commit('addNewUsers', [user])
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index ef0267aa..dcbedd8b 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -72,6 +72,7 @@ const MASTODON_MUTE_CONVERSATION = id => `/api/v1/statuses/${id}/mute`
const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = `/api/v2/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 oldfetch = window.fetch
@@ -948,6 +949,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 = {} }) => {
return Object.entries({
...(credentials
@@ -1110,7 +1133,10 @@ const apiService = {
reportUser,
updateNotificationSettings,
search2,
- searchUsers
+ searchUsers,
+ fetchDomainMutes,
+ muteDomain,
+ unmuteDomain
}
export default apiService
From f052ac4a1e59685332bf3798ce3978d6304816d8 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Wed, 15 Jan 2020 22:38:31 +0200
Subject: [PATCH 07/30] Add domain mutes to changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 42554607..b09eb3a0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Private mode support
- Support for 'Move' type notifications
- Pleroma AMOLED dark theme
+- User level domain mutes, under User Settings -> Mutes
### Changed
- Captcha now resets on failed registrations
- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
From 8080981fcdaab4efd07c2c3f4ff3e2131f8aa802 Mon Sep 17 00:00:00 2001
From: lain
Date: Tue, 21 Jan 2020 16:51:49 +0100
Subject: [PATCH 08/30] Fix follower request fetching
---
src/components/nav_panel/nav_panel.js | 2 +-
src/components/side_drawer/side_drawer.js | 2 +-
src/modules/api.js | 1 +
.../backend_interactor_service/backend_interactor_service.js | 2 +-
4 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index d9268585..8f7edb7f 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -3,7 +3,7 @@ import { mapState } from 'vuex'
const NavPanel = {
created () {
if (this.currentUser && this.currentUser.locked) {
- this.$store.dispatch('startFetchingFollowRequest')
+ this.$store.dispatch('startFetchingFollowRequests')
}
},
computed: mapState({
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 2534eb8f..2181ecc7 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -12,7 +12,7 @@ const SideDrawer = {
this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer)
if (this.currentUser && this.currentUser.locked) {
- this.$store.dispatch('startFetchingFollowRequest')
+ this.$store.dispatch('startFetchingFollowRequests')
}
},
components: { UserCard },
diff --git a/src/modules/api.js b/src/modules/api.js
index 9c296275..748570e5 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -146,6 +146,7 @@ const api = {
startFetchingFollowRequests (store) {
if (store.state.fetchers['followRequests']) return
const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })
+
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
},
stopFetchingFollowRequests (store) {
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index b7372ed0..e1c32860 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -16,7 +16,7 @@ const backendInteractorService = credentials => ({
return notificationsFetcher.fetchAndUpdate({ store, credentials })
},
- startFetchingFollowRequest ({ store }) {
+ startFetchingFollowRequests ({ store }) {
return followRequestFetcher.startFetching({ store, credentials })
},
From 9d8dbd83400bd48006873ff2dae220c0a46c5f2e Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Thu, 23 Jan 2020 12:00:50 -0600
Subject: [PATCH 09/30] Fix missing TWKN when logged in, but server is set to
private mode.
---
src/components/nav_panel/nav_panel.vue | 2 +-
src/components/side_drawer/side_drawer.vue | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 034259d9..0f3296eb 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -33,7 +33,7 @@
{{ $t("nav.public_tl") }}
-
+
{{ $t("nav.twkn") }}
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 3fba9058..28637afc 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -88,7 +88,7 @@
From 2c61eb8e7f4674253d65cce6048ca272075064e2 Mon Sep 17 00:00:00 2001
From: eugenijm
Date: Thu, 23 Jan 2020 23:53:48 +0300
Subject: [PATCH 10/30] Added polyfills for EventTarget (needed for Safari) and
CustomEvent (needed for IE)
---
package.json | 2 ++
src/lib/event_target_polyfill.js | 9 +++++++++
src/main.js | 3 +++
yarn.lock | 10 ++++++++++
4 files changed, 24 insertions(+)
create mode 100644 src/lib/event_target_polyfill.js
diff --git a/package.json b/package.json
index 38936b23..9ec8c1eb 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.6",
"@babel/register": "^7.7.4",
+ "@ungap/event-target": "^0.1.0",
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
"@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
"@vue/test-utils": "^1.0.0-beta.26",
@@ -56,6 +57,7 @@
"connect-history-api-fallback": "^1.1.0",
"cross-spawn": "^4.0.2",
"css-loader": "^0.28.0",
+ "custom-event-polyfill": "^1.0.7",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-friendly-formatter": "^2.0.5",
diff --git a/src/lib/event_target_polyfill.js b/src/lib/event_target_polyfill.js
new file mode 100644
index 00000000..2042c770
--- /dev/null
+++ b/src/lib/event_target_polyfill.js
@@ -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
+}
diff --git a/src/main.js b/src/main.js
index a9db1cff..baf73ac8 100644
--- a/src/main.js
+++ b/src/main.js
@@ -2,6 +2,9 @@ import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'
+import 'custom-event-polyfill'
+import './lib/event_target_polyfill.js'
+
import interfaceModule from './modules/interface.js'
import instanceModule from './modules/instance.js'
import statusesModule from './modules/statuses.js'
diff --git a/yarn.lock b/yarn.lock
index 4b20a6a1..1a5d4cef 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -710,6 +710,11 @@
dependencies:
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":
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"
@@ -2281,6 +2286,11 @@ currently-unhandled@^0.4.1:
dependencies:
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:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
From a018ea622c4ae34fd204e840b20aba53f84cd051 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Sun, 26 Jan 2020 15:45:12 +0200
Subject: [PATCH 11/30] change emoji reactions to use new format
---
src/components/conversation/conversation.js | 2 +-
.../emoji_reactions/emoji_reactions.js | 9 ++-
.../emoji_reactions/emoji_reactions.vue | 12 ++--
src/components/status/status.vue | 1 -
src/modules/statuses.js | 66 +++++++++++--------
.../entity_normalizer.service.js | 1 +
test/unit/specs/modules/statuses.spec.js | 45 +++++++++++++
7 files changed, 99 insertions(+), 37 deletions(-)
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 7ff0ac08..45fb2bf6 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -150,7 +150,7 @@ const conversation = {
if (!id) return
this.highlight = id
this.$store.dispatch('fetchFavsAndRepeats', id)
- this.$store.dispatch('fetchEmojiReactions', id)
+ this.$store.dispatch('fetchEmojiReactionsBy', id)
},
getHighlight () {
return this.isExpanded ? this.highlight : null
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index e81e6e25..b37cce3d 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -4,12 +4,17 @@ const EmojiReactions = {
props: ['status'],
computed: {
emojiReactions () {
- return this.status.emojiReactions
+ console.log(this.status.emoji_reactions)
+ return this.status.emoji_reactions
}
},
methods: {
reactedWith (emoji) {
- return this.status.reactedWithEmoji.includes(emoji)
+ // return []
+ const user = this.$store.state.users.currentUser
+ const reaction = this.status.emoji_reactions.find(r => r.emoji === emoji)
+ console.log(reaction)
+ return reaction.accounts && reaction.accounts.find(u => u.id === user.id)
},
reactWith (emoji) {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index d83f60b6..8a229240 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,14 +1,14 @@
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 87e8b5da..d5739304 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -355,7 +355,6 @@
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index dbae9d38..ea0c1749 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -10,10 +10,7 @@ import {
first,
last,
isArray,
- omitBy,
- flow,
- filter,
- keys
+ omitBy
} from 'lodash'
import { set } from 'vue'
import apiService from '../services/api/api.service.js'
@@ -534,33 +531,48 @@ export const mutations = {
newStatus.fave_num = newStatus.favoritedBy.length
newStatus.favorited = !!newStatus.favoritedBy.find(({ id }) => currentUser.id === id)
},
- addEmojiReactions (state, { id, emojiReactions, currentUser }) {
+ addEmojiReactionsBy (state, { id, emojiReactions, currentUser }) {
const status = state.allStatusesObject[id]
- set(status, 'emojiReactions', emojiReactions)
- const reactedWithEmoji = flow(
- keys,
- filter(reaction => find(reaction, { id: currentUser.id }))
- )(emojiReactions)
- set(status, 'reactedWithEmoji', reactedWithEmoji)
+ set(status, 'emoji_reactions', emojiReactions)
},
addOwnReaction (state, { id, emoji, currentUser }) {
const status = state.allStatusesObject[id]
- status.emojiReactions = status.emojiReactions || {}
- const listOfUsers = (status.emojiReactions && status.emojiReactions[emoji]) || []
- const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
- if (!hasSelfAlready) {
- set(status.emojiReactions, emoji, listOfUsers.concat([{ id: currentUser.id }]))
- set(status, 'reactedWithEmoji', [...status.reactedWithEmoji, emoji])
+ const reactionIndex = findIndex(status.emoji_reactions, { emoji })
+ const reaction = status.emoji_reactions[reactionIndex] || { emoji, count: 0, accounts: [] }
+
+ const newReaction = {
+ ...reaction,
+ count: reaction.count + 1,
+ 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 listOfUsers = status.emojiReactions[emoji] || []
- const hasSelfAlready = !!find(listOfUsers, { id: currentUser.id })
- if (hasSelfAlready) {
- const newUsers = filter(listOfUsers, user => user.id !== currentUser.id)
- set(status.emojiReactions, emoji, newUsers)
- set(status, 'reactedWithEmoji', status.reactedWithEmoji.filter(e => e !== emoji))
+ const reactionIndex = findIndex(status.emoji_reactions, { emoji })
+ if (reactionIndex < 0) return
+
+ const reaction = status.emoji_reactions[reactionIndex]
+
+ const newReaction = {
+ ...reaction,
+ count: reaction.count - 1,
+ accounts: reaction.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.emoji !== emoji))
}
},
updateStatusWithPoll (state, { id, poll }) {
@@ -672,7 +684,7 @@ const statuses = {
commit('addOwnReaction', { id, emoji, currentUser })
rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(
status => {
- dispatch('fetchEmojiReactions', id)
+ dispatch('fetchEmojiReactionsBy', id)
}
)
},
@@ -681,14 +693,14 @@ const statuses = {
commit('removeOwnReaction', { id, emoji, currentUser })
rootState.api.backendInteractor.unreactWithEmoji({ id, emoji }).then(
status => {
- dispatch('fetchEmojiReactions', id)
+ dispatch('fetchEmojiReactionsBy', id)
}
)
},
- fetchEmojiReactions ({ rootState, commit }, id) {
+ fetchEmojiReactionsBy ({ rootState, commit }, id) {
rootState.api.backendInteractor.fetchEmojiReactions({ id }).then(
emojiReactions => {
- commit('addEmojiReactions', { id, emojiReactions, currentUser: rootState.users.currentUser })
+ commit('addEmojiReactionsBy', { id, emojiReactions, currentUser: rootState.users.currentUser })
}
)
},
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index ee007bee..03eaa5d7 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -233,6 +233,7 @@ export const parseStatus = (data) => {
output.statusnet_html = addEmojis(data.content, data.emojis)
output.tags = data.tags
+ output.emoji_reactions = [{ emoji: 'A', count: 5 }] // data.pleroma.emoji_reactions
if (data.pleroma) {
const { pleroma } = data
diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js
index f794997b..e53aa388 100644
--- a/test/unit/specs/modules/statuses.spec.js
+++ b/test/unit/specs/modules/statuses.spec.js
@@ -241,6 +241,51 @@ describe('Statuses module', () => {
})
})
+ describe('emojiReactions', () => {
+ it('increments count in existing reaction', () => {
+ const state = defaultState()
+ const status = makeMockStatus({ id: '1' })
+ status.emoji_reactions = [ { emoji: '😂', 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].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].accounts[0].id).to.eql('me')
+ })
+
+ it('decreases count in existing reaction', () => {
+ const state = defaultState()
+ const status = makeMockStatus({ id: '1' })
+ status.emoji_reactions = [ { emoji: '😂', count: 2, accounts: [{ id: 'me' }] } ]
+
+ mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
+ mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} })
+ expect(state.allStatusesObject['1'].emoji_reactions[0].count).to.eql(1)
+ 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 = [{ emoji: '😂', count: 1, accounts: [{ id: 'me' }] }]
+
+ mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
+ mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: {} })
+ expect(state.allStatusesObject['1'].emoji_reactions.length).to.eql(0)
+ })
+ })
+
describe('showNewStatuses', () => {
it('resets the minId to the min of the visible statuses when adding new to visible statuses', () => {
const state = defaultState()
From 7cfe1b05e8d16fcbb6eab3b42f19e464d57ea35b Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Sun, 26 Jan 2020 15:57:40 +0200
Subject: [PATCH 12/30] remove mock data
---
src/services/entity_normalizer/entity_normalizer.service.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 03eaa5d7..f66d09ac 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -233,7 +233,7 @@ export const parseStatus = (data) => {
output.statusnet_html = addEmojis(data.content, data.emojis)
output.tags = data.tags
- output.emoji_reactions = [{ emoji: 'A', count: 5 }] // data.pleroma.emoji_reactions
+ output.emoji_reactions = data.pleroma.emoji_reactions
if (data.pleroma) {
const { pleroma } = data
From 0de627baae53d4d284920c1f6d7daf64769be4a6 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Sun, 26 Jan 2020 16:18:57 +0200
Subject: [PATCH 13/30] remove favs count from react button
---
src/components/react_button/react_button.vue | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index ae975dee..7f1bc492 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -44,7 +44,6 @@
class="button-icon add-reaction-button"
:title="$t('tool_tip.add_reaction')"
/>
- {{ status.fave_num }}
From e4e3a28838f431872ab5fd6b10bb8db4a03af389 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Mon, 27 Jan 2020 15:49:05 +0200
Subject: [PATCH 14/30] remove logs/commented code
---
src/components/emoji_reactions/emoji_reactions.js | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index b37cce3d..95d52cb6 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -4,16 +4,13 @@ const EmojiReactions = {
props: ['status'],
computed: {
emojiReactions () {
- console.log(this.status.emoji_reactions)
return this.status.emoji_reactions
}
},
methods: {
reactedWith (emoji) {
- // return []
const user = this.$store.state.users.currentUser
const reaction = this.status.emoji_reactions.find(r => r.emoji === emoji)
- console.log(reaction)
return reaction.accounts && reaction.accounts.find(u => u.id === user.id)
},
reactWith (emoji) {
From cb205036f931e143726790cbc3292e1b53f435ce Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 27 Jan 2020 14:18:15 +0000
Subject: [PATCH 15/30] Apply suggestion to src/services/api/api.service.js
---
src/services/api/api.service.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index aa31f123..11aa0675 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -894,7 +894,7 @@ const reactWithEmoji = ({ id, emoji, credentials }) => {
method: 'POST',
credentials,
payload: { emoji }
- }).then(status => parseStatus(status))
+ }).then(parseStatus)
}
const unreactWithEmoji = ({ id, emoji, credentials }) => {
From e6291e4ee179ab85f212b1eef7d9e03565e6a8f8 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Mon, 27 Jan 2020 18:43:26 +0200
Subject: [PATCH 16/30] remove unnecessary anonymous function
---
src/services/api/api.service.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index aa31f123..11aa0675 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -894,7 +894,7 @@ const reactWithEmoji = ({ id, emoji, credentials }) => {
method: 'POST',
credentials,
payload: { emoji }
- }).then(status => parseStatus(status))
+ }).then(parseStatus)
}
const unreactWithEmoji = ({ id, emoji, credentials }) => {
From 566f013ac49139cb3411b24920b8b235c8c3c424 Mon Sep 17 00:00:00 2001
From: eugenijm
Date: Tue, 28 Jan 2020 06:12:32 +0300
Subject: [PATCH 17/30] Fix password and email update
---
src/components/user_settings/user_settings.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 1709b48f..38373056 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -307,7 +307,7 @@ const UserSettings = {
newPassword: this.changePasswordInputs[1],
newPasswordConfirmation: this.changePasswordInputs[2]
}
- this.$store.state.api.backendInteractor.changePassword({ params })
+ this.$store.state.api.backendInteractor.changePassword(params)
.then((res) => {
if (res.status === 'success') {
this.changedPassword = true
@@ -324,7 +324,7 @@ const UserSettings = {
email: this.newEmail,
password: this.changeEmailPassword
}
- this.$store.state.api.backendInteractor.changeEmail({ params })
+ this.$store.state.api.backendInteractor.changeEmail(params)
.then((res) => {
if (res.status === 'success') {
this.changedEmail = true
From 6afff4f8c205ec70d3694564c706f6a46a61db9e Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Tue, 28 Jan 2020 17:09:25 +0200
Subject: [PATCH 18/30] review changes
---
src/components/emoji_reactions/emoji_reactions.vue | 6 +++---
src/components/react_button/react_button.js | 9 +--------
src/components/react_button/react_button.vue | 3 +--
.../entity_normalizer/entity_normalizer.service.js | 2 +-
4 files changed, 6 insertions(+), 14 deletions(-)
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 8a229240..741fc11e 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -7,8 +7,8 @@
:class="{ 'picked-reaction': reactedWith(reaction.emoji) }"
@click="emojiOnClick(reaction.emoji, $event)"
>
- {{ reaction.count }}
{{ reaction.emoji }}
+ {{ reaction.count }}
@@ -31,10 +31,10 @@
align-items: center;
justify-content: center;
box-sizing: border-box;
- :first-child {
+ &:first-child {
margin-right: 0.25em;
}
- :last-child {
+ &:last-child {
width: 1.5em;
}
&:focus {
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index d1a179bc..6fb2a780 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -4,7 +4,6 @@ const ReactButton = {
props: ['status', 'loggedIn'],
data () {
return {
- animated: false,
showTooltip: false,
filterWord: '',
popperOptions: {
@@ -29,7 +28,7 @@ const ReactButton = {
},
computed: {
commonEmojis () {
- return ['💖', '😠', '👀', '😂', '🔥']
+ return ['❤️', '😠', '👀', '😂', '🔥']
},
emojis () {
if (this.filterWord !== '') {
@@ -37,12 +36,6 @@ const ReactButton = {
}
return this.$store.state.instance.emoji || []
},
- classes () {
- return {
- 'icon-smile': true,
- 'animate-spin': this.animated
- }
- },
...mapGetters(['mergedConfig'])
}
}
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index 7f1bc492..c925dd71 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -40,8 +40,7 @@
@click.prevent="openReactionSelect"
>
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index f66d09ac..a3d0b782 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -233,7 +233,6 @@ export const parseStatus = (data) => {
output.statusnet_html = addEmojis(data.content, data.emojis)
output.tags = data.tags
- output.emoji_reactions = data.pleroma.emoji_reactions
if (data.pleroma) {
const { pleroma } = data
@@ -243,6 +242,7 @@ export const parseStatus = (data) => {
output.is_local = pleroma.local
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
output.thread_muted = pleroma.thread_muted
+ output.emoji_reactions = pleroma.emoji_reactions
} else {
output.text = data.content
output.summary = data.spoiler_text
From 29806c962972091cda2c7d55380b3326b5a07ba4 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Tue, 28 Jan 2020 17:53:47 +0200
Subject: [PATCH 19/30] fix emoji reaction classes broken in develop
---
src/components/emoji_reactions/emoji_reactions.vue | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 741fc11e..00d6d2b7 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -7,7 +7,7 @@
:class="{ 'picked-reaction': reactedWith(reaction.emoji) }"
@click="emojiOnClick(reaction.emoji, $event)"
>
- {{ reaction.emoji }}
+ {{ reaction.emoji }}
{{ reaction.count }}
@@ -31,12 +31,10 @@
align-items: center;
justify-content: center;
box-sizing: border-box;
- &:first-child {
+ .reaction-emoji {
+ width: 1.25em;
margin-right: 0.25em;
}
- &:last-child {
- width: 1.5em;
- }
&:focus {
outline: none;
}
From c54111797ae1058e59931b2d1f12e6ab6a6f96a9 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson
Date: Tue, 28 Jan 2020 17:54:40 +0200
Subject: [PATCH 20/30] add emoji reactions to changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b09eb3a0..65973dbb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Support for 'Move' type notifications
- Pleroma AMOLED dark theme
- User level domain mutes, under User Settings -> Mutes
+- Emoji reactions for statuses
### Changed
- Captcha now resets on failed registrations
- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
From 746416207bd15f7883af18e359b84f0c4444a12a Mon Sep 17 00:00:00 2001
From: rinpatch
Date: Thu, 30 Jan 2020 19:55:01 +0300
Subject: [PATCH 21/30] Escape HTML from display name and subject fields
Closes #724
---
package.json | 1 +
src/services/entity_normalizer/entity_normalizer.service.js | 6 ++++--
yarn.lock | 3 ++-
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index 9ec8c1eb..5c7fa31e 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"chromatism": "^3.0.0",
"cropperjs": "^1.4.3",
"diff": "^3.0.1",
+ "escape-html": "^1.0.3",
"karma-mocha-reporter": "^2.2.1",
"localforage": "^1.5.0",
"object-path": "^0.11.3",
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index a3d0b782..3116d211 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -1,3 +1,5 @@
+import escape from 'escape-html'
+
const qvitterStatusType = (status) => {
if (status.is_post_verb) {
return 'status'
@@ -41,7 +43,7 @@ export const parseUser = (data) => {
}
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_html = addEmojis(data.note, data.emojis)
@@ -256,7 +258,7 @@ export const parseStatus = (data) => {
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.poll = data.poll
output.pinned = data.pinned
diff --git a/yarn.lock b/yarn.lock
index 1a5d4cef..b794042f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2757,9 +2757,10 @@ es6-promisify@^5.0.0:
dependencies:
es6-promise "^4.0.3"
-escape-html@~1.0.3:
+escape-html@^1.0.3, escape-html@~1.0.3:
version "1.0.3"
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:
version "1.0.5"
From 4a266a4d0547c0f68628001e8948dd171ef4554b Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson
Date: Fri, 31 Jan 2020 00:24:54 +0000
Subject: [PATCH 22/30] Fix one click nsfw unhide on videos
---
CHANGELOG.md | 1 +
src/components/attachment/attachment.js | 11 ++++++++---
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 65973dbb..abefd958 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,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
- Registration fixed
- 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
### Added
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index 06b496b0..b832e10f 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -2,6 +2,7 @@ import StillImage from '../still-image/still-image.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
import nsfwImage from '../../assets/nsfw.png'
import fileTypeService from '../../services/file_type/file_type.service.js'
+import { mapGetters } from 'vuex'
const Attachment = {
props: [
@@ -49,7 +50,8 @@ const Attachment = {
},
fullwidth () {
return this.type === 'html' || this.type === 'audio'
- }
+ },
+ ...mapGetters(['mergedConfig'])
},
methods: {
linkClicked ({ target }) {
@@ -58,7 +60,7 @@ const Attachment = {
}
},
openModal (event) {
- const modalTypes = this.$store.getters.mergedConfig.playVideosInModal
+ const modalTypes = this.mergedConfig.playVideosInModal
? ['image', 'video']
: ['image']
if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) ||
@@ -71,7 +73,10 @@ const Attachment = {
}
},
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)
return
}
From 9bbf10b55d97f6dbe3197ebbd1bb29d294ff6b55 Mon Sep 17 00:00:00 2001
From: kPherox
Date: Tue, 4 Feb 2020 04:26:32 +0900
Subject: [PATCH 23/30] Add setting for allow_following_move
---
src/components/user_settings/user_settings.js | 2 ++
src/components/user_settings/user_settings.vue | 9 ++++++---
.../entity_normalizer/entity_normalizer.service.js | 2 ++
3 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js
index 38373056..eca6f9b1 100644
--- a/src/components/user_settings/user_settings.js
+++ b/src/components/user_settings/user_settings.js
@@ -55,6 +55,7 @@ const UserSettings = {
showRole: this.$store.state.users.currentUser.show_role,
role: this.$store.state.users.currentUser.role,
discoverable: this.$store.state.users.currentUser.discoverable,
+ allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
pickAvatarBtnVisible: true,
bannerUploading: false,
backgroundUploading: false,
@@ -162,6 +163,7 @@ const UserSettings = {
hide_follows: this.hideFollows,
hide_followers: this.hideFollowers,
discoverable: this.discoverable,
+ allow_following_move: this.allowFollowingMove,
hide_follows_count: this.hideFollowsCount,
hide_followers_count: this.hideFollowersCount,
show_role: this.showRole
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 2222c293..8b2336b4 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -90,9 +90,7 @@
-
+
{{ $t('settings.hide_followers_description') }}
@@ -104,6 +102,11 @@
{{ $t('settings.hide_followers_count_description') }}
+
+
+ {{ $t('settings.allow_following_move') }}
+
+
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index a3d0b782..3bc46886 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -81,6 +81,8 @@ export const parseUser = (data) => {
output.subscribed = relationship.subscribing
}
+ output.allow_following_move = data.pleroma.allow_following_move
+
output.hide_follows = data.pleroma.hide_follows
output.hide_followers = data.pleroma.hide_followers
output.hide_follows_count = data.pleroma.hide_follows_count
From 9b7497a65957b3f1d3b9f920266fae9bdae11dd5 Mon Sep 17 00:00:00 2001
From: kPherox
Date: Tue, 4 Feb 2020 04:30:31 +0900
Subject: [PATCH 24/30] Change to hide User migrates tab when allow following
move
---
src/components/interactions/interactions.js | 1 +
src/components/interactions/interactions.vue | 1 +
2 files changed, 2 insertions(+)
diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js
index cc31ff20..7fe5e76d 100644
--- a/src/components/interactions/interactions.js
+++ b/src/components/interactions/interactions.js
@@ -10,6 +10,7 @@ const tabModeDict = {
const Interactions = {
data () {
return {
+ allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
filterMode: tabModeDict['mentions']
}
},
diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue
index a2e252ab..57d5d87c 100644
--- a/src/components/interactions/interactions.vue
+++ b/src/components/interactions/interactions.vue
@@ -22,6 +22,7 @@
:label="$t('interactions.follows')"
/>
From a06f3a7fbc40cd51df3c2d04406494cf60b0cf8a Mon Sep 17 00:00:00 2001
From: kPherox
Date: Tue, 4 Feb 2020 04:37:29 +0900
Subject: [PATCH 25/30] Add `with_move` param for fetching notification
---
src/services/api/api.service.js | 6 +++++-
.../notifications_fetcher/notifications_fetcher.service.js | 3 +++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 11aa0675..b794fd58 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -495,7 +495,8 @@ const fetchTimeline = ({
until = false,
userId = false,
tag = false,
- withMuted = false
+ withMuted = false,
+ withMove = false
}) => {
const timelineUrls = {
public: MASTODON_PUBLIC_TIMELINE,
@@ -535,6 +536,9 @@ const fetchTimeline = ({
if (timeline === 'public' || timeline === 'publicAndExternal') {
params.push(['only_media', false])
}
+ if (timeline === 'notifications') {
+ params.push(['with_move', withMove])
+ }
params.push(['count', 20])
params.push(['with_muted', withMuted])
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index 64499a1b..864e32f8 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -11,9 +11,12 @@ const fetchAndUpdate = ({ store, credentials, older = false }) => {
const rootState = store.rootState || store.state
const timelineData = rootState.statuses.notifications
const hideMutedPosts = getters.mergedConfig.hideMutedPosts
+ const allowFollowingMove = rootState.users.currentUser.allow_following_move
args['withMuted'] = !hideMutedPosts
+ args['withMove'] = !allowFollowingMove
+
args['timeline'] = 'notifications'
if (older) {
if (timelineData.minId !== Number.POSITIVE_INFINITY) {
From ce68ef0138f43fed6617f197d46cc09ac68f9e31 Mon Sep 17 00:00:00 2001
From: kPherox
Date: Tue, 4 Feb 2020 04:50:44 +0900
Subject: [PATCH 26/30] Add option text
---
src/i18n/en.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/i18n/en.json b/src/i18n/en.json
index db2ce54d..54ddbf82 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -232,6 +232,7 @@
"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",
"attachments": "Attachments",
"autoload": "Enable automatic loading when scrolled to the bottom",
From 36e19128bf958559437144b26a3e71f30c9b3377 Mon Sep 17 00:00:00 2001
From: xenofem
Date: Sat, 8 Feb 2020 13:15:09 -0500
Subject: [PATCH 27/30] Indicate whether collapsed statuses contain gallery
media or link preview cards
---
src/components/status/status.vue | 12 +++++++++++-
static/fontello.json | 8 +++++++-
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index d5739304..b9e3fa1d 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -277,7 +277,17 @@
href="#"
class="cw-status-hider"
@click.prevent="toggleShowMore"
- >{{ $t("general.show_more") }}
+ >
+ {{ $t("general.show_more") }}
+
+
+
Date: Sat, 8 Feb 2020 16:01:01 -0500
Subject: [PATCH 28/30] Include non-gallery attachments and distinguish between
images and videos
---
src/components/status/status.js | 10 ++++++++++
src/components/status/status.vue | 6 +++++-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 81b57667..fc5956ec 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -256,6 +256,16 @@ const Status = {
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 () {
return this.mergedConfig.maxThumbnails
},
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index b9e3fa1d..0a82dcbe 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -280,9 +280,13 @@
>
{{ $t("general.show_more") }}
+
Date: Mon, 10 Feb 2020 08:04:58 +0000
Subject: [PATCH 29/30] MRF Keyword Policy Disclosure
---
CHANGELOG.md | 1 +
.../mrf_transparency_panel.js | 10 ++++-
.../mrf_transparency_panel.vue | 43 +++++++++++++++++++
src/i18n/en.json | 11 ++++-
4 files changed, 63 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index abefd958..c011835c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Pleroma AMOLED dark theme
- User level domain mutes, under User Settings -> Mutes
- Emoji reactions for statuses
+- MRF keyword policy disclosure
### Changed
- Captcha now resets on failed registrations
- Notifications column now cleans itself up to optimize performance when tab is left open for a long time
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
index 6a1baec8..a0b600d2 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -11,7 +11,10 @@ const MRFTransparencyPanel = {
rejectInstances: state => get(state, 'instance.federationPolicy.mrf_simple.reject', []),
ftlRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []),
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 () {
return this.quarantineInstances.length ||
@@ -20,6 +23,11 @@ const MRFTransparencyPanel = {
this.ftlRemovalInstances.length ||
this.mediaNsfwInstances.length ||
this.mediaRemovalInstances.length
+ },
+ hasKeywordPolicies () {
+ return this.keywordsFtlRemoval.length ||
+ this.keywordsReject.length ||
+ this.keywordsReplace.length
}
}
}
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
index d6495dc6..8038e587 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -109,6 +109,49 @@
/>
+
+
+ {{ $t("about.mrf.keyword.keyword_policies") }}
+
+
+
+
{{ $t("about.mrf.keyword.ftl_removal") }}
+
+
+
+
+
+
{{ $t("about.mrf.keyword.reject") }}
+
+
+
+
+
+
{{ $t("about.mrf.keyword.replace") }}
+
+
+ -
+ {{ keyword.pattern }}
+ {{ $t("about.mrf.keyword.is_replaced_by") }}
+ {{ keyword.replacement }}
+
+
+
diff --git a/src/i18n/en.json b/src/i18n/en.json
index db2ce54d..ef841bbb 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -16,7 +16,16 @@
"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_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": {
"title": "Chat"
From f6b482be515ea4f0281050f71296ffe0ec2ab305 Mon Sep 17 00:00:00 2001
From: Shpuld Shpludson
Date: Tue, 11 Feb 2020 12:24:51 +0000
Subject: [PATCH 30/30] Emoji Reactions - fixes and improvements
---
src/App.scss | 2 +-
src/_variables.scss | 2 +
.../emoji_reactions/emoji_reactions.js | 48 +++++++-
.../emoji_reactions/emoji_reactions.vue | 103 ++++++++++++++++--
src/components/notification/notification.vue | 7 ++
.../notifications/notifications.scss | 4 +
src/components/react_button/react_button.js | 7 +-
src/components/react_button/react_button.vue | 4 +
src/components/settings/settings.vue | 10 ++
src/components/status/status.vue | 1 +
src/i18n/en.json | 5 +-
src/i18n/fi.json | 5 +-
src/modules/config.js | 4 +-
src/modules/statuses.js | 36 ++++--
src/services/api/api.service.js | 28 ++---
.../entity_normalizer.service.js | 1 +
.../notification_utils/notification_utils.js | 3 +-
test/unit/specs/modules/statuses.spec.js | 13 ++-
18 files changed, 236 insertions(+), 47 deletions(-)
diff --git a/src/App.scss b/src/App.scss
index 754ca62e..922e39b6 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -75,7 +75,7 @@ button {
border-radius: $fallback--btnRadius;
border-radius: var(--btnRadius, $fallback--btnRadius);
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);
font-size: 14px;
font-family: sans-serif;
diff --git a/src/_variables.scss b/src/_variables.scss
index e18101f0..30dc3e42 100644
--- a/src/_variables.scss
+++ b/src/_variables.scss
@@ -27,3 +27,5 @@ $fallback--tooltipRadius: 5px;
$fallback--avatarRadius: 4px;
$fallback--avatarAltRadius: 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;
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index 95d52cb6..b799ac9a 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -1,17 +1,55 @@
+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.status.emoji_reactions
+ 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) {
- const user = this.$store.state.users.currentUser
- const reaction = this.status.emoji_reactions.find(r => r.emoji === emoji)
- return reaction.accounts && reaction.accounts.find(u => u.id === user.id)
+ 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 })
@@ -20,6 +58,8 @@ const EmojiReactions = {
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
},
emojiOnClick (emoji, event) {
+ if (!this.loggedIn) return
+
if (this.reactedWith(emoji)) {
this.unreact(emoji)
} else {
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 00d6d2b7..e5b6d9f5 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,16 +1,58 @@
+
@@ -23,6 +65,31 @@
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;
@@ -38,6 +105,26 @@
&: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 {
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 16124e50..411c0271 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -78,6 +78,13 @@
{{ $t('notifications.migrated_to') }}
+
+
+
+ {{ notification.emoji }}
+
+
+
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()
}
},
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index c925dd71..fb43ebaf 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -54,6 +54,10 @@
.reaction-picker-filter {
padding: 0.5em;
+ display: flex;
+ input {
+ flex: 1;
+ }
}
.reaction-picker-divider {
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index cef492f3..60cb8a87 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -92,6 +92,11 @@
{{ $t('settings.reply_link_preview') }}
+
+
+ {{ $t('settings.emoji_reactions_on_timeline') }}
+
+
@@ -328,6 +333,11 @@
{{ $t('settings.notification_visibility_moves') }}
+
+
+ {{ $t('settings.notification_visibility_emoji_reactions') }}
+
+
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 0a82dcbe..83f07dac 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -369,6 +369,7 @@
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 74e71fc8..d0d654d3 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -126,7 +126,8 @@
"read": "Read!",
"repeated_you": "repeated your status",
"no_more_notifications": "No more notifications",
- "migrated_to": "migrated to"
+ "migrated_to": "migrated to",
+ "reacted_with": "reacted with {0}"
},
"polls": {
"add_poll": "Add Poll",
@@ -283,6 +284,7 @@
"domain_mutes": "Domains",
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
"pad_emoji": "Pad emoji with spaces when adding from picker",
+ "emoji_reactions_on_timeline": "Show emoji reactions on timeline",
"export_theme": "Save preset",
"filtering": "Filtering",
"filtering_explanation": "All statuses containing these words will be muted, one per line",
@@ -331,6 +333,7 @@
"notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats",
"notification_visibility_moves": "User Migrates",
+ "notification_visibility_emoji_reactions": "Reactions",
"no_rich_text_description": "Strip rich text formatting from all posts",
"no_blocks": "No blocks",
"no_mutes": "No mutes",
diff --git a/src/i18n/fi.json b/src/i18n/fi.json
index e7ed5408..ac8b2ac9 100644
--- a/src/i18n/fi.json
+++ b/src/i18n/fi.json
@@ -53,7 +53,8 @@
"notifications": "Ilmoitukset",
"read": "Lue!",
"repeated_you": "toisti viestisi",
- "no_more_notifications": "Ei enempää ilmoituksia"
+ "no_more_notifications": "Ei enempää ilmoituksia",
+ "reacted_with": "lisäsi reaktion {0}"
},
"polls": {
"add_poll": "Lisää äänestys",
@@ -140,6 +141,7 @@
"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_instructions": "Syötä salasanasi vahvistaaksesi tilin poiston.",
+ "emoji_reactions_on_timeline": "Näytä emojireaktiot aikajanalla",
"export_theme": "Tallenna teema",
"filtering": "Suodatus",
"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_mentions": "Maininnat",
"notification_visibility_repeats": "Toistot",
+ "notification_visibility_emoji_reactions": "Reaktiot",
"no_rich_text_description": "Älä näytä tekstin muotoilua.",
"hide_network_description": "Älä näytä seurauksiani tai seuraajiani",
"nsfw_clickthrough": "Piilota NSFW liitteet klikkauksen taakse",
diff --git a/src/modules/config.js b/src/modules/config.js
index de9f041b..8381fa53 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -20,6 +20,7 @@ export const defaultState = {
autoLoad: true,
streaming: false,
hoverPreview: true,
+ emojiReactionsOnTimeline: true,
autohideFloatingPostButton: false,
pauseOnUnfocused: true,
stopGifs: false,
@@ -29,7 +30,8 @@ export const defaultState = {
mentions: true,
likes: true,
repeats: true,
- moves: true
+ moves: true,
+ emojiReactions: false
},
webPushNotifications: false,
muteWords: [],
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index ea0c1749..25b62ac7 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -81,7 +81,8 @@ const visibleNotificationTypes = (rootState) => {
rootState.config.notificationVisibility.mentions && 'mention',
rootState.config.notificationVisibility.repeats && 'repeat',
rootState.config.notificationVisibility.follows && 'follow',
- rootState.config.notificationVisibility.moves && 'move'
+ rootState.config.notificationVisibility.moves && 'move',
+ rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reactions'
].filter(_ => _)
}
@@ -325,6 +326,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
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
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
state.notifications.maxId = notification.id > state.notifications.maxId
@@ -358,7 +363,9 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
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)
} else {
notifObj.body = notification.status.text
@@ -371,10 +378,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
}
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
// according to MDN, anyway.
- setTimeout(notification.close.bind(notification), 5000)
+ setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
}
}
} else if (notification.seen) {
@@ -537,12 +544,13 @@ export const mutations = {
},
addOwnReaction (state, { id, emoji, currentUser }) {
const status = state.allStatusesObject[id]
- const reactionIndex = findIndex(status.emoji_reactions, { emoji })
- const reaction = status.emoji_reactions[reactionIndex] || { emoji, count: 0, accounts: [] }
+ 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
@@ -558,21 +566,23 @@ export const mutations = {
},
removeOwnReaction (state, { id, emoji, currentUser }) {
const status = state.allStatusesObject[id]
- const reactionIndex = findIndex(status.emoji_reactions, { emoji })
+ 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,
- accounts: reaction.accounts.filter(acc => acc.id === currentUser.id)
+ 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.emoji !== emoji))
+ set(status, 'emoji_reactions', status.emoji_reactions.filter(r => r.name !== emoji))
}
},
updateStatusWithPoll (state, { id, poll }) {
@@ -681,18 +691,22 @@ const statuses = {
},
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(
- status => {
+ 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(
- status => {
+ ok => {
dispatch('fetchEmojiReactionsBy', id)
}
)
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index b794fd58..20eaa9a0 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -74,9 +74,9 @@ const MASTODON_SEARCH_2 = `/api/v2/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 PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/emoji_reactions_by`
-const PLEROMA_EMOJI_REACT_URL = id => `/api/v1/pleroma/statuses/${id}/react_with_emoji`
-const PLEROMA_EMOJI_UNREACT_URL = id => `/api/v1/pleroma/statuses/${id}/unreact_with_emoji`
+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
@@ -888,25 +888,27 @@ const fetchRebloggedByUsers = ({ id }) => {
return promisedRequest({ url: MASTODON_STATUS_REBLOGGEDBY_URL(id) }).then((users) => users.map(parseUser))
}
-const fetchEmojiReactions = ({ id }) => {
- return promisedRequest({ url: PLEROMA_EMOJI_REACTIONS_URL(id) })
+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),
- method: 'POST',
- credentials,
- payload: { emoji }
+ 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),
- method: 'POST',
- credentials,
- payload: { emoji }
+ url: PLEROMA_EMOJI_UNREACT_URL(id, emoji),
+ method: 'DELETE',
+ credentials
}).then(parseStatus)
}
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 0a8abbbd..84169a7b 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -354,6 +354,7 @@ export const parseNotification = (data) => {
? null
: parseUser(data.target)
output.from_profile = parseUser(data.account)
+ output.emoji = data.emoji
} else {
const parsedNotice = parseStatus(data.notice)
output.type = data.ntype
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index 860620fc..b17bd7bf 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -7,7 +7,8 @@ export const visibleTypes = store => ([
store.state.config.notificationVisibility.mentions && 'mention',
store.state.config.notificationVisibility.repeats && 'repeat',
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(_ => _))
const sortById = (a, b) => {
diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js
index e53aa388..fe42e85b 100644
--- a/test/unit/specs/modules/statuses.spec.js
+++ b/test/unit/specs/modules/statuses.spec.js
@@ -245,11 +245,12 @@ describe('Statuses module', () => {
it('increments count in existing reaction', () => {
const state = defaultState()
const status = makeMockStatus({ id: '1' })
- status.emoji_reactions = [ { emoji: '😂', count: 1, accounts: [] } ]
+ 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')
})
@@ -261,27 +262,29 @@ describe('Statuses module', () => {
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 = [ { emoji: '😂', count: 2, accounts: [{ id: 'me' }] } ]
+ 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: {} })
+ 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 = [{ emoji: '😂', count: 1, accounts: [{ id: 'me' }] }]
+ 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: {} })
+ mutations.removeOwnReaction(state, { id: '1', emoji: '😂', currentUser: { id: 'me' } })
expect(state.allStatusesObject['1'].emoji_reactions.length).to.eql(0)
})
})