From 6af870cd9069a8fc45b7684d264656dad0cf4a70 Mon Sep 17 00:00:00 2001 From: kPherox Date: Wed, 11 Dec 2019 00:00:10 +0900 Subject: Add view for moves notifications --- src/components/notifications/notifications.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/components/notifications/notifications.scss') diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 71876b14..148ac7f2 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -76,7 +76,7 @@ } } - .follow-text { + .follow-text, .move-text { padding: 0.5em 0; } @@ -151,6 +151,11 @@ color: var(--cOrange, $fallback--cOrange); } + .icon-arrow-curved.lit { + color: $fallback--cBlue; + color: var(--cBlue, $fallback--cBlue); + } + .status-content { margin: 0; max-height: 300px; -- cgit v1.2.3-70-g09d2 From 526b43eba66c597c78857da6ded3c7f672f96e49 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 28 Jan 2020 01:29:20 +0200 Subject: Kenomo (see: #624) theme. Ability to define link color for post contents. Fixes --- src/components/emoji_input/emoji_input.vue | 2 + src/components/notifications/notifications.scss | 3 + src/components/popper/popper.scss | 2 + src/components/status/status.vue | 7 +++ src/components/style_switcher/style_switcher.vue | 17 ++++++ src/components/user_card/user_card.vue | 5 ++ src/services/theme_data/pleromafe.js | 34 ++++++++++++ static/themes/kenomo.json | 71 ++++++++++++++++++++++++ 8 files changed, 141 insertions(+) create mode 100644 static/themes/kenomo.json (limited to 'src/components/notifications/notifications.scss') diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index 94eae560..e9ac09c3 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -116,6 +116,8 @@ --faint: var(--popoverFaintText, $fallback--faint); --faintLink: var(--popoverFaintLink, $fallback--faint); --lightText: var(--popoverLightText, $fallback--lightText); + --postLink: var(--popoverPostLink, $fallback--link); + --postFaintLink: var(--popoverPostFaintLink, $fallback--link); --icon: var(--popoverIcon, $fallback--icon); } } diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 71876b14..ca762432 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -68,6 +68,9 @@ a { color: var(--faintLink); } + .status-content a { + color: var(--postFaintLink); + } } padding: 0; .media-body { diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss index 341416c1..8e2dcd55 100644 --- a/src/components/popper/popper.scss +++ b/src/components/popper/popper.scss @@ -15,6 +15,8 @@ --faint: var(--popoverFaintText, $fallback--faint); --faintLink: var(--popoverFaintLink, $fallback--faint); --lightText: var(--popoverLightText, $fallback--lightText); + --postLink: var(--popoverPostLink, $fallback--link); + --postFaintLink: var(--popoverPostFaintLink, $fallback--link); --icon: var(--popoverIcon, $fallback--icon); } diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 1997e187..b1048832 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -451,6 +451,8 @@ $status-margin: 0.75em; --lightText: var(--selectedPostLightText, $fallback--light); --faint: var(--selectedPostFaintText, $fallback--faint); --faintLink: var(--selectedPostFaintLink, $fallback--faint); + --postLink: var(--selectedPostPostLink, $fallback--faint); + --postFaintLink: var(--selectedPostFaintPostLink, $fallback--faint); --icon: var(--selectedPostIcon, $fallback--icon); } @@ -640,6 +642,11 @@ $status-margin: 0.75em; line-height: 1.4em; white-space: pre-wrap; + a { + color: $fallback--link; + color: var(--postLink, $fallback--link); + } + img, video { max-width: 100%; max-height: 400px; diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index 6e38bd8e..0b3b0fbf 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -248,6 +248,23 @@
+

{{ $t('settings.style.advanced_colors.post') }}

+ + + +

{{ $t('settings.style.advanced_colors.alert') }}

