From 9bbf10b55d97f6dbe3197ebbd1bb29d294ff6b55 Mon Sep 17 00:00:00 2001
From: kPherox
Date: Tue, 4 Feb 2020 04:26:32 +0900
Subject: [PATCH 1/8] 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 3837305634..eca6f9b16f 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 2222c29331..8b2336b4dd 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 a3d0b78278..3bc468860b 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 2/8] 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 cc31ff203d..7fe5e76d97 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 a2e252ab1c..57d5d87c3d 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 3/8] 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 11aa06750f..b794fd5816 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 64499a1b65..864e32f878 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 4/8] 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 db2ce54dd7..54ddbf82c2 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 5/8] 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 d573930472..b9e3fa1d76 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 6/8] 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 81b5766748..fc5956ecec 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 b9e3fa1d76..0a82dcbeb0 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 7/8] 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 abefd958bb..c011835c3f 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 6a1baec816..a0b600d2a5 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 d6495dc685..8038e587cb 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 db2ce54dd7..ef841bbba0 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 8/8] 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 754ca62e6b..922e39b66e 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 e18101f0a7..30dc3e42e5 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 95d52cb6c8..b799ac9a24 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 00d6d2b7ac..e5b6d9f576 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 16124e506d..411c027119 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 c925dd7187..fb43ebaf6a 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 cef492f350..60cb8a8761 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 0a82dcbeb0..83f07dacbd 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 74e71fc8c8..d0d654d331 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 e7ed5408fc..ac8b2ac965 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 de9f041b0d..8381fa530c 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 ea0c1749db..25b62ac7fa 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 b794fd5816..20eaa9a0f9 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 0a8abbbd6e..84169a7b05 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 860620fc07..b17bd7bf62 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 e53aa388b1..fe42e85bed 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)
})
})