Merge branch 'develop' of git.pleroma.social:pleroma/pleroma-fe into feature/custom-theme

This commit is contained in:
shpuld 2017-11-17 02:17:47 +02:00
commit e13c8c3fd2
12 changed files with 132 additions and 70 deletions

View file

@ -2,7 +2,7 @@
> A single column frontend for both Pleroma and GS servers. > A single column frontend for both Pleroma and GS servers.
![screenshot](https://my.mixtape.moe/kjzioz.PNG) ![screenshot](https://i.imgur.com/DJVqSJ0.png)
# For Translators # For Translators

View file

@ -1,4 +1,4 @@
import { reduce, find, filter, sortBy } from 'lodash' import { reduce, filter, sortBy } from 'lodash'
import { statusType } from '../../modules/statuses.js' import { statusType } from '../../modules/statuses.js'
import Status from '../status/status.vue' import Status from '../status/status.vue'
@ -10,12 +10,7 @@ const sortAndFilterConversation = (conversation) => {
const conversation = { const conversation = {
data () { data () {
return { return {
highlight: null, highlight: null
preview: {
x: 0,
y: 0,
status: null
}
} }
}, },
props: [ props: [
@ -86,15 +81,6 @@ const conversation = {
}, },
setHighlight (id) { setHighlight (id) {
this.highlight = Number(id) this.highlight = Number(id)
},
setPreview (id, x, y) {
if (id) {
this.preview.x = x
this.preview.y = y
this.preview.status = find(this.conversation, { id: id })
} else {
this.preview.status = null
}
} }
} }
} }

View file

@ -8,48 +8,10 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="timeline"> <div class="timeline">
<status v-for="status in conversation" @goto="setHighlight" :key="status.id" @preview="setPreview" :statusoid="status" :expandable='false' :focused="focused(status.id)" :inConversation='true' :highlight="highlight" :replies="getReplies(status.id)"></status> <status v-for="status in conversation" @goto="setHighlight" :key="status.id" :statusoid="status" :expandable='false' :focused="focused(status.id)" :inConversation='true' :highlight="highlight" :replies="getReplies(status.id)"></status>
</div>
</div>
<div class="status-preview base00-background base03-border" :style="{ left: preview.x + 'px', top: preview.y + 'px'}" v-if="preview.status">
<img class="avatar" :src="preview.status.user.profile_image_url_original">
<div class="text">
<h4>
{{ preview.status.user.name }}
<small><a>{{ preview.status.user.screen_name}}</a></small>
</h4>
<div @click.prevent="linkClicked" class="status-content" v-html="preview.status.statusnet_html"></div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script src="./conversation.js"></script> <script src="./conversation.js"></script>
<style lang="scss">
.status-preview {
position: absolute;
max-width: 35em;
padding: 0.5em;
display: flex;
border-color: inherit;
border-style: solid;
border-width: 1px;
border-radius: 4px;
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
.text {
h4 {
margin-bottom: 0.4em;
small {
font-weight: lighter;
}
}
padding: 0 0.5em 0.5em 0.5em;
}
}
</style>

View file

@ -4,7 +4,7 @@ import RetweetButton from '../retweet_button/retweet_button.vue'
import DeleteButton from '../delete_button/delete_button.vue' import DeleteButton from '../delete_button/delete_button.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue' import PostStatusForm from '../post_status_form/post_status_form.vue'
import UserCardContent from '../user_card_content/user_card_content.vue' import UserCardContent from '../user_card_content/user_card_content.vue'
import { filter } from 'lodash' import { filter, find } from 'lodash'
const Status = { const Status = {
props: [ props: [
@ -20,7 +20,9 @@ const Status = {
replying: false, replying: false,
expanded: false, expanded: false,
unmuted: false, unmuted: false,
userExpanded: false userExpanded: false,
preview: null,
showPreview: false
}), }),
computed: { computed: {
muteWords () { muteWords () {
@ -90,7 +92,9 @@ const Status = {
}, },
gotoOriginal (id) { gotoOriginal (id) {
// only handled by conversation, not status_or_conversation // only handled by conversation, not status_or_conversation
this.$emit('goto', id) if (this.inConversation) {
this.$emit('goto', id)
}
}, },
toggleExpanded () { toggleExpanded () {
this.$emit('toggleExpanded') this.$emit('toggleExpanded')
@ -102,13 +106,25 @@ const Status = {
this.userExpanded = !this.userExpanded this.userExpanded = !this.userExpanded
}, },
replyEnter (id, event) { replyEnter (id, event) {
if (this.$store.state.config.hoverPreview) { this.showPreview = true
let rect = event.target.getBoundingClientRect() const targetId = Number(id)
this.$emit('preview', Number(id), rect.left + 20, rect.top + 20 + window.pageYOffset) const statuses = this.$store.state.statuses.allStatuses
if (!this.preview) {
// if we have the status somewhere already
this.preview = find(statuses, { 'id': targetId })
// or if we have to fetch it
if (!this.preview) {
this.$store.state.api.backendInteractor.fetchStatus({id}).then((status) => {
this.preview = status
})
}
} else if (this.preview.id !== targetId) {
this.preview = find(statuses, { 'id': targetId })
} }
}, },
replyLeave () { replyLeave () {
this.$emit('preview', 0, 0, 0) this.showPreview = false
} }
}, },
watch: { watch: {

View file

@ -54,7 +54,7 @@
{{status.in_reply_to_screen_name}} {{status.in_reply_to_screen_name}}
</router-link> </router-link>
</small> </small>
<template v-if="isReply && !expandable"> <template v-if="isReply">
<small> <small>
<a href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)"><i class="icon-reply" @mouseenter="replyEnter(status.in_reply_to_status_id, $event)" @mouseout="replyLeave()"></i></a> <a href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)"><i class="icon-reply" @mouseenter="replyEnter(status.in_reply_to_status_id, $event)" @mouseout="replyLeave()"></i></a>
</small> </small>
@ -83,6 +83,20 @@
</div> </div>
</div> </div>
<div class="status-preview base00-background base03-border" v-if="showPreview && preview">
<img class="avatar" :src="preview.user.profile_image_url_original">
<div class="text">
<h4>
{{ preview.user.name }}
<small><a>{{ preview.user.screen_name}}</a></small>
</h4>
<div @click.prevent="linkClicked" class="status-content" v-html="preview.statusnet_html"></div>
</div>
</div>
<div class="status-preview status-preview-loading base00-background base03-border" v-else-if="showPreview">
<i class="fa icon-spin4 animate-spin"></i>
</div>
<div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div> <div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div>
<div v-if='status.attachments' class='attachments'> <div v-if='status.attachments' class='attachments'>
@ -120,7 +134,44 @@
status-text-container { status-text-container {
display: block; display: block;
} }
.status-preview {
position: absolute;
max-width: 34em;
padding: 0.5em;
display: flex;
border-color: inherit;
border-style: solid;
border-width: 1px;
border-radius: 4px;
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
margin-top: 0.5em;
margin-left: 1em;
.avatar {
flex-shrink: 0;
width: 32px;
height: 32px;
border-radius: 50%;
}
.text {
h4 {
margin-bottom: 0.4em;
small {
font-weight: lighter;
}
}
padding: 0 0.5em 0.5em 0.5em;
}
}
.status-preview-loading {
display: block;
font-size: 2em;
min-width: 8em;
text-align: center;
}
.status-el { .status-el {
hyphens: auto; hyphens: auto;

View file

@ -60,6 +60,7 @@ const Timeline = {
}, },
destroyed () { destroyed () {
window.removeEventListener('scroll', this.scrollLoad) window.removeEventListener('scroll', this.scrollLoad)
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
}, },
methods: { methods: {
showNewStatuses () { showNewStatuses () {

View file

@ -7,6 +7,7 @@ const UserFinder = {
}), }),
methods: { methods: {
findUser (username) { findUser (username) {
username = username[0] === '@' ? username.slice(1) : username
this.loading = true this.loading = true
this.$store.state.api.backendInteractor.externalProfile(username) this.$store.state.api.backendInteractor.externalProfile(username)
.then((user) => { .then((user) => {

View file

@ -5,6 +5,9 @@ const UserProfile = {
created () { created () {
this.$store.commit('clearTimeline', { timeline: 'user' }) this.$store.commit('clearTimeline', { timeline: 'user' })
this.$store.dispatch('startFetching', ['user', this.userId]) this.$store.dispatch('startFetching', ['user', this.userId])
if (!this.$store.state.users.usersObject[this.userId]) {
this.$store.dispatch('fetchUser', this.userId)
}
}, },
destroyed () { destroyed () {
this.$store.dispatch('stopFetching', 'user') this.$store.dispatch('stopFetching', 'user')
@ -18,7 +21,7 @@ const UserProfile = {
if (this.timeline.statuses[0]) { if (this.timeline.statuses[0]) {
return this.timeline.statuses[0].user return this.timeline.statuses[0].user
} else { } else {
return false return this.$store.state.users.usersObject[this.userId] || false
} }
} }
}, },

View file

@ -9,6 +9,8 @@ const de = {
follows_you: 'Folgt dir!', follows_you: 'Folgt dir!',
following: 'Folgst du!', following: 'Folgst du!',
follow: 'Folgen', follow: 'Folgen',
blocked: 'Blockiert!',
block: 'Blockieren',
statuses: 'Beiträge', statuses: 'Beiträge',
mute: 'Stummschalten', mute: 'Stummschalten',
muted: 'Stummgeschaltet', muted: 'Stummgeschaltet',
@ -20,7 +22,8 @@ const de = {
show_new: 'Zeige Neuere', show_new: 'Zeige Neuere',
error_fetching: 'Error beim Laden', error_fetching: 'Error beim Laden',
up_to_date: 'Aktuell', up_to_date: 'Aktuell',
load_older: 'Lade ältere Beiträge' load_older: 'Lade ältere Beiträge',
conversation: 'Unterhaltung'
}, },
settings: { settings: {
user_settings: 'Benutzereinstellungen', user_settings: 'Benutzereinstellungen',
@ -36,14 +39,15 @@ const de = {
profile_background: 'Profil Hintergrund', profile_background: 'Profil Hintergrund',
set_new_profile_background: 'Setze neuen Profil Hintergrund', set_new_profile_background: 'Setze neuen Profil Hintergrund',
settings: 'Einstellungen', settings: 'Einstellungen',
theme: 'Theme', theme: 'Farbschema',
filtering: 'Filter', filtering: 'Filter',
filtering_explanation: 'Alle Beiträge die diese Wörter enthalten werden ausgeblendet. Ein Wort pro Zeile.', filtering_explanation: 'Alle Beiträge die diese Wörter enthalten werden ausgeblendet. Ein Wort pro Zeile.',
attachments: 'Anhänge', attachments: 'Anhänge',
hide_attachments_in_tl: 'Anhänge in der Timeline ausblenden', hide_attachments_in_tl: 'Anhänge in der Timeline ausblenden',
hide_attachments_in_convo: 'Anhänge in Unterhaltungen ausblenden', hide_attachments_in_convo: 'Anhänge in Unterhaltungen ausblenden',
nsfw_clickthrough: 'Aktiviere ausblendbares Overlay für als NSFW markierte Anhänge', nsfw_clickthrough: 'Aktiviere ausblendbares Overlay für als NSFW markierte Anhänge',
autoload: 'Aktiviere automatisches Laden von Beiträgen beim scrollen', autoload: 'Aktiviere automatisches Laden von älteren Beiträgen beim scrollen',
streaming: 'Aktiviere automatisches Laden (Streaming) von neuen Beiträgen',
reply_link_preview: 'Aktiviere reply-link Vorschau bei Maus-Hover' reply_link_preview: 'Aktiviere reply-link Vorschau bei Maus-Hover'
}, },
notifications: { notifications: {
@ -51,6 +55,28 @@ const de = {
read: 'Gelesen!', read: 'Gelesen!',
followed_you: 'folgt dir' followed_you: 'folgt dir'
}, },
login: {
login: 'Anmelden',
username: 'Benutzername',
password: 'Passwort',
register: 'Registrieren',
logout: 'Abmelden'
},
registration: {
registration: 'Registrierung',
fullname: 'Angezeigter Name',
email: 'Email',
bio: 'Bio',
password_confirm: 'Passwort bestätigen'
},
post_status: {
posting: 'Veröffentlichen',
default: 'Sitze gerade im Hofbräuhaus.'
},
finder: {
find_user: 'Finde Benutzer',
error_fetching_user: 'Fehler beim Suchen des Benutzers'
},
general: { general: {
submit: 'Absenden' submit: 'Absenden'
} }

View file

@ -57,6 +57,10 @@ const users = {
state: defaultState, state: defaultState,
mutations, mutations,
actions: { actions: {
fetchUser (store, id) {
store.rootState.api.backendInteractor.fetchUser({id})
.then((user) => store.commit('addNewUsers', user))
},
addNewStatuses (store, { statuses }) { addNewStatuses (store, { statuses }) {
const users = map(statuses, 'user') const users = map(statuses, 'user')
const retweetedUsers = compact(map(statuses, 'retweeted_status.user')) const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))

View file

@ -28,7 +28,7 @@ const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json' const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json'
const BLOCKING_URL = '/api/blocks/create.json' const BLOCKING_URL = '/api/blocks/create.json'
const UNBLOCKING_URL = '/api/blocks/destroy.json' const UNBLOCKING_URL = '/api/blocks/destroy.json'
// const USER_URL = '/api/users/show.json' const USER_URL = '/api/users/show.json'
import { each, map } from 'lodash' import { each, map } from 'lodash'
import 'whatwg-fetch' import 'whatwg-fetch'
@ -202,6 +202,12 @@ const unblockUser = ({id, credentials}) => {
}).then((data) => data.json()) }).then((data) => data.json())
} }
const fetchUser = ({id, credentials}) => {
let url = `${USER_URL}?user_id=${id}`
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
}
const fetchFriends = ({id, credentials}) => { const fetchFriends = ({id, credentials}) => {
let url = `${FRIENDS_URL}?user_id=${id}` let url = `${FRIENDS_URL}?user_id=${id}`
return fetch(url, { headers: authHeaders(credentials) }) return fetch(url, { headers: authHeaders(credentials) })
@ -363,6 +369,7 @@ const apiService = {
unfollowUser, unfollowUser,
blockUser, blockUser,
unblockUser, unblockUser,
fetchUser,
favorite, favorite,
unfavorite, unfavorite,
retweet, retweet,

View file

@ -22,6 +22,10 @@ const backendInteractorService = (credentials) => {
return apiService.fetchAllFollowing({username, credentials}) return apiService.fetchAllFollowing({username, credentials})
} }
const fetchUser = ({id}) => {
return apiService.fetchUser({id, credentials})
}
const followUser = (id) => { const followUser = (id) => {
return apiService.followUser({credentials, id}) return apiService.followUser({credentials, id})
} }
@ -65,6 +69,7 @@ const backendInteractorService = (credentials) => {
unfollowUser, unfollowUser,
blockUser, blockUser,
unblockUser, unblockUser,
fetchUser,
fetchAllFollowing, fetchAllFollowing,
verifyCredentials: apiService.verifyCredentials, verifyCredentials: apiService.verifyCredentials,
startFetching, startFetching,