diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f601a5..056a0881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,16 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added - New option to optimize timeline rendering to make the site more responsive (enabled by default) +- New instance option `logoLeft` to move logo to the left side in desktop nav bar +- Import/export a muted users +- Proper handling of deletes when using websocket streaming +- Added optimistic chat message sending, so you can start writing next message before the previous one has been sent -## [Unreleased patch] ### Fixed - Fixed chats list not updating its order when new messages come in - Fixed chat messages sometimes getting lost when you receive a message at the same time - Fixed clicking NSFW hider through status popover +- Fixed chat-view back button being hard to click +- Fixed fresh chat notifications being cleared immediately while leaving the chat view and not having time to actually see the messages +- Fixed multiple regressions in CSS styles +- Fixed multiple issues with input fields when using CJK font as default +- Fixed search field in navbar infringing into logo in some cases +- Fixed not being able to load the chat history in vertical screens when the message list doesn't take the full height of the scrollable container on the first fetch. -### Added -- Import/export a muted users -- Proper handling of deletes when using websocket streaming +### Changed +- Clicking immediately when timeline shifts is now blocked to prevent misclicks +- Icons changed from fontello (FontAwesome 4 + others) to FontAwesome 5 due to problems with fontello. +- Some icons changed for better accessibility (lock, globe) +- Logo is now clickable +- Changed default logo to SVG version ## [2.1.1] - 2020-09-08 ### Changed diff --git a/src/App.js b/src/App.js index 2cc9a499..768c406d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,6 @@ import UserPanel from './components/user_panel/user_panel.vue' import NavPanel from './components/nav_panel/nav_panel.vue' import Notifications from './components/notifications/notifications.vue' -import SearchBar from './components/search_bar/search_bar.vue' import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue' import FeaturesPanel from './components/features_panel/features_panel.vue' import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue' @@ -12,6 +11,7 @@ import MediaModal from './components/media_modal/media_modal.vue' import SideDrawer from './components/side_drawer/side_drawer.vue' import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' import MobileNav from './components/mobile_nav/mobile_nav.vue' +import DesktopNav from './components/desktop_nav/desktop_nav.vue' import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' import PostStatusModal from './components/post_status_modal/post_status_modal.vue' import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue' @@ -23,7 +23,6 @@ export default { UserPanel, NavPanel, Notifications, - SearchBar, InstanceSpecificPanel, FeaturesPanel, WhoToFollowPanel, @@ -33,22 +32,14 @@ export default { SideDrawer, MobilePostStatusButton, MobileNav, + DesktopNav, SettingsModal, UserReportingModal, PostStatusModal, GlobalNoticeList }, data: () => ({ - mobileActivePanel: 'timeline', - searchBarHidden: true, - supportsMask: window.CSS && window.CSS.supports && ( - window.CSS.supports('mask-size', 'contain') || - window.CSS.supports('-webkit-mask-size', 'contain') || - window.CSS.supports('-moz-mask-size', 'contain') || - window.CSS.supports('-ms-mask-size', 'contain') || - window.CSS.supports('-o-mask-size', 'contain') - ), - transitionName: 'fade' + mobileActivePanel: 'timeline' }), created () { // Load the locale from the storage @@ -64,28 +55,6 @@ export default { background () { return this.currentUser.background_image || this.$store.state.instance.background }, - enableMask () { return this.supportsMask && this.$store.state.instance.logoMask }, - logoStyle () { - return { - 'visibility': this.enableMask ? 'hidden' : 'visible' - } - }, - logoMaskStyle () { - return this.enableMask ? { - 'mask-image': `url(${this.$store.state.instance.logo})` - } : { - 'background-color': this.enableMask ? '' : 'transparent' - } - }, - logoBgStyle () { - return Object.assign({ - 'margin': `${this.$store.state.instance.logoMargin} 0`, - opacity: this.searchBarHidden ? 1 : 0 - }, this.enableMask ? {} : { - 'background-color': this.enableMask ? '' : 'transparent' - }) - }, - logo () { return this.$store.state.instance.logo }, bgStyle () { return { 'background-image': `url(${this.background})` @@ -96,9 +65,7 @@ export default { '--body-background-image': `url(${this.background})` } }, - sitename () { return this.$store.state.instance.name }, chat () { return this.$store.state.chat.channel.state === 'joined' }, - hideSitename () { return this.$store.state.instance.hideSitename }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel && @@ -123,19 +90,6 @@ export default { } }, methods: { - scrollToTop () { - window.scrollTo(0, 0) - }, - logout () { - this.$router.replace('/main/public') - this.$store.dispatch('logout') - }, - onSearchBarToggled (hidden) { - this.searchBarHidden = hidden - }, - openSettingsModal () { - this.$store.dispatch('openSettingsModal') - }, updateMobileState () { const mobileLayout = windowWidth() <= 800 const layoutHeight = windowHeight() diff --git a/src/App.scss b/src/App.scss index 2e6107a0..0bcf281a 100644 --- a/src/App.scss +++ b/src/App.scss @@ -359,119 +359,10 @@ i[class*=icon-], .svg-inline--fa { padding: 0 10px 0 10px; } -.item { - flex: 1; - line-height: 50px; - height: 50px; - overflow: hidden; - display: flex; - flex-wrap: wrap; - - .nav-icon { - margin-left: 0.2em; - width: 2em; - text-align: center; - } - - &.right { - justify-content: flex-end; - } -} - .auto-size { flex: 1 } -.nav-bar { - padding: 0; - width: 100%; - align-items: center; - position: fixed; - height: 50px; - box-sizing: border-box; - - button { - &, i[class*=icon-], svg { - color: $fallback--text; - color: var(--btnTopBarText, $fallback--text); - } - - &:active { - background-color: $fallback--fg; - background-color: var(--btnPressedTopBar, $fallback--fg); - color: $fallback--text; - color: var(--btnPressedTopBarText, $fallback--text); - } - - &:disabled { - color: $fallback--text; - color: var(--btnDisabledTopBarText, $fallback--text); - } - - &.toggled { - color: $fallback--text; - color: var(--btnToggledTopBarText, $fallback--text); - background-color: $fallback--fg; - background-color: var(--btnToggledTopBar, $fallback--fg) - } - } - - - .logo { - display: flex; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - - align-items: stretch; - justify-content: center; - flex: 0 0 auto; - z-index: -1; - transition: opacity; - transition-timing-function: ease-out; - transition-duration: 100ms; - - .mask { - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - background-color: $fallback--fg; - background-color: var(--topBarText, $fallback--fg); - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - } - - img { - height: 100%; - object-fit: contain; - display: block; - flex: 0; - } - } - - .inner-nav { - position: relative; - margin: auto; - box-sizing: border-box; - padding-left: 10px; - padding-right: 10px; - display: flex; - align-items: center; - flex-basis: 970px; - height: 50px; - - a, a i, a svg { - color: $fallback--link; - color: var(--topBarLink, $fallback--link); - } - } -} - main-router { flex: 1; } @@ -712,19 +603,24 @@ nav { flex-grow: 0; } } + .badge { + box-sizing: border-box; display: inline-block; border-radius: 99px; - min-width: 22px; - max-width: 22px; - min-height: 22px; - max-height: 22px; - font-size: 15px; - line-height: 22px; - text-align: center; + max-width: 10em; + min-width: 1.7em; + height: 1.3em; + padding: 0.15em 0.15em; vertical-align: middle; + font-weight: normal; + font-style: normal; + font-size: 0.9em; + line-height: 1; + text-align: center; white-space: nowrap; - padding: 0; + overflow: hidden; + text-overflow: ellipsis; &.badge-notification { background-color: $fallback--cRed; @@ -781,16 +677,6 @@ nav { } } -@media all and (min-width: 800px) { - .logo { - opacity: 1 !important; - } -} - -.item.right { - text-align: right; -} - .visibility-notice { padding: .5em; border: 1px solid $fallback--faint; @@ -943,22 +829,6 @@ nav { background-color: var(--panel, $fallback--fg); } -.unread-chat-count { - font-size: 0.9em; - font-weight: bolder; - font-style: normal; - position: absolute; - right: 0.6rem; - padding: 0 0.3em; - min-width: 1.3rem; - min-height: 1.3rem; - max-height: 1.3rem; - line-height: 1.3rem; - max-width: 10em; - overflow: hidden; - text-overflow: ellipsis; -} - .chat-layout { // Needed for smoother chat navigation in the desktop Safari (otherwise the chat layout "jumps" as the chat opens). overflow: hidden; diff --git a/src/App.vue b/src/App.vue index 89a3978d..574fa8f5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -9,80 +9,7 @@ :style="bgStyle" /> - +
{ ? 0 : config.logoMargin }) + copyInstanceOption('logoLeft') store.commit('authFlow/setInitialStrategy', config.loginMethod) copyInstanceOption('redirectRootNoLogin') diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js index 083f850f..e57fcb91 100644 --- a/src/components/chat/chat.js +++ b/src/components/chat/chat.js @@ -6,12 +6,13 @@ import PostStatusForm from '../post_status_form/post_status_form.vue' import ChatTitle from '../chat_title/chat_title.vue' import chatService from '../../services/chat_service/chat_service.js' import { promiseInterval } from '../../services/promise_interval/promise_interval.js' -import { getScrollPosition, getNewTopPosition, isBottomedOut, scrollableContainerHeight } from './chat_layout_utils.js' +import { getScrollPosition, getNewTopPosition, isBottomedOut, scrollableContainerHeight, isScrollable } from './chat_layout_utils.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons' +import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js' library.add( faChevronDown, @@ -21,6 +22,8 @@ library.add( const BOTTOMED_OUT_OFFSET = 10 const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 150 const SAFE_RESIZE_TIME_OFFSET = 100 +const MARK_AS_READ_DELAY = 1500 +const MAX_RETRIES = 10 const Chat = { components: { @@ -34,7 +37,8 @@ const Chat = { hoveredMessageChainId: undefined, lastScrollPosition: {}, scrollableContainerHeight: '100%', - errorLoadingChat: false + errorLoadingChat: false, + messageRetriers: {} } }, created () { @@ -104,7 +108,7 @@ const Chat = { const bottomedOutBeforeUpdate = this.bottomedOut(BOTTOMED_OUT_OFFSET) this.$nextTick(() => { if (bottomedOutBeforeUpdate) { - this.scrollDown({ forceRead: !document.hidden }) + this.scrollDown() } }) }, @@ -210,7 +214,7 @@ const Chat = { this.$nextTick(() => { scrollable.scrollTo({ top: scrollable.scrollHeight, left: 0, behavior }) }) - if (forceRead || this.newMessageCount > 0) { + if (forceRead) { this.readChat() } }, @@ -218,7 +222,10 @@ const Chat = { if (!(this.currentChatMessageService && this.currentChatMessageService.maxId)) { return } if (document.hidden) { return } const lastReadId = this.currentChatMessageService.maxId - this.$store.dispatch('readChat', { id: this.currentChat.id, lastReadId }) + this.$store.dispatch('readChat', { + id: this.currentChat.id, + lastReadId + }) }, bottomedOut (offset) { return isBottomedOut(this.$refs.scrollable, offset) @@ -235,12 +242,18 @@ const Chat = { } else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) { this.jumpToBottomButtonVisible = false if (this.newMessageCount > 0) { - this.readChat() + // Use a delay before marking as read to prevent situation where new messages + // arrive just as you're leaving the view and messages that you didn't actually + // get to see get marked as read. + window.setTimeout(() => { + // Don't mark as read if the element doesn't exist, user has left chat view + if (this.$el) this.readChat() + }, MARK_AS_READ_DELAY) } } else { this.jumpToBottomButtonVisible = true } - }, 100), + }, 200), handleScrollUp (positionBeforeLoading) { const positionAfterLoading = getScrollPosition(this.$refs.scrollable) this.$refs.scrollable.scrollTo({ @@ -274,6 +287,14 @@ const Chat = { if (isFirstFetch) { this.updateScrollableContainerHeight() } + + // In vertical screens, the first batch of fetched messages may not always take the + // full height of the scrollable container. + // If this is the case, we want to fetch the messages until the scrollable container + // is fully populated so that the user has the ability to scroll up and load the history. + if (!isScrollable(this.$refs.scrollable) && messages.length > 0) { + this.fetchChat({ maxId: this.currentChatMessageService.minId }) + } }) }) }) @@ -302,42 +323,74 @@ const Chat = { }) this.fetchChat({ isFirstFetch: true }) }, - sendMessage ({ status, media }) { + handleAttachmentPosting () { + this.$nextTick(() => { + this.handleResize() + // When the posting form size changes because of a media attachment, we need an extra resize + // to account for the potential delay in the DOM update. + setTimeout(() => { + this.updateScrollableContainerHeight() + }, SAFE_RESIZE_TIME_OFFSET) + this.scrollDown({ forceRead: true }) + }) + }, + sendMessage ({ status, media, idempotencyKey }) { const params = { id: this.currentChat.id, - content: status + content: status, + idempotencyKey } if (media[0]) { params.mediaId = media[0].id } - return this.backendInteractor.sendChatMessage(params) + const fakeMessage = buildFakeMessage({ + attachments: media, + chatId: this.currentChat.id, + content: status, + userId: this.currentUser.id, + idempotencyKey + }) + + this.$store.dispatch('addChatMessages', { + chatId: this.currentChat.id, + messages: [fakeMessage] + }).then(() => { + this.handleAttachmentPosting() + }) + + return this.doSendMessage({ params, fakeMessage, retriesLeft: MAX_RETRIES }) + }, + doSendMessage ({ params, fakeMessage, retriesLeft = MAX_RETRIES }) { + if (retriesLeft <= 0) return + + this.backendInteractor.sendChatMessage(params) .then(data => { this.$store.dispatch('addChatMessages', { chatId: this.currentChat.id, - messages: [data], - updateMaxId: false - }).then(() => { - this.$nextTick(() => { - this.handleResize() - // When the posting form size changes because of a media attachment, we need an extra resize - // to account for the potential delay in the DOM update. - setTimeout(() => { - this.updateScrollableContainerHeight() - }, SAFE_RESIZE_TIME_OFFSET) - this.scrollDown({ forceRead: true }) - }) + updateMaxId: false, + messages: [{ ...data, fakeId: fakeMessage.id }] }) return data }) .catch(error => { console.error('Error sending message', error) - return { - error: this.$t('chats.error_sending_message') + this.$store.dispatch('handleMessageError', { + chatId: this.currentChat.id, + fakeId: fakeMessage.id, + isRetry: retriesLeft !== MAX_RETRIES + }) + if ((error.statusCode >= 500 && error.statusCode < 600) || error.message === 'Failed to fetch') { + this.messageRetriers[fakeMessage.id] = setTimeout(() => { + this.doSendMessage({ params, fakeMessage, retriesLeft: retriesLeft - 1 }) + }, 1000 * (2 ** (MAX_RETRIES - retriesLeft))) } + return {} }) + + return Promise.resolve(fakeMessage) }, goBack () { this.$router.push({ name: 'chats', params: { username: this.currentUser.screen_name } }) diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss index b7b0d377..aef58495 100644 --- a/src/components/chat/chat.scss +++ b/src/components/chat/chat.scss @@ -25,7 +25,7 @@ min-height: 100%; margin: 0 0 0 0; border-radius: 10px 10px 0 0; - border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0 ; + border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0; &::after { border-radius: 0; @@ -58,8 +58,10 @@ .go-back-button { cursor: pointer; - margin-right: 1.7em; - margin-left: 0.3em; + width: 28px; + text-align: center; + padding: 0.6em; + margin: -0.6em 0.6em -0.6em -0.6em; } .jump-to-bottom-button { @@ -74,7 +76,7 @@ display: flex; justify-content: center; align-items: center; - box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3), 0px 2px 4px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3); z-index: 10; transition: 0.35s all; transition-timing-function: cubic-bezier(0, 1, 0.5, 1); @@ -136,11 +138,21 @@ } .chat-view-heading { + box-sizing: border-box; position: static; z-index: 9999; top: 0; margin-top: 0; border-radius: 0; + + /* This practically overlays the panel heading color over panel background + * color. This is needed because we allow transparent panel background and + * it doesn't work well in this "disjointed panel header" case + */ + background: + linear-gradient(to top, var(--panel), var(--panel)), + linear-gradient(to top, var(--bg), var(--bg)); + height: 50px; } .scrollable-message-list { diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue index 5f58b9a6..94a0097c 100644 --- a/src/components/chat/chat.vue +++ b/src/components/chat/chat.vue @@ -80,6 +80,7 @@ :disable-sensitivity-checkbox="true" :disable-submit="errorLoadingChat || !currentChat" :disable-preview="true" + :optimistic-posting="true" :post-handler="sendMessage" :submit-on-enter="!mobileLayout" :preserve-focus="!mobileLayout" diff --git a/src/components/chat/chat_layout_utils.js b/src/components/chat/chat_layout_utils.js index 609dc0c9..50a933ac 100644 --- a/src/components/chat/chat_layout_utils.js +++ b/src/components/chat/chat_layout_utils.js @@ -24,3 +24,10 @@ export const isBottomedOut = (el, offset = 0) => { export const scrollableContainerHeight = (inner, header, footer) => { return inner.offsetHeight - header.clientHeight - footer.clientHeight } + +// Returns whether or not the scrollbar is visible. +export const isScrollable = (el) => { + if (!el) return + + return el.scrollHeight > el.clientHeight +} diff --git a/src/components/chat_list_item/chat_list_item.vue b/src/components/chat_list_item/chat_list_item.vue index 1f8ecdf6..cd3f436e 100644 --- a/src/components/chat_list_item/chat_list_item.vue +++ b/src/components/chat_list_item/chat_list_item.vue @@ -21,6 +21,12 @@ /> +
+ +
-
- -
diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss index 53ca7cce..5af744a3 100644 --- a/src/components/chat_message/chat_message.scss +++ b/src/components/chat_message/chat_message.scss @@ -101,6 +101,19 @@ } } + .pending { + .status-content.media-body, .created-at { + color: var(--faint); + } + } + + .error { + .status-content.media-body, .created-at { + color: $fallback--cRed; + color: var(--badgeNotification, $fallback--cRed); + } + } + .incoming { a { color: var(--chatMessageIncomingLink, $fallback--link); diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue index d5b8bb9e..3849ab6e 100644 --- a/src/components/chat_message/chat_message.vue +++ b/src/components/chat_message/chat_message.vue @@ -32,7 +32,7 @@ >
({ + searchBarHidden: true, + supportsMask: window.CSS && window.CSS.supports && ( + window.CSS.supports('mask-size', 'contain') || + window.CSS.supports('-webkit-mask-size', 'contain') || + window.CSS.supports('-moz-mask-size', 'contain') || + window.CSS.supports('-ms-mask-size', 'contain') || + window.CSS.supports('-o-mask-size', 'contain') + ) + }), + computed: { + enableMask () { return this.supportsMask && this.$store.state.instance.logoMask }, + logoStyle () { + return { + 'visibility': this.enableMask ? 'hidden' : 'visible' + } + }, + logoMaskStyle () { + return this.enableMask ? { + 'mask-image': `url(${this.$store.state.instance.logo})` + } : { + 'background-color': this.enableMask ? '' : 'transparent' + } + }, + logoBgStyle () { + return Object.assign({ + 'margin': `${this.$store.state.instance.logoMargin} 0`, + opacity: this.searchBarHidden ? 1 : 0 + }, this.enableMask ? {} : { + 'background-color': this.enableMask ? '' : 'transparent' + }) + }, + logo () { return this.$store.state.instance.logo }, + sitename () { return this.$store.state.instance.name }, + hideSitename () { return this.$store.state.instance.hideSitename }, + logoLeft () { return this.$store.state.instance.logoLeft }, + currentUser () { return this.$store.state.users.currentUser }, + privateMode () { return this.$store.state.instance.private } + }, + methods: { + scrollToTop () { + window.scrollTo(0, 0) + }, + logout () { + this.$router.replace('/main/public') + this.$store.dispatch('logout') + }, + onSearchBarToggled (hidden) { + this.searchBarHidden = hidden + }, + openSettingsModal () { + this.$store.dispatch('openSettingsModal') + } + } +} diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss new file mode 100644 index 00000000..028692a9 --- /dev/null +++ b/src/components/desktop_nav/desktop_nav.scss @@ -0,0 +1,112 @@ +@import '../../_variables.scss'; + +.DesktopNav { + height: 50px; + width: 100%; + position: fixed; + + .inner-nav { + display: grid; + grid-template-rows: 50px; + grid-template-columns: 2fr auto 2fr; + grid-template-areas: "sitename logo actions"; + box-sizing: border-box; + padding: 0 1.2em; + margin: auto; + max-width: 980px; + } + + &.-logoLeft { + grid-template-columns: auto 2fr 2fr; + grid-template-areas: "logo sitename actions"; + } + + button { + &, svg { + color: $fallback--text; + color: var(--btnTopBarText, $fallback--text); + } + + &:active { + background-color: $fallback--fg; + background-color: var(--btnPressedTopBar, $fallback--fg); + color: $fallback--text; + color: var(--btnPressedTopBarText, $fallback--text); + } + + &:disabled { + color: $fallback--text; + color: var(--btnDisabledTopBarText, $fallback--text); + } + + &.toggled { + color: $fallback--text; + color: var(--btnToggledTopBarText, $fallback--text); + background-color: $fallback--fg; + background-color: var(--btnToggledTopBar, $fallback--fg) + } + } + + .logo { + grid-area: logo; + position: relative; + transition: opacity; + transition-timing-function: ease-out; + transition-duration: 100ms; + + @media all and (min-width: 800px) { + opacity: 1 !important; + } + + .mask { + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + background-color: $fallback--fg; + background-color: var(--topBarText, $fallback--fg); + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } + + img { + display: inline-block; + height: 50px; + } + } + + .nav-icon { + margin-left: 0.2em; + width: 2em; + text-align: center; + } + + a, a svg { + color: $fallback--link; + color: var(--topBarLink, $fallback--link); + } + + .sitename { + grid-area: sitename; + } + + .actions { + grid-area: actions; + } + + .item { + flex: 1; + line-height: 50px; + height: 50px; + overflow: hidden; + display: flex; + flex-wrap: wrap; + + &.right { + justify-content: flex-end; + text-align: right; + } + } +} diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue new file mode 100644 index 00000000..3a6e4033 --- /dev/null +++ b/src/components/desktop_nav/desktop_nav.vue @@ -0,0 +1,79 @@ + + + + diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue index f32ea02d..a33f6e87 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -16,7 +16,6 @@ @click.prevent="muteConversation" > {{ $t("status.mute_conversation") }} @@ -27,7 +26,6 @@ @click.prevent="unmuteConversation" > {{ $t("status.unmute_conversation") }} @@ -39,7 +37,6 @@ @click="close" > {{ $t("status.pin") }} @@ -51,7 +48,6 @@ @click="close" > {{ $t("status.unpin") }} @@ -63,7 +59,6 @@ @click="close" > {{ $t("status.bookmark") }} @@ -75,7 +70,6 @@ @click="close" > {{ $t("status.unbookmark") }} @@ -87,7 +81,6 @@ @click="close" > {{ $t("status.delete") }} @@ -98,7 +91,6 @@ @click="close" > {{ $t("status.copy_link") }} @@ -109,7 +101,6 @@ diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index d2bc65ab..5304a500 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -1,55 +1,53 @@