aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/App.scss28
-rw-r--r--src/App.vue14
-rw-r--r--src/components/attachment/attachment.js8
-rw-r--r--src/components/attachment/attachment.vue26
-rw-r--r--src/components/conversation/conversation.vue7
-rw-r--r--src/components/post_status_form/post_status_form.vue1
-rw-r--r--src/components/settings/settings.js4
-rw-r--r--src/components/settings/settings.vue4
-rw-r--r--src/components/status/status.vue33
-rw-r--r--src/components/timeline/timeline.js24
-rw-r--r--src/components/timeline/timeline.vue6
-rw-r--r--src/i18n/messages.js88
-rw-r--r--src/main.js1
-rw-r--r--src/modules/config.js1
-rw-r--r--src/modules/statuses.js27
-rw-r--r--src/modules/users.js4
16 files changed, 229 insertions, 47 deletions
diff --git a/src/App.scss b/src/App.scss
index 0e89a429..5a117f04 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -127,6 +127,15 @@ main-router {
margin: 0.5em;
border-radius: 10px;
+ box-shadow: 1px 1px 3px rgba(0,0,0,.5);
+ overflow: hidden;
+}
+
+.panel-body:empty::before {
+ content: "¯\\_(ツ)_/¯"; // Could use words but it'd require translations
+ display: block;
+ margin: 1em;
+ text-align: center;
}
.panel-heading {
@@ -182,7 +191,7 @@ nav {
flex-shrink: 1;
}
-.sidebar {
+.sidebar-bounds {
flex: 0;
flex-basis: 35%;
}
@@ -210,21 +219,28 @@ nav {
}
@media all and (min-width: 960px) {
- .sidebar {
+ body {
+ overflow-y: scroll;
+ }
+ .sidebar-bounds {
overflow: hidden;
max-height: 100vh;
- width: 350px;
+ width: 345px;
position: fixed;
margin-top: -10px;
- .sidebar-container {
+ .sidebar-scroller {
height: 96vh;
- width: 362px;
+ width: 365px;
padding-top: 10px;
- padding-right: 20px;
+ padding-right: 50px;
overflow-x: hidden;
overflow-y: scroll;
}
+
+ .sidebar {
+ width: 345px;
+ }
}
.sidebar-flexer {
max-height: 96vh;
diff --git a/src/App.vue b/src/App.vue
index 8e802a02..0d004665 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,6 +1,6 @@
<template>
<div id="app" v-bind:style="style" class="base02-background">
- <nav class='container base01-background base04' @click="scrollToTop()">
+ <nav class='container base01-background base04' @click="scrollToTop()" id="nav">
<div class='inner-nav' :style="logoStyle">
<div class='item'>
<router-link :to="{ name: 'root'}">{{sitename}}</router-link>
@@ -18,11 +18,13 @@
<button @click="activatePanel('timeline')" class="base01-background base04">Timeline</button>
</div>
<div class="sidebar-flexer" :class="{ 'mobile-hidden': mobileActivePanel != 'sidebar'}">
- <div class="sidebar" :class="{ 'mobile-hidden': mobileActivePanel != 'sidebar' }">
- <div class="sidebar-container">
- <user-panel></user-panel>
- <nav-panel></nav-panel>
- <notifications v-if="currentUser"></notifications>
+ <div class="sidebar-bounds">
+ <div class="sidebar-scroller">
+ <div class="sidebar">
+ <user-panel></user-panel>
+ <nav-panel></nav-panel>
+ <notifications v-if="currentUser"></notifications>
+ </div>
</div>
</div>
</div>
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index ccf26b79..de551611 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -23,12 +23,8 @@ const Attachment = {
hidden () {
return this.nsfw && this.hideNsfwLocal && !this.showHidden
},
- autoHeight () {
- if (this.type === 'image' && this.nsfw) {
- return {
- 'min-height': '109px'
- }
- }
+ isEmpty () {
+ return this.type === 'html' && !this.attachment.oembed
}
},
methods: {
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index 98cd97fb..954c46b4 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -1,5 +1,5 @@
<template>
- <div class="attachment base03-border" :class="{[type]: true, loading}" :style="autoHeight">
+ <div class="attachment base03-border" :class="{[type]: true, loading}" v-show="!isEmpty">
<a class="image-attachment" v-if="hidden" @click.prevent="toggleHidden()">
<img :key="nsfwImage" :src="nsfwImage"/>
</a>
@@ -39,8 +39,19 @@
margin: 0.5em 0.7em 0.6em 0.0em;
align-self: flex-start;
+ border-style: solid;
+ border-width: 1px;
+ border-radius: 5px;
+ overflow: hidden;
+
+ // fixes small gap below video
+ &.video {
+ line-height: 0;
+ }
+
&.html {
- flex-basis: 100%;
+ flex-basis: 90%;
+ width: 100%;
display: flex;
}
@@ -53,7 +64,6 @@
margin: 10px;
padding: 5px;
background: rgba(230,230,230,0.6);
- border-radius: 5px;
font-weight: bold;
z-index: 4;
}
@@ -61,8 +71,6 @@
video {
max-height: 500px;
height: 100%;
- border: 1px solid;
- border-radius: 5px;
width: 100%;
z-index: 0;
}
@@ -75,14 +83,9 @@
width: 100%;
height: 100%;
flex: 1;
- border: 1px solid;
- border-radius: 5px;
}
.oembed {
- border: 1px solid;
- border-radius: 5px;
- border-color: inherit;
width: 100%;
margin-right: 15px;
display: flex;
@@ -117,9 +120,6 @@
flex: 1;
img {
- border-style: solid;
- border-width: 1px;
- border-radius: 5px;
object-fit: contain;
width: 100%;
height: 100%; /* If this isn't here, chrome will stretch the images */
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index b430d875..3dea4985 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -1,5 +1,5 @@
<template>
- <div class="timeline panel panel-default base00-background">
+ <div class="timeline panel panel-default">
<div class="panel-heading base01-background base04 base03-border conversation-heading">
{{ $t('timeline.conversation') }}
<span v-if="collapsable" style="float:right;">
@@ -27,11 +27,6 @@
<script src="./conversation.js"></script>
<style lang="scss">
- .conversation-heading {
- border-bottom-style: solid;
- border-bottom-width: 1px;
- }
-
.status-preview {
position: absolute;
max-width: 35em;
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 0a744c10..33614637 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -164,6 +164,7 @@
width: 24px;
height: 24px;
border-radius: 2px;
+ object-fit: contain;
}
span {
line-height: 24px;
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index b3bb8290..b88937bb 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -9,6 +9,7 @@ const settings = {
hideNsfwLocal: this.$store.state.config.hideNsfw,
muteWordsString: this.$store.state.config.muteWords.join('\n'),
autoLoadLocal: this.$store.state.config.autoLoad,
+ streamingLocal: this.$store.state.config.streaming,
hoverPreviewLocal: this.$store.state.config.hoverPreview
}
},
@@ -33,6 +34,9 @@ const settings = {
autoLoadLocal (value) {
this.$store.dispatch('setOption', { name: 'autoLoad', value })
},
+ streamingLocal (value) {
+ this.$store.dispatch('setOption', { name: 'streaming', value })
+ },
hoverPreviewLocal (value) {
this.$store.dispatch('setOption', { name: 'hoverPreview', value })
},
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index 5978e4ed..6a311d55 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -33,6 +33,10 @@
<label for="autoLoad">{{$t('settings.autoload')}}</label>
</li>
<li>
+ <input type="checkbox" id="streaming" v-model="streamingLocal">
+ <label for="streaming">{{$t('settings.streaming')}}</label>
+ </li>
+ <li>
<input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
<label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label>
</li>
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index cc315a90..84397bfa 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -14,7 +14,7 @@
</div>
<post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" v-on:posted="toggleReplying" v-if="replying"/>
</div>
- <div class="status-el base00-background base03-border" v-else-if="!status.deleted" v-bind:class="[{ 'base01-background': isFocused }, { 'status-conversation': inConversation }]" >
+ <div class="status-el base00-background base03-border status-fadein" v-else-if="!status.deleted" v-bind:class="[{ 'base01-background': isFocused }, { 'status-conversation': inConversation }]" >
<template v-if="muted">
<div class="media status container muted">
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
@@ -130,6 +130,11 @@
border-left-width: 0px;
line-height: 18px;
+ .timeline & {
+ border-bottom-width: 1px;
+ border-bottom-style: solid;
+ }
+
.notify {
.avatar {
border-width: 3px;
@@ -188,6 +193,7 @@
img, video {
max-width: 100%;
max-height: 400px;
+ object-fit: contain;
}
blockquote {
@@ -226,6 +232,20 @@
}
}
+ .status-fadein {
+ animation-duration: 0.5s;
+ animation-name: fadein;
+ }
+
+ @keyframes fadein {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+
.greentext {
color: green;
}
@@ -279,12 +299,19 @@
.status {
padding: 0.4em 0.7em 0.45em 0.7em;
- border-bottom: 1px solid;
- border-bottom-color: inherit;
border-left: 4px rgba(255, 48, 16, 0.65);
border-left-style: inherit;
}
+ .status-conversation:last-child {
+ border-bottom: none;
+ }
+
+ .timeline .panel.timeline {
+ border-radius: 10px;
+ overflow: hidden;
+ }
+
.muted {
padding: 0.1em 0.4em 0.1em 0.8em;
button {
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 6968bc6f..613b8a34 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -11,6 +11,11 @@ const Timeline = {
'userId',
'tag'
],
+ data () {
+ return {
+ paused: false
+ }
+ },
computed: {
timelineError () { return this.$store.state.statuses.error },
followers () {
@@ -21,6 +26,9 @@ const Timeline = {
},
viewing () {
return this.timeline.viewing
+ },
+ newStatusCount () {
+ return this.timeline.newStatusCount
}
},
components: {
@@ -56,6 +64,7 @@ const Timeline = {
methods: {
showNewStatuses () {
this.$store.commit('showNewStatuses', { timeline: this.timelineName })
+ this.paused = false
},
fetchOlderStatuses () {
const store = this.$store
@@ -90,6 +99,21 @@ const Timeline = {
this.fetchOlderStatuses()
}
}
+ },
+ watch: {
+ newStatusCount (count) {
+ if (!this.$store.state.config.streaming) {
+ return
+ }
+ if (count > 0) {
+ // only 'stream' them when you're scrolled to the top
+ if (window.pageYOffset < 15 && !this.paused) {
+ this.showNewStatuses()
+ } else {
+ this.paused = true
+ }
+ }
+ }
}
}
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 01f330c2..18e27c71 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -14,7 +14,7 @@
{{$t('timeline.up_to_date')}}
</div>
</div>
- <div class="panel-body">
+ <div class="panel-body base02-background">
<div class="timeline">
<status-or-conversation v-for="status in timeline.visibleStatuses" :key="status.id" v-bind:statusoid="status"></status-or-conversation>
<a href="#" v-on:click.prevent='fetchOlderStatuses()' v-if="!timeline.loading">
@@ -30,7 +30,7 @@
{{$t('user_card.followers')}}
</div>
</div>
- <div class="panel-body">
+ <div class="panel-body base02-background">
<div class="timeline">
<user-card v-for="follower in followers" :user="follower" :showFollows="false"></user-card>
</div>
@@ -42,7 +42,7 @@
{{$t('user_card.followees')}}
</div>
</div>
- <div class="panel-body">
+ <div class="panel-body base02-background">
<div class="timeline">
<user-card v-for="friend in friends" :user="friend" :showFollows="true"></user-card>
</div>
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index f4147cd8..8671346d 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -103,6 +103,7 @@ const fi = {
hide_attachments_in_convo: 'Piilota liitteet keskusteluissa',
nsfw_clickthrough: 'Piilota NSFW liitteet klikkauksen taakse.',
autoload: 'Lataa vanhempia viestejä automaattisesti ruudun pohjalla',
+ streaming: 'Näytä uudet viestit automaattisesti ollessasi ruudun huipulla',
reply_link_preview: 'Keskusteluiden vastauslinkkien esikatselu'
},
notifications: {
@@ -186,6 +187,7 @@ const en = {
hide_attachments_in_convo: 'Hide attachments in conversations',
nsfw_clickthrough: 'Enable clickthrough NSFW attachment hiding',
autoload: 'Enable automatic loading when scrolled to the bottom',
+ streaming: 'Enable automatic streaming of new posts when scrolled to the top',
reply_link_preview: 'Enable reply-link preview on mouse hover'
},
notifications: {
@@ -856,6 +858,89 @@ const es = {
}
}
+const pt = {
+ nav: {
+ timeline: 'Linha do tempo',
+ mentions: 'Menções',
+ public_tl: 'Linha do tempo pública',
+ twkn: 'Toda a rede conhecida'
+ },
+ user_card: {
+ follows_you: 'Segue você!',
+ following: 'Seguindo!',
+ follow: 'Seguir',
+ blocked: 'Bloqueado!',
+ block: 'Bloquear',
+ statuses: 'Postagens',
+ mute: 'Mutar',
+ muted: 'Mudo',
+ followers: 'Seguidores',
+ followees: 'Seguindo',
+ per_day: 'por dia'
+ },
+ timeline: {
+ show_new: 'Mostrar novas',
+ error_fetching: 'Erro buscando atualizações',
+ up_to_date: 'Atualizado',
+ load_older: 'Carregar postagens antigas',
+ conversation: 'Conversa'
+ },
+ settings: {
+ user_settings: 'Configurações de Usuário',
+ name_bio: 'Nome & Biografia',
+ name: 'Nome',
+ bio: 'Biografia',
+ avatar: 'Avatar',
+ current_avatar: 'Seu avatar atual',
+ set_new_avatar: 'Mudar avatar',
+ profile_banner: 'Capa de perfil',
+ current_profile_banner: 'Sua capa de perfil atual',
+ set_new_profile_banner: 'Mudar capa de perfil',
+ profile_background: 'Plano de fundo de perfil',
+ set_new_profile_background: 'Mudar o plano de fundo de perfil',
+ settings: 'Configurações',
+ theme: 'Tema',
+ filtering: 'Filtragem',
+ filtering_explanation: 'Todas as postagens contendo estas palavras serão silenciadas, uma por linha.',
+ attachments: 'Anexos',
+ hide_attachments_in_tl: 'Ocultar anexos na linha do tempo.',
+ hide_attachments_in_convo: 'Ocultar anexos em conversas',
+ nsfw_clickthrough: 'Habilitar clique para ocultar anexos NSFW',
+ autoload: 'Habilitar carregamento automático quando a rolagem chegar ao fim.',
+ reply_link_preview: 'Habilitar a pré-visualização de link de respostas ao passar o mouse.'
+ },
+ notifications: {
+ notifications: 'Notificações',
+ read: 'Ler!',
+ followed_you: 'seguiu você'
+ },
+ login: {
+ login: 'Entrar',
+ username: 'Usuário',
+ password: 'Senha',
+ register: 'Registrar',
+ logout: 'Sair'
+ },
+ registration: {
+ registration: 'Registro',
+ fullname: 'Nome para exibição',
+ email: 'Correio eletônico',
+ bio: 'Biografia',
+ password_confirm: 'Confirmação de senha'
+ },
+ post_status: {
+ posting: 'Publicando',
+ default: 'Acabo de aterrizar em L.A.'
+ },
+ finder: {
+ find_user: 'Buscar usuário',
+ error_fetching_user: 'Erro procurando usuário'
+ },
+ general: {
+ submit: 'Enviar'
+ }
+}
+
const messages = {
de,
fi,
@@ -867,7 +952,8 @@ const messages = {
fr,
it,
pl,
- es
+ es,
+ pt
}
export default messages
diff --git a/src/main.js b/src/main.js
index 9e0b9e7c..87a46ee9 100644
--- a/src/main.js
+++ b/src/main.js
@@ -45,6 +45,7 @@ const persistedStateOptions = {
'config.hideNsfw',
'config.autoLoad',
'config.hoverPreview',
+ 'config.streaming',
'config.muteWords',
'users.lastLoginName'
]
diff --git a/src/modules/config.js b/src/modules/config.js
index f7d6e9c8..ac17747e 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -8,6 +8,7 @@ const defaultState = {
hideAttachmentsInConv: false,
hideNsfw: true,
autoLoad: true,
+ streaming: false,
hoverPreview: true,
muteWords: []
}
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 5f2f8152..6d4b4843 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -131,7 +131,7 @@ export const statusType = (status) => {
return 'favorite'
}
- if (status.text.match(/deleted notice {{tag/)) {
+ if (status.text.match(/deleted notice {{tag/) || status.qvitter_delete_notice) {
return 'deletion'
}
@@ -211,8 +211,10 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
sortTimeline(mentions)
}
-
- addNotification({ type: 'mention', status, action: status })
+ // Don't add notification for self-mention
+ if (status.user.id !== user.id) {
+ addNotification({ type: 'mention', status, action: status })
+ }
}
}
@@ -239,6 +241,25 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
// Only add a new notification if we don't have one for the same action
if (!find(state.notifications, (oldNotification) => oldNotification.action.id === action.id)) {
state.notifications.push({type, status, action, seen: false})
+
+ if ('Notification' in window && window.Notification.permission === 'granted') {
+ const title = action.user.name
+ const result = {}
+ result.icon = action.user.profile_image_url
+ result.body = action.text // there's a problem that it doesn't put a space before links tho
+
+ // Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
+ if (action.attachments.length > 0 && !action.nsfw &&
+ action.attachments[0].mimetype.startsWith('image/')) {
+ result.image = action.attachments[0].url
+ }
+
+ let notification = new window.Notification(title, result)
+
+ // Chrome is known for not closing notifications automatically
+ // according to MDN, anyway.
+ setTimeout(notification.close.bind(notification), 5000)
+ }
}
}
diff --git a/src/modules/users.js b/src/modules/users.js
index f29cbf98..c8b6f0de 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -102,6 +102,10 @@ const users = {
store.commit('addNewUsers', mutedUsers)
})
+ if ('Notification' in window && window.Notification.permission === 'default') {
+ window.Notification.requestPermission()
+ }
+
// Fetch our friends
store.rootState.api.backendInteractor.fetchFriends()
.then((friends) => commit('addNewUsers', friends))