diff options
Diffstat (limited to 'src')
22 files changed, 238 insertions, 56 deletions
@@ -1,9 +1,11 @@ import UserPanel from './components/user_panel/user_panel.vue' +import NavPanel from './components/nav_panel/nav_panel.vue' export default { name: 'app', components: { - UserPanel + UserPanel, + NavPanel }, computed: { user () { return this.$store.state.users.currentUser || {} }, diff --git a/src/App.scss b/src/App.scss index 4856f990..2850867d 100644 --- a/src/App.scss +++ b/src/App.scss @@ -2,12 +2,13 @@ $main-color: #f58d2c; $main-background: white; $darkened-background: whitesmoke; -body { +#app { background-color: $main-color; background-size: cover; background-attachment: fixed; background-repeat: no-repeat; background-position: 0 50px; + min-height: 100vh; } h4 { @@ -197,27 +198,6 @@ status.ng-enter.ng-enter-active { cursor: pointer; } -nav-panel ul { - list-style: none; - margin: 0; - padding: 0; -} - -nav-panel li { - border-bottom: 1px solid silver; - padding: 0.5em; - padding-left: 1em; -} - -nav-panel li:last-child { - border: none; -} - -nav-panel a { - display: block; - width: 100%; -} - .status-el p { margin: 0; margin-top: 0.2em; @@ -279,11 +259,6 @@ attention { display: flex; padding: 0.5em; - media-upload { - font-size: 26px; - flex: 1; - } - button { flex: 2; } diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 161c6b2b..85d924d0 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -19,8 +19,8 @@ const Attachment = { type = 'image'; } - if(this.attachment.mimetype.match(/video\/webm/)) { - type = 'webm'; + if(this.attachment.mimetype.match(/video\/(webm|mp4)/)) { + type = 'video'; }; return type diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index b8c55a1a..8c088cf1 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -6,7 +6,7 @@ <a class="image-attachment" v-if="type === 'image' && !nsfw" :href="attachment.url" target="_blank"><img :src="attachment.url"></img></a> - <video v-if="type === 'webm' && !nsfw" :src="attachment.url" controls></video> + <video v-if="type === 'video' && !nsfw" :src="attachment.url" controls></video> <span v-if="type === 'unknown'">Don't know how to display this...</span> diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js new file mode 100644 index 00000000..c24c71e9 --- /dev/null +++ b/src/components/media_upload/media_upload.js @@ -0,0 +1,22 @@ +/* eslint-env browser */ +import statusPosterService from '../../services/status_poster/status_poster.service.js' + +const mediaUpload = { + mounted () { + const store = this.$store + const input = this.$el.querySelector('input') + const self = this + + input.addEventListener('change', ({target}) => { + const file = target.files[0]; + const formData = new FormData(); + formData.append('media', file); + statusPosterService.uploadMedia({ store, formData }) + .then((fileData) => { + self.$emit('uploaded', fileData) + }) + }) + } +} + +export default mediaUpload diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue new file mode 100644 index 00000000..a62d8316 --- /dev/null +++ b/src/components/media_upload/media_upload.vue @@ -0,0 +1,17 @@ +<template> + <div class="media-upload"> + <label class="btn btn-default"> + <i class="fa icon-upload"></i> + <input type=file style="position: fixed; top: -100em"></input> + </label> + </div> +</template> + +<script src="./media_upload.js" ></script> + +<style> + .media-upload { + font-size: 26px; + flex: 1; + } +</style> diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js new file mode 100644 index 00000000..318d30cc --- /dev/null +++ b/src/components/nav_panel/nav_panel.js @@ -0,0 +1,4 @@ +const NavPanel = { +} + +export default NavPanel diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue new file mode 100644 index 00000000..d493fad3 --- /dev/null +++ b/src/components/nav_panel/nav_panel.vue @@ -0,0 +1,54 @@ +<template> + <div class="nav-panel"> + <div class="panel panel-default"> + <ul> + <li ng-if='currentUser'> + <router-link to='/main/friends'> + Timeline + </router-link> + </li> + <li> + <router-link to='/main/public'> + Public Timeline + </router-link> + </li> + <li> + <router-link to='/main/all'> + The Whole Known Network + </router-link> + </li> + </ul> + </div> + </div> +</template> + +<script src="./nav_panel.js" ></script> + +<style lang="scss"> + + .nav-panel ul { + list-style: none; + margin: 0; + padding: 0; + } + + .nav-panel li { + border-bottom: 1px solid silver; + padding: 0.5em; + padding-left: 1em; + } + + .nav-panel li:last-child { + border: none; + } + + .nav-panel a { + display: block; + width: 100%; + + &.router-link-active { + font-weight: bold + } + } + +</style> diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 2c015154..30a7cd40 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -1,4 +1,6 @@ import statusPoster from '../../services/status_poster/status_poster.service.js' +import MediaUpload from '../media_upload/media_upload.vue' + import { reject, map, uniqBy } from 'lodash'; const buildMentionsString = ({user, attentions}, currentUser) => { @@ -23,6 +25,9 @@ const PostStatusForm = { 'repliedUser', 'attentions' ], + components: { + MediaUpload + }, data () { let statusText = '' @@ -33,7 +38,8 @@ const PostStatusForm = { return { newStatus: { - status: statusText + status: statusText, + files: [] } } }, @@ -41,11 +47,18 @@ const PostStatusForm = { postStatus (newStatus) { statusPoster.postStatus({ status: newStatus.status, + media: newStatus.files, store: this.$store, inReplyToStatusId: this.replyTo }) - this.newStatus = { } + this.newStatus = { + status: '', + files: [] + } this.$emit('posted') + }, + addMediaFile (fileInfo) { + this.newStatus.files.push(fileInfo) } } } diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index d2106d5a..943bf422 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -10,7 +10,7 @@ </div> </div> <div class='form-bottom'> - <media-upload files="newStatus.files"></media-upload> + <media-upload v-on:uploaded="addMediaFile"></media-upload> <button type="submit" class="btn btn-default" >Submit</button> </div> </form> diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js new file mode 100644 index 00000000..138118ad --- /dev/null +++ b/src/components/public_and_external_timeline/public_and_external_timeline.js @@ -0,0 +1,11 @@ +import Timeline from '../timeline/timeline.vue' +const PublicAndExternalTimeline = { + components: { + Timeline + }, + computed: { + timeline () { return this.$store.state.statuses.timelines.publicAndExternal } + } +} + +export default PublicAndExternalTimeline diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.vue b/src/components/public_and_external_timeline/public_and_external_timeline.vue new file mode 100644 index 00000000..94cdaf17 --- /dev/null +++ b/src/components/public_and_external_timeline/public_and_external_timeline.vue @@ -0,0 +1,10 @@ +<template> + <div class="timeline panel panel-default"> + <div class="panel-heading">THE WHOLE KNOWN NETWORK</div> + <div class="panel-body"> + <Timeline v-bind:timeline="timeline" v-bind:timeline-name="'publicAndExternal'"/> + </div> + </div> +</template> + +<script src="./public_and_external_timeline.js"></script> diff --git a/src/components/status/status.js b/src/components/status/status.js index 2e6565e8..6253d334 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -16,6 +16,9 @@ const Status = { } else { return this.statusoid } + }, + loggedIn () { + return !!this.$store.state.users.currentUser } }, components: { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 8361aa52..1c5dc458 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -32,7 +32,7 @@ </attachment> </div> - <div> + <div v-if="loggedIn"> <div class='status-actions'> <div> <a href="#" v-on:click.prevent="toggleReplying"> diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 433bca11..e0c75d76 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -1,4 +1,5 @@ import Status from '../status/status.vue' +import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' const Timeline = { props: [ @@ -8,9 +9,32 @@ const Timeline = { components: { Status }, + created () { + const store = this.$store + const credentials = store.state.users.currentUser.credentials + + timelineFetcher.fetchAndUpdate({ + store, + credentials, + timeline: this.timelineName, + showImmediately: true + }) + }, methods: { showNewStatuses () { this.$store.commit('showNewStatuses', { timeline: this.timelineName }) + }, + fetchOlderStatuses () { + const store = this.$store + const credentials = store.state.users.currentUser.credentials + store.commit('setLoading', { timeline: this.timelineName, value: true }); + timelineFetcher.fetchAndUpdate({ + store, + credentials, + timeline: this.timelineName, + older: true, + showImmediately: true + }).then(() => store.commit('setLoading', { timeline: this.timelineName, value: false })) } } } diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 1e779638..2f1b8c28 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -8,6 +8,13 @@ </div> </a> <status v-for="status in timeline.visibleStatuses" :key="status.id" v-bind:statusoid="status"></status> + <a href="#" v-on:click.prevent='fetchOlderStatuses()' v-if="!timeline.loading"> + <div class="new-status-notification"> + <p class="text-center" > + Load older statuses. + </p> + </div> + </a> </div> </template> <script src="./timeline.js"></script> diff --git a/src/main.js b/src/main.js index 6ce2ca1b..de3b2af1 100644 --- a/src/main.js +++ b/src/main.js @@ -3,6 +3,7 @@ import VueRouter from 'vue-router' import Vuex from 'vuex' 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 statusesModule from './modules/statuses.js' @@ -19,12 +20,16 @@ const store = new Vuex.Store({ }) const routes = [ - { path: '/', redirect: '/main/public' }, + { path: '/', redirect: '/main/all' }, + { path: '/main/all', component: PublicAndExternalTimeline }, { path: '/main/public', component: PublicTimeline }, { path: '/main/friends', component: FriendsTimeline } ] -const router = new VueRouter({routes}) +const router = new VueRouter({ + mode: 'history', + routes +}) /* eslint-disable no-new */ new Vue({ @@ -34,9 +39,3 @@ new Vue({ template: '<App/>', components: { App } }) - -const statusesEx = require('../test/fixtures/statuses.json') - -setTimeout(() => { - store.commit('addNewStatuses', { statuses: statusesEx, timeline: 'public', showImmediately: false }) -}, 3000) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index eee368a3..5fb57a4f 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -12,7 +12,8 @@ const defaultState = { visibleStatuses: [], newStatusCount: 0, maxId: 0, - minVisibleId: 0 + minVisibleId: 0, + loading: false }, publicAndExternal: { statuses: [], @@ -20,7 +21,8 @@ const defaultState = { visibleStatuses: [], newStatusCount: 0, maxId: 0, - minVisibleId: 0 + minVisibleId: 0, + loading: false }, friends: { statuses: [], @@ -28,7 +30,8 @@ const defaultState = { visibleStatuses: [], newStatusCount: 0, maxId: 0, - minVisibleId: 0 + minVisibleId: 0, + loading: false } } } @@ -37,7 +40,7 @@ const statusType = (status) => { return !status.is_post_verb && status.uri.match(/fave/) ? 'fave' : 'status' } -const addStatusesToTimeline = (addedStatuses, showImmediately, { statuses, visibleStatuses, newStatusCount, faves }) => { +const addStatusesToTimeline = (addedStatuses, showImmediately, { statuses, visibleStatuses, newStatusCount, faves, loading }) => { const statusesAndFaves = groupBy(addedStatuses, statusType) const addedFaves = statusesAndFaves['fave'] || [] const unseenFaves = differenceBy(addedFaves, faves, 'id') @@ -55,6 +58,9 @@ const addStatusesToTimeline = (addedStatuses, showImmediately, { statuses, visib // Add some html and nsfw to the statuses. each(addedStatuses, (status) => { const statusoid = status.retweeted_status || status + + statusoid.created_at_parsed = statusoid.created_at + if (statusoid.parsedText === undefined) { // statusoid.parsedText = statusParserService.parse(statusoid) statusoid.parsedText = statusoid.text @@ -89,7 +95,8 @@ const addStatusesToTimeline = (addedStatuses, showImmediately, { statuses, visib newStatusCount: newNewStatusCount, maxId: newStatuses[0].id, minVisibleId: (last(newVisibleStatuses) || { id: undefined }).id, - faves: unionBy(faves, addedFaves, 'id') + faves: unionBy(faves, addedFaves, 'id'), + loading } } @@ -135,6 +142,9 @@ const statuses = { const newStatus = find(state.allStatuses, status) newStatus.favorited = value }, + setLoading (state, { timeline, value }) { + state.timelines[timeline].loading = value + }, setNsfw (state, { id, nsfw }) { // For now, walk through all the statuses because the stuff might be in the replied_to_status // TODO: Save the replied_tos as references. diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index df7b4190..06585ac7 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -6,21 +6,27 @@ const PUBLIC_AND_EXTERNAL_TIMELINE_URL = '/api/statuses/public_and_external_time const FAVORITE_URL = '/api/favorites/create' const UNFAVORITE_URL = '/api/favorites/destroy' const STATUS_UPDATE_URL = '/api/statuses/update.json' +const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload' // const CONVERSATION_URL = '/api/statusnet/conversation/'; -// const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload'; // const FORM_CONTENT_TYPE = {'Content-Type': 'application/x-www-form-urlencoded'}; // import { param, ajax } from 'jquery'; // import { merge } from 'lodash'; -const authHeaders = (user) => ({ 'Authorization': `Basic ${btoa(`${user.username}:${user.password}`)}` }) +const authHeaders = (user) => { + if (user) { + return { 'Authorization': `Basic ${btoa(`${user.username}:${user.password}`)}` } + } else { + return { } + } +} const fetchTimeline = ({timeline, credentials, since = false, until = false}) => { const timelineUrls = { public: PUBLIC_TIMELINE_URL, friends: FRIENDS_TIMELINE_URL, - 'public-and-external': PUBLIC_AND_EXTERNAL_TIMELINE_URL + 'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL } let url = timelineUrls[timeline] @@ -75,12 +81,23 @@ const postStatus = ({credentials, status, mediaIds, inReplyToStatusId}) => { }) } +const uploadMedia = ({formData, credentials}) => { + return fetch(MEDIA_UPLOAD_URL, { + body: formData, + method: 'POST', + headers: authHeaders(credentials) + }) + .then((response) => response.text()) + .then((text) => (new DOMParser()).parseFromString(text, 'application/xml')) +} + const apiService = { verifyCredentials, fetchTimeline, favorite, unfavorite, - postStatus + postStatus, + uploadMedia } export default apiService diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 1a96b5f6..2a324541 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -12,8 +12,21 @@ const postStatus = ({ store, status, media = [], inReplyToStatusId = undefined } }) } +const uploadMedia = ({ store, formData }) => { + const credentials = store.state.users.currentUser.credentials + + return apiService.uploadMedia({ credentials, formData }).then((xml) => { + return { + id: xml.getElementsByTagName('media_id')[0].textContent, + url: xml.getElementsByTagName('media_url')[0].textContent, + image: xml.getElementsByTagName('atom:link')[0].getAttribute('href') + } + }) +} + const statusPosterService = { - postStatus + postStatus, + uploadMedia } export default statusPosterService diff --git a/src/services/timeline_fetcher/.#timeline_fetcher.service.js b/src/services/timeline_fetcher/.#timeline_fetcher.service.js deleted file mode 120000 index 8315cdae..00000000 --- a/src/services/timeline_fetcher/.#timeline_fetcher.service.js +++ /dev/null @@ -1 +0,0 @@ -roger@yuuyuu.18961
\ No newline at end of file diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index a3d9b9d1..8a39eeb5 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -16,7 +16,8 @@ const update = ({store, statuses, timeline, showImmediately}) => { const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false, showImmediately = false}) => { const args = { timeline, credentials } - const timelineData = store.rootState.statuses.timelines[camelCase(timeline)] + const rootState = store.rootState || store.state + const timelineData = rootState.statuses.timelines[camelCase(timeline)] if (older) { args['until'] = timelineData.minVisibleId @@ -24,7 +25,7 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false args['since'] = timelineData.maxId } - apiService.fetchTimeline(args) + return apiService.fetchTimeline(args) .then((statuses) => update({store, statuses, timeline, showImmediately})) } @@ -35,6 +36,7 @@ const startFetching = ({ timeline = 'friends', credentials, store }) => { setInterval(boundFetchAndUpdate, 10000) } const timelineFetcher = { + fetchAndUpdate, startFetching } |
