.
This commit is contained in:
parent
191c02af1e
commit
4c2764c747
19 changed files with 2605 additions and 95 deletions
14
src/App.html
Normal file
14
src/App.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div id="app">
|
||||
<nav class='container'>
|
||||
<div class='item'>
|
||||
<a route-to='friends-timeline' href="#">Pleroma FE</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container" id="content">
|
||||
<sidebar>
|
||||
<user-panel></user-panel>
|
||||
<nav-panel></nav-panel>
|
||||
</sidebar>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</div>
|
8
src/App.js
Normal file
8
src/App.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import Hello from './components/hello/Hello'
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
Hello
|
||||
}
|
||||
}
|
383
src/App.scss
Normal file
383
src/App.scss
Normal file
|
@ -0,0 +1,383 @@
|
|||
$main-color: #f58d2c;
|
||||
$main-background: white;
|
||||
$darkened-background: whitesmoke;
|
||||
|
||||
body {
|
||||
background-color: $main-color;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 50px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $main-color;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 0 10px 0 10px;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.gaps {
|
||||
margin: -1em 0 0 -1em;
|
||||
}
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.gaps > .item {
|
||||
padding: 1em 0 0 1em;
|
||||
}
|
||||
|
||||
.auto-size {
|
||||
flex: 1
|
||||
}
|
||||
|
||||
nav {
|
||||
background: black;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
height: 50px;
|
||||
|
||||
}
|
||||
|
||||
sidebar {
|
||||
width: 33.333% !important;
|
||||
}
|
||||
|
||||
main-router {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status:hover {
|
||||
background-color: $darkened-background;
|
||||
}
|
||||
|
||||
/* The starting CSS styles for the enter animation */
|
||||
status.ng-enter {
|
||||
transition:0.5s linear all;
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
/* The finishing CSS styles for the enter animation */
|
||||
status.ng-enter.ng-enter-active {
|
||||
opacity:1;
|
||||
}
|
||||
|
||||
.new-status-notification {
|
||||
font-size: 1.1em;
|
||||
background-color: $darkened-background;
|
||||
border-bottom-color: darken($darkened-background, 5%);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
|
||||
&:hover {
|
||||
background-color: darken($darkened-background, 5%);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.status.compact {
|
||||
color: rgba(0, 0, 0, 0.42);
|
||||
font-weight: 300;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 0.8em
|
||||
}
|
||||
}
|
||||
|
||||
/* Panel */
|
||||
|
||||
.panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: $main-background;
|
||||
margin: 0.5em;
|
||||
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
border-radius: 0.5em 0.5em 0 0;
|
||||
background-size: cover;
|
||||
background-color: bisque;
|
||||
padding-top: 0.3em;
|
||||
padding-bottom: 0.3em;
|
||||
text-align: center;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
background-color: bisque;
|
||||
border-radius: 0 0 0.5em 0.5em;
|
||||
}
|
||||
|
||||
.panel-body > p {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.attachments {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.attachment, attachment {
|
||||
flex: 1 0 30%;
|
||||
display: flex;
|
||||
margin: 0.2em;
|
||||
align-self: flex-start;
|
||||
|
||||
img {
|
||||
border: 1px solid;
|
||||
border-radius: 0.5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
video {
|
||||
border: 1px solid;
|
||||
border-radius: 0.5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.oembed {
|
||||
border: 1px solid rgba(0, 0, 0, 0.14);
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
.image {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
img {
|
||||
border: 0px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 2;
|
||||
margin: 8px;
|
||||
h1 {
|
||||
font-size: 14px;
|
||||
margin: 0px;
|
||||
|
||||
a {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-body {
|
||||
flex: 1
|
||||
}
|
||||
|
||||
#content {
|
||||
margin: auto;
|
||||
max-width: 920px;
|
||||
}
|
||||
|
||||
.media-left {
|
||||
width: 10% !important;
|
||||
}
|
||||
|
||||
.media-body {
|
||||
flex: 1;
|
||||
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
|
||||
}
|
||||
|
||||
[ng-click] {
|
||||
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;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
padding: 1em;
|
||||
img {
|
||||
border: 3px solid;
|
||||
border-radius: 0.5em
|
||||
}
|
||||
|
||||
.user-screen-name {
|
||||
font-weight: lighter;
|
||||
}
|
||||
}
|
||||
|
||||
.user-counts {
|
||||
display: flex;
|
||||
padding: 1em 1em 0em 1em;
|
||||
}
|
||||
|
||||
.user-count {
|
||||
flex: 1;
|
||||
|
||||
h5 {
|
||||
font-weight: lighter;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $main-color;
|
||||
}
|
||||
}
|
||||
|
||||
.fa {
|
||||
color: $main-color;
|
||||
}
|
||||
|
||||
.status-actions {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
|
||||
div, favorite-button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
status-text-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
attention {
|
||||
color: $main-color;
|
||||
}
|
||||
|
||||
.form-bottom {
|
||||
display: flex;
|
||||
padding: 0.5em;
|
||||
|
||||
media-upload {
|
||||
font-size: 26px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.status-el {
|
||||
line-height: 18px;
|
||||
|
||||
.notify {
|
||||
.avatar {
|
||||
border-width: 3px;
|
||||
border-color: $main-color;
|
||||
border-style: solid;
|
||||
}
|
||||
}
|
||||
|
||||
.media-left {
|
||||
img {
|
||||
margin-top: 0.2em;
|
||||
float: right;
|
||||
margin-right: 0.3em;
|
||||
border-radius: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.retweet-info {
|
||||
padding: 0.3em;
|
||||
|
||||
.media-left {
|
||||
display: flex;
|
||||
|
||||
i {
|
||||
align-self: center;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
padding-right: 0.3em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-heading {
|
||||
small {
|
||||
font-weight: lighter;
|
||||
}
|
||||
}
|
||||
}
|
30
src/App.vue
30
src/App.vue
|
@ -1,28 +1,4 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<img src="./assets/logo.png">
|
||||
<hello></hello>
|
||||
</div>
|
||||
</template>
|
||||
<template src="./App.html"></template>
|
||||
|
||||
<script>
|
||||
import Hello from './components/hello/Hello'
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
Hello
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
<script src="./App.js"></script>
|
||||
<style lang="scss" src="./App.scss"></style>
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<h2>Essential Links</h2>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
|
||||
<li><a href="https://gitter.im/vuejs/vue" target="_blank">Gitter Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
|
||||
<br>
|
||||
<li><a href="http://vuejs-templates.github.io/webpack/" target="_blank">Docs for This Template</a></li>
|
||||
</ul>
|
||||
<h2>Ecosystem</h2>
|
||||
<ul>
|
||||
<li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
|
||||
<li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
|
||||
<li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src='./Hello.js' />
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h1, h2 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
|
@ -3,6 +3,6 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
msg: 'Welcome to Your Vue.js app'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
11
src/components/public_timeline/public_timeline.js
Normal file
11
src/components/public_timeline/public_timeline.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import Timeline from '../timeline/timeline.vue'
|
||||
const PublicTimeline = {
|
||||
components: {
|
||||
Timeline
|
||||
},
|
||||
computed: {
|
||||
timeline () { return this.$store.state.statuses.timelines.public }
|
||||
}
|
||||
}
|
||||
|
||||
export default PublicTimeline
|
10
src/components/public_timeline/public_timeline.vue
Normal file
10
src/components/public_timeline/public_timeline.vue
Normal file
|
@ -0,0 +1,10 @@
|
|||
<template>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Public Timeline</div>
|
||||
<div class="panel-body">
|
||||
<Timeline v-bind:timeline="timeline" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./public_timeline.js"></script>
|
7
src/components/timeline/timeline.js
Normal file
7
src/components/timeline/timeline.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
const Timeline = {
|
||||
props: [
|
||||
'timeline'
|
||||
]
|
||||
}
|
||||
|
||||
export default Timeline;
|
7
src/components/timeline/timeline.vue
Normal file
7
src/components/timeline/timeline.vue
Normal file
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<div class="timeline">
|
||||
<h1>Timeline goes here</h1>
|
||||
<h2 v-for="status in timeline.visibleStatuses">{{status.text}}</h2>
|
||||
</div>
|
||||
</template>
|
||||
<script src="./timeline.js"></script>
|
25
src/main.js
25
src/main.js
|
@ -1,8 +1,31 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import VueRouter from 'vue-router'
|
||||
import Vuex from 'vuex'
|
||||
import App from './App.vue'
|
||||
import PublicTimeline from './components/public_timeline/public_timeline.vue'
|
||||
|
||||
import statuses from './modules/statuses.js'
|
||||
|
||||
Vue.use(Vuex)
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
statuses
|
||||
}
|
||||
})
|
||||
|
||||
const routes = [
|
||||
{ path: '/', redirect: '/main/public' },
|
||||
{ path: '/main/public', component: PublicTimeline }
|
||||
]
|
||||
|
||||
const router = new VueRouter({routes})
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
el: '#app',
|
||||
template: '<App/>',
|
||||
components: { App }
|
||||
|
|
100
src/modules/statuses.js
Normal file
100
src/modules/statuses.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
import { last, intersectionBy, sortBy, unionBy, toInteger, groupBy, differenceBy, each, find } from 'lodash'
|
||||
// import moment from 'moment'
|
||||
|
||||
const defaultState = {
|
||||
allStatuses: [],
|
||||
maxId: 0,
|
||||
timelines: {
|
||||
public: {
|
||||
statuses: [],
|
||||
faves: [],
|
||||
visibleStatuses: [],
|
||||
newStatusCount: 0,
|
||||
maxId: 0,
|
||||
minVisibleId: 0
|
||||
},
|
||||
publicAndExternal: {
|
||||
statuses: [],
|
||||
faves: [],
|
||||
visibleStatuses: [],
|
||||
newStatusCount: 0,
|
||||
maxId: 0,
|
||||
minVisibleId: 0
|
||||
},
|
||||
friends: {
|
||||
statuses: [],
|
||||
faves: [],
|
||||
visibleStatuses: [],
|
||||
newStatusCount: 0,
|
||||
maxId: 0,
|
||||
minVisibleId: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const statusType = (status) => {
|
||||
return !status.is_post_verb && status.uri.match(/fave/) ? 'fave' : 'status'
|
||||
}
|
||||
|
||||
const addStatusesToTimeline = (addedStatuses, showImmediately, { statuses, visibleStatuses, newStatusCount, faves }) => {
|
||||
const statusesAndFaves = groupBy(addedStatuses, statusType)
|
||||
const addedFaves = statusesAndFaves['fave'] || []
|
||||
const unseenFaves = differenceBy(addedFaves, faves, 'id')
|
||||
|
||||
// Update fave count
|
||||
each(unseenFaves, ({in_reply_to_status_id}) => {
|
||||
const status = find(statuses, { id: toInteger(in_reply_to_status_id) })
|
||||
if (status) {
|
||||
status.fave_num += 1
|
||||
}
|
||||
})
|
||||
|
||||
addedStatuses = statusesAndFaves['status'] || []
|
||||
|
||||
// Add some html to the statuses.
|
||||
each(addedStatuses, (status) => {
|
||||
const statusoid = status.retweeted_status || status
|
||||
if (statusoid.parsedText === undefined) {
|
||||
// statusoid.parsedText = statusParserService.parse(statusoid)
|
||||
statusoid.parsedText = statusoid.text
|
||||
}
|
||||
})
|
||||
|
||||
const newStatuses = sortBy(
|
||||
unionBy(addedStatuses, statuses, 'id'),
|
||||
({id}) => -id
|
||||
)
|
||||
|
||||
let newNewStatusCount = newStatusCount + (newStatuses.length - statuses.length)
|
||||
|
||||
let newVisibleStatuses = visibleStatuses
|
||||
|
||||
if (showImmediately) {
|
||||
newVisibleStatuses = unionBy(addedStatuses, newVisibleStatuses, 'id')
|
||||
newVisibleStatuses = sortBy(newVisibleStatuses, ({id}) => -id)
|
||||
newNewStatusCount = newStatusCount
|
||||
};
|
||||
|
||||
newVisibleStatuses = intersectionBy(newStatuses, newVisibleStatuses, 'id')
|
||||
|
||||
return {
|
||||
statuses: newStatuses,
|
||||
visibleStatuses: newVisibleStatuses,
|
||||
newStatusCount: newNewStatusCount,
|
||||
maxId: newStatuses[0].id,
|
||||
minVisibleId: last(newVisibleStatuses).id,
|
||||
faves: unionBy(faves, addedFaves, 'id')
|
||||
}
|
||||
}
|
||||
|
||||
const statuses = {
|
||||
state: defaultState,
|
||||
mutations: {
|
||||
addNewStatuses (state, { statuses, showImmediately = false, timeline }) {
|
||||
state.timelines[timeline] = addStatusesToTimeline(statuses, showImmediately, state.timelines[timeline])
|
||||
state.allStatuses = unionBy(state.timelines[timeline].statuses, state.allStatuses.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default statuses
|
101
src/services/api/api.service.js
Normal file
101
src/services/api/api.service.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
const LOGIN_URL='/api/account/verify_credentials.json';
|
||||
const FRIENDS_TIMELINE_URL='/api/statuses/friends_timeline.json';
|
||||
const PUBLIC_TIMELINE_URL='/api/statuses/public_timeline.json';
|
||||
const PUBLIC_AND_EXTERNAL_TIMELINE_URL='/api/statuses/public_and_external_timeline.json';
|
||||
const CONVERSATION_URL = '/api/statusnet/conversation/';
|
||||
const STATUS_UPDATE_URL = '/api/statuses/update.json';
|
||||
const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload';
|
||||
const FAVORITE_URL = '/api/favorites/create';
|
||||
const UNFAVORITE_URL = '/api/favorites/destroy';
|
||||
|
||||
const FORM_CONTENT_TYPE = {'Content-Type': 'application/x-www-form-urlencoded'};
|
||||
|
||||
import { param, ajax } from 'jquery';
|
||||
import { merge } from 'lodash';
|
||||
|
||||
// TODO: This should probably be in redux.
|
||||
let authHeaders = {};
|
||||
|
||||
const apiServiceFactory = ($http) => {
|
||||
// Public
|
||||
const fetchConversation = (id) => {
|
||||
return $http.get(`${CONVERSATION_URL}/${id}.json?count=100`);
|
||||
};
|
||||
|
||||
const fetchTimeline = ({timeline, since = false, until = false}) => {
|
||||
const timelineUrls = {
|
||||
public: PUBLIC_TIMELINE_URL,
|
||||
friends: FRIENDS_TIMELINE_URL,
|
||||
'public-and-external': PUBLIC_AND_EXTERNAL_TIMELINE_URL
|
||||
};
|
||||
|
||||
let url = timelineUrls[timeline];
|
||||
|
||||
if(since) {
|
||||
url += `?since_id=${since}`;
|
||||
}
|
||||
|
||||
if(until) {
|
||||
url += `?max_id=${until}`;
|
||||
}
|
||||
|
||||
return fetch(url, { headers: authHeaders }).then((data) => data.json());
|
||||
};
|
||||
|
||||
// Need credentials
|
||||
const verifyCredentials = (user) => {
|
||||
const base64 = btoa(`${user.username}:${user.password}`);
|
||||
authHeaders = { "Authorization": `Basic ${base64}` };
|
||||
return $http.post(LOGIN_URL, null, { headers: authHeaders });
|
||||
};
|
||||
|
||||
const postStatus = ({status, mediaIds, in_reply_to_status_id}) => {
|
||||
const idsText = mediaIds.join(',');
|
||||
const form = new FormData();
|
||||
|
||||
form.append('status', status);
|
||||
form.append('source', 'The Wired FE');
|
||||
form.append('media_ids', idsText);
|
||||
if(in_reply_to_status_id) {
|
||||
form.append('in_reply_to_status_id', in_reply_to_status_id);
|
||||
};
|
||||
|
||||
return fetch(STATUS_UPDATE_URL, {
|
||||
body: form,
|
||||
method: 'POST',
|
||||
headers: authHeaders
|
||||
});
|
||||
};
|
||||
|
||||
const favorite = (id) => $http.post(`${FAVORITE_URL}/${id}.json`, null, {headers: authHeaders});
|
||||
const unfavorite = (id) => $http.post(`${UNFAVORITE_URL}/${id}.json`, null, {headers: authHeaders});
|
||||
|
||||
// This was impossible to get to work with $http. You're supposed to set Content-Type
|
||||
// undefined in the header so it sends the correct header. It would always send a json
|
||||
// content type. This method from jQuery worked right away...
|
||||
// Also, this method is only available as XML output. OLOLOLOLO
|
||||
const uploadMedia = (formData) => ajax({
|
||||
url: MEDIA_UPLOAD_URL,
|
||||
data: formData,
|
||||
type: 'POST',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
const apiService = {
|
||||
verifyCredentials,
|
||||
fetchConversation,
|
||||
postStatus,
|
||||
uploadMedia,
|
||||
favorite,
|
||||
unfavorite,
|
||||
fetchTimeline
|
||||
};
|
||||
|
||||
return apiService;
|
||||
};
|
||||
|
||||
apiServiceFactory.$inject = ['$http'];
|
||||
|
||||
export default apiServiceFactory;
|
1
src/services/timeline_fetcher/.#timeline_fetcher.service.js
Symbolic link
1
src/services/timeline_fetcher/.#timeline_fetcher.service.js
Symbolic link
|
@ -0,0 +1 @@
|
|||
roger@yuuyuu.18961
|
53
src/services/timeline_fetcher/timeline_fetcher.service.js
Normal file
53
src/services/timeline_fetcher/timeline_fetcher.service.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { upperFirst, camelCase } from 'lodash';
|
||||
|
||||
const timelineFetcherServiceFactory = ($ngRedux, apiService, $interval) => {
|
||||
let fetcher;
|
||||
|
||||
const update = ({statuses, timeline, showImmediately}) => {
|
||||
const ccTimeline = camelCase(timeline);
|
||||
|
||||
const action = {
|
||||
type: 'ADD_NEW_STATUSES',
|
||||
data: {
|
||||
statuses,
|
||||
timeline: ccTimeline,
|
||||
showImmediately
|
||||
}
|
||||
};
|
||||
|
||||
$ngRedux.dispatch(action);
|
||||
$ngRedux.dispatch({type: 'UPDATE_TIMESTAMPS'});
|
||||
};
|
||||
|
||||
const fetchAndUpdate = ({timeline = 'friends', older = false, showImmediately = false}) => {
|
||||
const args = { timeline };
|
||||
const timelineData = $ngRedux.getState().statuses.timelines[camelCase(timeline)];
|
||||
|
||||
if(older) {
|
||||
args['until'] = timelineData.minVisibleId;
|
||||
} else {
|
||||
args['since'] = timelineData.maxId;
|
||||
}
|
||||
|
||||
apiService.fetchTimeline(args).
|
||||
then((statuses) => update({statuses, timeline, showImmediately}));
|
||||
};
|
||||
|
||||
const startFetching = ({timeline = 'friends'}) => {
|
||||
fetchAndUpdate({timeline, showImmediately: true});
|
||||
|
||||
const boundFetchAndUpdate = () => fetchAndUpdate({timeline});
|
||||
fetcher = $interval(boundFetchAndUpdate, 10000);
|
||||
};
|
||||
|
||||
const timelineFetcherService = {
|
||||
startFetching,
|
||||
fetchAndUpdate
|
||||
};
|
||||
|
||||
return timelineFetcherService;
|
||||
};
|
||||
|
||||
timelineFetcherServiceFactory.$inject = ['$ngRedux', 'apiService', '$interval'];
|
||||
|
||||
export default timelineFetcherServiceFactory;
|
Loading…
Add table
Add a link
Reference in a new issue