Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85629458
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index f02ced8d3b..84dd36fa61 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -1,92 +1,96 @@
@import '../../_variables.scss';
.notifications {
// a bit of a hack to allow scrolling below notifications
padding-bottom: 15em;
.panel-heading {
// force the text to stay centered, while keeping
// the button in the right side of the panel heading
position: relative;
.read-button {
position: absolute;
right: 0.7em;
height: 1.8em;
line-height: 100%;
}
}
.unseen-count {
display: inline-block;
background-color: rgba(255, 16, 8, 0.8);
text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.5);
min-width: 1.3em;
border-radius: 1.3em;
margin: 0 0.2em 0 -0.4em;
color: white;
font-size: 0.9em;
text-align: center;
line-height: 1.3em;
}
.notification {
// Will have to use pixels here to ensure consistent distance with
// pad alone and pad + border, browsers bad at rounding this with em,
// they love to give a 1 pixel ghost offset with 0.7em vs 0.3em + 0.4em,
// which does not happen with 10px vs 4px + 6px.
padding: 0.4em 0 0 10px;
display: flex;
border-bottom: 1px solid;
border-bottom-color: inherit;
.text {
min-width: 0px;
word-wrap: break-word;
line-height:18px;
.icon-retweet.lit {
color: $green;
}
+ .icon-user-plus.lit {
+ color: $blue;
+ }
+
.icon-reply.lit {
color: $blue;
}
h1 {
word-break: break-all;
margin: 0 0 0.3em;
padding: 0;
font-size: 1em;
line-height:20px;
small {
font-weight: lighter;
}
}
padding: 0.3em 0.8em 0.5em;
p {
margin: 0;
margin-top: 0;
margin-bottom: 0.3em;
}
}
.avatar {
padding-top: 0.3em;
width: 32px;
height: 32px;
border-radius: 50%;
}
&:last-child {
border-bottom: none;
border-radius: 0 0 10px 10px;
}
}
.unseen {
border-left: 4px solid rgba(255, 16, 8, 0.75);
padding-left: 6px;
}
}
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 6c0419a57e..c9113bc482 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -1,47 +1,56 @@
<template>
<div class="notifications">
<div class="panel panel-default base00-background">
<div class="panel-heading base01-background base04">
<span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span>
Notifications
<button @click.prevent="markAsSeen" class="base05 base01-background read-button">Read!</button>
</div>
<div class="panel-body base03-border">
<div v-for="notification in visibleNotifications" :key="notification" class="notification" :class='{"unseen": !notification.seen}'>
<a :href="notification.action.user.statusnet_profile_url">
<img class='avatar' :src="notification.action.user.profile_image_url_original">
</a>
<div class='text' style="width: 100%;">
<div v-if="notification.type === 'favorite'">
<h1>
<span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
<i class="fa icon-star"></i>
<small><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
</h1>
<div v-html="notification.status.statusnet_html"></div>
</div>
<div v-if="notification.type === 'repeat'">
<h1>
<span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
<i class="fa icon-retweet lit"></i>
<small><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
</h1>
<div v-html="notification.status.statusnet_html"></div>
</div>
<div v-if="notification.type === 'mention'">
<h1>
<span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
<i class="fa icon-reply lit"></i>
<small><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
</h1>
<status :compact="true" :statusoid="notification.status"></status>
</div>
+ <div v-if="notification.type === 'follow'">
+ <h1>
+ <span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
+ <i class="fa icon-user-plus lit"></i>
+ </h1>
+ <div>
+ <router-link :to="{ name: 'user-profile', params: { id: notification.action.user.id } }">@{{ notification.action.user.screen_name }}</router-link> followed you
+ </div>
+ </div>
</div>
</div>
</div>
</div>
</div>
</template>
<script src="./notifications.js"></script>
<style lang="scss" src="./notifications.scss"></style>
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 084800fa5d..de5d7d2314 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -1,378 +1,385 @@
import { includes, remove, slice, sortBy, toInteger, each, find, flatten, maxBy, last, merge, max, isArray } from 'lodash'
import apiService from '../services/api/api.service.js'
// import parse from '../services/status_parser/status_parser.js'
export const defaultState = {
allStatuses: [],
allStatusesObject: {},
maxId: 0,
notifications: [],
favorites: new Set(),
error: false,
timelines: {
mentions: {
statuses: [],
statusesObject: {},
faves: [],
visibleStatuses: [],
visibleStatusesObject: {},
newStatusCount: 0,
maxId: 0,
minVisibleId: 0,
loading: false
},
public: {
statuses: [],
statusesObject: {},
faves: [],
visibleStatuses: [],
visibleStatusesObject: {},
newStatusCount: 0,
maxId: 0,
minVisibleId: 0,
loading: false
},
user: {
statuses: [],
statusesObject: {},
faves: [],
visibleStatuses: [],
visibleStatusesObject: {},
newStatusCount: 0,
maxId: 0,
minVisibleId: 0,
loading: false
},
publicAndExternal: {
statuses: [],
statusesObject: {},
faves: [],
visibleStatuses: [],
visibleStatusesObject: {},
newStatusCount: 0,
maxId: 0,
minVisibleId: 0,
loading: false
},
friends: {
statuses: [],
statusesObject: {},
faves: [],
visibleStatuses: [],
visibleStatusesObject: {},
newStatusCount: 0,
maxId: 0,
minVisibleId: 0,
loading: false
}
}
}
const isNsfw = (status) => {
const nsfwRegex = /#nsfw/i
return includes(status.tags, 'nsfw') || !!status.text.match(nsfwRegex)
}
export const prepareStatus = (status) => {
// Parse nsfw tags
if (status.nsfw === undefined) {
status.nsfw = isNsfw(status)
}
// Set deleted flag
status.deleted = false
// To make the array reactive
status.attachments = status.attachments || []
return status
}
export const statusType = (status) => {
if (status.is_post_verb) {
return 'status'
}
if (status.retweeted_status) {
return 'retweet'
}
if (typeof status.uri === 'string' && status.uri.match(/(fave|objectType=Favourite)/)) {
return 'favorite'
}
if (status.text.match(/deleted notice {{tag/)) {
return 'deletion'
}
+ if (status.text.match(/started following/)) {
+ return 'follow'
+ }
+
return 'unknown'
}
export const findMaxId = (...args) => {
return (maxBy(flatten(args), 'id') || {}).id
}
const mergeOrAdd = (arr, obj, item) => {
const oldItem = obj[item.id]
if (oldItem) {
// We already have this, so only merge the new info.
merge(oldItem, item)
// Reactivity fix.
oldItem.attachments.splice(oldItem.attachments.length)
return {item: oldItem, new: false}
} else {
// This is a new item, prepare it
prepareStatus(item)
arr.push(item)
obj[item.id] = item
return {item, new: true}
}
}
const sortTimeline = (timeline) => {
timeline.visibleStatuses = sortBy(timeline.visibleStatuses, ({id}) => -id)
timeline.statuses = sortBy(timeline.statuses, ({id}) => -id)
timeline.minVisibleId = (last(timeline.visibleStatuses) || {}).id
return timeline
}
const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {}, noIdUpdate = false }) => {
// Sanity check
if (!isArray(statuses)) {
return false
}
const allStatuses = state.allStatuses
const allStatusesObject = state.allStatusesObject
const timelineObject = state.timelines[timeline]
// Set the maxId to the new id if it's larger.
const updateMaxId = ({id}) => {
if (!timeline || noIdUpdate) { return false }
timelineObject.maxId = max([id, timelineObject.maxId])
}
const addStatus = (status, showImmediately, addToTimeline = true) => {
const result = mergeOrAdd(allStatuses, allStatusesObject, status)
status = result.item
if (result.new) {
updateMaxId(status)
if (statusType(status) === 'retweet' && status.retweeted_status.user.id === user.id) {
addNotification({ type: 'repeat', status: status.retweeted_status, action: status })
}
// We are mentioned in a post
if (statusType(status) === 'status' && find(status.attentions, { id: user.id })) {
const mentions = state.timelines.mentions
// Add the mention to the mentions timeline
if (timelineObject !== mentions) {
mergeOrAdd(mentions.statuses, mentions.statusesObject, status)
mentions.newStatusCount += 1
sortTimeline(mentions)
}
addNotification({ type: 'mention', status, action: status })
}
}
// Decide if we should treat the status as new for this timeline.
let resultForCurrentTimeline
// Some statuses should only be added to the global status repository.
if (timeline && addToTimeline) {
resultForCurrentTimeline = mergeOrAdd(timelineObject.statuses, timelineObject.statusesObject, status)
}
if (timeline && showImmediately) {
// Add it directly to the visibleStatuses, don't change
// newStatusCount
mergeOrAdd(timelineObject.visibleStatuses, timelineObject.visibleStatusesObject, status)
} else if (timeline && addToTimeline && resultForCurrentTimeline.new) {
// Just change newStatuscount
timelineObject.newStatusCount += 1
}
return status
}
const addNotification = ({type, status, action}) => {
// Only add a new notification if we don't have one for the same action
if (!find(state.notifications, (oldNotification) => oldNotification.action.id === action.id)) {
state.notifications.push({type, status, action, seen: false})
}
}
const favoriteStatus = (favorite) => {
const status = find(allStatuses, { id: toInteger(favorite.in_reply_to_status_id) })
if (status) {
status.fave_num += 1
// This is our favorite, so the relevant bit.
if (favorite.user.id === user.id) {
status.favorited = true
}
// Add a notification if the user's status is favorited
if (status.user.id === user.id) {
addNotification({type: 'favorite', status, action: favorite})
}
}
return status
}
const processors = {
'status': (status) => {
addStatus(status, showImmediately)
},
'retweet': (status) => {
// RetweetedStatuses are never shown immediately
const retweetedStatus = addStatus(status.retweeted_status, false, false)
let retweet
// If the retweeted status is already there, don't add the retweet
// to the timeline.
if (timeline && find(timelineObject.statuses, {id: retweetedStatus.id})) {
// Already have it visible, don't add to timeline, don't show.
retweet = addStatus(status, false, false)
} else {
retweet = addStatus(status, showImmediately)
}
retweet.retweeted_status = retweetedStatus
},
'favorite': (favorite) => {
// Only update if this is a new favorite.
if (!state.favorites.has(favorite.id)) {
state.favorites.add(favorite.id)
updateMaxId(favorite)
favoriteStatus(favorite)
}
},
+ 'follow': (status) => {
+ addNotification({ type: 'follow', status: status, action: status })
+ },
'deletion': (deletion) => {
const uri = deletion.uri
updateMaxId(deletion)
// Remove possible notification
const status = find(allStatuses, {uri})
if (!status) {
return
}
remove(state.notifications, ({action: {id}}) => id === status.id)
remove(allStatuses, { uri })
if (timeline) {
remove(timelineObject.statuses, { uri })
remove(timelineObject.visibleStatuses, { uri })
}
},
'default': (unknown) => {
console.log('unknown status type')
console.log(unknown)
}
}
each(statuses, (status) => {
const type = statusType(status)
const processor = processors[type] || processors['default']
processor(status)
})
// Keep the visible statuses sorted
if (timeline) {
sortTimeline(timelineObject)
}
}
export const mutations = {
addNewStatuses,
showNewStatuses (state, { timeline }) {
const oldTimeline = (state.timelines[timeline])
oldTimeline.newStatusCount = 0
oldTimeline.visibleStatuses = slice(oldTimeline.statuses, 0, 50)
oldTimeline.visibleStatusesObject = {}
each(oldTimeline.visibleStatuses, (status) => { oldTimeline.visibleStatusesObject[status.id] = status })
},
clearTimeline (state, { timeline }) {
const emptyTimeline = {
statuses: [],
statusesObject: {},
faves: [],
visibleStatuses: [],
visibleStatusesObject: {},
newStatusCount: 0,
maxId: 0,
minVisibleId: 0,
loading: false
}
state.timelines[timeline] = emptyTimeline
},
setFavorited (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.favorited = value
},
setRetweeted (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.repeated = value
},
setDeleted (state, { status }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.deleted = true
},
setLoading (state, { timeline, value }) {
state.timelines[timeline].loading = value
},
setNsfw (state, { id, nsfw }) {
const newStatus = state.allStatusesObject[id]
newStatus.nsfw = nsfw
},
setError (state, { value }) {
state.error = value
},
markNotificationsAsSeen (state, notifications) {
each(notifications, (notification) => {
notification.seen = true
})
}
}
const statuses = {
state: defaultState,
actions: {
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false }) {
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser })
},
setError ({ rootState, commit }, { value }) {
commit('setError', { value })
},
deleteStatus ({ rootState, commit }, status) {
commit('setDeleted', { status })
apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials })
},
favorite ({ rootState, commit }, status) {
// Optimistic favoriting...
commit('setFavorited', { status, value: true })
apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
},
unfavorite ({ rootState, commit }, status) {
// Optimistic favoriting...
commit('setFavorited', { status, value: false })
apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
},
retweet ({ rootState, commit }, status) {
// Optimistic retweeting...
commit('setRetweeted', { status, value: true })
apiService.retweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
}
},
mutations
}
export default statuses
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Aug 8, 9:41 PM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1723992
Default Alt Text
(17 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline
Log In to Comment