diff options
| author | Shpuld Shpludson <shp@cock.li> | 2019-03-11 18:53:34 +0000 |
|---|---|---|
| committer | Shpuld Shpludson <shp@cock.li> | 2019-03-11 18:53:34 +0000 |
| commit | 7c26435e66fd7e142ea4b88fbe51eede32eeb5ce (patch) | |
| tree | d7b4ea789ceaaa8e5dacd7e74100962853853132 /src | |
| parent | 7e9c8c3d219aa3b787c5606efbb54a73c1738b07 (diff) | |
| parent | f397537642a6b8a4079d8d45d835ddd50f2d2b4a (diff) | |
Merge branch 'develop' into 'master'
Update master with bugfixes (and other changes)
See merge request pleroma/pleroma-fe!673
Diffstat (limited to 'src')
41 files changed, 388 insertions, 190 deletions
@@ -8,6 +8,7 @@ import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_pan import ChatPanel from './components/chat_panel/chat_panel.vue' import MediaModal from './components/media_modal/media_modal.vue' import SideDrawer from './components/side_drawer/side_drawer.vue' +import MobilePostStatusModal from './components/mobile_post_status_modal/mobile_post_status_modal.vue' import { unseenNotificationsFromStore } from './services/notification_utils/notification_utils' export default { @@ -22,7 +23,8 @@ export default { WhoToFollowPanel, ChatPanel, MediaModal, - SideDrawer + SideDrawer, + MobilePostStatusModal }, data: () => ({ mobileActivePanel: 'timeline', diff --git a/src/App.scss b/src/App.scss index a0d1a804..598735d9 100644 --- a/src/App.scss +++ b/src/App.scss @@ -671,6 +671,31 @@ nav { border-radius: var(--inputRadius, $fallback--inputRadius); } +@keyframes modal-background-fadein { + from { + background-color: rgba(0, 0, 0, 0); + } + to { + background-color: rgba(0, 0, 0, 0.5); + } +} + +.modal-view { + z-index: 1000; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + overflow: auto; + animation-duration: 0.2s; + background-color: rgba(0, 0, 0, 0.5); + animation-name: modal-background-fadein; +} + .button-icon { font-size: 1.2em; } diff --git a/src/App.vue b/src/App.vue index acbbeb75..4fff3d1d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -50,6 +50,7 @@ <media-modal></media-modal> </div> <chat-panel :floating="true" v-if="currentUser && chat" class="floating-chat mobile-hidden"></chat-panel> + <MobilePostStatusModal /> </div> </template> diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 53ecc083..cd88c188 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -89,10 +89,8 @@ const afterStoreSetup = ({ store, i18n }) => { copyInstanceOption('noAttachmentLinks') copyInstanceOption('showFeaturesPanel') - if ((config.chatDisabled)) { + if (config.chatDisabled) { store.dispatch('disableChat') - } else { - store.dispatch('initializeSocket') } return store.dispatch('setTheme', config['theme']) @@ -169,6 +167,8 @@ const afterStoreSetup = ({ store, i18n }) => { store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) + store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) + store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) const suggestions = metadata.suggestions diff --git a/src/boot/routes.js b/src/boot/routes.js index cfbcb1fe..7e54a98b 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -13,7 +13,6 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue' import OAuthCallback from 'components/oauth_callback/oauth_callback.vue' import UserSearch from 'components/user_search/user_search.vue' import Notifications from 'components/notifications/notifications.vue' -import UserPanel from 'components/user_panel/user_panel.vue' import LoginForm from 'components/login_form/login_form.vue' import ChatPanel from 'components/chat_panel/chat_panel.vue' import WhoToFollow from 'components/who_to_follow/who_to_follow.vue' @@ -43,7 +42,6 @@ export default (store) => { { name: 'friend-requests', path: '/friend-requests', component: FollowRequests }, { name: 'user-settings', path: '/user-settings', component: UserSettings }, { name: 'notifications', path: '/:username/notifications', component: Notifications }, - { name: 'new-status', path: '/:username/new-status', component: UserPanel }, { name: 'login', path: '/login', component: LoginForm }, { name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) }, { name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) }, diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 76affe2d..c58bebd3 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -160,6 +160,7 @@ .hider { position: absolute; + right: 0; white-space: nowrap; margin: 10px; padding: 5px; diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js index a8441446..87085a28 100644 --- a/src/components/basic_user_card/basic_user_card.js +++ b/src/components/basic_user_card/basic_user_card.js @@ -1,4 +1,4 @@ -import UserCardContent from '../user_card_content/user_card_content.vue' +import UserCard from '../user_card/user_card.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -12,7 +12,7 @@ const BasicUserCard = { } }, components: { - UserCardContent, + UserCard, UserAvatar }, methods: { diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 77fb0aa0..9b80c72b 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -1,18 +1,18 @@ <template> - <div class="user-card"> + <div class="basic-user-card"> <router-link :to="userProfileLink(user)"> <UserAvatar class="avatar" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/> </router-link> - <div class="user-card-expanded-content" v-if="userExpanded"> - <user-card-content :user="user" :switcher="false"></user-card-content> + <div class="basic-user-card-expanded-content" v-if="userExpanded"> + <UserCard :user="user" :rounded="true" :bordered="true"/> </div> - <div class="user-card-collapsed-content" v-else> - <div :title="user.name" class="user-card-user-name"> + <div class="basic-user-card-collapsed-content" v-else> + <div :title="user.name" class="basic-user-card-user-name"> <span v-if="user.name_html" v-html="user.name_html"></span> <span v-else>{{ user.name }}</span> </div> <div> - <router-link class="user-card-screen-name" :to="userProfileLink(user)"> + <router-link class="basic-user-card-screen-name" :to="userProfileLink(user)"> @{{user.screen_name}} </router-link> </div> @@ -26,15 +26,15 @@ <style lang="scss"> @import '../../_variables.scss'; -.user-card { +.basic-user-card { display: flex; flex: 1 0; + margin: 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); @@ -57,23 +57,6 @@ &-expanded-content { flex: 1; margin-left: 0.7em; - 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; - - .panel-heading { - background: transparent; - flex-direction: column; - align-items: stretch; - } - - p { - margin-bottom: 0; - } } } </style> diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 427bf12b..7f666603 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -1,5 +1,5 @@ <template> - <div class="modal-view" v-if="showing" @click.prevent="hide"> + <div class="modal-view media-modal-view" v-if="showing" @click.prevent="hide"> <img class="modal-image" v-if="type === 'image'" :src="currentMedia.url"></img> <VideoAttachment class="modal-image" @@ -32,18 +32,7 @@ <style lang="scss"> @import '../../_variables.scss'; -.modal-view { - z-index: 1000; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - background-color: rgba(0, 0, 0, 0.5); - +.media-modal-view { &:hover { .modal-view-button-arrow { opacity: 0.75; diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.js b/src/components/mobile_post_status_modal/mobile_post_status_modal.js new file mode 100644 index 00000000..2f24dd08 --- /dev/null +++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.js @@ -0,0 +1,91 @@ +import PostStatusForm from '../post_status_form/post_status_form.vue' +import { throttle } from 'lodash' + +const MobilePostStatusModal = { + components: { + PostStatusForm + }, + data () { + return { + hidden: false, + postFormOpen: false, + scrollingDown: false, + inputActive: false, + oldScrollPos: 0, + amountScrolled: 0 + } + }, + created () { + window.addEventListener('scroll', this.handleScroll) + window.addEventListener('resize', this.handleOSK) + }, + destroyed () { + window.removeEventListener('scroll', this.handleScroll) + window.removeEventListener('resize', this.handleOSK) + }, + computed: { + currentUser () { + return this.$store.state.users.currentUser + }, + isHidden () { + return this.hidden || this.inputActive + } + }, + methods: { + openPostForm () { + this.postFormOpen = true + this.hidden = true + + const el = this.$el.querySelector('textarea') + this.$nextTick(function () { + el.focus() + }) + }, + closePostForm () { + this.postFormOpen = false + this.hidden = false + }, + handleOSK () { + // This is a big hack: we're guessing from changed window sizes if the + // on-screen keyboard is active or not. This is only really important + // for phones in portrait mode and it's more important to show the button + // in normal scenarios on all phones, than it is to hide it when the + // keyboard is active. + // Guesswork based on https://www.mydevice.io/#compare-devices + + // for example, iphone 4 and android phones from the same time period + const smallPhone = window.innerWidth < 350 + const smallPhoneKbOpen = smallPhone && window.innerHeight < 345 + + const biggerPhone = !smallPhone && window.innerWidth < 450 + const biggerPhoneKbOpen = biggerPhone && window.innerHeight < 560 + if (smallPhoneKbOpen || biggerPhoneKbOpen) { + this.inputActive = true + } else { + this.inputActive = false + } + }, + handleScroll: throttle(function () { + const scrollAmount = window.scrollY - this.oldScrollPos + const scrollingDown = scrollAmount > 0 + + if (scrollingDown !== this.scrollingDown) { + this.amountScrolled = 0 + this.scrollingDown = scrollingDown + if (!scrollingDown) { + this.hidden = false + } + } else if (scrollingDown) { + this.amountScrolled += scrollAmount + if (this.amountScrolled > 100 && !this.hidden) { + this.hidden = true + } + } + + this.oldScrollPos = window.scrollY + this.scrollingDown = scrollingDown + }, 100) + } +} + +export default MobilePostStatusModal diff --git a/src/components/mobile_post_status_modal/mobile_post_status_modal.vue b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue new file mode 100644 index 00000000..0a451c28 --- /dev/null +++ b/src/components/mobile_post_status_modal/mobile_post_status_modal.vue @@ -0,0 +1,76 @@ +<template> +<div v-if="currentUser"> + <div + class="post-form-modal-view modal-view" + v-show="postFormOpen" + @click="closePostForm" + > + <div class="post-form-modal-panel panel" @click.stop=""> + <div class="panel-heading">{{$t('post_status.new_status')}}</div> + <PostStatusForm class="panel-body" @posted="closePostForm"/> + </div> + </div> + <button + class="new-status-button" + :class="{ 'hidden': isHidden }" + @click="openPostForm" + > + <i class="icon-edit" /> + </button> +</div> +</template> + +<script src="./mobile_post_status_modal.js"></script> + +<style lang="scss"> +@import '../../_variables.scss'; + +.post-form-modal-view { + max-height: 100%; + display: block; +} + +.post-form-modal-panel { + flex-shrink: 0; + margin: 25% 0 4em 0; + width: 100%; +} + +.new-status-button { + width: 5em; + height: 5em; + border-radius: 100%; + position: fixed; + bottom: 1.5em; + right: 1.5em; + // TODO: this needs its own color, it has to stand out enough and link color + // is not very optimal for this particular use. + background-color: $fallback--fg; + background-color: var(--btn, $fallback--fg); + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3); + z-index: 10; + + transition: 0.35s transform; + transition-timing-function: cubic-bezier(0, 1, 0.5, 1); + + &.hidden { + transform: translateY(150%); + } + + i { + font-size: 1.5em; + color: $fallback--text; + color: var(--text, $fallback--text); + } +} + +@media all and (min-width: 801px) { + .new-status-button { + display: none; + } +} + +</style> diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 7d9807de..fe5b7018 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -1,6 +1,6 @@ import Status from '../status/status.vue' import UserAvatar from '../user_avatar/user_avatar.vue' -import UserCardContent from '../user_card_content/user_card_content.vue' +import UserCard from '../user_card/user_card.vue' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -13,7 +13,7 @@ const Notification = { }, props: [ 'notification' ], components: { - Status, UserAvatar, UserCardContent + Status, UserAvatar, UserCard }, methods: { toggleUserExpanded () { diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue index 87925cfc..5e9cef97 100644 --- a/src/components/notification/notification.vue +++ b/src/components/notification/notification.vue @@ -5,9 +5,7 @@ <UserAvatar :compact="true" :betterShadow="betterShadow" :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> + <UserCard :user="notification.action.user" :rounded="true" :bordered="true" v-if="userExpanded"/> <span class="notification-details"> <div class="name-and-action"> <span class="username" v-if="!!notification.action.user.name_html" :title="'@'+notification.action.user.screen_name" v-html="notification.action.user.name_html"></span> diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 5e95631a..9fc5e38a 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -11,7 +11,8 @@ const Notifications = { const store = this.$store const credentials = store.state.users.currentUser.credentials - notificationsFetcher.startFetching({ store, credentials }) + const fetcherId = notificationsFetcher.startFetching({ store, credentials }) + this.$store.commit('setNotificationFetcher', { fetcherId }) }, data () { return { diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 2240c10a..c0b458cc 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -45,10 +45,6 @@ } } - .notification-usercard { - margin: 0; - } - .non-mention { display: flex; flex: 1; diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index c28c51bf..1f0df35a 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -171,6 +171,9 @@ const PostStatusForm = { }, formattingOptionsEnabled () { return this.$store.state.instance.formattingOptionsEnabled + }, + postFormats () { + return this.$store.state.instance.postFormats || [] } }, methods: { @@ -219,6 +222,9 @@ const PostStatusForm = { this.highlighted = 0 } }, + onKeydown (e) { + e.stopPropagation() + }, setCaret ({target: {selectionStart}}) { this.caret = selectionStart }, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 40d24b97..3d1df91b 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -20,6 +20,7 @@ ref="textarea" @click="setCaret" @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" + @keydown="onKeydown" @keydown.down="cycleForward" @keydown.up="cycleBackward" @keydown.shift.tab="cycleBackward" @@ -38,9 +39,9 @@ <span class="text-format" v-if="formattingOptionsEnabled"> <label for="post-content-type" class="select"> <select id="post-content-type" v-model="newStatus.contentType" class="form-control"> - <option value="text/plain">{{$t('post_status.content_type.plain_text')}}</option> - <option value="text/html">HTML</option> - <option value="text/markdown">Markdown</option> + <option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat"> + {{$t(`post_status.content_type["${postFormat}"]`)}} + </option> </select> <i class="icon-down-open"></i> </label> diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index 6e2dff7b..979457a5 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -93,6 +93,9 @@ const settings = { currentSaveStateNotice () { return this.$store.state.interface.settings.currentSaveStateNotice }, + postFormats () { + return this.$store.state.instance.postFormats || [] + }, instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel } }, watch: { diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index 5041b3a3..d2346747 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -105,17 +105,9 @@ {{$t('settings.post_status_content_type')}} <label for="postContentType" class="select"> <select id="postContentType" v-model="postContentTypeLocal"> - <option value="text/plain"> - {{$t('settings.status_content_type_plain')}} - {{postContentTypeDefault == 'text/plain' ? $t('settings.instance_default_simple') : ''}} - </option> - <option value="text/html"> - HTML - {{postContentTypeDefault == 'text/html' ? $t('settings.instance_default_simple') : ''}} - </option> - <option value="text/markdown"> - Markdown - {{postContentTypeDefault == 'text/markdown' ? $t('settings.instance_default_simple') : ''}} + <option v-for="postFormat in postFormats" :key="postFormat" :value="postFormat"> + {{$t(`post_status.content_type["${postFormat}"]`)}} + {{postContentTypeDefault === postFormat ? $t('settings.instance_default_simple') : ''}} </option> </select> <i class="icon-down-open"/> diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index b5c49059..ad3738d1 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -1,4 +1,4 @@ -import UserCardContent from '../user_card_content/user_card_content.vue' +import UserCard from '../user_card/user_card.vue' import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils' // TODO: separate touch gesture stuff into their own utils if more components want them @@ -12,7 +12,7 @@ const SideDrawer = { closed: true, touchCoord: [0, 0] }), - components: { UserCardContent }, + components: { UserCard }, computed: { currentUser () { return this.$store.state.users.currentUser diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index 6996380d..95ee21b4 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -8,19 +8,14 @@ @touchmove="touchMove" > <div class="side-drawer-heading" @click="toggleDrawer"> - <user-card-content :user="currentUser" :switcher="false" :hideBio="true" v-if="currentUser"/> + <UserCard :user="currentUser" :hideBio="true" v-if="currentUser"/> <div class="side-drawer-logo-wrapper" v-else> <img :src="logo"/> <span>{{sitename}}</span> </div> </div> <ul> - <li v-if="currentUser" @click="toggleDrawer"> - <router-link :to="{ name: 'new-status', params: { username: currentUser.screen_name } }"> - {{ $t("post_status.new_status") }} - </router-link> - </li> - <li v-else @click="toggleDrawer"> + <li v-if="!currentUser" @click="toggleDrawer"> <router-link :to="{ name: 'login' }"> {{ $t("login.login") }} </router-link> @@ -119,14 +114,14 @@ } .side-drawer-container-open { - transition-delay: 0.0s; - transition-property: left; + transition: 0.35s; + transition-property: background-color; + background-color: rgba(0, 0, 0, 0.5); } .side-drawer-container-closed { left: -100%; - transition-delay: 0.5s; - transition-property: left; + background-color: rgba(0, 0, 0, 0); } .side-drawer-click-outside { @@ -181,15 +176,6 @@ display: flex; padding: 0; margin: 0; - - .profile-panel-background { - border-radius: 0; - .panel-heading { - background: transparent; - flex-direction: column; - align-items: stretch; - } - } } .side-drawer ul { diff --git a/src/components/status/status.js b/src/components/status/status.js index fbbca6c4..9e18fe15 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -3,7 +3,7 @@ import FavoriteButton from '../favorite_button/favorite_button.vue' 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 UserCard from '../user_card/user_card.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import Gallery from '../gallery/gallery.vue' import LinkPreview from '../link-preview/link-preview.vue' @@ -259,7 +259,7 @@ const Status = { RetweetButton, DeleteButton, PostStatusForm, - UserCardContent, + UserCard, UserAvatar, Gallery, LinkPreview diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 4dd20362..1f6d0325 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -31,9 +31,7 @@ </router-link> </div> <div class="status-body"> - <div class="usercard" v-if="userExpanded"> - <user-card-content :user="status.user" :switcher="false"></user-card-content> - </div> + <UserCard :user="status.user" :rounded="true" :bordered="true" class="status-usercard" v-if="userExpanded"/> <div v-if="!noHeading" class="media-heading"> <div class="heading-name-row"> <div class="name-and-account-name"> @@ -248,8 +246,7 @@ $status-margin: 0.75em; padding: 0; } - .usercard { - margin: 0; + .status-usercard { margin-bottom: $status-margin; } diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 655bfb3f..c45f8947 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -132,7 +132,9 @@ const Timeline = { } if (count > 0) { // only 'stream' them when you're scrolled to the top - if (window.pageYOffset < 15 && + const doc = document.documentElement + const top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0) + if (top < 15 && !this.paused && !(this.unfocused && this.$store.state.config.pauseOnUnfocused) ) { diff --git a/src/components/user_card_content/user_card_content.js b/src/components/user_card/user_card.js index 7a7b89d4..80d15a27 100644 --- a/src/components/user_card_content/user_card_content.js +++ b/src/components/user_card/user_card.js @@ -4,7 +4,7 @@ import { requestFollow, requestUnfollow } from '../../services/follow_manipulate import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' export default { - props: [ 'user', 'switcher', 'selected', 'hideBio' ], + props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered' ], data () { return { followRequestInProgress: false, @@ -16,7 +16,14 @@ export default { } }, computed: { - headingStyle () { + classes () { + return [{ + 'user-card-rounded-t': this.rounded === 'top', // set border-top-left-radius and border-top-right-radius + 'user-card-rounded': this.rounded === true, // set border-radius for all sides + 'user-card-bordered': this.bordered === true // set border for all sides + }] + }, + style () { const color = this.$store.state.config.customTheme.colors ? this.$store.state.config.customTheme.colors.bg // v2 : this.$store.state.config.colors.bg // v1 @@ -93,22 +100,30 @@ export default { }, methods: { followUser () { + const store = this.$store this.followRequestInProgress = true - requestFollow(this.user, this.$store).then(({sent}) => { + requestFollow(this.user, store).then(({sent}) => { this.followRequestInProgress = false this.followRequestSent = sent }) }, unfollowUser () { + const store = this.$store this.followRequestInProgress = true - requestUnfollow(this.user, this.$store).then(() => { + requestUnfollow(this.user, store).then(() => { this.followRequestInProgress = false + store.commit('removeStatus', { timeline: 'friends', userId: this.user.id }) }) }, blockUser () { const store = this.$store store.state.api.backendInteractor.blockUser(this.user.id) - .then((blockedUser) => store.commit('addNewUsers', [blockedUser])) + .then((blockedUser) => { + store.commit('addNewUsers', [blockedUser]) + store.commit('removeStatus', { timeline: 'friends', userId: this.user.id }) + store.commit('removeStatus', { timeline: 'public', userId: this.user.id }) + store.commit('removeStatus', { timeline: 'publicAndExternal', userId: this.user.id }) + }) }, unblockUser () { const store = this.$store diff --git a/src/components/user_card_content/user_card_content.vue b/src/components/user_card/user_card.vue index 702c3385..690e1bde 100644 --- a/src/components/user_card_content/user_card_content.vue +++ b/src/components/user_card/user_card.vue @@ -1,6 +1,6 @@ <template> -<div id="heading" class="profile-panel-background" :style="headingStyle"> - <div class="panel-heading text-center"> +<div class="user-card" :class="classes" :style="style"> + <div class="panel-heading"> <div class='user-info'> <div class='container'> <router-link :to="userProfileLink(user)"> @@ -11,7 +11,7 @@ <div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div> <div :title="user.name" class='user-name' v-else>{{user.name}}</div> <router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser"> - <i class="button-icon icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i> + <i class="button-icon icon-pencil usersettings" :title="$t('tool_tip.user_settings')"></i> </router-link> <a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser && !user.is_local"> <i class="icon-link-ext usersettings"></i> @@ -108,7 +108,7 @@ </div> </div> </div> - <div class="panel-body profile-panel-body" v-if="!hideBio"> + <div class="panel-body" v-if="!hideBio"> <div v-if="!hideUserStatsLocal && switcher" class="user-counts"> <div class="user-count" v-on:click.prevent="setProfileView('statuses')"> <h5>{{ $t('user_card.statuses') }}</h5> @@ -123,40 +123,75 @@ <span>{{user.followers_count}}</span> </div> </div> - <p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="profile-bio" v-html="user.description_html"></p> - <p v-else-if="!hideBio" class="profile-bio">{{ user.description }}</p> + <p @click.prevent="linkClicked" v-if="!hideBio && user.description_html" class="user-card-bio" v-html="user.description_html"></p> + <p v-else-if="!hideBio" class="user-card-bio">{{ user.description }}</p> </div> </div> </template> -<script src="./user_card_content.js"></script> +<script src="./user_card.js"></script> <style lang="scss"> @import '../../_variables.scss'; -.profile-panel-background { +.user-card { background-size: cover; - border-radius: $fallback--panelRadius; - border-radius: var(--panelRadius, $fallback--panelRadius); overflow: hidden; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - .panel-heading { padding: .5em 0; text-align: center; box-shadow: none; + background: transparent; + flex-direction: column; + align-items: stretch; } -} -.profile-panel-body { - 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%); + .panel-body { + 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%); + } + + p { + margin-bottom: 0; + } - .profile-bio { + &-bio { text-align: center; + + img { + object-fit: contain; + vertical-align: middle; + max-width: 100%; + max-height: 400px; + + .emoji { + width: 32px; + height: 32px; + } + } + } + + // Modifiers + + &-rounded-t { + border-top-left-radius: $fallback--panelRadius; + border-top-left-radius: var(--panelRadius, $fallback--panelRadius); + border-top-right-radius: $fallback--panelRadius; + border-top-right-radius: var(--panelRadius, $fallback--panelRadius); + } + + &-rounded { + border-radius: $fallback--panelRadius; + border-radius: var(--panelRadius, $fallback--panelRadius); + } + + &-bordered { + border-width: 1px; + border-style: solid; + border-color: $fallback--border; + border-color: var(--border, $fallback--border); } } @@ -393,25 +428,4 @@ text-decoration: none; } } - -.usercard { - width: fill-available; - 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; - - .panel-heading { - background: transparent; - flex-direction: column; - align-items: stretch; - } - - p { - margin-bottom: 0; - } -} </style> diff --git a/src/components/user_panel/user_panel.js b/src/components/user_panel/user_panel.js index 15804b88..d4478290 100644 --- a/src/components/user_panel/user_panel.js +++ b/src/components/user_panel/user_panel.js @@ -1,6 +1,6 @@ import LoginForm from '../login_form/login_form.vue' import PostStatusForm from '../post_status_form/post_status_form.vue' -import UserCardContent from '../user_card_content/user_card_content.vue' +import UserCard from '../user_card/user_card.vue' const UserPanel = { computed: { @@ -9,7 +9,7 @@ const UserPanel = { components: { LoginForm, PostStatusForm, - UserCardContent + UserCard } } diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue index 2d5cb500..8310f30e 100644 --- a/src/components/user_panel/user_panel.vue +++ b/src/components/user_panel/user_panel.vue @@ -1,7 +1,7 @@ <template> <div class="user-panel"> <div v-if='user' class="panel panel-default" style="overflow: visible;"> - <user-card-content :user="user" :switcher="false" :hideBio="true"></user-card-content> + <UserCard :user="user" :hideBio="true" rounded="top"/> <div class="panel-footer"> <post-status-form v-if='user'></post-status-form> </div> @@ -11,13 +11,3 @@ </template> <script src="./user_panel.js"></script> - -<style lang="scss"> -.user-panel { - .profile-panel-background .panel-heading { - background: transparent; - flex-direction: column; - align-items: stretch; - } -} -</style> diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index cdf1cee9..54126514 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -1,6 +1,6 @@ import { compose } from 'vue-compose' import get from 'lodash/get' -import UserCardContent from '../user_card_content/user_card_content.vue' +import UserCard from '../user_card/user_card.vue' import FollowCard from '../follow_card/follow_card.vue' import Timeline from '../timeline/timeline.vue' import withLoadMore from '../../hocs/with_load_more/with_load_more' @@ -147,7 +147,7 @@ const UserProfile = { } }, components: { - UserCardContent, + UserCard, Timeline, FollowerList, FriendList diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 8090efa5..7d4a8b1f 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -1,11 +1,7 @@ <template> <div> <div v-if="user.id" class="user-profile panel panel-default"> - <user-card-content - :user="user" - :switcher="true" - :selected="timeline.viewing" - /> + <UserCard :user="user" :switcher="true" :selected="timeline.viewing" rounded="top"/> <tab-switcher :renderOnlyFocused="true" ref="tabSwitcher"> <Timeline :label="$t('user_card.statuses')" @@ -64,11 +60,6 @@ flex: 2; flex-basis: 500px; - .profile-panel-background .panel-heading { - background: transparent; - flex-direction: column; - align-items: stretch; - } .userlist-placeholder { display: flex; justify-content: center; diff --git a/src/i18n/cs.json b/src/i18n/cs.json index 6326032c..51e9d342 100644 --- a/src/i18n/cs.json +++ b/src/i18n/cs.json @@ -71,7 +71,9 @@ "account_not_locked_warning_link": "uzamčen", "attachments_sensitive": "Označovat přílohy jako citlivé", "content_type": { - "plain_text": "Prostý text" + "plain_text": "Prostý text", + "text/html": "HTML", + "text/markdown": "Markdown" }, "content_warning": "Předmět (volitelný)", "default": "Právě jsem přistál v L.A.", @@ -95,7 +97,7 @@ "new_captcha": "Kliknutím na obrázek získáte novou CAPTCHA", "username_placeholder": "např. lain", "fullname_placeholder": "např. Lain Iwakura", - "bio_placeholder": "např.\nNazdar, jsem Lain\nJsem anime dívka a žiji v příměstském Japonsku. Možná mě znáte z Wired.", + "bio_placeholder": "např.\nNazdar, jsem Lain\nJsem anime dívka žijící v příměstském Japonsku. Možná mě znáte z Wired.", "validations": { "username_required": "nemůže být prázdné", "fullname_required": "nemůže být prázdné", @@ -204,7 +206,7 @@ "radii_help": "Nastavit zakulacení rohů rozhraní (v pixelech)", "replies_in_timeline": "Odpovědi v časové ose", "reply_link_preview": "Povolit náhledy odkazu pro odpověď při přejetí myši", - "reply_visibility_all": "Zobrazit všechny odpovědiShow all replies", + "reply_visibility_all": "Zobrazit všechny odpovědi", "reply_visibility_following": "Zobrazit pouze odpovědi směřované na mě nebo uživatele, které sleduji", "reply_visibility_self": "Zobrazit pouze odpovědi směřované na mě", "saving_err": "Chyba při ukládání nastavení", @@ -221,7 +223,6 @@ "subject_line_mastodon": "Jako u Mastodonu: zkopírovat tak, jak je", "subject_line_noop": "Nekopírovat", "post_status_content_type": "Publikovat typ obsahu příspěvku", - "status_content_type_plain": "Prostý text", "stop_gifs": "Přehrávat GIFy při přejetí myši", "streaming": "Povolit automatické streamování nových příspěvků při rolování nahoru", "text": "Text", @@ -339,7 +340,7 @@ "button": "Tlačítko", "text": "Spousta dalšího {0} a {1}", "mono": "obsahu", - "input": "Just landed in L.A.", + "input": "Právě jsem přistál v L.A.", "faint_link": "pomocný manuál", "fine_print": "Přečtěte si náš {0} a nenaučte se nic užitečného!", "header_faint": "Tohle je v pohodě", @@ -361,7 +362,7 @@ "no_statuses": "Žádné příspěvky" }, "status": { - "reply_to": "Odpovědět uživateli", + "reply_to": "Odpověď uživateli", "replies_list": "Odpovědi:" }, @@ -413,7 +414,7 @@ "upload":{ "error": { "base": "Nahrávání selhalo.", - "file_too_big": "Soubor je úříliš velký [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", + "file_too_big": "Soubor je příliš velký [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]", "default": "Zkuste to znovu později" }, "file_size_units": { diff --git a/src/i18n/en.json b/src/i18n/en.json index c5a4a90d..01fe2fba 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -71,7 +71,9 @@ "account_not_locked_warning_link": "locked", "attachments_sensitive": "Mark attachments as sensitive", "content_type": { - "plain_text": "Plain text" + "text/plain": "Plain text", + "text/html": "HTML", + "text/markdown": "Markdown" }, "content_warning": "Subject (optional)", "default": "Just landed in L.A.", @@ -221,7 +223,6 @@ "subject_line_mastodon": "Like mastodon: copy as is", "subject_line_noop": "Do not copy", "post_status_content_type": "Post status content type", - "status_content_type_plain": "Plain text", "stop_gifs": "Play-on-hover GIFs", "streaming": "Enable automatic streaming of new posts when scrolled to the top", "text": "Text", diff --git a/src/i18n/eo.json b/src/i18n/eo.json index 2438b4d5..34851a44 100644 --- a/src/i18n/eo.json +++ b/src/i18n/eo.json @@ -221,7 +221,6 @@ "subject_line_mastodon": "Kiel Mastodon: kopii senŝanĝe", "subject_line_noop": "Ne kopii", "post_status_content_type": "Afiŝi specon de la enhavo de la stato", - "status_content_type_plain": "Plata teksto", "stop_gifs": "Movi GIF-bildojn dum musa ŝvebo", "streaming": "Ŝalti memfaran fluigon de novaj afiŝoj ĉe la supro de la paĝo", "text": "Teksto", diff --git a/src/i18n/es.json b/src/i18n/es.json index 167e8c42..fe96dd08 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -202,7 +202,6 @@ "subject_line_mastodon": "Tipo mastodon: copiar como es", "subject_line_noop": "No copiar", "post_status_content_type": "Formato de publicación", - "status_content_type_plain": "Texto plano", "stop_gifs": "Iniciar GIFs al pasar el ratón", "streaming": "Habilite la transmisión automática de nuevas publicaciones cuando se desplaza hacia la parte superior", "text": "Texto", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index b51fa7fd..f39a5a7c 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -202,7 +202,6 @@ "subject_line_mastodon": "マストドンふう: そのままコピー", "subject_line_noop": "コピーしない", "post_status_content_type": "とうこうのコンテントタイプ", - "status_content_type_plain": "プレーンテキスト", "stop_gifs": "カーソルをかさねたとき、GIFをうごかす", "streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする", "text": "もじ", diff --git a/src/i18n/oc.json b/src/i18n/oc.json index ef32f83b..fd5ccc97 100644 --- a/src/i18n/oc.json +++ b/src/i18n/oc.json @@ -221,7 +221,6 @@ "subject_line_mastodon": "Coma mastodon : copiar tal coma es", "subject_line_noop": "Copiar pas", "post_status_content_type": "Publicar lo tipe de contengut dels estatuts", - "status_content_type_plain": "Tèxte brut", "stop_gifs": "Lançar los GIFs al subrevòl", "streaming": "Activar lo cargament automatic dels novèls estatus en anar amont", "text": "Tèxt", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 39ff6c63..cbc2c9a3 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -221,7 +221,6 @@ "subject_line_mastodon": "Como o Mastodon: copiar como está", "subject_line_noop": "Não copiar", "post_status_content_type": "Postar tipo de conteúdo do status", - "status_content_type_plain": "Texto puro", "stop_gifs": "Reproduzir GIFs ao passar o cursor em cima", "streaming": "Habilitar o fluxo automático de postagens quando ao topo da página", "text": "Texto", diff --git a/src/modules/chat.js b/src/modules/chat.js index 383ac75c..2804e577 100644 --- a/src/modules/chat.js +++ b/src/modules/chat.js @@ -1,12 +1,16 @@ const chat = { state: { messages: [], - channel: {state: ''} + channel: {state: ''}, + socket: null }, mutations: { setChannel (state, channel) { state.channel = channel }, + setSocket (state, socket) { + state.socket = socket + }, addMessage (state, message) { state.messages.push(message) state.messages = state.messages.slice(-19, 20) @@ -16,8 +20,12 @@ const chat = { } }, actions: { + disconnectFromChat (store) { + store.state.socket.disconnect() + }, initializeChat (store, socket) { const channel = socket.channel('chat:public') + store.commit('setSocket', socket) channel.on('new_msg', (msg) => { store.commit('addMessage', msg) }) diff --git a/src/modules/instance.js b/src/modules/instance.js index c31d02b9..24c52f9c 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -37,6 +37,7 @@ const defaultState = { emoji: [], customEmoji: [], restrictedNicknames: [], + postFormats: [], // Feature-set, apparently, not everything here is reported... mediaProxyAvailable: false, diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 96a3549d..6b512fa3 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -1,4 +1,4 @@ -import { remove, slice, each, find, maxBy, minBy, merge, last, isArray } from 'lodash' +import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray } from 'lodash' import apiService from '../services/api/api.service.js' // import parse from '../services/status_parser/status_parser.js' @@ -19,7 +19,7 @@ const emptyTl = (userId = 0) => ({ flushMarker: 0 }) -export const defaultState = { +export const defaultState = () => ({ allStatuses: [], allStatusesObject: {}, maxId: 0, @@ -30,7 +30,8 @@ export const defaultState = { data: [], idStore: {}, loading: false, - error: false + error: false, + fetcherId: null }, favorites: new Set(), error: false, @@ -45,7 +46,7 @@ export const defaultState = { tag: emptyTl(), dms: emptyTl() } -} +}) export const prepareStatus = (status) => { // Set deleted flag @@ -312,18 +313,39 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot }) } +const removeStatus = (state, { timeline, userId }) => { + const timelineObject = state.timelines[timeline] + if (userId) { + remove(timelineObject.statuses, { user: { id: userId } }) + remove(timelineObject.visibleStatuses, { user: { id: userId } }) + timelineObject.minVisibleId = timelineObject.visibleStatuses.length > 0 ? last(timelineObject.visibleStatuses).id : 0 + timelineObject.maxId = timelineObject.statuses.length > 0 ? first(timelineObject.statuses).id : 0 + } +} + export const mutations = { addNewStatuses, addNewNotifications, + removeStatus, showNewStatuses (state, { timeline }) { const oldTimeline = (state.timelines[timeline]) oldTimeline.newStatusCount = 0 oldTimeline.visibleStatuses = slice(oldTimeline.statuses, 0, 50) oldTimeline.minVisibleId = last(oldTimeline.visibleStatuses).id + oldTimeline.minId = oldTimeline.minVisibleId oldTimeline.visibleStatusesObject = {} each(oldTimeline.visibleStatuses, (status) => { oldTimeline.visibleStatusesObject[status.id] = status }) }, + setNotificationFetcher (state, { fetcherId }) { + state.notifications.fetcherId = fetcherId + }, + resetStatuses (state) { + const emptyState = defaultState() + Object.entries(emptyState).forEach(([key, value]) => { + state[key] = value + }) + }, clearTimeline (state, { timeline }) { state.timelines[timeline] = emptyTl(state.timelines[timeline].userId) }, @@ -374,7 +396,7 @@ export const mutations = { } const statuses = { - state: defaultState, + state: defaultState(), actions: { addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId }) { commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId }) @@ -394,6 +416,12 @@ const statuses = { setNotificationsSilence ({ rootState, commit }, { value }) { commit('setNotificationsSilence', { value }) }, + stopFetchingNotifications ({ rootState, commit }) { + if (rootState.statuses.notifications.fetcherId) { + window.clearInterval(rootState.statuses.notifications.fetcherId) + } + commit('setNotificationFetcher', { fetcherId: null }) + }, deleteStatus ({ rootState, commit }, status) { commit('setDeleted', { status }) apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials }) diff --git a/src/modules/users.js b/src/modules/users.js index 093af497..26884750 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -292,9 +292,12 @@ const users = { logout (store) { store.commit('clearCurrentUser') + store.dispatch('disconnectFromChat') store.commit('setToken', false) store.dispatch('stopFetching', 'friends') store.commit('setBackendInteractor', backendInteractorService()) + store.dispatch('stopFetchingNotifications') + store.commit('resetStatuses') }, loginUser (store, accessToken) { return new Promise((resolve, reject) => { @@ -319,6 +322,9 @@ const users = { if (user.token) { store.dispatch('setWsToken', user.token) + + // Initialize the chat socket. + store.dispatch('initializeSocket') } // Start getting fresh posts. |