brightness(20 * mod, text).rgb }, + postLink: { + depends: ['link'] + }, + border: { depends: ['fg'], opacity: 'border', diff --git a/static/themes/kenomo.json b/static/themes/kenomo.json new file mode 100644 index 00000000..98ddf974 --- /dev/null +++ b/static/themes/kenomo.json @@ -0,0 +1,71 @@ +{ + "_pleroma_theme_version": 2, + "name": "Kenomo", + "source": { + "themeEngineVersion": 3, + "fonts": {}, + "shadows": { + "panel": [], + "topBar": [], + "button": [ + { + "x": 0, + "y": 1, + "blur": 0, + "spread": 0, + "color": "#FFFFFF", + "alpha": 0.2, + "inset": true + }, + { + "x": 0, + "y": -1, + "blur": 0, + "spread": 0, + "color": "#000000", + "alpha": 0.2, + "inset": true + } + ], + "input": [ + { + "x": 0, + "y": "0", + "blur": 0, + "spread": "1", + "color": "#576574", + "alpha": "1", + "inset": true + } + ] + }, + "opacity": { + "underlay": "1", + "border": "0" + }, + "colors": { + "bg": "#ffffff", + "fg": "#f6f6f6", + "text": "#494949", + "underlay": "#ffffff", + "link": "#818181", + "accent": "#818181", + "cBlue": "#2e86de", + "cRed": "#c96248", + "cGreen": "#0fa00f", + "cOrange": "#aa7623", + "postLink": "#2e86de", + "border": "#ffffff", + "icon": "#8a8a8a", + "panel": "transparent", + "topBarText": "#4b4b4b", + "tab": "--btn,-30", + "btn": "#576574" + }, + "radii": { + "panel": "0", + "avatar": "6", + "avatarAlt": "6" + } + } +} -- cgit v1.2.3-70-g09d2 From f6b482be515ea4f0281050f71296ffe0ec2ab305 Mon Sep 17 00:00:00 2001 From: Shpuld Shpludson Date: Tue, 11 Feb 2020 12:24:51 +0000 Subject: Emoji Reactions - fixes and improvements --- src/App.scss | 2 +- src/_variables.scss | 2 + src/components/emoji_reactions/emoji_reactions.js | 48 +++++++++- src/components/emoji_reactions/emoji_reactions.vue | 103 +++++++++++++++++++-- src/components/notification/notification.vue | 7 ++ src/components/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/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(-) (limited to 'src/components/notifications/notifications.scss') 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) }) }) -- cgit v1.2.3-70-g09d2 From b9820b84a14119f3a7a4e1f663927b1ad455a5c2 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 14 Mar 2020 19:41:38 +0000 Subject: Prevent overflow for long usernames/domains --- src/components/notifications/notifications.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'src/components/notifications/notifications.scss') diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index f5b13322..a8f4430f 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -81,6 +81,7 @@ .follow-text, .move-text { padding: 0.5em 0; + overflow-wrap: break-word; } .status-el { -- cgit v1.2.3-70-g09d2 From 01b07f01e9340935faf51e5a3c8034cc90423989 Mon Sep 17 00:00:00 2001 From: eugenijm Date: Sat, 25 Apr 2020 07:04:39 +0300 Subject: Add support for follow request notifications --- CHANGELOG.md | 3 ++ src/components/notification/notification.js | 19 ++++++++++ src/components/notification/notification.vue | 44 ++++++++++++++++------ src/components/notifications/notifications.scss | 15 ++++++++ src/i18n/en.json | 5 ++- src/modules/config.js | 3 +- src/modules/statuses.js | 22 ++++++++++- src/services/api/api.service.js | 11 ++++++ .../entity_normalizer/entity_normalizer.service.js | 5 +-- .../notification_utils/notification_utils.js | 7 +++- static/fontello.json | 14 ++++++- 11 files changed, 128 insertions(+), 20 deletions(-) (limited to 'src/components/notifications/notifications.scss') diff --git a/CHANGELOG.md b/CHANGELOG.md index f45561d0..685fe629 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach: +### Add +- Follow request notification support + ## [2.0.2] - 2020-04-08 ### Fixed - Favorite/Repeat avatars not showing up on private instances/non-public posts diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index e7bd769e..6deee7d5 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -2,6 +2,7 @@ import Status from '../status/status.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import UserCard from '../user_card/user_card.vue' import Timeago from '../timeago/timeago.vue' +import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -32,6 +33,21 @@ const Notification = { }, toggleMute () { this.unmuted = !this.unmuted + }, + approveUser () { + this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) + this.$store.dispatch('removeFollowRequest', this.user) + this.$store.dispatch('updateNotification', { + id: this.notification.id, + updater: notification => { + notification.type = 'follow' + } + }) + }, + denyUser () { + this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) + this.$store.dispatch('removeFollowRequest', this.user) + this.$store.dispatch('dismissNotification', { id: this.notification.id }) } }, computed: { @@ -57,6 +73,9 @@ const Notification = { }, needMute () { return this.user.muted + }, + isStatusNotification () { + return isStatusNotification(this.notification.type) } } } diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 51875747..02802776 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -74,6 +74,10 @@ {{ $t('notifications.followed_you') }} + + + {{ $t('notifications.follow_request') }} + {{ $t('notifications.migrated_to') }} @@ -87,30 +91,30 @@
    - + - +
    - + - +
    { each(notifications, (notification) => { - if (notification.type !== 'follow' && notification.type !== 'move') { + if (isStatusNotification(notification.type)) { notification.action = addStatusToGlobalStorage(state, notification.action).item notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item } @@ -361,13 +362,16 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot case 'move': i18nString = 'migrated_to' break + case 'follow_request': + i18nString = 'follow_request' + break } 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 { + } else if (isStatusNotification(notification.type)) { notifObj.body = notification.status.text } @@ -521,6 +525,13 @@ export const mutations = { notification.seen = true }) }, + dismissNotification (state, { id }) { + state.notifications.data = state.notifications.data.filter(n => n.id !== id) + }, + updateNotification (state, { id, updater }) { + const notification = find(state.notifications.data, n => n.id === id) + notification && updater(notification) + }, queueFlush (state, { timeline, id }) { state.timelines[timeline].flushMarker = id }, @@ -680,6 +691,13 @@ const statuses = { credentials: rootState.users.currentUser.credentials }) }, + dismissNotification ({ rootState, commit }, { id }) { + rootState.api.backendInteractor.dismissNotification({ id }) + .then(() => commit('dismissNotification', { id })) + }, + updateNotification ({ rootState, commit }, { id, updater }) { + commit('updateNotification', { id, updater }) + }, fetchFavsAndRepeats ({ rootState, commit }, id) { Promise.all([ rootState.api.backendInteractor.fetchFavoritedByUsers({ id }), diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index ad2b2ad5..cda61ee2 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -29,6 +29,7 @@ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' +const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss` const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite` const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite` const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog` @@ -1010,6 +1011,15 @@ const unmuteDomain = ({ domain, credentials }) => { }) } +const dismissNotification = ({ credentials, id }) => { + return promisedRequest({ + url: MASTODON_DISMISS_NOTIFICATION_URL(id), + method: 'POST', + payload: { id }, + credentials + }) +} + export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => { return Object.entries({ ...(credentials @@ -1165,6 +1175,7 @@ const apiService = { denyUser, suggestions, markNotificationsAsSeen, + dismissNotification, vote, fetchPoll, fetchFavoritedByUsers, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 84169a7b..6cacd0b8 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -1,4 +1,5 @@ import escape from 'escape-html' +import { isStatusNotification } from '../notification_utils/notification_utils.js' const qvitterStatusType = (status) => { if (status.is_post_verb) { @@ -346,9 +347,7 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type output.seen = data.pleroma.is_seen - output.status = output.type === 'follow' || output.type === 'move' - ? null - : parseStatus(data.status) + output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null output.action = output.status // TODO: Refactor, this is unneeded output.target = output.type !== 'move' ? null diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index b17bd7bf..eb479227 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -1,4 +1,4 @@ -import { filter, sortBy } from 'lodash' +import { filter, sortBy, includes } from 'lodash' export const notificationsFromStore = store => store.state.statuses.notifications.data @@ -7,10 +7,15 @@ 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.followRequest && 'follow_request', store.state.config.notificationVisibility.moves && 'move', store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' ].filter(_ => _)) +const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction'] + +export const isStatusNotification = (type) => includes(statusNotifications, type) + const sortById = (a, b) => { const seqA = Number(a.id) const seqB = Number(b.id) diff --git a/static/fontello.json b/static/fontello.json index 5a7086a2..5963b68b 100755 --- a/static/fontello.json +++ b/static/fontello.json @@ -345,6 +345,18 @@ "css": "link", "code": 59427, "src": "fontawesome" + }, + { + "uid": "8b80d36d4ef43889db10bc1f0dc9a862", + "css": "user", + "code": 59428, + "src": "fontawesome" + }, + { + "uid": "12f4ece88e46abd864e40b35e05b11cd", + "css": "ok", + "code": 59431, + "src": "fontawesome" } ] -} +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 406fdd8edec210d14589e0ff684a166250236779 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Sat, 2 May 2020 10:19:47 +0300 Subject: follow request bugfixes, wrong text, notifs not being marked as read, approving from follow request view --- .../follow_request_card/follow_request_card.js | 19 +++++++++++++++++++ src/components/notification/notification.js | 1 + src/components/notification/notification.vue | 6 +++--- src/components/notifications/notifications.scss | 19 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) (limited to 'src/components/notifications/notifications.scss') diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index a8931787..2a9d3db5 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -1,4 +1,5 @@ import BasicUserCard from '../basic_user_card/basic_user_card.vue' +import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js' const FollowRequestCard = { props: ['user'], @@ -6,13 +7,31 @@ const FollowRequestCard = { BasicUserCard }, methods: { + findFollowRequestNotificationId () { + const notif = notificationsFromStore(this.$store).find( + (notif) => notif.from_profile.id === this.user.id && notif.type === 'follow_request' + ) + return notif && notif.id + }, approveUser () { this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.dispatch('removeFollowRequest', this.user) + + const notifId = this.findFollowRequestNotificationId() + this.$store.dispatch('updateNotification', { + id: notifId, + updater: notification => { + notification.type = 'follow' + notification.seen = true + } + }) }, denyUser () { this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) this.$store.dispatch('removeFollowRequest', this.user) + + const notifId = this.findFollowRequestNotificationId() + this.$store.dispatch('dismissNotification', { id: notifId }) } } } diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 6deee7d5..8c20ff09 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -41,6 +41,7 @@ const Notification = { id: this.notification.id, updater: notification => { notification.type = 'follow' + notification.seen = true } }) }, diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 02802776..f6da07dd 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -137,13 +137,13 @@ style="white-space: nowrap;" >
    diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 80dad28b..9efcfcf8 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -79,6 +79,25 @@ } } + .follow-request-accept { + cursor: pointer; + + &:hover { + color: $fallback--text; + color: var(--text, $fallback--text); + } + } + + .follow-request-reject { + cursor: pointer; + + &:hover { + color: $fallback--cRed; + color: var(--cRed, $fallback--cRed); + } + } + + .follow-text, .move-text { padding: 0.5em 0; overflow-wrap: break-word; -- cgit v1.2.3-70-g09d2 From ebf2ce84fdd7e175f5715452400ec3bdf789310b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 6 Jun 2020 23:08:52 +0300 Subject: alignment fixes --- src/components/notifications/notifications.scss | 6 +-- src/components/status/status.vue | 62 +++++++++++++++++++------ src/i18n/en.json | 4 +- 3 files changed, 52 insertions(+), 20 deletions(-) (limited to 'src/components/notifications/notifications.scss') diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 9efcfcf8..b675af5a 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -36,6 +36,8 @@ border-bottom: 1px solid; border-color: $fallback--border; border-color: var(--border, $fallback--border); + word-wrap: break-word; + word-break: break-word; &:hover .animated.avatar { canvas { @@ -46,10 +48,6 @@ } } - .muted { - padding: .25em .6em; - } - .non-mention { display: flex; flex: 1; diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 3137cb9d..336f912a 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -17,7 +17,7 @@