Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • pleroma/pleroma-fe
  • eal/pleroma-fe
  • peterspark/pleroma-fe
  • hb2k8/pleroma-fe
  • tibike/pleroma-fe
  • obrez/pleroma-fe
  • partial/pleroma-fe
  • href/pleroma-fe
  • hakabahitoyo/pleroma-fe
  • hsgw/pleroma-fe
  • Azurolu/pleroma-fe
  • cobalto/pleroma-fe
  • qwexvf/pleroma-fe
  • boner.engineer/pleroma-fe
  • f0x/pleroma-fe
  • ataalik/pleroma-fe
  • normandy/pleroma-fe
  • Sir_Boops/pleroma-fe
  • morguldir/pleroma-fe
  • csaurus/pleroma-fe
  • kaniini/pleroma-fe
  • bhtooefr/pleroma-fe
  • andarna/pleroma-fe
  • ktsukik/pleroma-fe
  • Steph/pleroma-fe
  • andrewzah/pleroma-fe
  • lanodan/pleroma-fe
  • pea/pleroma-fe
  • fotfd/pleroma-fe
  • pizzaiolo/pleroma-fe
  • Syldexia/pleroma-fe
  • riking/pleroma-fe
  • dr1ft/pleroma-fe
  • animeirl/pleroma-fe
  • elomatreb/pleroma-fe
  • viv/pleroma-fe
  • goofy/pleroma-fe
  • hoodie/pleroma-fe
  • stolas/pleroma-fe
  • peterpan/pleroma-fe
  • Lumitas/pleroma-fe
  • Toromino/pleroma-fe
  • galen/pleroma-fe
  • scarlett/pleroma-fe
  • ButterflyOfFire/pleroma-fe
  • vaartis/pleroma-fe
  • meireikei/pleroma-fe
  • darko/pleroma-fe
  • pony/pleroma-fe
  • succfemboi/pleroma-fe
  • fadelkon/pleroma-fe
  • dgold/pleroma-fe
  • nebula_moe/pleroma-fe
  • vinzv/pleroma-fe
  • slice/pleroma-fe
  • rinpatch/pleroma-fe
  • maxf/pleroma-fe
  • raeno/pleroma-fe
  • oceanvald/pleroma-fe
  • nuklearfiziks/pleroma-fe
  • feld/pleroma-fe
  • minibikini/pleroma-fe
  • link0ff/pleroma-fe
  • qadeer/pleroma-fe
  • FloatingGhost/pleroma-fe
  • cascode/pleroma-fe
  • hikaruaikawa/pleroma-fe
  • kjwon15/pleroma-fe
  • ukrop/pleroma-fe
  • ilja/pleroma-fe
  • shadowfacts/pleroma-fe
  • edijs/pleroma-fe
  • jdorman632/pleroma-fe
  • xruselfmadex/pleroma-fe
  • futureweb/pleroma-fe
  • eugenijm/pleroma-fe
  • tae/pleroma-fe
  • Dave/pleroma-fe
  • jasper/pleroma-fe
  • Lidar/pleroma-fe
  • parallel588/pleroma-fe
  • jaredr/pleroma-fe
  • rondnelly/pleroma-fe
  • Aditoo/pleroma-fe
  • FongWan/pleroma-fe
  • mkljczk/pleroma-fe
  • nik/pleroma-fe
  • brendenbice1222/pleroma-fe
  • Satak/pleroma-fe
  • xse/pleroma-fe
  • moonman/pleroma-fe
  • Artik/pleroma-fe
  • ssuprunenko/pleroma-fe
  • uncletrunks/pleroma-fe
  • absturztaube/pleroma-fe
  • wyatt777/pleroma-fe
  • hauvophuoc/pleroma-fe
  • dashie/pleroma-fe
  • shmibs/pleroma-fe
  • Elepow/pleroma-fe
  • raven/pleroma-fe
  • buoyantair/pleroma-fe
  • Exilat_a_Tolosa/pleroma-fe
  • matrixsasuke/pleroma-fe
  • njoseph/pleroma-fe
  • ELR/pleroma-fe
  • sjw/pleroma-fe
  • davidyin/pleroma-fe
  • pescetarian/pleroma-fe
  • kphrx/pleroma-fe
  • mewmew/pleroma-fe
  • h3poteto/pleroma-fe
  • Alexpono/pleroma-fe
  • seven/pleroma-fe
  • mparvin/pleroma-fe
  • tuxcrafting/pleroma-fe
  • nekojanai/pleroma-fe
  • xenofem/pleroma-fe
  • p/pleroma-fe
  • creme/pleroma-fe
  • jp/pleroma-fe
  • Jeder/pleroma-fe
  • gensogrips/pleroma-fe
  • caskd/pleroma-fe
  • arkSong/pleroma-fe
  • Hikali/pleroma-fe
  • Duponin/pleroma-fe
  • gashapwn/pleroma-fe
  • fence/pleroma-fe
  • Duder-onomy/pleroma-fe
  • translate/pleroma-fe
  • okl/pleroma-fe
  • bird/pleroma-fe
  • NEETzsche/pleroma-fe
  • Ewoke19CMR/pleroma-fe
  • shevek/pleroma-fe
  • cutienautica/pleroma-fe
  • Nakaya/pleroma-fe
  • Snow/pleroma-fe
  • seanking/pleroma-fe
  • kkcake/pleroma-fe
  • Testacc/pleroma-fe
  • flxy/pleroma-fe
  • xerz/pleroma-fe
  • maronu/pleroma-fe
  • matildepark/pleroma-fe
  • Craftplacer/pleroma-fe
