diff --git a/src/App.scss b/src/App.scss index a97ad56d..47886e31 100644 --- a/src/App.scss +++ b/src/App.scss @@ -145,10 +145,6 @@ status.ng-enter.ng-enter-active { } -.media-body { - flex: 1 -} - #content { margin: auto; max-width: 920px; @@ -163,34 +159,14 @@ status.ng-enter.ng-enter-active { padding-left: 0.3em; } -.status .avatar { - width: 48px; -} - -.status.compact .avatar { - width: 32px; -} - -.status { - padding: 0.5em; - padding-right: 1em; - border-bottom: 1px solid silver; -} - -.status-el:last-child .status { - border: none +.container > * { + min-width: 0px; } [ng-click] { cursor: pointer; } -.status-el p { - margin: 0; - margin-top: 0.2em; - margin-bottom: 0.5em; -} - .user-info { padding: 1em; img { diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index fa43c12f..9f751863 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -20,4 +20,4 @@ const Attachment = { } } -export default Attachment +export default Attachment \ No newline at end of file diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 1cb53c32..1e49cbeb 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -11,6 +11,8 @@ + + Don't know how to display this...
@@ -45,6 +47,10 @@ width: 100%; } + audio { + width: 100%; + } + img.media-upload { width: 100%; height: 100%; @@ -94,7 +100,6 @@ img { width: 100%; - flex: 1; border: 1px solid; border-radius: 0.5em; width: 100%; diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js new file mode 100644 index 00000000..ea26d958 --- /dev/null +++ b/src/components/conversation/conversation.js @@ -0,0 +1,48 @@ +import { find, filter, sortBy, toInteger } from 'lodash' +import Status from '../status/status.vue' +import apiService from '../../services/api/api.service.js' + +const conversation = { + computed: { + status () { + const id = toInteger(this.$route.params.id) + const statuses = this.$store.state.statuses.allStatuses + const status = find(statuses, {id}) + + return status + }, + conversation () { + if (!this.status) { + return false + } + + const conversationId = this.status.statusnet_conversation_id + const statuses = this.$store.state.statuses.allStatuses + const conversation = filter(statuses, { statusnet_conversation_id: conversationId }) + return sortBy(conversation, 'id') + } + }, + components: { + Status + }, + created () { + this.fetchConversation() + }, + methods: { + fetchConversation () { + if (this.status) { + const conversationId = this.status.statusnet_conversation_id + apiService.fetchConversation({id: conversationId}) + .then((statuses) => this.$store.dispatch('addNewStatuses', { statuses })) + .then(() => this.$store.commit('updateTimestamps')) + } else { + const id = this.$route.params.id + apiService.fetchStatus({id}) + .then((status) => this.$store.dispatch('addNewStatuses', { statuses: [status] })) + .then(() => this.fetchConversation()) + } + } + } +} + +export default conversation diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue new file mode 100644 index 00000000..60b3f044 --- /dev/null +++ b/src/components/conversation/conversation.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/hello/Hello.css b/src/components/hello/Hello.css deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/hello/Hello.html b/src/components/hello/Hello.html deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/hello/Hello.js b/src/components/hello/Hello.js deleted file mode 100644 index c701c560..00000000 --- a/src/components/hello/Hello.js +++ /dev/null @@ -1,8 +0,0 @@ -export default { - name: 'hello', - data () { - return { - msg: 'Welcome to Your Vue.js app' - } - } -} diff --git a/src/components/hello/Hello.vue b/src/components/hello/Hello.vue deleted file mode 100644 index 828136a8..00000000 --- a/src/components/hello/Hello.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - diff --git a/src/components/status/status.vue b/src/components/status/status.vue index d4bcc279..9d17b8a7 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -20,7 +20,14 @@ {{status.user.screen_name}} > {{status.in_reply_to_screen_name}} - - {{status.created_at_parsed}} + + + {{status.created_at_parsed}} + + + + Source +
@@ -51,13 +58,21 @@ diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 4ebc383f..8799e69c 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -12,12 +12,13 @@ const Timeline = { created () { const store = this.$store const credentials = store.state.users.currentUser.credentials + const showImmediately = this.timeline.visibleStatuses.length === 0 timelineFetcher.fetchAndUpdate({ store, credentials, timeline: this.timelineName, - showImmediately: true + showImmediately }) }, methods: { diff --git a/src/main.js b/src/main.js index de3b2af1..3d3ef1b4 100644 --- a/src/main.js +++ b/src/main.js @@ -5,6 +5,7 @@ import App from './App.vue' import PublicTimeline from './components/public_timeline/public_timeline.vue' import PublicAndExternalTimeline from './components/public_and_external_timeline/public_and_external_timeline.vue' import FriendsTimeline from './components/friends_timeline/friends_timeline.vue' +import Conversation from './components/conversation/conversation.vue' import statusesModule from './modules/statuses.js' import usersModule from './modules/users.js' @@ -23,12 +24,16 @@ const routes = [ { path: '/', redirect: '/main/all' }, { path: '/main/all', component: PublicAndExternalTimeline }, { path: '/main/public', component: PublicTimeline }, - { path: '/main/friends', component: FriendsTimeline } + { path: '/main/friends', component: FriendsTimeline }, + { name: 'conversation', path: '/notice/:id', component: Conversation } ] const router = new VueRouter({ mode: 'history', - routes + routes, + scrollBehavior: (to, from, savedPosition) => { + return savedPosition || { x: 0, y: 0 } + } }) /* eslint-disable no-new */ diff --git a/src/modules/statuses.js b/src/modules/statuses.js index a3031b31..37115506 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -1,4 +1,4 @@ -import { remove, map, slice, sortBy, toInteger, each, find, flatten, maxBy, last, merge, max } from 'lodash' +import { remove, map, slice, sortBy, toInteger, each, find, flatten, maxBy, last, merge, max, isArray } from 'lodash' import moment from 'moment' import apiService from '../services/api/api.service.js' // import parse from '../services/status_parser/status_parser.js' @@ -7,6 +7,7 @@ export const defaultState = { allStatuses: [], maxId: 0, notifications: [], + favorites: new Set(), timelines: { public: { statuses: [], @@ -100,11 +101,17 @@ const mergeOrAdd = (arr, item) => { } const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {} }) => { + // Sanity check + if (!isArray(statuses)) { + return false + } + const allStatuses = state.allStatuses const timelineObject = state.timelines[timeline] // Set the maxId to the new id if it's larger. const updateMaxId = ({id}) => { + if (!timeline) { return false } timelineObject.maxId = max([id, timelineObject.maxId]) } @@ -117,15 +124,15 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us } // Some statuses should only be added to the global status repository. - if (addToTimeline) { + if (timeline && addToTimeline) { mergeOrAdd(timelineObject.statuses, status) } - if (showImmediately) { + if (timeline && showImmediately) { // Add it directly to the visibleStatuses, don't change // newStatusCount mergeOrAdd(timelineObject.visibleStatuses, status) - } else if (addToTimeline && result.new) { + } else if (timeline && addToTimeline && result.new) { // Just change newStatuscount timelineObject.newStatusCount += 1 } @@ -141,6 +148,13 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us 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}) } @@ -159,7 +173,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us let retweet // If the retweeted status is already there, don't add the retweet // to the timeline. - if (find(timelineObject.visibleStatuses, {id: retweetedStatus.id})) { + if (timeline && find(timelineObject.visibleStatuses, {id: retweetedStatus.id})) { // Already have it visible, don't add to timeline, don't show. retweet = addStatus(status, false, false) } else { @@ -169,16 +183,22 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us retweet.retweeted_status = retweetedStatus }, 'favorite': (favorite) => { - updateMaxId(favorite) - favoriteStatus(favorite) + // Only update if this is a new favorite. + if (!state.favorites.has(favorite.id)) { + state.favorites.add(favorite.id) + updateMaxId(favorite) + favoriteStatus(favorite) + } }, 'deletion': (deletion) => { const uri = deletion.uri updateMaxId(deletion) remove(allStatuses, { uri }) - remove(timelineObject.statuses, { uri }) - remove(timelineObject.visibleStatuses, { uri }) + if (timeline) { + remove(timelineObject.statuses, { uri }) + remove(timelineObject.visibleStatuses, { uri }) + } }, 'default': (unknown) => { console.log(unknown) @@ -192,9 +212,11 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us }) // Keep the visible statuses sorted - timelineObject.visibleStatuses = sortBy(timelineObject.visibleStatuses, ({id}) => -id) - timelineObject.statuses = sortBy(timelineObject.statuses, ({id}) => -id) - timelineObject.minVisibleId = (last(timelineObject.statuses) || {}).id + if (timeline) { + timelineObject.visibleStatuses = sortBy(timelineObject.visibleStatuses, ({id}) => -id) + timelineObject.statuses = sortBy(timelineObject.statuses, ({id}) => -id) + timelineObject.minVisibleId = (last(timelineObject.statuses) || {}).id + } } export const mutations = { @@ -228,7 +250,7 @@ export const mutations = { const statuses = { state: defaultState, actions: { - addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline }) { + addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false }) { commit('addNewStatuses', { statuses, showImmediately, timeline, user: rootState.users.currentUser }) }, favorite ({ rootState, commit }, status) { diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index d828aff0..87102376 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -7,18 +7,16 @@ const FAVORITE_URL = '/api/favorites/create' const UNFAVORITE_URL = '/api/favorites/destroy' const RETWEET_URL = '/api/statuses/retweet' const STATUS_UPDATE_URL = '/api/statuses/update.json' +const STATUS_URL = '/api/statuses/show' const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' -// const CONVERSATION_URL = '/api/statusnet/conversation/'; +const CONVERSATION_URL = '/api/statusnet/conversation' -// const FORM_CONTENT_TYPE = {'Content-Type': 'application/x-www-form-urlencoded'}; - -// import { param, ajax } from 'jquery'; -// import { merge } from 'lodash'; +const oldfetch = window.fetch let fetch = (url, options) => { const baseUrl = '' const fullUrl = baseUrl + url - return window.fetch(fullUrl, options) + return oldfetch(fullUrl, options) } const authHeaders = (user) => { @@ -29,6 +27,16 @@ const authHeaders = (user) => { } } +const fetchConversation = ({id}) => { + let url = `${CONVERSATION_URL}/${id}.json?count=100` + return fetch(url).then((data) => data.json()) +} + +const fetchStatus = ({id}) => { + let url = `${STATUS_URL}/${id}.json` + return fetch(url).then((data) => data.json()) +} + const fetchTimeline = ({timeline, credentials, since = false, until = false}) => { const timelineUrls = { public: PUBLIC_TIMELINE_URL, @@ -108,6 +116,8 @@ const uploadMedia = ({formData, credentials}) => { const apiService = { verifyCredentials, fetchTimeline, + fetchConversation, + fetchStatus, favorite, unfavorite, retweet, diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js index 574e4f74..f068bb92 100644 --- a/test/unit/specs/modules/statuses.spec.js +++ b/test/unit/specs/modules/statuses.spec.js @@ -67,6 +67,18 @@ describe('The Statuses module', () => { expect(state.timelines.public.newStatusCount).to.equal(1) }) + it('add the statuses to allStatuses if no timeline is given', () => { + const state = cloneDeep(defaultState) + const status = makeMockStatus({id: 1}) + + mutations.addNewStatuses(state, { statuses: [status] }) + + expect(state.allStatuses).to.eql([status]) + expect(state.timelines.public.statuses).to.eql([]) + expect(state.timelines.public.visibleStatuses).to.eql([]) + expect(state.timelines.public.newStatusCount).to.equal(0) + }) + it('adds the status to allStatuses and to the given timeline, directly visible', () => { const state = cloneDeep(defaultState) const status = makeMockStatus({id: 1}) @@ -185,7 +197,8 @@ describe('The Statuses module', () => { is_post_verb: false, in_reply_to_status_id: '1', // The API uses strings here... uri: 'tag:shitposter.club,2016-08-21:fave:3895:note:773501:2016-08-21T16:52:15+00:00', - text: 'a favorited something by b' + text: 'a favorited something by b', + user: {} } mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' }) @@ -194,6 +207,33 @@ describe('The Statuses module', () => { expect(state.timelines.public.visibleStatuses.length).to.eql(1) expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(1) expect(state.timelines.public.maxId).to.eq(favorite.id) + + // Adding it again does nothing + mutations.addNewStatuses(state, { statuses: [favorite], showImmediately: true, timeline: 'public' }) + + expect(state.timelines.public.visibleStatuses.length).to.eql(1) + expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(1) + expect(state.timelines.public.maxId).to.eq(favorite.id) + + // If something is favorited by the current user, it also sets the 'favorited' property + const user = { + id: 1 + } + + const ownFavorite = { + id: 3, + is_post_verb: false, + in_reply_to_status_id: '1', // The API uses strings here... + uri: 'tag:shitposter.club,2016-08-21:fave:3895:note:773501:2016-08-21T16:52:15+00:00', + text: 'a favorited something by b', + user + } + + mutations.addNewStatuses(state, { statuses: [ownFavorite], showImmediately: true, timeline: 'public', user }) + + expect(state.timelines.public.visibleStatuses.length).to.eql(1) + expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(2) + expect(state.timelines.public.visibleStatuses[0].favorited).to.eql(true) }) describe('notifications', () => { @@ -208,7 +248,8 @@ describe('The Statuses module', () => { is_post_verb: false, in_reply_to_status_id: '1', // The API uses strings here... uri: 'tag:shitposter.club,2016-08-21:fave:3895:note:773501:2016-08-21T16:52:15+00:00', - text: 'a favorited something by b' + text: 'a favorited something by b', + user: {} } mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public', user })