forked from AkkomaGang/akkoma-fe
Merge remote-tracking branch 'upstream/develop' into feature/scope_preferences
* 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.
This commit is contained in:
commit
e06717fd0d
32 changed files with 1576 additions and 279 deletions
|
@ -32,3 +32,9 @@ npm run unit
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
Edit config.json for configuration. scopeOptionsEnabled gives you input fields for CWs and the scope settings.
|
Edit config.json for configuration. scopeOptionsEnabled gives you input fields for CWs and the scope settings.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### Login methods
|
||||||
|
|
||||||
|
```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations.
|
||||||
|
|
|
@ -23,17 +23,17 @@ module.exports = {
|
||||||
assetsPublicPath: '/',
|
assetsPublicPath: '/',
|
||||||
proxyTable: {
|
proxyTable: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:4000/',
|
target: 'https://shigusegubu.club/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
cookieDomainRewrite: 'localhost'
|
cookieDomainRewrite: 'localhost'
|
||||||
},
|
},
|
||||||
'/nodeinfo': {
|
'/nodeinfo': {
|
||||||
target: 'http://localhost:4000/',
|
target: 'https://shigusegubu.club/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
cookieDomainRewrite: 'localhost'
|
cookieDomainRewrite: 'localhost'
|
||||||
},
|
},
|
||||||
'/socket': {
|
'/socket': {
|
||||||
target: 'http://localhost:4000/',
|
target: 'https://shigusegubu.club/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
cookieDomainRewrite: 'localhost',
|
cookieDomainRewrite: 'localhost',
|
||||||
ws: true
|
ws: true
|
||||||
|
|
|
@ -73,6 +73,7 @@ export default {
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
},
|
},
|
||||||
logout () {
|
logout () {
|
||||||
|
this.$router.replace('/main/public')
|
||||||
this.$store.dispatch('logout')
|
this.$store.dispatch('logout')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
184
src/boot/after_store.js
Normal file
184
src/boot/after_store.js
Normal file
|
@ -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
|
14
src/components/dm_timeline/dm_timeline.js
Normal file
14
src/components/dm_timeline/dm_timeline.js
Normal file
|
@ -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
|
5
src/components/dm_timeline/dm_timeline.vue
Normal file
5
src/components/dm_timeline/dm_timeline.vue
Normal file
|
@ -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>
|
|
@ -1,22 +1,40 @@
|
||||||
|
import oauthApi from '../../services/new_api/oauth.js'
|
||||||
const LoginForm = {
|
const LoginForm = {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
user: {},
|
user: {},
|
||||||
authError: false
|
authError: false
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
|
loginMethod () { return this.$store.state.instance.loginMethod },
|
||||||
loggingIn () { return this.$store.state.users.loggingIn },
|
loggingIn () { return this.$store.state.users.loggingIn },
|
||||||
registrationOpen () { return this.$store.state.instance.registrationOpen }
|
registrationOpen () { return this.$store.state.instance.registrationOpen }
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
oAuthLogin () {
|
||||||
|
oauthApi.login({
|
||||||
|
oauth: this.$store.state.oauth,
|
||||||
|
instance: this.$store.state.instance.server,
|
||||||
|
commit: this.$store.commit
|
||||||
|
})
|
||||||
|
},
|
||||||
submit () {
|
submit () {
|
||||||
this.$store.dispatch('loginUser', this.user).then(
|
const data = {
|
||||||
() => {},
|
oauth: this.$store.state.oauth,
|
||||||
(error) => {
|
instance: this.$store.state.instance.server
|
||||||
this.authError = error
|
|
||||||
this.user.username = ''
|
|
||||||
this.user.password = ''
|
|
||||||
}
|
}
|
||||||
)
|
oauthApi.getOrCreateApp(data).then((app) => {
|
||||||
|
oauthApi.getTokenWithCredentials(
|
||||||
|
{
|
||||||
|
app,
|
||||||
|
instance: data.instance,
|
||||||
|
username: this.user.username,
|
||||||
|
password: this.user.password})
|
||||||
|
.then((result) => {
|
||||||
|
this.$store.commit('setToken', result.access_token)
|
||||||
|
this.$store.dispatch('loginUser', result.access_token)
|
||||||
|
this.$router.push('/main/friends')
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
{{$t('login.login')}}
|
{{$t('login.login')}}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form v-on:submit.prevent='submit(user)' class='login-form'>
|
<form v-if="loginMethod == 'password'" v-on:submit.prevent='submit(user)' class='login-form'>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='username'>{{$t('login.username')}}</label>
|
<label for='username'>{{$t('login.username')}}</label>
|
||||||
<input :disabled="loggingIn" v-model='user.username' class='form-control' id='username' v-bind:placeholder="$t('login.placeholder')">
|
<input :disabled="loggingIn" v-model='user.username' class='form-control' id='username' v-bind:placeholder="$t('login.placeholder')">
|
||||||
|
@ -20,8 +20,17 @@
|
||||||
<button :disabled="loggingIn" type='submit' class='btn btn-default'>{{$t('login.login')}}</button>
|
<button :disabled="loggingIn" type='submit' class='btn btn-default'>{{$t('login.login')}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="authError" class='form-group'>
|
</form>
|
||||||
<div class='alert error'>{{authError}}</div>
|
|
||||||
|
<form v-if="loginMethod == 'token'" v-on:submit.prevent='oAuthLogin' class="login-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<p>{{$t('login.description')}}</p>
|
||||||
|
</div>
|
||||||
|
<div class='form-group'>
|
||||||
|
<div class='login-bottom'>
|
||||||
|
<div><router-link :to="{name: 'registration'}" v-if='registrationOpen' class='register'>{{$t('login.register')}}</router-link></div>
|
||||||
|
<button :disabled="loggingIn" type='submit' class='btn btn-default'>{{$t('login.login')}}</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,6 +12,11 @@
|
||||||
{{ $t("nav.mentions") }}
|
{{ $t("nav.mentions") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if='currentUser'>
|
||||||
|
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
|
||||||
|
{{ $t("nav.dms") }}
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
<li v-if='currentUser && currentUser.locked'>
|
<li v-if='currentUser && currentUser.locked'>
|
||||||
<router-link to='/friend-requests'>
|
<router-link to='/friend-requests'>
|
||||||
{{ $t("nav.friend_requests") }}
|
{{ $t("nav.friend_requests") }}
|
||||||
|
|
20
src/components/oauth_callback/oauth_callback.js
Normal file
20
src/components/oauth_callback/oauth_callback.js
Normal file
|
@ -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
|
5
src/components/oauth_callback/oauth_callback.vue
Normal file
5
src/components/oauth_callback/oauth_callback.vue
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<h1>...</h1>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./oauth_callback.js"></script>
|
|
@ -1,3 +1,5 @@
|
||||||
|
import oauthApi from '../../services/new_api/oauth.js'
|
||||||
|
|
||||||
const registration = {
|
const registration = {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
user: {},
|
user: {},
|
||||||
|
@ -25,9 +27,23 @@ const registration = {
|
||||||
this.$store.state.api.backendInteractor.register(this.user).then(
|
this.$store.state.api.backendInteractor.register(this.user).then(
|
||||||
(response) => {
|
(response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
this.$store.dispatch('loginUser', this.user)
|
const data = {
|
||||||
this.$router.push('/main/all')
|
oauth: this.$store.state.oauth,
|
||||||
this.registering = false
|
instance: this.$store.state.instance.server
|
||||||
|
}
|
||||||
|
oauthApi.getOrCreateApp(data).then((app) => {
|
||||||
|
oauthApi.getTokenWithCredentials(
|
||||||
|
{
|
||||||
|
app,
|
||||||
|
instance: data.instance,
|
||||||
|
username: this.user.username,
|
||||||
|
password: this.user.password})
|
||||||
|
.then((result) => {
|
||||||
|
this.$store.commit('setToken', result.access_token)
|
||||||
|
this.$store.dispatch('loginUser', result.access_token)
|
||||||
|
this.$router.push('/main/friends')
|
||||||
|
})
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.registering = false
|
this.registering = false
|
||||||
response.json().then((data) => {
|
response.json().then((data) => {
|
||||||
|
|
|
@ -7,25 +7,10 @@ const UserFinder = {
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
findUser (username) {
|
findUser (username) {
|
||||||
username = username[0] === '@' ? username.slice(1) : username
|
this.$router.push({ name: 'user-search', query: { query: username } })
|
||||||
this.loading = true
|
|
||||||
this.$store.state.api.backendInteractor.externalProfile(username)
|
|
||||||
.then((user) => {
|
|
||||||
this.loading = false
|
|
||||||
this.hidden = true
|
|
||||||
if (!user.error) {
|
|
||||||
this.$store.commit('addNewUsers', [user])
|
|
||||||
this.$router.push({name: 'user-profile', params: {id: user.id}})
|
|
||||||
} else {
|
|
||||||
this.error = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
toggleHidden () {
|
toggleHidden () {
|
||||||
this.hidden = !this.hidden
|
this.hidden = !this.hidden
|
||||||
},
|
|
||||||
dismissError () {
|
|
||||||
this.error = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="user-finder-container">
|
<span class="user-finder-container">
|
||||||
<span class="alert error" v-if="error">
|
|
||||||
<i class="icon-cancel user-finder-icon" @click="dismissError"/>
|
|
||||||
{{$t('finder.error_fetching_user')}}
|
|
||||||
</span>
|
|
||||||
<i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" />
|
<i class="icon-spin4 user-finder-icon animate-spin-slow" v-if="loading" />
|
||||||
<a href="#" v-if="hidden"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden"/></a>
|
<a href="#" v-if="hidden"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden"/></a>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
|
|
33
src/components/user_search/user_search.js
Normal file
33
src/components/user_search/user_search.js
Normal file
|
@ -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
|
12
src/components/user_search/user_search.vue
Normal file
12
src/components/user_search/user_search.vue
Normal file
|
@ -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>
|
|
@ -21,6 +21,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"login": "Anmelden",
|
"login": "Anmelden",
|
||||||
|
"description": "Mit OAuth anmelden",
|
||||||
"logout": "Abmelden",
|
"logout": "Abmelden",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"placeholder": "z.B. lain",
|
"placeholder": "z.B. lain",
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"login": "Log in",
|
"login": "Log in",
|
||||||
|
"description": "Log in with OAuth",
|
||||||
"logout": "Log out",
|
"logout": "Log out",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"placeholder": "e.g. lain",
|
"placeholder": "e.g. lain",
|
||||||
|
@ -31,9 +32,11 @@
|
||||||
"chat": "Local Chat",
|
"chat": "Local Chat",
|
||||||
"friend_requests": "Follow Requests",
|
"friend_requests": "Follow Requests",
|
||||||
"mentions": "Mentions",
|
"mentions": "Mentions",
|
||||||
|
"dms": "Direct Messages",
|
||||||
"public_tl": "Public Timeline",
|
"public_tl": "Public Timeline",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
"twkn": "The Whole Known Network"
|
"twkn": "The Whole Known Network",
|
||||||
|
"user_search": "User Search"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"broken_favorite": "Unknown status, searching for it...",
|
"broken_favorite": "Unknown status, searching for it...",
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"login": "Connexion",
|
"login": "Connexion",
|
||||||
|
"description": "Connexion avec OAuth",
|
||||||
"logout": "Déconnexion",
|
"logout": "Déconnexion",
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
"placeholder": "p.e. lain",
|
"placeholder": "p.e. lain",
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"nav": {
|
"nav": {
|
||||||
"chat": "Chat local",
|
"chat": "Chat local",
|
||||||
"friend_requests": "Demandes d'ami",
|
"friend_requests": "Demandes d'ami",
|
||||||
|
"dms": "Messages adressés",
|
||||||
"mentions": "Notifications",
|
"mentions": "Notifications",
|
||||||
"public_tl": "Statuts locaux",
|
"public_tl": "Statuts locaux",
|
||||||
"timeline": "Journal",
|
"timeline": "Journal",
|
||||||
|
@ -86,7 +88,7 @@
|
||||||
"cRed": "Rouge (Annuler)",
|
"cRed": "Rouge (Annuler)",
|
||||||
"change_password": "Changez votre mot de passe",
|
"change_password": "Changez votre mot de passe",
|
||||||
"change_password_error": "Il y a eu un problème pour changer votre mot de passe.",
|
"change_password_error": "Il y a eu un problème pour changer votre mot de passe.",
|
||||||
"changed_password": "Mot de passe changé avec succès!",
|
"changed_password": "Mot de passe modifié avec succès !",
|
||||||
"collapse_subject": "Réduire les messages avec des sujets",
|
"collapse_subject": "Réduire les messages avec des sujets",
|
||||||
"confirm_new_password": "Confirmation du nouveau mot de passe",
|
"confirm_new_password": "Confirmation du nouveau mot de passe",
|
||||||
"current_avatar": "Avatar actuel",
|
"current_avatar": "Avatar actuel",
|
||||||
|
@ -100,17 +102,19 @@
|
||||||
"delete_account_instructions": "Indiquez votre mot de passe ci-dessous pour confirmer la suppression de votre compte.",
|
"delete_account_instructions": "Indiquez votre mot de passe ci-dessous pour confirmer la suppression de votre compte.",
|
||||||
"export_theme": "Enregistrer le thème",
|
"export_theme": "Enregistrer le thème",
|
||||||
"filtering": "Filtre",
|
"filtering": "Filtre",
|
||||||
"filtering_explanation": "Tous les statuts contenant ces mots seront masqués. Un mot par ligne.",
|
"filtering_explanation": "Tous les statuts contenant ces mots seront masqués. Un mot par ligne",
|
||||||
"follow_export": "Exporter les abonnements",
|
"follow_export": "Exporter les abonnements",
|
||||||
"follow_export_button": "Exporter les abonnements en csv",
|
"follow_export_button": "Exporter les abonnements en csv",
|
||||||
"follow_export_processing": "Exportation en cours…",
|
"follow_export_processing": "Exportation en cours…",
|
||||||
"follow_import": "Importer des abonnements",
|
"follow_import": "Importer des abonnements",
|
||||||
"follow_import_error": "Erreur lors de l'importation des abonnements.",
|
"follow_import_error": "Erreur lors de l'importation des abonnements",
|
||||||
"follows_imported": "Abonnements importés ! Le traitement peut prendre un moment.",
|
"follows_imported": "Abonnements importés ! Le traitement peut prendre un moment.",
|
||||||
"foreground": "Premier plan",
|
"foreground": "Premier plan",
|
||||||
"general": "Général",
|
"general": "Général",
|
||||||
"hide_attachments_in_convo": "Masquer les pièces jointes dans les conversations",
|
"hide_attachments_in_convo": "Masquer les pièces jointes dans les conversations",
|
||||||
"hide_attachments_in_tl": "Masquer les pièces jointes dans le journal",
|
"hide_attachments_in_tl": "Masquer les pièces jointes dans le journal",
|
||||||
|
"hide_post_stats": "Masquer les statistiques de publication (le nombre de favoris)",
|
||||||
|
"hide_user_stats": "Masquer les statistiques de profil (le nombre d'amis)",
|
||||||
"import_followers_from_a_csv_file": "Importer des abonnements depuis un fichier csv",
|
"import_followers_from_a_csv_file": "Importer des abonnements depuis un fichier csv",
|
||||||
"import_theme": "Charger le thème",
|
"import_theme": "Charger le thème",
|
||||||
"inputRadius": "Champs de texte",
|
"inputRadius": "Champs de texte",
|
||||||
|
@ -156,7 +160,7 @@
|
||||||
"streaming": "Charger automatiquement les nouveaux statuts lorsque vous êtes au haut de la page",
|
"streaming": "Charger automatiquement les nouveaux statuts lorsque vous êtes au haut de la page",
|
||||||
"text": "Texte",
|
"text": "Texte",
|
||||||
"theme": "Thème",
|
"theme": "Thème",
|
||||||
"theme_help": "Spécifiez des codes couleur hexadécimaux (#aabbcc) pour personnaliser les couleurs du thème",
|
"theme_help": "Spécifiez des codes couleur hexadécimaux (#rrvvbb) pour personnaliser les couleurs du thème.",
|
||||||
"tooltipRadius": "Info-bulles/alertes",
|
"tooltipRadius": "Info-bulles/alertes",
|
||||||
"user_settings": "Paramètres utilisateur",
|
"user_settings": "Paramètres utilisateur",
|
||||||
"values": {
|
"values": {
|
||||||
|
@ -177,7 +181,7 @@
|
||||||
"user_card": {
|
"user_card": {
|
||||||
"approve": "Accepter",
|
"approve": "Accepter",
|
||||||
"block": "Bloquer",
|
"block": "Bloquer",
|
||||||
"blocked": "Bloqué",
|
"blocked": "Bloqué !",
|
||||||
"deny": "Rejeter",
|
"deny": "Rejeter",
|
||||||
"follow": "Suivre",
|
"follow": "Suivre",
|
||||||
"followees": "Suivis",
|
"followees": "Suivis",
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"login": "ログイン",
|
"login": "ログイン",
|
||||||
|
"description": "OAuthでログイン",
|
||||||
"logout": "ログアウト",
|
"logout": "ログアウト",
|
||||||
"password": "パスワード",
|
"password": "パスワード",
|
||||||
"placeholder": "れい: lain",
|
"placeholder": "れい: lain",
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
"chat": "ローカルチャット",
|
"chat": "ローカルチャット",
|
||||||
"friend_requests": "フォローリクエスト",
|
"friend_requests": "フォローリクエスト",
|
||||||
"mentions": "メンション",
|
"mentions": "メンション",
|
||||||
|
"dms": "ダイレクトメッセージ",
|
||||||
"public_tl": "パブリックタイムライン",
|
"public_tl": "パブリックタイムライン",
|
||||||
"timeline": "タイムライン",
|
"timeline": "タイムライン",
|
||||||
"twkn": "つながっているすべてのネットワーク"
|
"twkn": "つながっているすべてのネットワーク"
|
||||||
|
@ -111,6 +113,8 @@
|
||||||
"general": "ぜんぱん",
|
"general": "ぜんぱん",
|
||||||
"hide_attachments_in_convo": "スレッドのファイルをかくす",
|
"hide_attachments_in_convo": "スレッドのファイルをかくす",
|
||||||
"hide_attachments_in_tl": "タイムラインのファイルをかくす",
|
"hide_attachments_in_tl": "タイムラインのファイルをかくす",
|
||||||
|
"hide_post_stats": "とうこうのとうけいをかくす (れい: おきにいりのかず)",
|
||||||
|
"hide_user_stats": "ユーザーのとうけいをかくす (れい: フォロワーのかず)",
|
||||||
"import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
|
"import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
|
||||||
"import_theme": "ロード",
|
"import_theme": "ロード",
|
||||||
"inputRadius": "インプットフィールド",
|
"inputRadius": "インプットフィールド",
|
||||||
|
|
|
@ -17,7 +17,9 @@ const saveImmedeatelyActions = [
|
||||||
'clearCurrentUser',
|
'clearCurrentUser',
|
||||||
'setCurrentUser',
|
'setCurrentUser',
|
||||||
'setHighlight',
|
'setHighlight',
|
||||||
'setOption'
|
'setOption',
|
||||||
|
'setClientData',
|
||||||
|
'setToken'
|
||||||
]
|
]
|
||||||
|
|
||||||
const defaultStorage = (() => {
|
const defaultStorage = (() => {
|
||||||
|
@ -43,8 +45,8 @@ export default function createPersistedState ({
|
||||||
storage = defaultStorage,
|
storage = defaultStorage,
|
||||||
subscriber = store => handler => store.subscribe(handler)
|
subscriber = store => handler => store.subscribe(handler)
|
||||||
} = {}) {
|
} = {}) {
|
||||||
|
return getState(key, storage).then((savedState) => {
|
||||||
return store => {
|
return store => {
|
||||||
getState(key, storage).then((savedState) => {
|
|
||||||
try {
|
try {
|
||||||
if (typeof savedState === 'object') {
|
if (typeof savedState === 'object') {
|
||||||
// build user cache
|
// build user cache
|
||||||
|
@ -67,16 +69,14 @@ export default function createPersistedState ({
|
||||||
value: store.state.config.customTheme
|
value: store.state.config.customTheme
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (store.state.users.lastLoginName) {
|
if (store.state.oauth.token) {
|
||||||
store.dispatch('loginUser', {username: store.state.users.lastLoginName, password: 'xxx'})
|
store.dispatch('loginUser', store.state.oauth.token)
|
||||||
}
|
}
|
||||||
loaded = true
|
loaded = true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Couldn't load state")
|
console.log("Couldn't load state")
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
subscriber(store)((mutation, state) => {
|
subscriber(store)((mutation, state) => {
|
||||||
try {
|
try {
|
||||||
if (saveImmedeatelyActions.includes(mutation.type)) {
|
if (saveImmedeatelyActions.includes(mutation.type)) {
|
||||||
|
@ -99,4 +99,5 @@ export default function createPersistedState ({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
197
src/main.js
197
src/main.js
|
@ -1,18 +1,6 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import VueRouter from 'vue-router'
|
import VueRouter from 'vue-router'
|
||||||
import Vuex from 'vuex'
|
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 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 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 interfaceModule from './modules/interface.js'
|
import interfaceModule from './modules/interface.js'
|
||||||
import instanceModule from './modules/instance.js'
|
import instanceModule from './modules/instance.js'
|
||||||
|
@ -21,6 +9,7 @@ import usersModule from './modules/users.js'
|
||||||
import apiModule from './modules/api.js'
|
import apiModule from './modules/api.js'
|
||||||
import configModule from './modules/config.js'
|
import configModule from './modules/config.js'
|
||||||
import chatModule from './modules/chat.js'
|
import chatModule from './modules/chat.js'
|
||||||
|
import oauthModule from './modules/oauth.js'
|
||||||
|
|
||||||
import VueTimeago from 'vue-timeago'
|
import VueTimeago from 'vue-timeago'
|
||||||
import VueI18n from 'vue-i18n'
|
import VueI18n from 'vue-i18n'
|
||||||
|
@ -31,6 +20,8 @@ import messages from './i18n/messages.js'
|
||||||
|
|
||||||
import VueChatScroll from 'vue-chat-scroll'
|
import VueChatScroll from 'vue-chat-scroll'
|
||||||
|
|
||||||
|
import afterStoreSetup from './boot/after_store.js'
|
||||||
|
|
||||||
const currentLocale = (window.navigator.language || 'en').split('-')[0]
|
const currentLocale = (window.navigator.language || 'en').split('-')[0]
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
@ -45,14 +36,23 @@ Vue.use(VueTimeago, {
|
||||||
Vue.use(VueI18n)
|
Vue.use(VueI18n)
|
||||||
Vue.use(VueChatScroll)
|
Vue.use(VueChatScroll)
|
||||||
|
|
||||||
|
const i18n = new VueI18n({
|
||||||
|
// By default, use the browser locale, we will update it if neccessary
|
||||||
|
locale: currentLocale,
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
messages
|
||||||
|
})
|
||||||
|
|
||||||
|
=======
|
||||||
const persistedStateOptions = {
|
const persistedStateOptions = {
|
||||||
paths: [
|
paths: [
|
||||||
'config',
|
'config',
|
||||||
'users.lastLoginName',
|
'users.lastLoginName',
|
||||||
'statuses.notifications.maxSavedId'
|
'statuses.notifications.maxSavedId',
|
||||||
|
'oauth'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
createPersistedState(persistedStateOptions).then((persistedState) => {
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
interface: interfaceModule,
|
interface: interfaceModule,
|
||||||
|
@ -61,173 +61,14 @@ const store = new Vuex.Store({
|
||||||
users: usersModule,
|
users: usersModule,
|
||||||
api: apiModule,
|
api: apiModule,
|
||||||
config: configModule,
|
config: configModule,
|
||||||
chat: chatModule
|
chat: chatModule,
|
||||||
|
oauth: oauthModule
|
||||||
},
|
},
|
||||||
plugins: [createPersistedState(persistedStateOptions)],
|
plugins: [persistedState],
|
||||||
strict: false // Socket modifies itself, let's ignore this for now.
|
strict: false // Socket modifies itself, let's ignore this for now.
|
||||||
// strict: process.env.NODE_ENV !== 'production'
|
// strict: process.env.NODE_ENV !== 'production'
|
||||||
|
>>>>>>> upstream/develop
|
||||||
})
|
})
|
||||||
|
|
||||||
const i18n = new VueI18n({
|
afterStoreSetup({store, i18n})
|
||||||
// By default, use the browser locale, we will update it if neccessary
|
|
||||||
locale: currentLocale,
|
|
||||||
fallbackLocale: 'en',
|
|
||||||
messages
|
|
||||||
})
|
|
||||||
|
|
||||||
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 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: '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: '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 }
|
|
||||||
]
|
|
||||||
|
|
||||||
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 })
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,6 +23,7 @@ const defaultState = {
|
||||||
disableChat: false,
|
disableChat: false,
|
||||||
scopeCopy: true,
|
scopeCopy: true,
|
||||||
subjectLineBehavior: 'email',
|
subjectLineBehavior: 'email',
|
||||||
|
loginMethod: 'password',
|
||||||
|
|
||||||
// Nasty stuff
|
// Nasty stuff
|
||||||
pleromaBackend: true,
|
pleromaBackend: true,
|
||||||
|
|
18
src/modules/oauth.js
Normal file
18
src/modules/oauth.js
Normal file
|
@ -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
|
|
@ -41,7 +41,8 @@ export const defaultState = {
|
||||||
own: emptyTl(),
|
own: emptyTl(),
|
||||||
publicAndExternal: emptyTl(),
|
publicAndExternal: emptyTl(),
|
||||||
friends: emptyTl(),
|
friends: emptyTl(),
|
||||||
tag: emptyTl()
|
tag: emptyTl(),
|
||||||
|
dms: emptyTl()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +172,14 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
||||||
sortTimeline(mentions)
|
sortTimeline(mentions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (status.visibility === 'direct') {
|
||||||
|
const dms = state.timelines.dms
|
||||||
|
|
||||||
|
mergeOrAdd(dms.statuses, dms.statusesObject, status)
|
||||||
|
dms.newStatusCount += 1
|
||||||
|
|
||||||
|
sortTimeline(dms)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decide if we should treat the status as new for this timeline.
|
// Decide if we should treat the status as new for this timeline.
|
||||||
|
|
|
@ -82,24 +82,26 @@ const users = {
|
||||||
},
|
},
|
||||||
logout (store) {
|
logout (store) {
|
||||||
store.commit('clearCurrentUser')
|
store.commit('clearCurrentUser')
|
||||||
|
store.commit('setToken', false)
|
||||||
store.dispatch('stopFetching', 'friends')
|
store.dispatch('stopFetching', 'friends')
|
||||||
store.commit('setBackendInteractor', backendInteractorService())
|
store.commit('setBackendInteractor', backendInteractorService())
|
||||||
},
|
},
|
||||||
loginUser (store, userCredentials) {
|
loginUser (store, accessToken) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const commit = store.commit
|
const commit = store.commit
|
||||||
commit('beginLogin')
|
commit('beginLogin')
|
||||||
store.rootState.api.backendInteractor.verifyCredentials(userCredentials)
|
store.rootState.api.backendInteractor.verifyCredentials(accessToken)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
response.json()
|
response.json()
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
user.credentials = userCredentials
|
// user.credentials = userCredentials
|
||||||
|
user.credentials = accessToken
|
||||||
commit('setCurrentUser', user)
|
commit('setCurrentUser', user)
|
||||||
commit('addNewUsers', [user])
|
commit('addNewUsers', [user])
|
||||||
|
|
||||||
// Set our new backend interactor
|
// Set our new backend interactor
|
||||||
commit('setBackendInteractor', backendInteractorService(userCredentials))
|
commit('setBackendInteractor', backendInteractorService(accessToken))
|
||||||
|
|
||||||
if (user.token) {
|
if (user.token) {
|
||||||
store.dispatch('initializeSocket', user.token)
|
store.dispatch('initializeSocket', user.token)
|
||||||
|
|
|
@ -15,6 +15,7 @@ const STATUS_URL = '/api/statuses/show'
|
||||||
const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload'
|
const MEDIA_UPLOAD_URL = '/api/statusnet/media/upload'
|
||||||
const CONVERSATION_URL = '/api/statusnet/conversation'
|
const CONVERSATION_URL = '/api/statusnet/conversation'
|
||||||
const MENTIONS_URL = '/api/statuses/mentions.json'
|
const MENTIONS_URL = '/api/statuses/mentions.json'
|
||||||
|
const DM_TIMELINE_URL = '/api/statuses/dm_timeline.json'
|
||||||
const FOLLOWERS_URL = '/api/statuses/followers.json'
|
const FOLLOWERS_URL = '/api/statuses/followers.json'
|
||||||
const FRIENDS_URL = '/api/statuses/friends.json'
|
const FRIENDS_URL = '/api/statuses/friends.json'
|
||||||
const FOLLOWING_URL = '/api/friendships/create.json'
|
const FOLLOWING_URL = '/api/friendships/create.json'
|
||||||
|
@ -52,16 +53,6 @@ let fetch = (url, options) => {
|
||||||
return oldfetch(fullUrl, options)
|
return oldfetch(fullUrl, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
|
||||||
let utoa = (str) => {
|
|
||||||
// first we use encodeURIComponent to get percent-encoded UTF-8,
|
|
||||||
// then we convert the percent encodings into raw bytes which
|
|
||||||
// can be fed into btoa.
|
|
||||||
return btoa(encodeURIComponent(str)
|
|
||||||
.replace(/%([0-9A-F]{2})/g,
|
|
||||||
(match, p1) => { return String.fromCharCode('0x' + p1) }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Params
|
// Params
|
||||||
// cropH
|
// cropH
|
||||||
// cropW
|
// cropW
|
||||||
|
@ -175,9 +166,9 @@ const register = (params) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const authHeaders = (user) => {
|
const authHeaders = (accessToken) => {
|
||||||
if (user && user.username && user.password) {
|
if (accessToken) {
|
||||||
return { 'Authorization': `Basic ${utoa(`${user.username}:${user.password}`)}` }
|
return { 'Authorization': `Bearer ${accessToken}` }
|
||||||
} else {
|
} else {
|
||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
|
@ -302,6 +293,7 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
|
||||||
public: PUBLIC_TIMELINE_URL,
|
public: PUBLIC_TIMELINE_URL,
|
||||||
friends: FRIENDS_TIMELINE_URL,
|
friends: FRIENDS_TIMELINE_URL,
|
||||||
mentions: MENTIONS_URL,
|
mentions: MENTIONS_URL,
|
||||||
|
dms: DM_TIMELINE_URL,
|
||||||
notifications: QVITTER_USER_NOTIFICATIONS_URL,
|
notifications: QVITTER_USER_NOTIFICATIONS_URL,
|
||||||
'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL,
|
'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL,
|
||||||
user: QVITTER_USER_TIMELINE_URL,
|
user: QVITTER_USER_TIMELINE_URL,
|
||||||
|
|
82
src/services/new_api/oauth.js
Normal file
82
src/services/new_api/oauth.js
Normal file
|
@ -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
|
16
src/services/new_api/user_search.js
Normal file
16
src/services/new_api/user_search.js
Normal file
|
@ -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
|
36
src/services/new_api/utils.js
Normal file
36
src/services/new_api/utils.js
Normal file
|
@ -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
|
|
@ -14,5 +14,6 @@
|
||||||
"scopeCopy": false,
|
"scopeCopy": false,
|
||||||
"subjectLineBehavior": "email",
|
"subjectLineBehavior": "email",
|
||||||
"hidePostStats": false,
|
"hidePostStats": false,
|
||||||
"hideUserStats": false
|
"hideUserStats": false,
|
||||||
|
"loginMethod": "password"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue