* upstream/develop: DM timeline: stream new statuses update-japanese-translation Add actual user search. incorporate most translation changes from MR 368 update french translation Always show dm panel. Add direct message tab. api service url On logout switch to public timeline. Put oauth text into description. Display OAuth login on login form button. Add login form back in. Linting. Re-activate registration, use oauth password flow to fetch token. Fix typo. Remove gonsole.logg :DD Fix linting. Move login to oauth.fixV2toV1
commit
e06717fd0d
@ -0,0 +1,184 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import VueRouter from 'vue-router'
|
||||||
|
|
||||||
|
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 TagTimeline from '../components/tag_timeline/tag_timeline.vue'
|
||||||
|
import ConversationPage from '../components/conversation-page/conversation-page.vue'
|
||||||
|
import Mentions from '../components/mentions/mentions.vue'
|
||||||
|
import DMs from '../components/dm_timeline/dm_timeline.vue'
|
||||||
|
import UserProfile from '../components/user_profile/user_profile.vue'
|
||||||
|
import Settings from '../components/settings/settings.vue'
|
||||||
|
import Registration from '../components/registration/registration.vue'
|
||||||
|
import UserSettings from '../components/user_settings/user_settings.vue'
|
||||||
|
import FollowRequests from '../components/follow_requests/follow_requests.vue'
|
||||||
|
import OAuthCallback from '../components/oauth_callback/oauth_callback.vue'
|
||||||
|
import UserSearch from '../components/user_search/user_search.vue'
|
||||||
|
|
||||||
|
const afterStoreSetup = ({store, i18n}) => {
|
||||||
|
window.fetch('/api/statusnet/config.json')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
const {name, closed: registrationClosed, textlimit, server} = data.site
|
||||||
|
|
||||||
|
store.dispatch('setInstanceOption', { name: 'name', value: name })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'server', value: server })
|
||||||
|
|
||||||
|
var apiConfig = data.site.pleromafe
|
||||||
|
|
||||||
|
window.fetch('/static/config.json')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn('Failed to load static/config.json, continuing without it.')
|
||||||
|
console.warn(err)
|
||||||
|
return {}
|
||||||
|
})
|
||||||
|
.then((staticConfig) => {
|
||||||
|
// This takes static config and overrides properties that are present in apiConfig
|
||||||
|
var config = Object.assign({}, staticConfig, apiConfig)
|
||||||
|
|
||||||
|
var theme = (config.theme)
|
||||||
|
var background = (config.background)
|
||||||
|
var hidePostStats = (config.hidePostStats)
|
||||||
|
var hideUserStats = (config.hideUserStats)
|
||||||
|
var logo = (config.logo)
|
||||||
|
var logoMask = (typeof config.logoMask === 'undefined' ? true : config.logoMask)
|
||||||
|
var logoMargin = (typeof config.logoMargin === 'undefined' ? 0 : config.logoMargin)
|
||||||
|
var redirectRootNoLogin = (config.redirectRootNoLogin)
|
||||||
|
var redirectRootLogin = (config.redirectRootLogin)
|
||||||
|
var chatDisabled = (config.chatDisabled)
|
||||||
|
var showInstanceSpecificPanel = (config.showInstanceSpecificPanel)
|
||||||
|
var scopeOptionsEnabled = (config.scopeOptionsEnabled)
|
||||||
|
var formattingOptionsEnabled = (config.formattingOptionsEnabled)
|
||||||
|
var collapseMessageWithSubject = (config.collapseMessageWithSubject)
|
||||||
|
var loginMethod = (config.loginMethod)
|
||||||
|
var scopeCopy = (config.scopeCopy)
|
||||||
|
var subjectLineBehavior = (config.subjectLineBehavior)
|
||||||
|
|
||||||
|
store.dispatch('setInstanceOption', { name: 'theme', value: theme })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'background', value: background })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'hidePostStats', value: hidePostStats })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'hideUserStats', value: hideUserStats })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'logo', value: logo })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'logoMask', value: logoMask })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'logoMargin', value: logoMargin })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'redirectRootNoLogin', value: redirectRootNoLogin })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'redirectRootLogin', value: redirectRootLogin })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'showInstanceSpecificPanel', value: showInstanceSpecificPanel })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'scopeOptionsEnabled', value: scopeOptionsEnabled })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'formattingOptionsEnabled', value: formattingOptionsEnabled })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'collapseMessageWithSubject', value: collapseMessageWithSubject })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'loginMethod', value: loginMethod })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'scopeCopy', value: scopeCopy })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'subjectLineBehavior', value: subjectLineBehavior })
|
||||||
|
if (chatDisabled) {
|
||||||
|
store.dispatch('disableChat')
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ name: 'root',
|
||||||
|
path: '/',
|
||||||
|
redirect: to => {
|
||||||
|
return (store.state.users.currentUser
|
||||||
|
? store.state.instance.redirectRootLogin
|
||||||
|
: store.state.instance.redirectRootNoLogin) || '/main/all'
|
||||||
|
}},
|
||||||
|
{ path: '/main/all', component: PublicAndExternalTimeline },
|
||||||
|
{ path: '/main/public', component: PublicTimeline },
|
||||||
|
{ path: '/main/friends', component: FriendsTimeline },
|
||||||
|
{ path: '/tag/:tag', component: TagTimeline },
|
||||||
|
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
|
||||||
|
{ name: 'user-profile', path: '/users/:id', component: UserProfile },
|
||||||
|
{ name: 'mentions', path: '/:username/mentions', component: Mentions },
|
||||||
|
{ name: 'dms', path: '/:username/dms', component: DMs },
|
||||||
|
{ name: 'settings', path: '/settings', component: Settings },
|
||||||
|
{ name: 'registration', path: '/registration', component: Registration },
|
||||||
|
{ name: 'registration', path: '/registration/:token', component: Registration },
|
||||||
|
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests },
|
||||||
|
{ name: 'user-settings', path: '/user-settings', component: UserSettings },
|
||||||
|
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
|
||||||
|
{ name: 'user-search', path: '/user-search', component: UserSearch, props: (route) => ({ query: route.query.query }) }
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
mode: 'history',
|
||||||
|
routes,
|
||||||
|
scrollBehavior: (to, from, savedPosition) => {
|
||||||
|
if (to.matched.some(m => m.meta.dontScroll)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return savedPosition || { x: 0, y: 0 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/* eslint-disable no-new */
|
||||||
|
new Vue({
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
i18n,
|
||||||
|
el: '#app',
|
||||||
|
render: h => h(App)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
window.fetch('/static/terms-of-service.html')
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((html) => {
|
||||||
|
store.dispatch('setInstanceOption', { name: 'tos', value: html })
|
||||||
|
})
|
||||||
|
|
||||||
|
window.fetch('/api/pleroma/emoji.json')
|
||||||
|
.then(
|
||||||
|
(res) => res.json()
|
||||||
|
.then(
|
||||||
|
(values) => {
|
||||||
|
const emoji = Object.keys(values).map((key) => {
|
||||||
|
return { shortcode: key, image_url: values[key] }
|
||||||
|
})
|
||||||
|
store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true })
|
||||||
|
},
|
||||||
|
(failure) => {
|
||||||
|
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false })
|
||||||
|
}
|
||||||
|
),
|
||||||
|
(error) => console.log(error)
|
||||||
|
)
|
||||||
|
|
||||||
|
window.fetch('/static/emoji.json')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((values) => {
|
||||||
|
const emoji = Object.keys(values).map((key) => {
|
||||||
|
return { shortcode: key, image_url: false, 'utf': values[key] }
|
||||||
|
})
|
||||||
|
store.dispatch('setInstanceOption', { name: 'emoji', value: emoji })
|
||||||
|
})
|
||||||
|
|
||||||
|
window.fetch('/instance/panel.html')
|
||||||
|
.then((res) => res.text())
|
||||||
|
.then((html) => {
|
||||||
|
store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
|
||||||
|
})
|
||||||
|
|
||||||
|
window.fetch('/nodeinfo/2.0.json')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
const metadata = data.metadata
|
||||||
|
|
||||||
|
const features = metadata.features
|
||||||
|
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
||||||
|
|
||||||
|
const suggestions = metadata.suggestions
|
||||||
|
store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default afterStoreSetup
|
@ -0,0 +1,14 @@
|
|||||||
|
import Timeline from '../timeline/timeline.vue'
|
||||||
|
|
||||||
|
const DMs = {
|
||||||
|
computed: {
|
||||||
|
timeline () {
|
||||||
|
return this.$store.state.statuses.timelines.dms
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Timeline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DMs
|
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<Timeline :title="$t('nav.dms')" v-bind:timeline="timeline" v-bind:timeline-name="'dms'"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./dm_timeline.js"></script>
|
@ -0,0 +1,20 @@
|
|||||||
|
import oauth from '../../services/new_api/oauth.js'
|
||||||
|
|
||||||
|
const oac = {
|
||||||
|
props: ['code'],
|
||||||
|
mounted () {
|
||||||
|
if (this.code) {
|
||||||
|
oauth.getToken({
|
||||||
|
app: this.$store.state.oauth,
|
||||||
|
instance: this.$store.state.instance.server,
|
||||||
|
code: this.code
|
||||||
|
}).then((result) => {
|
||||||
|
this.$store.commit('setToken', result.access_token)
|
||||||
|
this.$store.dispatch('loginUser', result.access_token)
|
||||||
|
this.$router.push('/main/friends')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default oac
|
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<h1>...</h1>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./oauth_callback.js"></script>
|
@ -0,0 +1,33 @@
|
|||||||
|
import UserCard from '../user_card/user_card.vue'
|
||||||
|
import userSearchApi from '../../services/new_api/user_search.js'
|
||||||
|
const userSearch = {
|
||||||
|
components: {
|
||||||
|
UserCard
|
||||||
|
},
|
||||||
|
props: [
|
||||||
|
'query'
|
||||||
|
],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
users: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.search(this.query)
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
query (newV) {
|
||||||
|
this.search(newV)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
search (query) {
|
||||||
|
userSearchApi.search({query, store: this.$store})
|
||||||
|
.then((res) => {
|
||||||
|
this.users = res
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default userSearch
|
@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<div class="user-seach panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
{{$t('nav.user_search')}}
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<user-card v-for="user in users" :key="user.id" :user="user" :showFollows="true"></user-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./user_search.js"></script>
|
@ -0,0 +1,18 @@
|
|||||||
|
const oauth = {
|
||||||
|
state: {
|
||||||
|
client_id: false,
|
||||||
|
client_secret: false,
|
||||||
|
token: false
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setClientData (state, data) {
|
||||||
|
state.client_id = data.client_id
|
||||||
|
state.client_secret = data.client_secret
|
||||||
|
},
|
||||||
|
setToken (state, token) {
|
||||||
|
state.token = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default oauth
|
@ -0,0 +1,82 @@
|
|||||||
|
import {reduce} from 'lodash'
|
||||||
|
|
||||||
|
const getOrCreateApp = ({oauth, instance}) => {
|
||||||
|
const url = `${instance}/api/v1/apps`
|
||||||
|
const form = new window.FormData()
|
||||||
|
|
||||||
|
form.append('client_name', `PleromaFE_${Math.random()}`)
|
||||||
|
form.append('redirect_uris', `${window.location.origin}/oauth-callback`)
|
||||||
|
form.append('scopes', 'read write follow')
|
||||||
|
|
||||||
|
return window.fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: form
|
||||||
|
}).then((data) => data.json())
|
||||||
|
}
|
||||||
|
const login = (args) => {
|
||||||
|
getOrCreateApp(args).then((app) => {
|
||||||
|
args.commit('setClientData', app)
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
response_type: 'code',
|
||||||
|
client_id: app.client_id,
|
||||||
|
redirect_uri: app.redirect_uri,
|
||||||
|
scope: 'read write follow'
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataString = reduce(data, (acc, v, k) => {
|
||||||
|
const encoded = `${k}=${encodeURIComponent(v)}`
|
||||||
|
if (!acc) {
|
||||||
|
return encoded
|
||||||
|
} else {
|
||||||
|
return `${acc}&${encoded}`
|
||||||
|
}
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
// Do the redirect...
|
||||||
|
const url = `${args.instance}/oauth/authorize?${dataString}`
|
||||||
|
|
||||||
|
window.location.href = url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTokenWithCredentials = ({app, instance, username, password}) => {
|
||||||
|
const url = `${instance}/oauth/token`
|
||||||
|
const form = new window.FormData()
|
||||||
|
|
||||||
|
form.append('client_id', app.client_id)
|
||||||
|
form.append('client_secret', app.client_secret)
|
||||||
|
form.append('grant_type', 'password')
|
||||||
|
form.append('username', username)
|
||||||
|
form.append('password', password)
|
||||||
|
|
||||||
|
return window.fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: form
|
||||||
|
}).then((data) => data.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
const getToken = ({app, instance, code}) => {
|
||||||
|
const url = `${instance}/oauth/token`
|
||||||
|
const form = new window.FormData()
|
||||||
|
|
||||||
|
form.append('client_id', app.client_id)
|
||||||
|
form.append('client_secret', app.client_secret)
|
||||||
|
form.append('grant_type', 'authorization_code')
|
||||||
|
form.append('code', code)
|
||||||
|
form.append('redirect_uri', `${window.location.origin}/oauth-callback`)
|
||||||
|
|
||||||
|
return window.fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: form
|
||||||
|
}).then((data) => data.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
const oauth = {
|
||||||
|
login,
|
||||||
|
getToken,
|
||||||
|
getTokenWithCredentials,
|
||||||
|
getOrCreateApp
|
||||||
|
}
|
||||||
|
|
||||||
|
export default oauth
|
@ -0,0 +1,16 @@
|
|||||||
|
import utils from './utils.js'
|
||||||
|
|
||||||
|
const search = ({query, store}) => {
|
||||||
|
return utils.request({
|
||||||
|
store,
|
||||||
|
url: '/api/pleroma/search_user',
|
||||||
|
params: {
|
||||||
|
query
|
||||||
|
}
|
||||||
|
}).then((data) => data.json())
|
||||||
|
}
|
||||||
|
const UserSearch = {
|
||||||
|
search
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserSearch
|
@ -0,0 +1,36 @@
|
|||||||
|
const queryParams = (params) => {
|
||||||
|
return Object.keys(params)
|
||||||
|
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
|
||||||
|
.join('&')
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = (store) => {
|
||||||
|
const accessToken = store.state.oauth.token
|
||||||
|
if (accessToken) {
|
||||||
|
return {'Authorization': `Bearer ${accessToken}`}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = ({method = 'GET', url, params, store}) => {
|
||||||
|
const instance = store.state.instance.server
|
||||||
|
let fullUrl = `${instance}${url}`
|
||||||
|
|
||||||
|
if (method === 'GET' && params) {
|
||||||
|
fullUrl = fullUrl + `?${queryParams(params)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.fetch(fullUrl, {
|
||||||
|
method,
|
||||||
|
headers: headers(store),
|
||||||
|
credentials: 'same-origin'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const utils = {
|
||||||
|
queryParams,
|
||||||
|
request
|
||||||
|
}
|
||||||
|
|
||||||
|
export default utils
|
Loading…
Reference in new issue