147 results
Show changes
Commits on Source (108)
Showing
with 192 additions and 111 deletions
......@@ -97,6 +97,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('showInstanceSpecificPanel')
copyInstanceOption('scopeOptionsEnabled')
copyInstanceOption('formattingOptionsEnabled')
copyInstanceOption('hideMutedPosts')
copyInstanceOption('collapseMessageWithSubject')
copyInstanceOption('loginMethod')
copyInstanceOption('scopeCopy')
......@@ -240,7 +241,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
// Now we have the server settings and can try logging in
if (store.state.oauth.token) {
store.dispatch('loginUser', store.state.oauth.token)
await store.dispatch('loginUser', store.state.oauth.token)
}
const router = new VueRouter({
......
import Conversation from '../conversation/conversation.vue'
import { find } from 'lodash'
const conversationPage = {
components: {
......@@ -8,8 +7,8 @@ const conversationPage = {
computed: {
statusoid () {
const id = this.$route.params.id
const statuses = this.$store.state.statuses.allStatuses
const status = find(statuses, {id})
const statuses = this.$store.state.statuses.allStatusesObject
const status = statuses[id]
return status
}
......
<template>
<conversation :collapsable="false" :statusoid="statusoid"></conversation>
<conversation
:collapsable="false"
isPage="true"
:statusoid="statusoid"
></conversation>
</template>
<script src="./conversation-page.js"></script>
import { reduce, filter } from 'lodash'
import { reduce, filter, findIndex } from 'lodash'
import { set } from 'vue'
import Status from '../status/status.vue'
const sortById = (a, b) => {
const seqA = Number(a.id)
const seqB = Number(b.id)
const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id
const idB = b.type === 'retweet' ? b.retweeted_status.id : b.id
const seqA = Number(idA)
const seqB = Number(idB)
const isSeqA = !Number.isNaN(seqA)
const isSeqB = !Number.isNaN(seqB)
if (isSeqA && isSeqB) {
......@@ -13,29 +16,53 @@ const sortById = (a, b) => {
} else if (!isSeqA && isSeqB) {
return 1
} else {
return a.id < b.id ? -1 : 1
return idA < idB ? -1 : 1
}
}
const sortAndFilterConversation = (conversation) => {
conversation = filter(conversation, (status) => status.type !== 'retweet')
const sortAndFilterConversation = (conversation, statusoid) => {
if (statusoid.type === 'retweet') {
conversation = filter(
conversation,
(status) => (status.type === 'retweet' || status.id !== statusoid.retweeted_status.id)
)
} else {
conversation = filter(conversation, (status) => status.type !== 'retweet')
}
return conversation.filter(_ => _).sort(sortById)
}
const conversation = {
data () {
return {
highlight: null
highlight: null,
expanded: false,
converationStatusIds: []
}
},
props: [
'statusoid',
'collapsable'
'collapsable',
'isPage'
],
created () {
if (this.isPage) {
this.fetchConversation()
}
},
computed: {
status () {
return this.statusoid
},
idsToShow () {
if (this.converationStatusIds.length > 0) {
return this.converationStatusIds
} else if (this.statusId) {
return [this.statusId]
} else {
return []
}
},
statusId () {
if (this.statusoid.retweeted_status) {
return this.statusoid.retweeted_status.id
......@@ -48,10 +75,22 @@ const conversation = {
return []
}
const conversationId = this.status.statusnet_conversation_id
const statuses = this.$store.state.statuses.allStatuses
const conversation = filter(statuses, { statusnet_conversation_id: conversationId })
return sortAndFilterConversation(conversation)
if (!this.isExpanded) {
return [this.status]
}
const statusesObject = this.$store.state.statuses.allStatusesObject
const conversation = this.idsToShow.reduce((acc, id) => {
acc.push(statusesObject[id])
return acc
}, [])
const statusIndex = findIndex(conversation, { id: this.statusId })
if (statusIndex !== -1) {
conversation[statusIndex] = this.status
}
return sortAndFilterConversation(conversation, this.status)
},
replies () {
let i = 1
......@@ -69,23 +108,34 @@ const conversation = {
i++
return result
}, {})
},
isExpanded () {
return this.expanded || this.isPage
}
},
components: {
Status
},
created () {
this.fetchConversation()
},
watch: {
'$route': 'fetchConversation'
'$route': 'fetchConversation',
expanded (value) {
if (value) {
this.fetchConversation()
}
}
},
methods: {
fetchConversation () {
if (this.status) {
const conversationId = this.status.statusnet_conversation_id
this.$store.state.api.backendInteractor.fetchConversation({id: conversationId})
.then((statuses) => this.$store.dispatch('addNewStatuses', { statuses }))
this.$store.state.api.backendInteractor.fetchConversation({id: this.status.id})
.then(({ancestors, descendants}) => {
this.$store.dispatch('addNewStatuses', { statuses: ancestors })
this.$store.dispatch('addNewStatuses', { statuses: descendants })
set(this, 'converationStatusIds', [].concat(
ancestors.map(_ => _.id).filter(_ => _ !== this.statusId),
this.statusId,
descendants.map(_ => _.id).filter(_ => _ !== this.statusId)))
})
.then(() => this.setHighlight(this.statusId))
} else {
const id = this.$route.params.id
......@@ -98,10 +148,19 @@ const conversation = {
return this.replies[id] || []
},
focused (id) {
return id === this.statusId
return (this.isExpanded) && id === this.status.id
},
setHighlight (id) {
this.highlight = id
},
getHighlight () {
return this.isExpanded ? this.highlight : null
},
toggleExpanded () {
this.expanded = !this.expanded
if (!this.expanded) {
this.setHighlight(null)
}
}
}
}
......
<template>
<div class="timeline panel panel-default">
<div class="panel-heading conversation-heading">
<div class="timeline panel-default" :class="[isExpanded ? 'panel' : 'panel-disabled']">
<div v-if="isExpanded" class="panel-heading conversation-heading">
<span class="title"> {{ $t('timeline.conversation') }} </span>
<span v-if="collapsable">
<a href="#" @click.prevent="$emit('toggleExpanded')">{{ $t('timeline.collapse') }}</a>
<a href="#" @click.prevent="toggleExpanded">{{ $t('timeline.collapse') }}</a>
</span>
</div>
<div class="panel-body">
<div class="timeline">
<status
v-for="status in conversation"
@goto="setHighlight" :key="status.id"
:inlineExpanded="collapsable" :statusoid="status"
:expandable='false' :focused="focused(status.id)"
:inConversation='true'
:highlight="highlight"
:replies="getReplies(status.id)"
class="status-fadein">
</status>
</div>
</div>
<status
v-for="status in conversation"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
:key="status.id"
:inlineExpanded="collapsable"
:statusoid="status"
:expandable='!expanded'
:focused="focused(status.id)"
:inConversation="isExpanded"
:highlight="getHighlight()"
:replies="getReplies(status.id)"
class="status-fadein panel-body"
/>
</div>
</template>
<script src="./conversation.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.timeline {
.panel-disabled {
.status-el {
border-left: none;
border-bottom-width: 1px;
border-bottom-style: solid;
border-color: var(--border, $fallback--border);
border-radius: 0;
}
}
}
</style>
......@@ -20,7 +20,7 @@ const mediaUpload = {
return
}
const formData = new FormData()
formData.append('media', file)
formData.append('file', file)
self.$emit('uploading')
self.uploading = true
......
<template>
<basic-user-card :user="user">
<template slot="secondary-area">
<div class="mute-card-content-container">
<button class="btn btn-default" @click="unmuteUser" :disabled="progress" v-if="muted">
<template v-if="progress">
{{ $t('user_card.unmute_progress') }}
......@@ -17,8 +17,18 @@
{{ $t('user_card.mute') }}
</template>
</button>
</template>
</div>
</basic-user-card>
</template>
<script src="./mute_card.js"></script>
\ No newline at end of file
<script src="./mute_card.js"></script>
<style lang="scss">
.mute-card-content-container {
margin-top: 0.5em;
text-align: right;
button {
width: 10em;
}
}
</style>
......@@ -296,6 +296,8 @@ const PostStatusForm = {
},
paste (e) {
if (e.clipboardData.files.length > 0) {
// prevent pasting of file as text
e.preventDefault()
// Strangely, files property gets emptied after event propagation
// Trying to wrap it in array doesn't work. Plus I doubt it's possible
// to hold more than one file in clipboard.
......
......@@ -84,10 +84,10 @@
<div class="media-upload-wrapper" v-for="file in newStatus.files">
<i class="fa button-icon icon-cancel" @click="removeMediaFile(file)"></i>
<div class="media-upload-container attachment">
<img class="thumbnail media-upload" :src="file.image" v-if="type(file) === 'image'"></img>
<video v-if="type(file) === 'video'" :src="file.image" controls></video>
<audio v-if="type(file) === 'audio'" :src="file.image" controls></audio>
<a v-if="type(file) === 'unknown'" :href="file.image">{{file.url}}</a>
<img class="thumbnail media-upload" :src="file.url" v-if="type(file) === 'image'"></img>
<video v-if="type(file) === 'video'" :src="file.url" controls></video>
<audio v-if="type(file) === 'audio'" :src="file.url" controls></audio>
<a v-if="type(file) === 'unknown'" :href="file.url">{{file.url}}</a>
</div>
</div>
</div>
......@@ -287,8 +287,6 @@
img {
width: 24px;
height: 24px;
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
object-fit: contain;
}
......
......@@ -35,6 +35,9 @@ const registration = {
},
computed: {
token () { return this.$route.params.token },
bioPlaceholder () {
return this.$t('registration.bio_placeholder').replace(/\s*\n\s*/g, ' \n')
},
...mapState({
registrationOpen: (state) => state.instance.registrationOpen,
signedIn: (state) => !!state.users.currentUser,
......
......@@ -45,7 +45,7 @@
<div class='form-group'>
<label class='form--label' for='bio'>{{$t('registration.bio')}} ({{$t('general.optional')}})</label>
<textarea :disabled="isPending" v-model='user.bio' class='form-control' id='bio' :placeholder="$t('registration.bio_placeholder')"></textarea>
<textarea :disabled="isPending" v-model='user.bio' class='form-control' id='bio' :placeholder="bioPlaceholder"></textarea>
</div>
<div class='form-group' :class="{ 'form-group--error': $v.user.password.$error }">
......
......@@ -47,6 +47,11 @@ const settings = {
pauseOnUnfocusedLocal: user.pauseOnUnfocused,
hoverPreviewLocal: user.hoverPreview,
hideMutedPostsLocal: typeof user.hideMutedPosts === 'undefined'
? instance.hideMutedPosts
: user.hideMutedPosts,
hideMutedPostsDefault: this.$t('settings.values.' + instance.hideMutedPosts),
collapseMessageWithSubjectLocal: typeof user.collapseMessageWithSubject === 'undefined'
? instance.collapseMessageWithSubject
: user.collapseMessageWithSubject,
......@@ -177,6 +182,9 @@ const settings = {
value = filter(value.split('\n'), (word) => trim(word).length > 0)
this.$store.dispatch('setOption', { name: 'muteWords', value })
},
hideMutedPostsLocal (value) {
this.$store.dispatch('setOption', { name: 'hideMutedPosts', value })
},
collapseMessageWithSubjectLocal (value) {
this.$store.dispatch('setOption', { name: 'collapseMessageWithSubject', value })
},
......
......@@ -36,6 +36,10 @@
<div class="setting-item">
<h2>{{$t('nav.timeline')}}</h2>
<ul class="setting-list">
<li>
<input type="checkbox" id="hideMutedPosts" v-model="hideMutedPostsLocal">
<label for="hideMutedPosts">{{$t('settings.hide_muted_posts')}} {{$t('settings.instance_default', { value: hideMutedPostsDefault })}}</label>
</li>
<li>
<input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
<label for="collapseMessageWithSubject">
......
import UserCard from '../user_card/user_card.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
// TODO: separate touch gesture stuff into their own utils if more components want them
const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY])
import GestureService from '../../services/gesture_service/gesture_service'
const SideDrawer = {
props: [ 'logout' ],
data: () => ({
closed: true,
touchCoord: [0, 0]
closeGesture: undefined
}),
created () {
this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer)
},
components: { UserCard },
computed: {
currentUser () {
......@@ -46,13 +45,10 @@ const SideDrawer = {
this.toggleDrawer()
},
touchStart (e) {
this.touchCoord = touchEventCoord(e)
GestureService.beginSwipe(e, this.closeGesture)
},
touchMove (e) {
const delta = deltaCoord(this.touchCoord, touchEventCoord(e))
if (delta[0] < -30 && Math.abs(delta[1]) < Math.abs(delta[0]) && !this.closed) {
this.toggleDrawer()
}
GestureService.updateSwipe(e, this.closeGesture)
}
}
}
......
......@@ -2,6 +2,7 @@
<div class="side-drawer-container"
:class="{ 'side-drawer-container-closed': closed, 'side-drawer-container-open': !closed }"
>
<div class="side-drawer-darken" :class="{ 'side-drawer-darken-closed': closed}" />
<div class="side-drawer"
:class="{'side-drawer-closed': closed}"
@touchstart="touchStart"
......@@ -111,16 +112,32 @@
height: 100%;
display: flex;
align-items: stretch;
transition-duration: 0s;
transition-property: transform;
}
.side-drawer-container-open {
transform: translate(0%);
}
.side-drawer-container-closed {
transition-delay: 0.35s;
transform: translate(-100%);
}
.side-drawer-darken {
top: 0;
left: 0;
width: 100vw;
height: 100vh;
position: fixed;
z-index: -1;
transition: 0.35s;
transition-property: background-color;
background-color: rgba(0, 0, 0, 0.5);
}
.side-drawer-container-closed {
left: -100%;
.side-drawer-darken-closed {
background-color: rgba(0, 0, 0, 0);
}
......@@ -130,8 +147,9 @@
.side-drawer {
overflow-x: hidden;
transition: 0.35s;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
transition: 0.35s;
transition-property: transform;
margin: 0 0 0 -100px;
padding: 0 0 1em 100px;
width: 80%;
......
......@@ -310,7 +310,6 @@ const Status = {
this.replying = !this.replying
},
gotoOriginal (id) {
// only handled by conversation, not status_or_conversation
if (this.inConversation) {
this.$emit('goto', id)
}
......
......@@ -12,7 +12,7 @@
</div>
</template>
<template v-else>
<div v-if="retweet && !noHeading" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
<div v-if="retweet && !noHeading && !inConversation" :class="[repeaterClass, { highlighted: repeaterStyle }]" :style="[repeaterStyle]" class="media container retweet-info">
<UserAvatar class="media-left" v-if="retweet" :betterShadow="betterShadow" :src="statusoid.user.profile_image_url_original"/>
<div class="media-body faint">
<span class="user-name">
......@@ -24,7 +24,7 @@
</div>
</div>
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet }]" :style="[ userStyle ]" class="media status">
<div :class="[userClass, { highlighted: userStyle, 'is-retweet': retweet && !inConversation }]" :style="[ userStyle ]" class="media status">
<div v-if="!noHeading" class="media-left">
<router-link :to="userProfileLink" @click.stop.prevent.capture.native="toggleUserExpanded">
<UserAvatar :compact="compact" :betterShadow="betterShadow" :src="status.user.profile_image_url_original"/>
......@@ -135,9 +135,8 @@
<div v-if="!noHeading && !isPreview" class='status-actions media-body'>
<div v-if="loggedIn">
<a href="#" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')">
<i class="button-icon icon-reply" :class="{'icon-reply-active': replying}"></i>
</a>
<i class="button-icon icon-reply" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')" :class="{'icon-reply-active': replying}"></i>
<span v-if="status.replies_count > 0">{{status.replies_count}}</span>
</div>
<retweet-button :visibility='status.visibility' :loggedIn='loggedIn' :status='status'></retweet-button>
<favorite-button :loggedIn='loggedIn' :status='status'></favorite-button>
......@@ -551,6 +550,7 @@ $status-margin: 0.75em;
.icon-reply:hover {
color: $fallback--cBlue;
color: var(--cBlue, $fallback--cBlue);
cursor: pointer;
}
.icon-reply.icon-reply-active {
......
import Status from '../status/status.vue'
import Conversation from '../conversation/conversation.vue'
const statusOrConversation = {
props: ['statusoid'],
data () {
return {
expanded: false
}
},
components: {
Status,
Conversation
},
methods: {
toggleExpanded () {
this.expanded = !this.expanded
}
}
}
export default statusOrConversation
<template>
<div>
<conversation v-if="expanded" @toggleExpanded="toggleExpanded" :collapsable="true" :statusoid="statusoid"></conversation>
<status v-if="!expanded" @toggleExpanded="toggleExpanded" :expandable="true" :inConversation="false" :focused="false" :statusoid="statusoid"></status>
</div>
</template>
<script src="./status_or_conversation.js"></script>
<style lang="scss">
.spacer {
height: 1em
}
</style>
import Status from '../status/status.vue'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue'
import Conversation from '../conversation/conversation.vue'
import { throttle } from 'lodash'
const Timeline = {
......@@ -43,7 +43,7 @@ const Timeline = {
},
components: {
Status,
StatusOrConversation
Conversation
},
created () {
const store = this.$store
......