diff options
Diffstat (limited to 'src/components')
38 files changed, 1879 insertions, 1239 deletions
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 8a2a3826..d9bc4477 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -1,3 +1,4 @@ +import StillImage from '../still-image/still-image.vue' import nsfwImage from '../../assets/nsfw.png' import fileTypeService from '../../services/file_type/file_type.service.js' @@ -5,7 +6,8 @@ const Attachment = { props: [ 'attachment', 'nsfw', - 'statusId' + 'statusId', + 'size' ], data () { return { @@ -16,6 +18,9 @@ const Attachment = { img: document.createElement('img') } }, + components: { + StillImage + }, computed: { type () { return fileTypeService.fileType(this.attachment.mimetype) @@ -25,6 +30,12 @@ const Attachment = { }, isEmpty () { return (this.type === 'html' && !this.attachment.oembed) || this.type === 'unknown' + }, + isSmall () { + return this.size === 'small' + }, + fullwidth () { + return fileTypeService.fileType(this.attachment.mimetype) === 'html' } }, methods: { diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index d6a51ffd..b2f63668 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -1,5 +1,8 @@ <template> - <div class="attachment base03-border" :class="{[type]: true, loading}" v-show="!isEmpty"> + <div v-if="size==='hide'"> + <a class="placeholder" v-if="type !== 'html'" target="_blank" :href="attachment.url">[{{nsfw ? "NSFW/" : ""}}{{type.toUpperCase()}}]</a> + </div> + <div v-else class="attachment" :class="{[type]: true, loading, 'small-attachment': isSmall, 'fullwidth': fullwidth}" v-show="!isEmpty"> <a class="image-attachment" v-if="hidden" @click.prevent="toggleHidden()"> <img :key="nsfwImage" :src="nsfwImage"/> </a> @@ -8,10 +11,10 @@ </div> <a v-if="type === 'image' && !hidden" class="image-attachment" :href="attachment.url" target="_blank"> - <img class="base03-border" referrerpolicy="no-referrer" :src="attachment.large_thumb_url || attachment.url"/> + <StillImage :class="{'small': isSmall}" referrerpolicy="no-referrer" :mimetype="attachment.mimetype" :src="attachment.large_thumb_url || attachment.url"/> </a> - <video class="base03" v-if="type === 'video' && !hidden" :src="attachment.url" controls loop></video> + <video :class="{'small': isSmall}" v-if="type === 'video' && !hidden" :src="attachment.url" controls loop></video> <audio v-if="type === 'audio'" :src="attachment.url" controls></audio> @@ -30,110 +33,145 @@ <script src="./attachment.js"></script> <style lang="scss"> - .attachments { - display: flex; - flex-wrap: wrap; - margin-right: -0.7em; - - .attachment.media-upload-container { - flex: 0 0 auto; - max-height: 300px; - max-width: 100%; +@import '../../_variables.scss'; + +.attachments { + display: flex; + flex-wrap: wrap; + margin-right: -0.7em; + + .attachment.media-upload-container { + flex: 0 0 auto; + max-height: 300px; + max-width: 100%; + } + + .placeholder { + margin-right: 0.5em; + } + + .small-attachment { + &.image, &.video { + max-width: 35%; + } + max-height: 100px; + } + + .attachment { + flex: 1 0 30%; + margin: 0.5em 0.7em 0.6em 0.0em; + align-self: flex-start; + line-height: 0; + + border-style: solid; + border-width: 1px; + border-radius: $fallback--attachmentRadius; + border-radius: var(--attachmentRadius, $fallback--attachmentRadius); + border-color: $fallback--border; + border-color: var(--border, $fallback--border); + overflow: hidden; + } + .fullwidth { + flex-basis: 100%; + } + // fixes small gap below video + &.video { + line-height: 0; + } + + &.html { + flex-basis: 90%; + width: 100%; + display: flex; + } + + &.loading { + cursor: progress; + } + + .hider { + position: absolute; + margin: 10px; + padding: 5px; + background: rgba(230,230,230,0.6); + font-weight: bold; + z-index: 4; + } + + .small { + max-height: 100px; + } + video { + max-height: 500px; + height: 100%; + width: 100%; + z-index: 0; + } + + audio { + width: 100%; + } + + img.media-upload { + line-height: 0; + max-height: 300px; + max-width: 100%; + } + + .oembed { + line-height: 1.2em; + flex: 1 0 100%; + width: 100%; + margin-right: 15px; + display: flex; + + img { + width: 100%; + } + + .image { + flex: 1; + img { + border: 0px; + border-radius: 5px; + height: 100%; + object-fit: cover; + } + } + + .text { + flex: 2; + margin: 8px; + word-break: break-all; + h1 { + font-size: 14px; + margin: 0px; } - - .attachment { - flex: 1 0 30%; - 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: 90%; - width: 100%; - display: flex; - } - - &.loading { - cursor: progress; - } - - .hider { - position: absolute; - margin: 10px; - padding: 5px; - background: rgba(230,230,230,0.6); - font-weight: bold; - z-index: 4; - } - - video { - max-height: 500px; - height: 100%; - width: 100%; - z-index: 0; - } - - audio { - width: 100%; - } - - img.media-upload { - margin-bottom: -2px; - max-height: 300px; - max-width: 100%; - } - - .oembed { - width: 100%; - margin-right: 15px; - display: flex; - - img { - width: 100%; - } - - .image { - flex: 1; - img { - border: 0px; - border-radius: 5px; - height: 100%; - object-fit: cover; - } - } - - .text { - flex: 2; - margin: 8px; - word-break: break-all; - h1 { - font-size: 14px; - margin: 0px; - } - } - } - - a.image-attachment { - display: flex; - flex: 1; - - img { - object-fit: contain; - width: 100%; - height: 100%; /* If this isn't here, chrome will stretch the images */ - max-height: 500px; - image-orientation: from-image; - } - } + } + } + + .image-attachment { + display: flex; + flex: 1; + + .still-image { + width: 100%; + height: 100%; + } + + .small { + img { + max-height: 100px; } - } + } + + img { + object-fit: contain; + width: 100%; + height: 100%; /* If this isn't here, chrome will stretch the images */ + max-height: 500px; + image-orientation: from-image; + } + } +} </style> diff --git a/src/components/chat_panel/chat_panel.js b/src/components/chat_panel/chat_panel.js index b146c5d9..d528d0a1 100644 --- a/src/components/chat_panel/chat_panel.js +++ b/src/components/chat_panel/chat_panel.js @@ -2,7 +2,8 @@ const chatPanel = { data () { return { currentMessage: '', - channel: null + channel: null, + collapsed: false } }, computed: { @@ -14,6 +15,9 @@ const chatPanel = { submit (message) { this.$store.state.chat.channel.push('new_msg', {text: message}, 10000) this.currentMessage = '' + }, + togglePanel () { + this.collapsed = !this.collapsed } } } diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue index ec379db5..30070d3e 100644 --- a/src/components/chat_panel/chat_panel.vue +++ b/src/components/chat_panel/chat_panel.vue @@ -1,26 +1,40 @@ <template> - <div class="chat-panel"> - <div class="panel panel-default base01-background"> - <div class="panel-heading timeline-heading base02-background base04"> + <div class="chat-panel" v-if="!this.collapsed"> + <div class="panel panel-default"> + <div class="panel-heading timeline-heading chat-heading" @click.stop.prevent="togglePanel"> <div class="title"> {{$t('chat.title')}} + <i class="icon-cancel" style="float: right;"></i> </div> </div> <div class="chat-window" v-chat-scroll> <div class="chat-message" v-for="message in messages" :key="message.id"> <span class="chat-avatar"> <img :src="message.author.avatar" /> - {{message.author.username}}: - </span> - <span class="chat-text"> - {{message.text}} </span> + <div class="chat-content"> + <router-link class="chat-name" :to="{ name: 'user-profile', params: { id: message.author.id } }"> + {{message.author.username}} + </router-link> + <br> + <span class="chat-text"> + {{message.text}} + </span> + </div> </div> </div> <div class="chat-input"> - <form @submit.prevent="submit(currentMessage)"> - <input v-model="currentMessage" type="text" > - </form> + <textarea @keyup.enter="submit(currentMessage)" v-model="currentMessage" class="chat-input-textarea" rows="1"></textarea> + </div> + </div> + </div> + <div v-else class="chat-panel"> + <div class="panel panel-default"> + <div class="panel-heading stub timeline-heading chat-heading" @click.stop.prevent="togglePanel"> + <div class="title"> + <i class="icon-comment-empty"></i> + {{$t('chat.title')}} + </div> </div> </div> </div> @@ -29,30 +43,56 @@ <script src="./chat_panel.js"></script> <style lang="scss"> - .chat-window { - max-height: 200px; - overflow-y: auto; - overflow-x: hidden; - } - .chat-message { - padding: 0.2em 0.5em - } - .chat-avatar { - img { - height: 32px; - width: 32px; - border-radius: 5px; - margin-right: 0.5em; - } - } - .chat-input { - display: flex; - form { - flex: auto; - input { - margin: 0.5em; - width: fill-available; - } - } - } +@import '../../_variables.scss'; + +.floating-chat { + position: fixed; + right: 0px; + bottom: 0px; + z-index: 1000; +} + +.chat-heading { + cursor: pointer; + .icon-comment-empty { + color: $fallback--fg; + color: var(--fg, $fallback--fg); + } +} + +.chat-window { + width: 345px; + max-height: 40vh; + overflow-y: auto; + overflow-x: hidden; +} + +.chat-name { +} + +.chat-message { + display: flex; + padding: 0.2em 0.5em +} + +.chat-avatar { + img { + height: 24px; + width: 24px; + border-radius: $fallback--avatarRadius; + border-radius: var(--avatarRadius, $fallback--avatarRadius); + margin-right: 0.5em; + margin-top: 0.25em; + } +} + +.chat-input { + display: flex; + textarea { + flex: 1; + margin: 0.6em; + min-height: 3.5em; + resize: none; + } +} </style> diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 60d43fff..308e5e7d 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -1,6 +1,6 @@ <template> <div class="timeline panel panel-default"> - <div class="panel-heading base02-background base04 base03-border conversation-heading"> + <div class="panel-heading conversation-heading"> {{ $t('timeline.conversation') }} <span v-if="collapsable" style="float:right;"> <small><a href="#" @click.prevent="$emit('toggleExpanded')">Collapse</a></small> @@ -8,7 +8,16 @@ </div> <div class="panel-body"> <div class="timeline"> - <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> + <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> </div> diff --git a/src/components/delete_button/delete_button.js b/src/components/delete_button/delete_button.js index 77da1b87..f2920666 100644 --- a/src/components/delete_button/delete_button.js +++ b/src/components/delete_button/delete_button.js @@ -10,7 +10,7 @@ const DeleteButton = { }, computed: { currentUser () { return this.$store.state.users.currentUser }, - canDelete () { return this.currentUser.rights.delete_others_notice || this.status.user.id === this.currentUser.id } + canDelete () { return this.currentUser && this.currentUser.rights.delete_others_notice || this.status.user.id === this.currentUser.id } } } diff --git a/src/components/delete_button/delete_button.vue b/src/components/delete_button/delete_button.vue index 845ac777..d13547e2 100644 --- a/src/components/delete_button/delete_button.vue +++ b/src/components/delete_button/delete_button.vue @@ -1,20 +1,21 @@ <template> <div v-if="canDelete"> <a href="#" v-on:click.prevent="deleteStatus()"> - <i class='base09 icon-cancel delete-status'></i> + <i class='icon-cancel delete-status'></i> </a> </div> </template> <script src="./delete_button.js" ></script> -<style lang='scss'> - @import '../../_variables.scss'; +<style lang="scss"> +@import '../../_variables.scss'; - .icon-cancel,.delete-status { - cursor: pointer; - &:hover { - color: $red; - } +.icon-cancel,.delete-status { + cursor: pointer; + &:hover { + color: var(--cRed, $fallback--cRed); + color: $fallback--cRed; } +} </style> diff --git a/src/components/favorite_button/favorite_button.vue b/src/components/favorite_button/favorite_button.vue index 65d368c7..1e1a6970 100644 --- a/src/components/favorite_button/favorite_button.vue +++ b/src/components/favorite_button/favorite_button.vue @@ -1,26 +1,31 @@ <template> <div v-if="loggedIn"> - <i :class='classes' class='favorite-button fav-active base09' @click.prevent='favorite()'/> + <i :class='classes' class='favorite-button fav-active' @click.prevent='favorite()'/> <span v-if='status.fave_num > 0'>{{status.fave_num}}</span> </div> <div v-else> - <i :class='classes' class='favorite-button base09'/> + <i :class='classes' class='favorite-button'/> <span v-if='status.fave_num > 0'>{{status.fave_num}}</span> </div> </template> <script src="./favorite_button.js" ></script> -<style lang='scss'> - .fav-active { - cursor: pointer; - animation-duration: 0.6s; - &:hover { - color: orange; - } - } - .favorite-button.icon-star { - color: orange; +<style lang="scss"> +@import '../../_variables.scss'; + +.fav-active { + cursor: pointer; + animation-duration: 0.6s; + + &:hover { + color: $fallback--cOrange; + color: var(--cOrange, $fallback--cOrange); } +} +.favorite-button.icon-star { + color: $fallback--cOrange; + color: var(--cOrange, $fallback--cOrange); +} </style> diff --git a/src/components/instance_specific_panel/instance_specific_panel.vue b/src/components/instance_specific_panel/instance_specific_panel.vue index b3ea019d..ca8e00c0 100644 --- a/src/components/instance_specific_panel/instance_specific_panel.vue +++ b/src/components/instance_specific_panel/instance_specific_panel.vue @@ -1,6 +1,6 @@ <template> <div class="instance-specific-panel"> - <div class="panel panel-default base01-background"> + <div class="panel panel-default"> <div class="panel-body"> <div v-html="instanceSpecificPanelContent"> </div> diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue index c293d611..67fa95a8 100644 --- a/src/components/login_form/login_form.vue +++ b/src/components/login_form/login_form.vue @@ -1,7 +1,7 @@ <template> - <div class="login panel panel-default base00-background"> + <div class="login panel panel-default"> <!-- Default panel contents --> - <div class="panel-heading base02-background base04"> + <div class="panel-heading"> {{$t('login.login')}} </div> <div class="panel-body"> @@ -17,11 +17,11 @@ <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 base04 base02-background'>{{$t('login.login')}}</button> + <button :disabled="loggingIn" type='submit' class='btn btn-default'>{{$t('login.login')}}</button> </div> </div> <div v-if="authError" class='form-group'> - <div class='error base05'>{{authError}}</div> + <div class='alert error'>{{authError}}</div> </div> </form> </div> @@ -31,27 +31,16 @@ <script src="./login_form.js" ></script> <style lang="scss"> +@import '../../_variables.scss'; .login-form { - input { - border-width: 1px; - border-style: solid; - border-color: silver; - border-radius: 5px; - padding: 0.1em 0.2em 0.2em 0.2em; - } - .btn { min-height: 28px; width: 10em; } .error { - border-radius: 5px; text-align: center; - background-color: rgba(255, 48, 16, 0.65); - min-height: 28px; - line-height: 28px; } .register { @@ -66,5 +55,4 @@ justify-content: space-between; } } - </style> diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue index 9e6ad608..8b931d2d 100644 --- a/src/components/media_upload/media_upload.vue +++ b/src/components/media_upload/media_upload.vue @@ -1,8 +1,8 @@ <template> <div class="media-upload" @drop.prevent @dragover.prevent="fileDrag" @drop="fileDrop"> <label class="btn btn-default"> - <i class="base09 icon-spin4 animate-spin" v-if="uploading"></i> - <i class="base09 icon-upload" v-if="!uploading"></i> + <i class="icon-spin4 animate-spin" v-if="uploading"></i> + <i class="icon-upload" v-if="!uploading"></i> <input type=file style="position: fixed; top: -100em"></input> </label> </div> diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index aea841e9..6f949afb 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -1,24 +1,24 @@ <template> <div class="nav-panel"> - <div class="panel panel-default base01-background"> - <ul class="base03-border"> + <div class="panel panel-default"> + <ul> <li v-if='currentUser'> - <router-link class="base00-background" to='/main/friends'> + <router-link to='/main/friends'> {{ $t("nav.timeline") }} </router-link> </li> <li v-if='currentUser'> - <router-link class="base00-background" :to="{ name: 'mentions', params: { username: currentUser.screen_name } }"> + <router-link :to="{ name: 'mentions', params: { username: currentUser.screen_name } }"> {{ $t("nav.mentions") }} </router-link> </li> <li> - <router-link class="base00-background" to='/main/public'> + <router-link to='/main/public'> {{ $t("nav.public_tl") }} </router-link> </li> <li> - <router-link class="base00-background" to='/main/all'> + <router-link to='/main/all'> {{ $t("nav.twkn") }} </router-link> </li> @@ -30,43 +30,61 @@ <script src="./nav_panel.js" ></script> <style lang="scss"> - .nav-panel ul { - list-style: none; - margin: 0; - padding: 0; - } +@import '../../_variables.scss'; - .nav-panel li { - border-bottom: 1px solid; - border-color: inherit; - padding: 0; - &:first-child a { - border-top-right-radius: 10px; - border-top-left-radius: 10px; - } - &:last-child a { - border-bottom-right-radius: 10px; - border-bottom-left-radius: 10px; - } - } +.nav-panel .panel { + overflow: hidden; +} +.nav-panel ul { + list-style: none; + margin: 0; + padding: 0; +} - .nav-panel li:last-child { - border: none; - } +.nav-panel li { + border-bottom: 1px solid; + border-color: $fallback--border; + border-color: var(--border, $fallback--border); + background-color: $fallback--bg; + background-color: var(--bg, $fallback--bg); + padding: 0; - .nav-panel a { - display: block; - padding: 0.8em 0.85em; - &:hover { - background-color: transparent; - } - &.router-link-active { - font-weight: bolder; - background-color: transparent; - &:hover { - text-decoration: underline; - } - } - } + &:first-child a { + border-top-right-radius: $fallback--panelRadius; + border-top-right-radius: var(--panelRadius, $fallback--panelRadius); + border-top-left-radius: $fallback--panelRadius; + border-top-left-radius: var(--panelRadius, $fallback--panelRadius); + } + &:last-child a { + border-bottom-right-radius: $fallback--panelRadius; + border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius); + border-bottom-left-radius: $fallback--panelRadius; + border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius); + } +} + +.nav-panel li:last-child { + border: none; +} + +.nav-panel a { + display: block; + padding: 0.8em 0.85em; + + &:hover { + background-color: $fallback--lightBg; + background-color: var(--lightBg, $fallback--lightBg); + } + + &.router-link-active { + font-weight: bolder; + background-color: $fallback--lightBg; + background-color: var(--lightBg, $fallback--lightBg); + + &:hover { + text-decoration: underline; + } + } +} </style> diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js new file mode 100644 index 00000000..3a274374 --- /dev/null +++ b/src/components/notification/notification.js @@ -0,0 +1,24 @@ +import Status from '../status/status.vue' +import StillImage from '../still-image/still-image.vue' +import UserCardContent from '../user_card_content/user_card_content.vue' + +const Notification = { + data () { + return { + userExpanded: false + } + }, + props: [ + 'notification' + ], + components: { + Status, StillImage, UserCardContent + }, + methods: { + toggleUserExpanded () { + this.userExpanded = !this.userExpanded + } + } +} + +export default Notification diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue new file mode 100644 index 00000000..eed598a8 --- /dev/null +++ b/src/components/notification/notification.vue @@ -0,0 +1,37 @@ +<template> + <status v-if="notification.type === 'mention'" :compact="true" :statusoid="notification.status"></status> + <div class="non-mention" v-else> + <a class='avatar-container' :href="notification.action.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded"> + <StillImage class='avatar-compact' :src="notification.action.user.profile_image_url_original"/> + </a> + <div class='notification-right'> + <div class="usercard notification-usercard" v-if="userExpanded"> + <user-card-content :user="notification.action.user" :switcher="false"></user-card-content> + </div> + <span class="notification-details"> + <div class="name-and-action"> + <span class="username" :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span> + <span v-if="notification.type === 'favorite'"> + <i class="fa icon-star lit"></i> + <small>{{$t('notifications.favorited_you')}}</small> + </span> + <span v-if="notification.type === 'repeat'"> + <i class="fa icon-retweet lit"></i> + <small>{{$t('notifications.repeated_you')}}</small> + </span> + <span v-if="notification.type === 'follow'"> + <i class="fa icon-user-plus lit"></i> + <small>{{$t('notifications.followed_you')}}</small> + </span> + </div> + <small class="timeago"><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small> + </span> + <div class="follow-text" v-if="notification.type === 'follow'"> + <router-link :to="{ name: 'user-profile', params: { id: notification.action.user.id } }">@{{notification.action.user.screen_name}}</router-link> + </div> + <status v-else class="faint" :compact="true" :statusoid="notification.status" :noHeading="true"></status> + </div> + </div> +</template> + +<script src="./notification.js"></script> diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 5f0d7f26..f8314bfc 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -1,11 +1,11 @@ -import Status from '../status/status.vue' +import Notification from '../notification/notification.vue' import { sortBy, take, filter } from 'lodash' const Notifications = { data () { return { - visibleNotificationCount: 10 + visibleNotificationCount: 20 } }, computed: { @@ -23,15 +23,10 @@ const Notifications = { }, unseenCount () { return this.unseenNotifications.length - }, - hiderStyle () { - return { - background: `linear-gradient(to bottom, rgba(0, 0, 0, 0), ${this.$store.state.config.colors['base00']} 80%)` - } } }, components: { - Status + Notification }, watch: { unseenCount (count) { diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 241f10b4..9cbb1226 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -4,10 +4,24 @@ // a bit of a hack to allow scrolling below notifications padding-bottom: 15em; + .panel { + background: $fallback--bg; + background: var(--bg, $fallback--bg) + } + + .panel-body { + border-color: $fallback--border; + border-color: var(--border, $fallback--border) + } + .panel-heading { // force the text to stay centered, while keeping // the button in the right side of the panel heading position: relative; + background: $fallback--btn; + background: var(--btn, $fallback--btn); + color: $fallback--fg; + color: var(--fg, $fallback--fg); .read-button { position: absolute; right: 0.7em; @@ -18,7 +32,8 @@ .unseen-count { display: inline-block; - background-color: rgba(255, 16, 8, 0.8); + background-color: $fallback--cRed; + background-color: var(--cRed, $fallback--cRed); text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.5); min-width: 1.3em; border-radius: 1.3em; @@ -29,92 +44,167 @@ line-height: 1.3em; } - .notification { - // Will have to use pixels here to ensure consistent distance with - // pad alone and pad + border, browsers bad at rounding this with em, - // they love to give a 1 pixel ghost offset with 0.7em vs 0.3em + 0.4em, - // which does not happen with 10px vs 4px + 6px. - padding: 0.4em 0 0 10px; - display: flex; - border-bottom: 1px solid; - border-bottom-color: inherit; + .unseen { + border-left: 4px solid $fallback--cRed; + border-left: 4px solid var(--cRed, $fallback--cRed); + padding-left: 0; + } +} +.notification { + box-sizing: border-box; + display: flex; + border-bottom: 1px solid; + border-bottom-color: inherit; + padding-left: 4px; + + .avatar-compact { + width: 32px; + height: 32px; + border-radius: $fallback--avatarAltRadius; + border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); + overflow: hidden; + line-height: 0; + + &.animated::before { + display: none; + } + } - .text { - min-width: 0px; - word-wrap: break-word; - line-height:18px; - position: relative; - overflow: hidden; + &:hover .animated.avatar { + canvas { + display: none; + } + img { + visibility: visible; + } + } - .icon-retweet.lit { - color: $green; - } + .notification-usercard { + margin: 0; + } - .icon-user-plus.lit { - color: $blue; + .non-mention { + display: flex; + flex: 1; + flex-wrap: nowrap; + padding: 0.6em; + min-width: 0; + .avatar-container { + width: 32px; + height: 32px; + } + .status-el { + .status { + padding: 0.25em 0; + color: $fallback--faint; + color: var($fallback--faint, --faint); } - - .icon-reply.lit { - color: $blue; + padding: 0; + .media-body { + margin: 0; } + } + } - .icon-star.lit { - color: orange; - } + .follow-text { + padding: 0.5em 0; + } - .status-content { - margin: 0; - max-height: 300px; - } + .status-el { + flex: 1; + } - h1 { - word-break: break-all; - margin: 0 0 0.3em; - padding: 0; - font-size: 1em; - line-height:20px; - small { - font-weight: lighter; - } - } + time { + white-space: nowrap; + } - padding: 0.3em 0.8em 0.5em; - p { - margin: 0; - margin-top: 0; - margin-bottom: 0.3em; - } + .notification-right { + flex: 1; + padding-left: 0.8em; + min-width: 0; + } + + .notification-details { + min-width: 0px; + word-wrap: break-word; + line-height:18px; + position: relative; + overflow: hidden; + width: 100%; + flex: 1 1 0; + display: flex; + flex-wrap: nowrap; + + .name-and-action { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; } - .avatar { - padding-top: 0.3em; - width: 32px; - height: 32px; - border-radius: 50%; + .username { + font-weight: bolder; + max-width: 100%; + text-overflow: ellipsis; + white-space: nowrap; + } + .timeago { + float: right; + font-size: 12px; } - &:last-child { - border-bottom: none; - border-radius: 0 0 10px 10px; + .icon-retweet.lit { + color: $fallback--cGreen; + color: var(--cGreen, $fallback--cGreen); } - } - .notification-content { - max-height: 12em; - overflow-y: hidden; - //text-overflow: ellipsis; - } + .icon-user-plus.lit { + color: $fallback--cBlue; + color: var(--cBlue, $fallback--cBlue); + } - .notification-gradient { - position: absolute; - width: 100%; - height: 4em; - margin-top:8em; + .icon-reply.lit { + color: $fallback--cBlue; + color: var(--cBlue, $fallback--cBlue); + } + + .icon-star.lit { + color: orange; + color: $fallback--cOrange; + color: var(--cOrange, $fallback--cOrange); + } + + .status-content { + margin: 0; + max-height: 300px; + } + + h1 { + word-break: break-all; + margin: 0 0 0.3em; + padding: 0; + font-size: 1em; + line-height:20px; + small { + font-weight: lighter; + } + } + + p { + margin: 0; + margin-top: 0; + margin-bottom: 0.3em; + } } - .unseen { - border-left: 4px solid rgba(255, 16, 8, 0.75); - padding-left: 6px; + // ugly as heck + &:last-child { + border-bottom: none; + border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; + border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + .status-el { + border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; + border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + } } } diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index b341fcef..4fa6e925 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -1,55 +1,14 @@ <template> <div class="notifications"> - <div class="panel panel-default base00-background"> - <div class="panel-heading base02-background base04"> + <div class="panel panel-default"> + <div class="panel-heading"> <span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span> {{$t('notifications.notifications')}} - <button v-if="unseenCount" @click.prevent="markAsSeen" class="base04 base02-background read-button">{{$t('notifications.read')}}</button> + <button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button> </div> - <div class="panel-body base03-border"> - <div v-for="notification in visibleNotifications" :key="notification" class="notification" :class='{"unseen": !notification.seen}'> - <div> - <a :href="notification.action.user.statusnet_profile_url" target="_blank"> - <img class='avatar' :src="notification.action.user.profile_image_url_original"> - </a> - </div> - <div class='text' style="width: 100%;"> - <div v-if="notification.type === 'favorite'"> - <h1> - <span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span> - <i class="fa icon-star lit"></i> - <small><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small> - </h1> - <div class="notification-gradient" :style="hiderStyle"></div> - <div class="notification-content" v-html="notification.status.statusnet_html"></div> - </div> - <div v-if="notification.type === 'repeat'"> - <h1> - <span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span> - <i class="fa icon-retweet lit"></i> - <small><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small> - </h1> - <div class="notification-gradient" :style="hiderStyle"></div> - <div class="notification-content" v-html="notification.status.statusnet_html"></div> - </div> - <div v-if="notification.type === 'mention'"> - <h1> - <span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span> - <i class="fa icon-reply lit"></i> - <small><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small> - </h1> - <status :compact="true" :statusoid="notification.status"></status> - </div> - <div v-if="notification.type === 'follow'"> - <h1> - <span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span> - <i class="fa icon-user-plus lit"></i> - </h1> - <div> - <router-link :to="{ name: 'user-profile', params: { id: notification.action.user.id } }">@{{ notification.action.user.screen_name }}</router-link> {{$t('notifications.followed_you')}} - </div> - </div> - </div> + <div class="panel-body"> + <div v-for="notification in visibleNotifications" :key="notification.action.id" class="notification" :class='{"unseen": !notification.seen}'> + <notification :notification="notification"></notification> </div> </div> </div> diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 999aa732..6c95873c 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -28,6 +28,9 @@ const PostStatusForm = { components: { MediaUpload }, + mounted () { + this.resize(this.$refs.textarea) + }, data () { let statusText = '' @@ -53,7 +56,8 @@ const PostStatusForm = { candidates () { const firstchar = this.textAtCaret.charAt(0) if (firstchar === '@') { - const matchedUsers = filter(this.users, (user) => (String(user.name + user.screen_name)).match(this.textAtCaret.slice(1))) + const matchedUsers = filter(this.users, (user) => (String(user.name + user.screen_name)).toUpperCase() + .match(this.textAtCaret.slice(1).toUpperCase())) if (matchedUsers.length <= 0) { return false } @@ -234,10 +238,14 @@ const PostStatusForm = { e.dataTransfer.dropEffect = 'copy' }, resize (e) { - e.target.style.height = 'auto' - e.target.style.height = `${e.target.scrollHeight - 10}px` - if (e.target.value === '') { - e.target.style.height = '16px' + const target = e.target || e + target.style.height = 'auto' + const heightPx = target.scrollHeight - 10 + if (heightPx > 54) { + target.style.height = `${target.scrollHeight - 10}px` + } + if (target.value === '') { + target.style.height = '16px' } }, clearError () { diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 4871bcae..88627e3a 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -1,21 +1,36 @@ <template> - <div class="post-status-form"> - <form @submit.prevent="postStatus(newStatus)"> - <div class="form-group base03-border" > - <textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" @keydown.down="cycleForward" @keydown.up="cycleBackward" @keydown.shift.tab="cycleBackward" @keydown.tab="cycleForward" @keydown.enter="replaceCandidate" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize" @paste="paste"></textarea> - </div> - <div style="position:relative;" v-if="candidates"> - <div class="autocomplete-panel base05-background"> +<div class="post-status-form"> + <form @submit.prevent="postStatus(newStatus)"> + <div class="form-group" > + <textarea + ref="textarea" + @click="setCaret" + @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" + @keydown.down="cycleForward" + @keydown.up="cycleBackward" + @keydown.shift.tab="cycleBackward" + @keydown.tab="cycleForward" + @keydown.enter="replaceCandidate" + @keydown.meta.enter="postStatus(newStatus)" + @keyup.ctrl.enter="postStatus(newStatus)" + @drop="fileDrop" + @dragover.prevent="fileDrag" + @input="resize" + @paste="paste"> + </textarea> + </div> + <div style="position:relative;" v-if="candidates"> + <div class="autocomplete-panel"> <div v-for="candidate in candidates" @click="replace(candidate.utf || (candidate.screen_name + ' '))"> - <div v-if="candidate.highlighted" class="autocomplete base02"> + <div v-if="candidate.highlighted" class="autocomplete"> <span v-if="candidate.img"><img :src="candidate.img"></span> <span v-else>{{candidate.utf}}</span> - <span>{{candidate.screen_name}}<small class="base02">{{candidate.name}}</small></span> + <span>{{candidate.screen_name}}<small>{{candidate.name}}</small></span> </div> - <div v-else class="autocomplete base04"> + <div v-else class="autocomplete"> <span v-if="candidate.img"><img :src="candidate.img"></img></span> <span v-else>{{candidate.utf}}</span> - <span>{{candidate.screen_name}}<small class="base02">{{candidate.name}}</small></span> + <span>{{candidate.screen_name}}<small>{{candidate.name}}</small></span> </div> </div> </div> @@ -24,18 +39,18 @@ <media-upload @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="enableSubmit" :drop-files="dropFiles"></media-upload> <p v-if="isOverLengthLimit" class="error">{{ charactersLeft }}</p> - <p v-else-if="hasStatusLengthLimit" class="base04">{{ charactersLeft }}</p> + <p class="faint" v-else-if="hasStatusLengthLimit">{{ charactersLeft }}</p> - <button v-if="posting" disabled class="btn btn-default base05 base02-background">{{$t('post_status.posting')}}</button> - <button v-else-if="isOverLengthLimit" disabled class="btn btn-default base05 base02-background">{{$t('general.submit')}}</button> - <button v-else :disabled="submitDisabled" type="submit" class="btn btn-default base05 base02-background">{{$t('general.submit')}}</button> + <button v-if="posting" disabled class="btn btn-default">{{$t('post_status.posting')}}</button> + <button v-else-if="isOverLengthLimit" disabled class="btn btn-default">{{$t('general.submit')}}</button> + <button v-else :disabled="submitDisabled" type="submit" class="btn btn-default">{{$t('general.submit')}}</button> </div> - <div class='error' v-if="error"> + <div class='alert error' v-if="error"> Error: {{ error }} <i class="icon-cancel" @click="clearError"></i> </div> <div class="attachments"> - <div class="media-upload-container attachment base03-border" v-for="file in newStatus.files"> + <div class="media-upload-container attachment" v-for="file in newStatus.files"> <i class="fa icon-cancel" @click="removeMediaFile(file)"></i> <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> @@ -50,147 +65,157 @@ <script src="./post_status_form.js"></script> <style lang="scss"> - - .tribute-container { - ul { - padding: 0px; - li { - display: flex; - align-items: center; - } - } - img { - padding: 3px; - width: 16px; - height: 16px; - border-radius: 50%; - } - } - - .post-status-form, .login { - .form-bottom { - display: flex; - padding: 0.5em; - height: 32px; - - button { - width: 10em; - } - - p { - margin: 0.35em; - padding: 0.35em; - display: flex; - } - } - .error { - border-radius: 5px; - text-align: center; - background-color: rgba(255, 48, 16, 0.65); - padding: 0.25em; - margin: 0.35em; - display: flex; - } - - .attachments { - padding: 0 0.5em; - - .attachment { - position: relative; - margin: 0.5em 0.8em 0.2em 0; - } - - i { - position: absolute; - margin: 10px; - padding: 5px; - background: rgba(230,230,230,0.6); - border-radius: 5px; - font-weight: bold; - } - } - - - .btn { - cursor: pointer; - } - - .btn[disabled] { - cursor: not-allowed; - } - - .icon-cancel { - cursor: pointer; - } - form { - display: flex; - flex-direction: column; - padding: 0.6em; - } - - .form-group { - display: flex; - flex-direction: column; - padding: 0.3em 0.5em 0.6em; - line-height:24px; - } - - form textarea { - border: solid; - border-width: 1px; - border-color: inherit; - border-radius: 5px; - line-height:16px; - padding: 5px; - resize: none; - overflow: hidden; - } - - form textarea:focus { - min-height: 48px; - } - - .btn { - cursor: pointer; - } - - .btn[disabled] { - cursor: not-allowed; - } - - .icon-cancel { - cursor: pointer; - z-index: 4; - } - - .autocomplete-panel { - margin: 0 0.5em 0 0.5em; - border-radius: 5px; - position: absolute; - z-index: 1; - box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5); - min-width: 75%; - } - - .autocomplete { - cursor: pointer; - padding: 0.2em 0.4em 0.2em 0.4em; - border-bottom: 1px solid rgba(0, 0, 0, 0.4); - display: flex; - img { - width: 24px; - height: 24px; - border-radius: 2px; - object-fit: contain; - } - span { - line-height: 24px; - margin: 0 0.1em 0 0.2em; - } - small { - font-style: italic; - } - } - } - +@import '../../_variables.scss'; + +.tribute-container { + ul { + padding: 0px; + li { + display: flex; + align-items: center; + } + } + img { + padding: 3px; + width: 16px; + height: 16px; + border-radius: $fallback--avatarAltRadius; + border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); + } +} + +.post-status-form, .login { + .form-bottom { + display: flex; + padding: 0.5em; + height: 32px; + + button { + width: 10em; + } + + p { + margin: 0.35em; + padding: 0.35em; + display: flex; + } + } + + .error { + text-align: center; + } + + .attachments { + padding: 0 0.5em; + + .attachment { + position: relative; + border: 1px solid $fallback--border; + border: 1px solid var(--border, $fallback--border); + margin: 0.5em 0.8em 0.2em 0; + } + + i { + position: absolute; + margin: 10px; + padding: 5px; + background: rgba(230,230,230,0.6); + border-radius: $fallback--attachmentRadius; + border-radius: var(--attachmentRadius, $fallback--attachmentRadius); + font-weight: bold; + } + } + + + .btn { + cursor: pointer; + } + + .btn[disabled] { + cursor: not-allowed; + } + + .icon-cancel { + cursor: pointer; + } + + form { + display: flex; + flex-direction: column; + padding: 0.6em; + } + + .form-group { + display: flex; + flex-direction: column; + padding: 0.3em 0.5em 0.6em; + line-height:24px; + } + + form textarea { + line-height:16px; + resize: none; + overflow: hidden; + transition: min-height 200ms 100ms; + min-height: 1px; + box-sizing: content-box; + } + + form textarea:focus { + min-height: 48px; + } + + .btn { + cursor: pointer; + } + + .btn[disabled] { + cursor: not-allowed; + } + + .icon-cancel { + cursor: pointer; + z-index: 4; + } + + .autocomplete-panel { + margin: 0 0.5em 0 0.5em; + border-radius: $fallback--tooltipRadius; + border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + position: absolute; + z-index: 1; + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5); + min-width: 75%; + background: $fallback--btn; + background: var(--btn, $fallback--btn); + color: $fallback--lightFg; + color: var(--lightFg, $fallback--lightFg); + } + + .autocomplete { + cursor: pointer; + padding: 0.2em 0.4em 0.2em 0.4em; + border-bottom: 1px solid rgba(0, 0, 0, 0.4); + display: flex; + + img { + width: 24px; + height: 24px; + border-radius: $fallback--avatarRadius; + border-radius: var(--avatarRadius, $fallback--avatarRadius); + object-fit: contain; + } + + span { + line-height: 24px; + margin: 0 0.1em 0 0.2em; + } + + small { + margin-left: .5em; + color: $fallback--faint; + color: var(--faint, $fallback--faint); + } + } +} </style> diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index adc751a3..00f665af 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -1,6 +1,6 @@ <template> - <div class="settings panel panel-default base00-background"> - <div class="panel-heading base02-background base04"> + <div class="settings panel panel-default"> + <div class="panel-heading"> {{$t('registration.registration')}} </div> <div class="panel-body"> @@ -39,14 +39,14 @@ </div> --> <div class='form-group'> - <button :disabled="registering" type='submit' class='btn btn-default base05 base02-background'>{{$t('general.submit')}}</button> + <button :disabled="registering" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button> </div> </div> <div class='terms-of-service' v-html="termsofservice"> </div> </div> <div v-if="error" class='form-group'> - <div class='error base05'>{{error}}</div> + <div class='alert error'>{{error}}</div> </div> </form> </div> @@ -55,6 +55,7 @@ <script src="./registration.js"></script> <style lang="scss"> +@import '../../_variables.scss'; .registration-form { display: flex; @@ -87,23 +88,10 @@ } form textarea { - border: solid; - border-width: 1px; - border-color: silver; - border-radius: 5px; line-height:16px; - padding: 5px; resize: vertical; } - input { - border-width: 1px; - border-style: solid; - border-color: silver; - border-radius: 5px; - padding: 0.1em 0.2em 0.2em 0.2em; - } - .captcha { max-width: 350px; margin-bottom: 0.4em; @@ -117,12 +105,7 @@ } .error { - border-radius: 5px; text-align: center; - margin: 0.5em 0.6em 0; - background-color: rgba(255, 48, 16, 0.65); - min-height: 28px; - line-height: 28px; } } diff --git a/src/components/retweet_button/retweet_button.vue b/src/components/retweet_button/retweet_button.vue index 7a7ea763..1bee3d08 100644 --- a/src/components/retweet_button/retweet_button.vue +++ b/src/components/retweet_button/retweet_button.vue @@ -1,26 +1,28 @@ <template> <div v-if="loggedIn"> - <i :class='classes' class='icon-retweet rt-active base09' v-on:click.prevent='retweet()'></i> + <i :class='classes' class='icon-retweet rt-active' v-on:click.prevent='retweet()'></i> <span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span> </div> <div v-else> - <i :class='classes' class='icon-retweet base09'></i> + <i :class='classes' class='icon-retweet'></i> <span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span> </div> </template> <script src="./retweet_button.js" ></script> -<style lang='scss'> - @import '../../_variables.scss'; - .rt-active { - cursor: pointer; - animation-duration: 0.6s; - &:hover { - color: $green; - } - } - .icon-retweet.retweeted { - color: $green; +<style lang="scss"> +@import '../../_variables.scss'; +.rt-active { + cursor: pointer; + animation-duration: 0.6s; + &:hover { + color: $fallback--cGreen; + color: var(--cGreen, $fallback--cGreen); } +} +.icon-retweet.retweeted { + color: $fallback--cGreen; + color: var(--cGreen, $fallback--cGreen); +} </style> diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index b88937bb..a26111d6 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -10,7 +10,8 @@ const settings = { 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 + hoverPreviewLocal: this.$store.state.config.hoverPreview, + stopGifs: this.$store.state.config.stopGifs } }, components: { @@ -43,6 +44,9 @@ const settings = { muteWordsString (value) { value = filter(value.split('\n'), (word) => trim(word).length > 0) this.$store.dispatch('setOption', { name: 'muteWords', value }) + }, + stopGifs (value) { + this.$store.dispatch('setOption', { name: 'stopGifs', value }) } } } diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index 8fdd09de..b4514ba1 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -1,6 +1,6 @@ <template> - <div class="settings panel panel-default base00-background"> - <div class="panel-heading base02-background base04"> + <div class="settings panel panel-default"> + <div class="panel-heading"> {{$t('settings.settings')}} </div> <div class="panel-body"> @@ -29,8 +29,8 @@ <label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label> </li> <li> - <input type="checkbox" id="autoLoad" v-model="autoLoadLocal"> - <label for="autoLoad">{{$t('settings.autoload')}}</label> + <input type="checkbox" id="autoload" v-model="autoLoadLocal"> + <label for="autoload">{{$t('settings.autoload')}}</label> </li> <li> <input type="checkbox" id="streaming" v-model="streamingLocal"> @@ -40,6 +40,10 @@ <input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal"> <label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label> </li> + <li> + <input type="checkbox" id="stopGifs" v-model="stopGifs"> + <label for="stopGifs">{{$t('settings.stop_gifs')}}</label> + </li> </ul> </div> </div> @@ -50,32 +54,40 @@ </script> <style lang="scss"> - .setting-item { - margin: 1em 1em 1.4em; - textarea { - width: 100%; - height: 100px; - } +@import '../../_variables.scss'; + +.setting-item { + margin: 1em 1em 1.4em; + + textarea { + width: 100%; + height: 100px; + } - .old-avatar { - width: 128px; - border-radius: 5px; - } + .old-avatar { + width: 128px; + border-radius: $fallback--avatarRadius; + border-radius: var(--avatarRadius, $fallback--avatarRadius); + } - .new-avatar { - object-fit: cover; - width: 128px; - height: 128px; - border-radius: 5px; - } + .new-avatar { + object-fit: cover; + width: 128px; + height: 128px; + border-radius: $fallback--avatarRadius; + border-radius: var(--avatarRadius, $fallback--avatarRadius); + } - .btn { - margin-top: 1em; - min-height: 28px; - width: 10em; - } - } - .setting-list { - list-style-type: none; - } + .btn { + margin-top: 1em; + min-height: 28px; + width: 10em; + } +} +.setting-list { + list-style-type: none; + li { + margin-bottom: 0.5em; + } +} </style> diff --git a/src/components/status/status.js b/src/components/status/status.js index 12f3bb25..73f4a7aa 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -4,9 +4,11 @@ import RetweetButton from '../retweet_button/retweet_button.vue' import DeleteButton from '../delete_button/delete_button.vue' import PostStatusForm from '../post_status_form/post_status_form.vue' import UserCardContent from '../user_card_content/user_card_content.vue' +import StillImage from '../still-image/still-image.vue' import { filter, find } from 'lodash' const Status = { + name: 'Status', props: [ 'statusoid', 'expandable', @@ -14,7 +16,10 @@ const Status = { 'focused', 'highlight', 'compact', - 'replies' + 'replies', + 'noReplyLinks', + 'noHeading', + 'inlineExpanded' ], data: () => ({ replying: false, @@ -22,7 +27,8 @@ const Status = { unmuted: false, userExpanded: false, preview: null, - showPreview: false + showPreview: false, + showingTall: false }), computed: { muteWords () { @@ -54,11 +60,6 @@ const Status = { }, muted () { return !this.unmuted && (this.status.user.muted || this.muteWordHits.length > 0) }, isReply () { return !!this.status.in_reply_to_status_id }, - borderColor () { - return { - borderBottomColor: this.$store.state.config.colors['base02'] - } - }, isFocused () { // retweet or root of an expanded conversation if (this.focused) { @@ -68,6 +69,29 @@ const Status = { } // use conversation highlight only when in conversation return this.status.id === this.highlight + }, + // This is a bit hacky, but we want to approximate post height before rendering + // so we count newlines (masto uses <p> for paragraphs, GS uses <br> between them) + // as well as approximate line count by counting characters and approximating ~80 + // per line. + // + // Using max-height + overflow: auto for status components resulted in false positives + // very often with japanese characters, and it was very annoying. + hideTallStatus () { + if (this.showingTall) { + return false + } + const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80 + return lengthScore > 20 + }, + attachmentSize () { + if ((this.$store.state.config.hideAttachments && !this.inConversation) || + (this.$store.state.config.hideAttachmentsInConv && this.inConversation)) { + return 'hide' + } else if (this.compact) { + return 'small' + } + return 'normal' } }, components: { @@ -76,7 +100,8 @@ const Status = { RetweetButton, DeleteButton, PostStatusForm, - UserCardContent + UserCardContent, + StillImage }, methods: { linkClicked ({target}) { @@ -105,6 +130,9 @@ const Status = { toggleUserExpanded () { this.userExpanded = !this.userExpanded }, + toggleShowTall () { + this.showingTall = !this.showingTall + }, replyEnter (id, event) { this.showPreview = true const targetId = Number(id) diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 28272b0b..f1163fd9 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -1,123 +1,99 @@ <template> - <div class="status-el base00-background" v-if="compact"> - <div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div> - <div v-if="loggedIn"> - <div class='status-actions'> - <div> - <a href="#" v-on:click.prevent="toggleReplying"> - <i class="base09 icon-reply" :class="{'icon-reply-active': replying}"></i> - </a> - </div> - <retweet-button :loggedIn="loggedIn" :status=status></retweet-button> - <favorite-button :loggedIn="loggedIn" :status=status></favorite-button> - </div> - </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 status-fadein" v-else-if="!status.deleted" v-bind:class="[{ 'base01-background': isFocused }, { 'status-conversation': inConversation }]" > - <template v-if="muted"> + <div class="status-el" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]"> + <template v-if="muted && !noReplyLinks"> <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> <small class="muteWords">{{muteWordHits.join(', ')}}</small> - <a href="#" class="unmute" @click.prevent="toggleMute"><i class="base09 icon-eye-off"></i></a> + <a href="#" class="unmute" @click.prevent="toggleMute"><i class="icon-eye-off"></i></a> </div> </template> - <template v-if="!muted"> - <div v-if="retweet" class="media container retweet-info"> - <div class="media-left"> + <template v-else> + <div v-if="retweet && !noHeading" class="media container retweet-info"> + <StillImage v-if="retweet" class='avatar' :src="statusoid.user.profile_image_url_original"/> + <div class="media-body faint"> + <a :href="statusoid.user.statusnet_profile_url" style="font-weight: bold;" :title="'@'+statusoid.user.screen_name">{{retweeter}}</a> <i class='fa icon-retweet retweeted'></i> - </div> - <div class="media-body"> - Repeated by <a :href="statusoid.user.statusnet_profile_url" style="font-weight: bold;" :title="'@'+statusoid.user.screen_name">{{retweeter}}</a> + {{$t('timeline.repeated')}} </div> </div> - <div class="media status container"> - <div class="media-left"> - <a :href="status.user.statusnet_profile_url"> - <img @click.prevent="toggleUserExpanded" :class="{retweeted: retweet}" class='avatar' :src="status.user.profile_image_url_original"> - <img v-if="retweet" class='avatar-retweeter' :src="statusoid.user.profile_image_url_original"></img> + + <div class="media status"> + <div v-if="!noHeading" class="media-left"> + <a :href="status.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded"> + <StillImage class='avatar' :class="{'avatar-compact': compact}" :src="status.user.profile_image_url_original"/> </a> </div> - <div class="media-body"> - <div class="base03-border usercard" v-if="userExpanded"> + <div class="status-body"> + <div class="usercard media-body" v-if="userExpanded"> <user-card-content :user="status.user" :switcher="false"></user-card-content> </div> - <div class="user-content"> - <div class="media-heading"> + <div v-if="!noHeading" class="media-body container media-heading"> + <div class="media-heading-left"> <div class="name-and-links"> <h4 class="user-name">{{status.user.name}}</h4> - <div class="links"> - <h4> - <small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small> - <small v-if="status.in_reply_to_screen_name"> > + <span class="links"> + <router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link> + <span v-if="status.in_reply_to_screen_name" class="faint reply-info"> + <i class="icon-right-open"></i> <router-link :to="{ name: 'user-profile', params: { id: status.in_reply_to_user_id } }"> {{status.in_reply_to_screen_name}} </router-link> - </small> - <template v-if="isReply"> - <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> - </small> - </template> - - - <small> - <router-link :to="{ name: 'conversation', params: { id: status.id } }"> - <timeago :since="status.created_at" :auto-update="60"></timeago> - </router-link> - </small> - </h4> - </div> - <h4 class="replies" v-if="inConversation"> - <small v-if="replies.length">Replies:</small> - <small v-for="reply in replies"> - <a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}} </a> - </small> - </h4> - </div> - <div class="heading-icons"> - <a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="base09 icon-eye-off"></i></a> - <a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="base09 icon-binoculars"></i></a> - <template v-if="expandable"> - <a href="#" @click.prevent="toggleExpanded" class="expand"><i class="base09 icon-plus-squared"></i></a> - </template> + </span> + <a v-if="isReply && !noReplyLinks" 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> + </span> </div> + <h4 class="replies" v-if="inConversation && !noReplyLinks"> + <small v-if="replies.length">Replies:</small> + <small class="reply-link" v-for="reply in replies"> + <a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}} </a> + </small> + </h4> </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 class="media-heading-right"> + <router-link class="timeago" :to="{ name: 'conversation', params: { id: status.id } }"> + <timeago :since="status.created_at" :auto-update="60"></timeago> + </router-link> + <a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="icon-link-ext"></i></a> + <template v-if="expandable"> + <a href="#" @click.prevent="toggleExpanded"><i class="icon-plus-squared"></i></a> + </template> + <a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="icon-eye-off"></i></a> </div> - <div class="status-preview status-preview-loading base00-background base03-border" v-else-if="showPreview"> - <i class="base09 icon-spin4 animate-spin"></i> + </div> + + <div v-if="showPreview" class="status-preview-container"> + <status class="status-preview" v-if="preview" :noReplyLinks="true" :statusoid="preview" :compact=true></status> + <div class="status-preview status-preview-loading" v-else> + <i class="icon-spin4 animate-spin"></i> </div> + </div> - <div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div> + <div :class="{'tall-status': hideTallStatus}" class="status-content-wrapper"> + <a class="tall-status-hider" :class="{ 'tall-status-hider_focused': isFocused }" v-if="hideTallStatus" href="#" @click.prevent="toggleShowTall">Show more</a> + <div @click.prevent="linkClicked" class="status-content media-body" v-html="status.statusnet_html"></div> + <a v-if="showingTall" href="#" class="tall-status-unhider" @click.prevent="toggleShowTall">Show less</a> + </div> - <div v-if='status.attachments' class='attachments'> - <attachment v-if="!hideAttachments" :status-id="status.id" :nsfw="status.nsfw" :attachment="attachment" v-for="attachment in status.attachments" :key="attachment.id"> - </attachment> - </div> + <div v-if='status.attachments' class='attachments media-body'> + <attachment :size="attachmentSize" :status-id="status.id" :nsfw="status.nsfw" :attachment="attachment" v-for="attachment in status.attachments" :key="attachment.id"> + </attachment> </div> - <div class='status-actions'> + <div v-if="!noHeading && !noReplyLinks" class='status-actions media-body'> <div v-if="loggedIn"> <a href="#" v-on:click.prevent="toggleReplying"> - <i class="base09 icon-reply" :class="{'icon-reply-active': replying}"></i> + <i class="icon-reply" :class="{'icon-reply-active': replying}"></i> </a> </div> - <retweet-button :loggedIn="loggedIn" :status=status></retweet-button> - <favorite-button :loggedIn="loggedIn" :status=status></favorite-button> - <delete-button :status=status></delete-button> + <retweet-button :loggedIn='loggedIn' :status='status'></retweet-button> + <favorite-button :loggedIn='loggedIn' :status='status'></favorite-button> + <delete-button :status='status'></delete-button> </div> </div> </div> - <div class="status base00-background container" v-if="replying"> + <div class="container" v-if="replying"> <div class="reply-left"/> <post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" v-on:posted="toggleReplying"/> </div> @@ -126,300 +102,387 @@ </template> <script src="./status.js" ></script> - <style lang="scss"> - @import '../../_variables.scss'; +@import '../../_variables.scss'; + +.status-body { + flex: 1; + min-width: 0; +} + +.status-preview.status-el { + border-style: solid; + border-width: 1px; + border-color: $fallback--border; + border-color: var(--border, $fallback--border); +} + +.status-preview-container { + position: relative; + max-width: 100%; +} + +.status-preview { + position: absolute; + max-width: 95%; + display: flex; + background-color: $fallback--bg; + background-color: var(--bg, $fallback--bg); + border-color: $fallback--border; + border-color: var(--border, $fallback--border); + border-style: solid; + border-width: 1px; + border-radius: $fallback--tooltipRadius; + border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5); + margin-top: 0.25em; + margin-left: 0.5em; + z-index: 50; + .status { + flex: 1; + border: 0; + min-width: 15em; + } +} + +.status-preview-loading { + display: block; + min-width: 15em; + padding: 1em; + text-align: center; + border-width: 1px; + border-style: solid; + i { + font-size: 2em; + } +} + +.status-el { + hyphens: auto; + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; + border-left-width: 0px; + line-height: 18px; + min-width: 0; + background-color: $fallback--bg; + background-color: var(--bg, $fallback--bg); + border-color: $fallback--border; + border-color: var(--border, $fallback--border); + + border-left: 4px $fallback--cRed; + border-left: 4px var(--cRed, $fallback--cRed); + + &_focused { + background-color: $fallback--lightBg; + background-color: var(--lightBg, $fallback--lightBg); + } - status-text-container { - display: block; + .timeline & { + border-bottom-width: 1px; + border-bottom-style: solid; } - .status-preview { - position: absolute; - max-width: 34em; - padding: 0.5em; + .media-body { + flex: 1; + padding: 0; + margin: 0 0 0.25em 0.8em; + } + + .media-heading { + flex-wrap: nowrap; + } + + .media-heading-left { + padding: 0; + vertical-align: bottom; + flex-basis: 100%; + + small { + font-weight: lighter; + } + h4 { + white-space: nowrap; + font-size: 14px; + margin-right: 0.25em; + overflow: hidden; + text-overflow: ellipsis; + } + .name-and-links { + padding: 0; + flex: 1 0; + display: flex; + flex-wrap: wrap; + align-content: center; + } + .links { + display: flex; + padding-top: 1px; + margin-left: 0.2em; + font-size: 12px; + color: $fallback--link; + color: var(--link, $fallback--link); + max-width: 100%; + a { + max-width: 100%; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + } + .reply-info { + display: flex; + } + .replies { + line-height: 16px; + } + .reply-link { + margin-right: 0.2em; + } + } + + .media-heading-right { + flex-shrink: 0; 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; + flex-wrap: nowrap; + max-height: 1.5em; + margin-left: 0.25em; + .timeago { + margin-right: 0.2em; + font-size: 12px; + padding-top: 1px; + } + i { + margin-left: 0.2em; + } + } + a { + display: inline-block; + word-break: break-all; + } + + .tall-status { + position: relative; + height: 220px; + overflow-x: hidden; + overflow-y: hidden; + } + + .tall-status-hider { + position: absolute; + height: 70px; + margin-top: 150px; + width: 100%; + text-align: center; + line-height: 110px; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%); + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%); + &_focused { + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--lightBg 80%); + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--lightBg, $fallback--lightBg) 80%); + } + } + + .tall-status-unhider { + width: 100%; + text-align: center; + } + + .status-content { + margin-right: 0.5em; + img, video { + max-width: 100%; + max-height: 400px; + vertical-align: middle; + object-fit: contain; + } + + blockquote { + margin: 0.2em 0 0.2em 2em; + font-style: italic; + } + + p { + margin: 0; + margin-top: 0.2em; + margin-bottom: 0.5em; + } + } + + .retweet-info { + padding: 0.4em 0.6em 0 0.6em; + margin: 0 0 -0.5em 0; .avatar { - flex-shrink: 0; - width: 32px; - height: 32px; - border-radius: 50%; + border-radius: $fallback--avatarAltRadius; + border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); + margin-left: 28px; + width: 20px; + height: 20px; } - .text { - h4 { - margin-bottom: 0.4em; - small { - font-weight: lighter; - } + + .media-body { + font-size: 1em; + line-height: 22px; + + display: flex; + align-content: center; + flex-wrap: wrap; + i { + padding: 0 0.2em; + } + a { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } - padding: 0 0.5em 0.5em 0.5em; } } +} - .status-preview-loading { - display: block; - font-size: 2em; - min-width: 8em; - text-align: center; +.status-fadein { + animation-duration: 0.4s; + animation-name: fadein; +} + +@keyframes fadein { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.greentext { + color: green; +} + +.status-conversation { + border-left-style: solid; +} + +.status-actions { + width: 100%; + display: flex; + + div, favorite-button { + padding-top: 0.25em; + max-width: 6em; + flex: 1; + } +} + +.icon-reply:hover { + color: $fallback--cBlue; + color: var(--cBlue, $fallback--cBlue); +} + +.icon-reply.icon-reply-active { + color: $fallback--cBlue; + color: var(--cBlue, $fallback--cBlue); +} + +.status .avatar-compact { + width: 32px; + height: 32px; + border-radius: $fallback--avatarAltRadius; + border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); +} + +.avatar { + width: 48px; + height: 48px; + border-radius: $fallback--avatarRadius; + border-radius: var(--avatarRadius, $fallback--avatarRadius); + overflow: hidden; + position: relative; + + img { + width: 100%; + height: 100%; + } + + &.animated::before { + display: none; + } + + &.retweeted { + } +} + +.status:hover .animated.avatar { + canvas { + display: none; + } + img { + visibility: visible; + } +} + +.status { + display: flex; + padding: 0.6em; +} + +.status-conversation:last-child { + border-bottom: none; +} + +.muted { + padding: 0.25em 0.5em; + button { + margin-left: auto; } - .status-el { - hyphens: auto; - overflow-wrap: break-word; - word-wrap: break-word; - word-break: break-word; - border-left-width: 0px; - line-height: 18px; - - .timeline & { - border-bottom-width: 1px; - border-bottom-style: solid; - } - - .notify { - .avatar { - border-width: 3px; - border-style: solid; - } - } - - .media-body { - flex: 1; - padding-left: 0.5em; - } - - - .user-content { - - min-height: 52px; - padding-top: 1px; - } - - .media-heading { - display: flex; - min-height: 1.4em; - margin-bottom: 0.3em; - - small { - font-weight: lighter; - } - h4 { - margin-right: 0.4em; - } - .name-and-links { - flex: 1 0; - display: flex; - flex-wrap: wrap; - } - .replies { - flex-basis: 100%; - } - } - - .source_url { - - } - - .expand { - margin-right: -0.3em; - } - - a { - display: inline-block; - word-break: break-all; - } - - .status-content { - margin: 3px 15px 4px 0; - max-height: 400px; - overflow-y: auto; - overflow-x: hidden; - - img, video { - max-width: 100%; - max-height: 400px; - vertical-align: middle; - object-fit: contain; - } - - blockquote { - margin: 0.2em 0 0.2em 2em; - font-style: italic; - } - } - - p { - margin: 0; - margin-top: 0.2em; - margin-bottom: 0.5em; - } - - .media-left { - margin: 0.2em 0.3em 0 0; - img { - float: right; - border-radius: 5px; - } - } - - .retweet-info { - padding: 0.7em 0 0 0.6em; - - .media-left { - display: flex; - - i { - align-self: center; - text-align: right; - flex: 1; - padding-right: 0.3em; - } - } - } - } - - .status-fadein { - animation-duration: 0.5s; - animation-name: fadein; - } - - @keyframes fadein { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - - .greentext { - color: green; - } - - .status-conversation { - border-left-style: solid; - } - - .status-actions { - padding-top: 0.15em; - width: 100%; - display: flex; - - div, favorite-button { - max-width: 6em; - flex: 1; - } - } - - .icon-reply:hover { - color: $blue; - } - - .icon-reply.icon-reply-active { - color: $blue; - } - - .status .avatar { - width: 48px; - height: 48px; - - &.retweeted { - width: 40px; - height: 40px; - margin-right: 8px; - margin-bottom: 8px; - } - } - - .status img.avatar-retweeter { - width: 24px; - height: 24px; - position: absolute; - margin-left: 24px; - margin-top: 24px; - } - - .status.compact .avatar { - width: 32px; - } - - .status { - padding: 0.4em 0.7em 0.45em 0.7em; - 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 { - margin-left: auto; - } - - .muteWords { - margin-left: 10px; - } - } - - a.unmute { - display: block; - margin-left: auto; - } - - .reply-left { - flex: 0; - min-width: 48px; - } - - .reply-body { - flex: 1; - } - - @media all and (max-width: 960px) { - .status-el { - .name-and-links { - margin-left: -0.25em; - } - } - .status { - max-width: 100%; - } - - .status .avatar { - width: 40px; - height: 40px; - - &.retweeted { - width: 34px; - height: 34px; - margin-right: 8px; - margin-bottom: 8px; - } - } - - .status img.avatar-retweeter { - width: 22px; - height: 22px; - position: absolute; - margin-left: 18px; - margin-top: 18px; - } - } + .muteWords { + margin-left: 10px; + } +} + +a.unmute { + display: block; + margin-left: auto; +} + +.reply-left { + flex: 0; + min-width: 48px; +} + +.reply-body { + flex: 1; +} + +.timeline > { + .status-el:last-child { + border-bottom-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;; + border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + } +} + +@media all and (max-width: 960px) { + .status-el { + .retweet-info { + .avatar { + margin-left: 20px; + } + } + } + .status { + max-width: 100%; + } + + .status .avatar { + width: 40px; + height: 40px; + } + + .status .avatar-compact { + width: 32px; + height: 32px; + } +} </style> diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js new file mode 100644 index 00000000..0839aca5 --- /dev/null +++ b/src/components/still-image/still-image.js @@ -0,0 +1,26 @@ +const StillImage = { + props: [ + 'src', + 'referrerpolicy', + 'mimetype' + ], + data () { + return { + stopGifs: this.$store.state.config.stopGifs + } + }, + computed: { + animated () { + return this.stopGifs && (this.mimetype === 'image/gif' || this.src.endsWith('.gif')) + } + }, + methods: { + onLoad () { + const canvas = this.$refs.canvas + if (!canvas) return + canvas.getContext('2d').drawImage(this.$refs.src, 1, 1, canvas.width, canvas.height) + } + } +} + +export default StillImage diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue new file mode 100644 index 00000000..a37c678d --- /dev/null +++ b/src/components/still-image/still-image.vue @@ -0,0 +1,65 @@ +<template> + <div class='still-image' :class='{ animated: animated }' > + <canvas ref="canvas" v-if="animated"></canvas> + <img ref="src" :src="src" :referrerpolicy="referrerpolicy" v-on:load="onLoad"/> + </div> +</template> + +<script src="./still-image.js"></script> + +<style lang="scss"> +@import '../../_variables.scss'; +.still-image { + position: relative; + line-height: 0; + overflow: hidden; + width: 100%; + height: 100%; + + &:hover canvas { + display: none; + } + + img { + width: 100%; + height: 100%; + } + + &.animated { + &:hover::before, + img { + visibility: hidden; + } + + &:hover img { + visibility: visible + } + + &::before { + content: 'gif'; + position: absolute; + line-height: 10px; + font-size: 10px; + top: 5px; + left: 5px; + background: rgba(127,127,127,.5); + color: #FFF; + display: block; + padding: 2px 4px; + border-radius: $fallback--tooltipRadius; + border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + z-index: 2; + } + } + + canvas { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + } +} +</style> diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index a762f914..08bc7113 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -6,9 +6,19 @@ export default { availableStyles: [], selected: this.$store.state.config.theme, bgColorLocal: '', - fgColorLocal: '', + btnColorLocal: '', textColorLocal: '', - linkColorLocal: '' + linkColorLocal: '', + redColorLocal: '', + blueColorLocal: '', + greenColorLocal: '', + orangeColorLocal: '', + btnRadiusLocal: '', + panelRadiusLocal: '', + avatarRadiusLocal: '', + avatarAltRadiusLocal: '', + attachmentRadiusLocal: '', + tooltipRadiusLocal: '' } }, created () { @@ -21,16 +31,29 @@ export default { }) }, mounted () { - this.bgColorLocal = rgbstr2hex(this.$store.state.config.colors['base00']) - this.fgColorLocal = rgbstr2hex(this.$store.state.config.colors['base02']) - this.textColorLocal = rgbstr2hex(this.$store.state.config.colors['base05']) - this.linkColorLocal = rgbstr2hex(this.$store.state.config.colors['base08']) + this.bgColorLocal = rgbstr2hex(this.$store.state.config.colors.bg) + this.btnColorLocal = rgbstr2hex(this.$store.state.config.colors.btn) + this.textColorLocal = rgbstr2hex(this.$store.state.config.colors.fg) + this.linkColorLocal = rgbstr2hex(this.$store.state.config.colors.link) + + this.redColorLocal = rgbstr2hex(this.$store.state.config.colors.cRed) + this.blueColorLocal = rgbstr2hex(this.$store.state.config.colors.cBlue) + this.greenColorLocal = rgbstr2hex(this.$store.state.config.colors.cGreen) + this.orangeColorLocal = rgbstr2hex(this.$store.state.config.colors.cOrange) + + this.btnRadiusLocal = this.$store.state.config.radii.btnRadius || 4 + this.panelRadiusLocal = this.$store.state.config.radii.panelRadius || 10 + this.avatarRadiusLocal = this.$store.state.config.radii.avatarRadius || 5 + this.avatarAltRadiusLocal = this.$store.state.config.radii.avatarAltRadius || 50 + this.tooltipRadiusLocal = this.$store.state.config.radii.tooltipRadius || 2 + this.attachmentRadiusLocal = this.$store.state.config.radii.attachmentRadius || 5 }, methods: { setCustomTheme () { - if (!this.bgColorLocal && !this.fgColorLocal && !this.linkColorLocal) { + if (!this.bgColorLocal && !this.btnColorLocal && !this.linkColorLocal) { // reset to picked themes } + const rgb = (hex) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result ? { @@ -40,17 +63,33 @@ export default { } : null } const bgRgb = rgb(this.bgColorLocal) - const fgRgb = rgb(this.fgColorLocal) + const btnRgb = rgb(this.btnColorLocal) const textRgb = rgb(this.textColorLocal) const linkRgb = rgb(this.linkColorLocal) - if (bgRgb && fgRgb && linkRgb) { + + const redRgb = rgb(this.redColorLocal) + const blueRgb = rgb(this.blueColorLocal) + const greenRgb = rgb(this.greenColorLocal) + const orangeRgb = rgb(this.orangeColorLocal) + + if (bgRgb && btnRgb && linkRgb) { this.$store.dispatch('setOption', { name: 'customTheme', value: { - fg: fgRgb, + fg: btnRgb, bg: bgRgb, text: textRgb, - link: linkRgb + link: linkRgb, + cRed: redRgb, + cBlue: blueRgb, + cGreen: greenRgb, + cOrange: orangeRgb, + btnRadius: this.btnRadiusLocal, + panelRadius: this.panelRadiusLocal, + avatarRadius: this.avatarRadiusLocal, + avatarAltRadius: this.avatarAltRadiusLocal, + tooltipRadius: this.tooltipRadiusLocal, + attachmentRadius: this.attachmentRadiusLocal }}) } } @@ -58,9 +97,13 @@ export default { watch: { selected () { this.bgColorLocal = this.selected[1] - this.fgColorLocal = this.selected[2] + this.btnColorLocal = this.selected[2] this.textColorLocal = this.selected[3] this.linkColorLocal = this.selected[4] + this.redColorLocal = this.selected[5] + this.greenColorLocal = this.selected[6] + this.blueColorLocal = this.selected[7] + this.orangeColorLocal = this.selected[8] } } } diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index 31bf546d..9c39b245 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -1,99 +1,231 @@ <template> <div> - <p>{{$t('settings.presets')}}</p> - <select v-model="selected" class="style-switcher"> - <option v-for="style in availableStyles" :value="style">{{style[0]}}</option> - </select> - <p>{{$t('settings.theme_help')}}</p> + <div>{{$t('settings.presets')}} + <label for="style-switcher" class='select'> + <select id="style-switcher" v-model="selected" class="style-switcher"> + <option v-for="style in availableStyles" :value="style">{{style[0]}}</option> + </select> + <i class="icon-down-open"/> + </label> + </div> <div class="color-container"> + <p>{{$t('settings.theme_help')}}</p> <div class="color-item"> - <label for="bgcolor" class="base04 theme-color-lb">{{$t('settings.background')}}</label> + <label for="bgcolor" class="theme-color-lb">{{$t('settings.background')}}</label> <input id="bgcolor" class="theme-color-cl" type="color" v-model="bgColorLocal"> <input id="bgcolor-t" class="theme-color-in" type="text" v-model="bgColorLocal"> </div> <div class="color-item"> - <label for="fgcolor" class="base04 theme-color-lb">{{$t('settings.foreground')}}</label> - <input id="fgcolor" class="theme-color-cl" type="color" v-model="fgColorLocal"> - <input id="fgcolor-t" class="theme-color-in" type="text" v-model="fgColorLocal"> + <label for="fgcolor" class="theme-color-lb">{{$t('settings.foreground')}}</label> + <input id="fgcolor" class="theme-color-cl" type="color" v-model="btnColorLocal"> + <input id="fgcolor-t" class="theme-color-in" type="text" v-model="btnColorLocal"> </div> <div class="color-item"> - <label for="textcolor" class="base04 theme-color-lb">{{$t('settings.text')}}</label> + <label for="textcolor" class="theme-color-lb">{{$t('settings.text')}}</label> <input id="textcolor" class="theme-color-cl" type="color" v-model="textColorLocal"> <input id="textcolor-t" class="theme-color-in" type="text" v-model="textColorLocal"> </div> <div class="color-item"> - <label for="linkcolor" class="base04 theme-color-lb">{{$t('settings.links')}}</label> + <label for="linkcolor" class="theme-color-lb">{{$t('settings.links')}}</label> <input id="linkcolor" class="theme-color-cl" type="color" v-model="linkColorLocal"> <input id="linkcolor-t" class="theme-color-in" type="text" v-model="linkColorLocal"> </div> + <div class="color-item"> + <label for="redcolor" class="theme-color-lb">{{$t('settings.cRed')}}</label> + <input id="redcolor" class="theme-color-cl" type="color" v-model="redColorLocal"> + <input id="redcolor-t" class="theme-color-in" type="text" v-model="redColorLocal"> + </div> + <div class="color-item"> + <label for="bluecolor" class="theme-color-lb">{{$t('settings.cBlue')}}</label> + <input id="bluecolor" class="theme-color-cl" type="color" v-model="blueColorLocal"> + <input id="bluecolor-t" class="theme-color-in" type="text" v-model="blueColorLocal"> + </div> + <div class="color-item"> + <label for="greencolor" class="theme-color-lb">{{$t('settings.cGreen')}}</label> + <input id="greencolor" class="theme-color-cl" type="color" v-model="greenColorLocal"> + <input id="greencolor-t" class="theme-color-in" type="green" v-model="greenColorLocal"> + </div> + <div class="color-item"> + <label for="orangecolor" class="theme-color-lb">{{$t('settings.cOrange')}}</label> + <input id="orangecolor" class="theme-color-cl" type="color" v-model="orangeColorLocal"> + <input id="orangecolor-t" class="theme-color-in" type="text" v-model="orangeColorLocal"> + </div> </div> - <div> - <div class="panel"> - <div class="panel-heading" :style="{ 'background-color': fgColorLocal, 'color': textColorLocal }">Preview</div> + <div class="radius-container"> + <p>{{$t('settings.radii_help')}}</p> + <div class="radius-item"> + <label for="btnradius" class="theme-radius-lb">{{$t('settings.btnRadius')}}</label> + <input id="btnradius" class="theme-radius-rn" type="range" v-model="btnRadiusLocal" max="16"> + <input id="btnradius-t" class="theme-radius-in" type="text" v-model="btnRadiusLocal"> + </div> + <div class="radius-item"> + <label for="panelradius" class="theme-radius-lb">{{$t('settings.panelRadius')}}</label> + <input id="panelradius" class="theme-radius-rn" type="range" v-model="panelRadiusLocal" max="50"> + <input id="panelradius-t" class="theme-radius-in" type="text" v-model="panelRadiusLocal"> + </div> + <div class="radius-item"> + <label for="avatarradius" class="theme-radius-lb">{{$t('settings.avatarRadius')}}</label> + <input id="avatarradius" class="theme-radius-rn" type="range" v-model="avatarRadiusLocal" max="28"> + <input id="avatarradius-t" class="theme-radius-in" type="green" v-model="avatarRadiusLocal"> + </div> + <div class="radius-item"> + <label for="avataraltradius" class="theme-radius-lb">{{$t('settings.avatarAltRadius')}}</label> + <input id="avataraltradius" class="theme-radius-rn" type="range" v-model="avatarAltRadiusLocal" max="28"> + <input id="avataraltradius-t" class="theme-radius-in" type="text" v-model="avatarAltRadiusLocal"> + </div> + <div class="radius-item"> + <label for="attachmentradius" class="theme-radius-lb">{{$t('settings.attachmentRadius')}}</label> + <input id="attachmentrradius" class="theme-radius-rn" type="range" v-model="attachmentRadiusLocal" max="50"> + <input id="attachmentradius-t" class="theme-radius-in" type="text" v-model="attachmentRadiusLocal"> + </div> + <div class="radius-item"> + <label for="tooltipradius" class="theme-radius-lb">{{$t('settings.tooltipRadius')}}</label> + <input id="tooltipradius" class="theme-radius-rn" type="range" v-model="tooltipRadiusLocal" max="20"> + <input id="tooltipradius-t" class="theme-radius-in" type="text" v-model="tooltipRadiusLocal"> + </div> + </div> + <div :style="{ + '--btnRadius': btnRadiusLocal + 'px', + '--panelRadius': panelRadiusLocal + 'px', + '--avatarRadius': avatarRadiusLocal + 'px', + '--avatarAltRadius': avatarAltRadiusLocal + 'px', + '--tooltipRadius': tooltipRadiusLocal + 'px', + '--attachmentRadius': attachmentRadiusLocal + 'px' + }"> + <div class="panel dummy"> + <div class="panel-heading" :style="{ 'background-color': btnColorLocal, 'color': textColorLocal }">Preview</div> <div class="panel-body theme-preview-content" :style="{ 'background-color': bgColorLocal, 'color': textColorLocal }"> + <div class="avatar" :style="{ + 'border-radius': avatarRadiusLocal + 'px' + }"> + ( ͡° ͜ʖ ͡°) + </div> <h4>Content</h4> <br> A bunch of more content and - <a :style="{ 'color': linkColorLocal }">a nice lil' link</a> + <a :style="{ color: linkColorLocal }">a nice lil' link</a> + <i :style="{ color: blueColorLocal }" class="icon-reply"/> + <i :style="{ color: greenColorLocal }" class="icon-retweet"/> + <i :style="{ color: redColorLocal }" class="icon-cancel"/> + <i :style="{ color: orangeColorLocal }" class="icon-star"/> <br> - <button class="btn" :style="{ 'background-color': fgColorLocal, 'color': textColorLocal }">Button</button> + <button class="btn" :style="{ 'background-color': btnColorLocal, 'color': textColorLocal }">Button</button> </div> </div> </div> - <button class="btn base02-background base04" @click="setCustomTheme">{{$t('general.apply')}}</button> + <button class="btn" @click="setCustomTheme">{{$t('general.apply')}}</button> </div> </template> <script src="./style_switcher.js"></script> <style lang="scss"> +@import '../../_variables.scss'; .style-switcher { - margin-right: 1em; + margin-right: 1em; } +.radius-container, .color-container { - display: flex; - flex-wrap: wrap; - justify-content: space-between; + display: flex; + + p { + margin-top: 2em; + margin-bottom: .5em; + } +} +.radius-container { + flex-direction: column; } +.color-container { + flex-wrap: wrap; + justify-content: space-between; +} + +.radius-item, .color-item { - min-width: 20em; - display:flex; - flex: 1 1 0; - align-items: baseline; - margin: 5px 6px 5px 0; + min-width: 20em; + display:flex; + flex: 1 1 0; + align-items: baseline; + margin: 5px 6px 5px 0; + + label { + color: var(--faint, $fallback--faint); + } } + +.radius-item { + flex-basis: auto; +} + +.theme-radius-rn, +.theme-color-cl { + border: 0; + box-shadow: none; + background: transparent; + color: var(--faint, $fallback--faint); + align-self: stretch; +} + .theme-color-cl, +.theme-radius-in, .theme-color-in { - margin-left: 4px; - border-radius: 2px; - border: 0; + margin-left: 4px; } .theme-color-in { - padding: 5px; - min-width: 4em; - max-width: 7em; - flex: 1; + min-width: 4em; } + +.theme-radius-in { + min-width: 1em; +} + +.theme-radius-in, +.theme-color-in { + max-width: 7em; + flex: 1; +} + +.theme-radius-lb, .theme-color-lb { - flex: 2; - min-width: 7em; - max-width: 10em; + flex: 2; + min-width: 7em; +} + +.theme-radius-lb{ + max-width: 50em; +} + +.theme-color-lb { + max-width: 10em; } .theme-color-cl { - padding: 1px; - max-width: 8em; - align-self: stretch; - height: 100%; - flex: 0; - min-width: 2em; - cursor: pointer; - } - - .theme-preview-content { - padding: 20px; - } + padding: 1px; + max-width: 8em; + height: 100%; + flex: 0; + min-width: 2em; + cursor: pointer; +} + +.theme-preview-content { + padding: 20px; +} + +.dummy { + .avatar { + background: linear-gradient(135deg, #b8e1fc 0%,#a9d2f3 10%,#90bae4 25%,#90bcea 37%,#90bff0 50%,#6ba8e5 51%,#a2daf5 83%,#bdf3fd 100%); + color: black; + text-align: center; + height: 48px; + line-height: 48px; + width: 48px; + float: left; + margin-right: 1em; + } +} </style> diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 660a8752..74ab85d3 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -105,7 +105,7 @@ const Timeline = { .then((friends) => this.$store.dispatch('addFriends', { friends })) }, scrollLoad (e) { - let height = Math.max(document.body.offsetHeight, document.body.scrollHeight) + const height = Math.max(document.body.offsetHeight, document.body.scrollHeight) if (this.timeline.loading === false && this.$store.state.config.autoLoad && this.$el.offsetHeight > 0 && diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index d6ecfd2f..c4e0fbce 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -1,48 +1,50 @@ <template> <div class="timeline panel panel-default" v-if="viewing == 'statuses'"> - <div class="panel-heading timeline-heading base02-background base04"> + <div class="panel-heading timeline-heading"> <div class="title"> {{title}} </div> - <button @click.prevent="showNewStatuses" class="base05 base02-background loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError"> + <button @click.prevent="showNewStatuses" class="loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError"> {{$t('timeline.show_new')}}{{newStatusCountStr}} </button> - <div @click.prevent class="base06 error loadmore-text" v-if="timelineError"> + <div @click.prevent class="loadmore-error alert error" v-if="timelineError"> {{$t('timeline.error_fetching')}} </div> - <div @click.prevent class="base04 base02-background loadmore-text" v-if="!timeline.newStatusCount > 0 && !timelineError"> + <div @click.prevent class="loadmore-text" v-if="!timeline.newStatusCount > 0 && !timelineError"> {{$t('timeline.up_to_date')}} </div> </div> - <div class="panel-body base01-background"> + <div class="panel-body"> <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"> - <div class="base02-background base03-border new-status-notification text-center">{{$t('timeline.load_older')}}</div> - </a> - <div class="base02-background base03-border new-status-notification text-center" v-else>...</div> + <status-or-conversation v-for="status in timeline.visibleStatuses" :key="status.id" v-bind:statusoid="status" class="status-fadein"></status-or-conversation> </div> </div> + <div class="panel-footer"> + <a href="#" v-on:click.prevent='fetchOlderStatuses()' v-if="!timeline.loading"> + <div class="new-status-notification text-center panel-footer">{{$t('timeline.load_older')}}</div> + </a> + <div class="new-status-notification text-center panel-footer" v-else>...</div> + </div> </div> <div class="timeline panel panel-default" v-else-if="viewing == 'followers'"> - <div class="panel-heading timeline-heading base02-background base04"> + <div class="panel-heading timeline-heading"> <div class="title"> {{$t('user_card.followers')}} </div> </div> - <div class="panel-body base01-background"> + <div class="panel-body"> <div class="timeline"> <user-card v-for="follower in followers" :key="follower.id" :user="follower" :showFollows="false"></user-card> </div> </div> </div> <div class="timeline panel panel-default" v-else-if="viewing == 'friends'"> - <div class="panel-heading timeline-heading base02-background base04"> + <div class="panel-heading timeline-heading"> <div class="title"> {{$t('user_card.followees')}} </div> </div> - <div class="panel-body base01-background"> + <div class="panel-body"> <div class="timeline"> <user-card v-for="friend in friends" :key="friend.id" :user="friend" :showFollows="true"></user-card> </div> @@ -52,51 +54,69 @@ <script src="./timeline.js"></script> <style lang="scss"> +@import '../../_variables.scss'; + +.timeline { + .timeline-heading { + position: relative; + display: flex; + } + + .title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 70%; + } + + .loadmore-button { + position: absolute; + right: 0.6em; + font-size: 14px; - .timeline { - .timeline-heading { - position: relative; - display: flex; - } - .title { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 70%; - } - .loadmore-button { - position: absolute; - right: 0.6em; - font-size: 14px; + min-width: 6em; + height: 1.8em; + line-height: 100%; + } - min-width: 6em; - height: 1.8em; - line-height: 100%; - } - .loadmore-text { - position: absolute; - right: 0.6em; - font-size: 14px; - min-width: 6em; - border-radius: 5px; - font-family: sans-serif; - text-align: center; - padding: 0 0.5em 0 0.5em; - opacity: 0.8; - } - .error { - background-color: rgba(255, 48, 16, 0.65); - } + .loadmore-text { + position: absolute; + right: 0.6em; + font-size: 14px; + min-width: 6em; + font-family: sans-serif; + text-align: center; + padding: 0 0.5em 0 0.5em; + opacity: 0.8; + background-color: transparent; + color: $fallback--faint; + color: var(--faint, $fallback--faint); } - .new-status-notification { - position:relative; - margin-top: -1px; - font-size: 1.1em; - border-width: 1px 0 0 0; - border-style: solid; - border-radius: 0 0 10px 10px; - padding: 10px; - z-index: 1; + .loadmore-error { + position: absolute; + right: 0.6em; + font-size: 14px; + min-width: 6em; + font-family: sans-serif; + text-align: center; + padding: 0 0.25em 0 0.25em; + margin: 0; + color: $fallback--fg; + color: var(--fg, $fallback--fg); } +} + +.new-status-notification { + position:relative; + margin-top: -1px; + font-size: 1.1em; + border-width: 1px 0 0 0; + border-style: solid; + border-color: var(--border, $fallback--border); + padding: 10px; + z-index: 1; + background-color: $fallback--btn; + background-color: var(--btn, $fallback--btn); +} </style> diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index dd14d1b4..51d6965f 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -1,5 +1,5 @@ <template> - <div class="card base00-background"> + <div class="card"> <a href="#"> <img @click.prevent="toggleUserExpanded" class="avatar" :src="user.profile_image_url"> </a> @@ -7,7 +7,7 @@ <user-card-content :user="user" :switcher="false"></user-card-content> </div> <div class="name-and-screen-name" v-else> - <div class="user-name"> + <div :title="user.name" class="user-name"> {{ user.name }} <span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you"> {{ $t('user_card.follows_you') }} @@ -21,54 +21,58 @@ <script src="./user_card.js"></script> <style lang="scss"> - .name-and-screen-name { - margin-left: 0.7em; - margin-top:0.0em; - margin-right: 2em; - text-align: left; - width: 100%; - } +@import '../../_variables.scss'; - .follows-you { - margin-left: 2em; - float: right; - } +.name-and-screen-name { + margin-left: 0.7em; + margin-top:0.0em; + text-align: left; + width: 100%; +} - .follows { +.follows-you { + margin-left: 2em; + float: right; +} - } +.card { + display: flex; + flex: 1 0; + padding-top: 0.6em; + padding-right: 1em; + padding-bottom: 0.6em; + padding-left: 1em; + border-bottom: 1px solid; + margin: 0; + border-bottom-color: $fallback--border; + border-bottom-color: var(--border, $fallback--border); - .card { - display: flex; - flex: 1 0; - padding-top: 0.6em; - padding-right: 1em; - padding-bottom: 0.6em; - padding-left: 1em; - border-bottom: 1px solid; - margin: 0; - border-bottom-color: inherit; + .avatar { + margin-top: 0.2em; + width:32px; + height: 32px; + border-radius: $fallback--avatarAltRadius; + border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); + } +} +.usercard { + width: fill-available; + margin: 0.2em 0 0.7em 0; + border-radius: $fallback--panelRadius; + border-radius: var(--panelRadius, $fallback--panelRadius); + border-style: solid; + border-color: $fallback--border; + border-color: var(--border, $fallback--border); + border-width: 1px; + overflow: hidden; - .avatar { - margin-top: 0.2em; - width:32px; - height: 32px; - border-radius: 50%; - } + .panel-heading { + background: transparent; } - .usercard { - width: fill-available; - margin: 0.2em 0 0.7em 0; - border-radius: 10px; - border-style: solid; - border-color: inherit; - border-width: 1px; - overflow: hidden; - - p { - margin-bottom: 0; - } + p { + margin-bottom: 0; } +} </style> diff --git a/src/components/user_card_content/user_card_content.js b/src/components/user_card_content/user_card_content.js index 32d62ebb..1e8c91de 100644 --- a/src/components/user_card_content/user_card_content.js +++ b/src/components/user_card_content/user_card_content.js @@ -1,10 +1,11 @@ +import StillImage from '../still-image/still-image.vue' import { hex2rgb } from '../../services/color_convert/color_convert.js' export default { - props: [ 'user', 'switcher' ], + props: [ 'user', 'switcher', 'hideBio' ], computed: { headingStyle () { - const color = this.$store.state.config.colors['base00'] + const color = this.$store.state.config.colors.bg if (color) { const rgb = hex2rgb(color) console.log(rgb) @@ -14,11 +15,6 @@ export default { } } }, - bodyStyle () { - return { - background: `linear-gradient(to bottom, rgba(0, 0, 0, 0), ${this.$store.state.config.colors['base00']} 80%)` - } - }, isOtherUser () { return this.user.id !== this.$store.state.users.currentUser.id }, @@ -35,6 +31,9 @@ export default { return Math.round(this.user.statuses_count / days) } }, + components: { + StillImage + }, methods: { followUser () { const store = this.$store diff --git a/src/components/user_card_content/user_card_content.vue b/src/components/user_card_content/user_card_content.vue index ef000c94..ca8428ca 100644 --- a/src/components/user_card_content/user_card_content.vue +++ b/src/components/user_card_content/user_card_content.vue @@ -5,43 +5,45 @@ <router-link to='/user-settings' style="float: right; margin-top:16px;" v-if="!isOtherUser"> <i class="icon-cog usersettings"></i> </router-link> + <a :href="user.statusnet_profile_url" target="_blank" style="float: right; margin-top:16px;" v-if="isOtherUser"> + <i class="icon-link-ext usersettings"></i> + </a> <div class='container'> <router-link :to="{ name: 'user-profile', params: { id: user.id } }"> - <img :src="user.profile_image_url_original"> + <StillImage class="avatar" :src="user.profile_image_url_original"/> </router-link> - <span class="glyphicon glyphicon-user"></span> <div class="name-and-screen-name"> - <div class='user-name'>{{user.name}}</div> + <div :title="user.name" class='user-name'>{{user.name}}</div> <router-link :to="{ name: 'user-profile', params: { id: user.id } }"> <div class='user-screen-name'>@{{user.screen_name}}</div> </router-link> </div> </div> <div v-if="isOtherUser" class="user-interactions"> - <div v-if="user.follows_you && loggedIn" class="following base06"> + <div v-if="user.follows_you && loggedIn" class="following"> {{ $t('user_card.follows_you') }} </div> <div class="follow" v-if="loggedIn"> <span v-if="user.following"> <!--Following them!--> - <button @click="unfollowUser" class="base04 base00-background pressed"> + <button @click="unfollowUser" class="pressed"> {{ $t('user_card.following') }} </button> </span> <span v-if="!user.following"> - <button @click="followUser" class="base05 base02-background"> + <button @click="followUser"> {{ $t('user_card.follow') }} </button> </span> </div> <div class='mute' v-if='isOtherUser'> <span v-if='user.muted'> - <button @click="toggleMute" class="base04 base00-background pressed"> + <button @click="toggleMute" class="pressed"> {{ $t('user_card.muted') }} </button> </span> <span v-if='!user.muted'> - <button @click="toggleMute" class="base05 base02-background"> + <button @click="toggleMute"> {{ $t('user_card.mute') }} </button> </span> @@ -50,19 +52,19 @@ <form method="POST" :action='subscribeUrl'> <input type="hidden" name="nickname" :value="user.screen_name"> <input type="hidden" name="profile" value=""> - <button click="submit" class="remote-button base05 base02-background"> + <button click="submit" class="remote-button"> {{ $t('user_card.remote_follow') }} </button> </form> </div> <div class='block' v-if='isOtherUser && loggedIn'> <span v-if='user.statusnet_blocking'> - <button @click="unblockUser" class="base04 base00-background pressed"> + <button @click="unblockUser" class="pressed"> {{ $t('user_card.blocked') }} </button> </span> <span v-if='!user.statusnet_blocking'> - <button @click="blockUser" class="base05 base02-background"> + <button @click="blockUser"> {{ $t('user_card.block') }} </button> </span> @@ -70,25 +72,25 @@ </div> </div> </div> - <div class="panel-body profile-panel-body" :style="bodyStyle"> + <div class="panel-body profile-panel-body"> <div class="user-counts"> <div class="user-count"> - <a href="#" v-on:click.prevent="setProfileView('statuses')" v-if="switcher"><h5 class="base05">{{ $t('user_card.statuses') }}</h5></a> + <a href="#" v-on:click.prevent="setProfileView('statuses')" v-if="switcher"><h5>{{ $t('user_card.statuses') }}</h5></a> <h5 v-else>{{ $t('user_card.statuses') }}</h5> - <span class="base05">{{user.statuses_count}} <br><span class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span></span> + <span>{{user.statuses_count}} <br><span class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span></span> </div> <div class="user-count"> - <a href="#" v-on:click.prevent="setProfileView('friends')" v-if="switcher"><h5 class="base05">{{ $t('user_card.followees') }}</h5></a> + <a href="#" v-on:click.prevent="setProfileView('friends')" v-if="switcher"><h5>{{ $t('user_card.followees') }}</h5></a> <h5 v-else>{{ $t('user_card.followees') }}</h5> - <span class="base05">{{user.friends_count}}</span> + <span>{{user.friends_count}}</span> </div> <div class="user-count"> - <a href="#" v-on:click.prevent="setProfileView('followers')" v-if="switcher"><h5 class="base05">{{ $t('user_card.followers') }}</h5></a> + <a href="#" v-on:click.prevent="setProfileView('followers')" v-if="switcher"><h5>{{ $t('user_card.followers') }}</h5></a> <h5 v-else>{{ $t('user_card.followers') }}</h5> - <span class="base05">{{user.followers_count}}</span> + <span>{{user.followers_count}}</span> </div> </div> - <p>{{user.description}}</p> + <p v-if="!hideBio">{{user.description}}</p> </div> </div> </template> @@ -100,7 +102,8 @@ .profile-panel-background { background-size: cover; - border-radius: 10px; + border-radius: $fallback--panelRadius; + border-radius: var(--panelRadius, $fallback--panelRadius); .panel-heading { padding: 0.6em 0em; @@ -112,39 +115,51 @@ top: -0em; padding-top: 4em; word-wrap: break-word; + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%); + background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%) } .user-info { - color: white; + color: white; padding: 0 16px 16px 16px; margin-bottom: -4em; - .usersettings { - color: white; - opacity: 0.8; - } - - .container{ + .container { padding: 16px 10px 4px 10px; display: flex; - flex-wrap: wrap; - flex-direction: column; - align-content: flex-start; - justify-content: center; max-height: 56px; overflow: hidden; + + .avatar { + border-radius: $fallback--avatarRadius; + border-radius: var(--avatarRadius, $fallback--avatarRadius); + flex: 1 0 100%; + width: 56px; + height: 56px; + box-shadow: 0px 1px 8px rgba(0,0,0,0.75); + object-fit: cover; + + &.animated::before { + display: none; + } + } } - img { - border-radius: 5px; - flex: 1 0 100%; - width: 56px; - height: 56px; - box-shadow: 0px 1px 8px rgba(0,0,0,0.75); - object-fit: cover; + &:hover .animated.avatar { + canvas { + display: none; + } + img { + visibility: visible; + } } - text-shadow: 0px 1px 1.5px rgba(0, 0, 0, 1.0); + text-shadow: 0px 1px 1.5px rgba(0, 0, 0, 1.0); + + .usersettings { + color: #fff; + opacity: .8; + } .name-and-screen-name { display: block; @@ -152,18 +167,20 @@ text-align: left; text-overflow: ellipsis; white-space: nowrap; + flex: 1 1 0; } - .user-name{ - color: white; - } + .user-name{ + color: white; + text-overflow: ellipsis; + overflow: hidden; + } .user-screen-name { - color: white; - font-weight: lighter; - font-size: 15px; - padding-right: 0.1em; - flex: 0 0 auto; + color: white; + font-weight: lighter; + font-size: 15px; + padding-right: 0.1em; } .user-interactions { @@ -219,23 +236,23 @@ } .user-counts { - display: flex; - line-height:16px; - padding: 1em 1.5em 0em 1em; - text-align: center; + display: flex; + line-height:16px; + padding: 1em 1.5em 0em 1em; + text-align: center; } .user-count { - flex: 1; + flex: 1; - h5 { - font-size:1em; - font-weight: bolder; - margin: 0 0 0.25em; - } - a { - text-decoration: none; - } + h5 { + font-size:1em; + font-weight: bolder; + margin: 0 0 0.25em; + } + a { + text-decoration: none; + } } .dailyAvg { diff --git a/src/components/user_finder/user_finder.vue b/src/components/user_finder/user_finder.vue index 3c69e3b4..69bd1d21 100644 --- a/src/components/user_finder/user_finder.vue +++ b/src/components/user_finder/user_finder.vue @@ -1,13 +1,13 @@ <template> <span class="user-finder-container"> - <span class="finder-error base05" v-if="error"> + <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" /> <a href="#" v-if="hidden"><i class="icon-user-plus user-finder-icon" @click.prevent.stop="toggleHidden"/></a> <span v-else> - <input class="user-finder-input base03-border" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/> + <input class="user-finder-input" @keyup.enter="findUser(username)" v-model="username" :placeholder="$t('finder.find_user')" id="user-finder-input" type="text"/> <i class="icon-cancel user-finder-icon" @click.prevent.stop="toggleHidden"/> </span> </span> @@ -16,27 +16,16 @@ <script src="./user_finder.js"></script> <style lang="scss"> - .user-finder-container { - height: 21px; - max-width: 100%; - } +@import '../../_variables.scss'; - .user-finder-icon { - } +.user-finder-container { + height: 29px; + max-width: 100%; +} - .user-finder-input { - border-width: 1px; - border-style: solid; - border-color: inherit; - border-radius: 5px; - max-width: 80%; - padding: 0.1em 0.2em 0.2em 0.2em; - } +.user-finder-input { + max-width: 80%; + vertical-align: middle; +} - .finder-error { - background-color: rgba(255, 48, 16, 0.65); - margin: 0.35em; - border-radius: 5px; - padding: 0.25em; - } </style> diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue index 1a5e20fb..3d4f873d 100644 --- a/src/components/user_panel/user_panel.vue +++ b/src/components/user_panel/user_panel.vue @@ -1,8 +1,8 @@ <template> <div class="user-panel"> <div v-if='user' class="panel panel-default" style="overflow: visible;"> - <user-card-content :user="user" :switcher="false"></user-card-content> - <div class="panel-footer base00-background"> + <user-card-content :user="user" :switcher="false" :hideBio="true"></user-card-content> + <div class="panel-footer"> <post-status-form v-if='user'></post-status-form> </div> </div> @@ -11,3 +11,11 @@ </template> <script src="./user_panel.js"></script> + +<style lang="scss"> +.user-panel { + .profile-panel-background .panel-heading { + background: transparent; + } +} +</style> diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 359abfef..838a43ab 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -1,6 +1,6 @@ <template> <div> - <div v-if="user" class="user-profile panel panel-default base00-background"> + <div v-if="user" class="user-profile panel panel-default"> <user-card-content :user="user" :switcher="true"></user-card-content> </div> <Timeline :title="$t('user_profile.timeline_title')" :timeline="timeline" :timeline-name="'user'" :user-id="userId"/> @@ -15,7 +15,8 @@ flex: 2; flex-basis: 500px; padding-bottom: 10px; - border-radius: 10px; + .panel-heading { + background: transparent; + } } - </style> diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index 1ef52bbf..ed1864cc 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -1,16 +1,16 @@ <template> - <div class="settings panel panel-default base00-background"> - <div class="panel-heading base02-background base04"> + <div class="settings panel panel-default"> + <div class="panel-heading"> {{$t('settings.user_settings')}} </div> <div class="panel-body profile-edit"> <div class="setting-item"> <h3>{{$t('settings.name_bio')}}</h3> <p>{{$t('settings.name')}}</p> - <input class='name-changer base03-border' id='username' v-model="newname"></input> + <input class='name-changer' id='username' v-model="newname"></input> <p>{{$t('settings.bio')}}</p> - <textarea class="bio base03-border" v-model="newbio"></textarea> - <button :disabled='newname.length <= 0' class="btn btn-default base05 base02-background" @click="updateProfile">{{$t('general.submit')}}</button> + <textarea class="bio" v-model="newbio"></textarea> + <button :disabled='newname.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button> </div> <div class="setting-item"> <h3>{{$t('settings.avatar')}}</h3> @@ -22,8 +22,8 @@ <div> <input type="file" @change="uploadFile(0, $event)" ></input> </div> - <i class="base09 icon-spin4 animate-spin" v-if="uploading[0]"></i> - <button class="btn btn-default base05 base02-background" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button> + <i class="icon-spin4 animate-spin" v-if="uploading[0]"></i> + <button class="btn btn-default" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button> </div> <div class="setting-item"> <h3>{{$t('settings.profile_banner')}}</h3> @@ -35,8 +35,8 @@ <div> <input type="file" @change="uploadFile(1, $event)" ></input> </div> - <i class="base09 icon-spin4 animate-spin uploading" v-if="uploading[1]"></i> - <button class="btn btn-default base05 base02-background" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button> + <i class=" icon-spin4 animate-spin uploading" v-if="uploading[1]"></i> + <button class="btn btn-default" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button> </div> <div class="setting-item"> <h3>{{$t('settings.profile_background')}}</h3> @@ -46,8 +46,8 @@ <div> <input type="file" @change="uploadFile(2, $event)" ></input> </div> - <i class="base09 icon-spin4 animate-spin uploading" v-if="uploading[2]"></i> - <button class="btn btn-default base05 base02-background" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button> + <i class=" icon-spin4 animate-spin uploading" v-if="uploading[2]"></i> + <button class="btn btn-default" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button> </div> <div class="setting-item" v-if="pleromaBackend"> <h3>{{$t('settings.follow_import')}}</h3> @@ -55,8 +55,8 @@ <form v-model="followImportForm"> <input type="file" ref="followlist" v-on:change="followListChange"></input> </form> - <i class="base09 icon-spin4 animate-spin uploading" v-if="uploading[3]"></i> - <button class="btn btn-default base05 base02-background" v-else @click="importFollows">{{$t('general.submit')}}</button> + <i class=" icon-spin4 animate-spin uploading" v-if="uploading[3]"></i> + <button class="btn btn-default" v-else @click="importFollows">{{$t('general.submit')}}</button> <div v-if="followsImported"> <i class="icon-cross" @click="dismissImported"></i> <p>{{$t('settings.follows_imported')}}</p> @@ -75,24 +75,16 @@ <style lang="scss"> .profile-edit { - .name-changer { - border-width: 1px; - border-style: solid; - border-radius: 5px; - padding: 0.2em 0.2em 0.2em 0.2em; - } - .name-submit { - padding: 0.2em 0.5em 0.2em 0.5em; - } .bio { - border-width: 1px; - border-style: solid; - border-radius: 5px; margin: 0; } + + input[type=file] { + padding: 5px; + } + .banner { max-width: 400px; - border-radius: 5px; } .uploading { |
