Merge branch 'threecolumn' into shigusegubu-vue3

* threecolumn:
  Fix active popover style
  Use panel text instead of text for shoutbox icon
  Fix dropdown menu style inside panel header
  Fix phoenix sockets in dev mode
  Fix no reactivity on vuex 4 values
  fix tegulu
  heck
  teleport bread
  wide mode initial implementation + cleanup
  make chatlist header sticky
  fix random error that sometimes occurs
  cleanup & code splitting
  fix chat loading endlessly
  chats work and look a bit better
  fix main column having wild widths
  fixed tons of stuff, at least it looks normalish on desktop
  refactored how main app layout works
This commit is contained in:
Henry Jameson 2022-04-07 10:34:17 +03:00
commit 4e74e9d08c
31 changed files with 704 additions and 613 deletions

View file

@ -1,5 +1,5 @@
<template>
<div class="sidebar">
<div class="column-inner">
<instance-specific-panel v-if="showInstanceSpecificPanel" />
<staff-panel />
<terms-of-service-panel />

View file

@ -6,7 +6,7 @@ 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, isScrollable } from './chat_layout_utils.js'
import { getScrollPosition, getNewTopPosition, isBottomedOut, isScrollable } from './chat_layout_utils.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown,
@ -19,8 +19,10 @@ library.add(
faChevronLeft
)
const scroller = () => document.getElementById('content')
const BOTTOMED_OUT_OFFSET = 10
const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 150
const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10
const SAFE_RESIZE_TIME_OFFSET = 100
const MARK_AS_READ_DELAY = 1500
const MAX_RETRIES = 10
@ -46,19 +48,18 @@ const Chat = {
window.addEventListener('resize', this.handleLayoutChange)
},
mounted () {
window.addEventListener('scroll', this.handleScroll)
scroller().addEventListener('scroll', this.handleScroll)
if (typeof document.hidden !== 'undefined') {
document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
}
this.$nextTick(() => {
this.updateScrollableContainerHeight()
this.handleResize()
})
this.setChatLayout()
},
unmounted () {
window.removeEventListener('scroll', this.handleScroll)
scroller().removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleLayoutChange)
this.unsetChatLayout()
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
@ -132,7 +133,6 @@ const Chat = {
onFilesDropped () {
this.$nextTick(() => {
this.handleResize()
this.updateScrollableContainerHeight()
})
},
handleVisibilityChange () {
@ -154,10 +154,6 @@ const Chat = {
if (html) {
html.classList.add('chat-layout')
}
this.$nextTick(() => {
this.updateScrollableContainerHeight()
})
},
unsetChatLayout () {
let html = document.querySelector('html')
@ -167,17 +163,9 @@ const Chat = {
},
handleLayoutChange () {
this.$nextTick(() => {
this.updateScrollableContainerHeight()
this.scrollDown()
})
},
// Ensures the proper position of the posting form in the mobile layout (the mobile browser panel does not overlap or hide it)
updateScrollableContainerHeight () {
const header = this.$refs.header
const footer = this.$refs.footer
const inner = this.mobileLayout ? window.document.body : this.$refs.inner
this.scrollableContainerHeight = scrollableContainerHeight(inner, header, footer) + 'px'
},
// Preserves the scroll position when OSK appears or the posting form changes its height.
handleResize (opts = {}) {
const { expand = false, delayed = false } = opts
@ -190,17 +178,14 @@ const Chat = {
}
this.$nextTick(() => {
this.updateScrollableContainerHeight()
const { offsetHeight = undefined } = this.lastScrollPosition
this.lastScrollPosition = getScrollPosition(this.$refs.scrollable)
this.lastScrollPosition = getScrollPosition(scroller())
const diff = this.lastScrollPosition.offsetHeight - offsetHeight
if (diff < 0 || (!this.bottomedOut() && expand)) {
this.$nextTick(() => {
this.updateScrollableContainerHeight()
this.$refs.scrollable.scrollTo({
top: this.$refs.scrollable.scrollTop - diff,
scroller().scrollTo({
top: scroller().scrollTop - diff,
left: 0
})
})
@ -209,7 +194,7 @@ const Chat = {
},
scrollDown (options = {}) {
const { behavior = 'auto', forceRead = false } = options
const scrollable = this.$refs.scrollable
const scrollable = scroller()
if (!scrollable) { return }
this.$nextTick(() => {
scrollable.scrollTo({ top: scrollable.scrollHeight, left: 0, behavior })
@ -228,10 +213,10 @@ const Chat = {
})
},
bottomedOut (offset) {
return isBottomedOut(this.$refs.scrollable, offset)
return isBottomedOut(scroller(), offset)
},
reachedTop () {
const scrollable = this.$refs.scrollable
const scrollable = scroller()
return scrollable && scrollable.scrollTop <= 0
},
cullOlderCheck () {
@ -263,8 +248,8 @@ const Chat = {
}
}, 200),
handleScrollUp (positionBeforeLoading) {
const positionAfterLoading = getScrollPosition(this.$refs.scrollable)
this.$refs.scrollable.scrollTo({
const positionAfterLoading = getScrollPosition(scroller())
scroller().scrollTo({
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
left: 0
})
@ -285,22 +270,18 @@ const Chat = {
chatService.clear(chatMessageService)
}
const positionBeforeUpdate = getScrollPosition(this.$refs.scrollable)
const positionBeforeUpdate = getScrollPosition(scroller())
this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
this.$nextTick(() => {
if (fetchOlderMessages) {
this.handleScrollUp(positionBeforeUpdate)
}
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) {
if (!isScrollable(scroller()) && messages.length > 0) {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
}
})
@ -336,9 +317,6 @@ const Chat = {
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 })
})
},

View file

@ -1,19 +1,12 @@
.chat-view {
display: flex;
height: calc(100vh - 60px);
width: 100%;
.chat-title {
// prevents chat header jumping on when the user avatar loads
height: 28px;
}
height: 100%;
.chat-view-inner {
height: auto;
width: 100%;
overflow: visible;
display: flex;
margin: 0.5em 0.5em 0 0.5em;
}
.chat-view-body {
@ -32,11 +25,9 @@
}
}
.scrollable-message-list {
.message-list {
padding: 0 0.8em;
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
display: flex;
flex-direction: column;
}
@ -44,12 +35,14 @@
.footer {
position: sticky;
bottom: 0;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
z-index: 10;
}
.chat-view-heading {
align-items: center;
justify-content: space-between;
top: 50px;
display: flex;
z-index: 2;
position: sticky;

View file

@ -8,7 +8,7 @@
>
<div
ref="header"
class="panel-heading chat-view-heading mobile-hidden"
class="panel-heading -sticky chat-view-heading mobile-hidden"
>
<a
class="go-back-button"
@ -27,10 +27,8 @@
</div>
</div>
<div
ref="scrollable"
class="scrollable-message-list"
class="message-list"
:style="{ height: scrollableContainerHeight }"
@scroll="handleScroll"
>
<template v-if="!errorLoadingChat">
<ChatMessage

View file

@ -6,7 +6,7 @@
v-else
class="chat-list panel panel-default"
>
<div class="panel-heading">
<div class="panel-heading -sticky">
<span class="title">
{{ $t("chats.chats") }}
</span>

View file

@ -15,7 +15,7 @@
</router-link>
<RichContent
class="username"
:title="'@'+user.screen_name_ui"
:title="'@'+(user && user.screen_name_ui)"
:html="htmlTitle"
:emoji="user.emoji"
/>

View file

@ -1,9 +1,7 @@
@import '../../_variables.scss';
.DesktopNav {
height: 50px;
width: 100%;
position: fixed;
a {
color: var(--topBarLink, $fallback--link);

View file

@ -78,7 +78,8 @@ const MobileNav = {
this.$store.dispatch('logout')
},
markNotificationsAsSeen () {
this.$refs.notifications.markAsSeen()
// this.$refs.notifications.markAsSeen()
this.$store.dispatch('markNotificationsAsSeen')
},
onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
if (scrollTop + clientHeight >= scrollHeight) {

View file

@ -69,12 +69,9 @@
</div>
<div
class="mobile-notifications"
id="mobile-notifications"
@scroll="onScroll"
>
<Notifications
ref="notifications"
:no-heading="true"
/>
</div>
</div>
<SideDrawer
@ -92,12 +89,10 @@
.MobileNav {
.mobile-nav {
display: grid;
line-height: 50px;
height: 50px;
line-height: var(--navbar-height);
grid-template-rows: 50px;
grid-template-columns: 2fr auto;
width: 100%;
position: fixed;
box-sizing: border-box;
}
@ -182,7 +177,7 @@
.mobile-notifications {
margin-top: 50px;
width: 100vw;
height: calc(100vh - 50px);
height: calc(100vh - var(--navbar-height));
overflow-x: hidden;
overflow-y: scroll;

View file

@ -23,8 +23,6 @@ const Notifications = {
NotificationFilters
},
props: {
// Disables display of panel header
noHeading: Boolean,
// Disables panel styles, unread mark, potentially other notification-related actions
// meant for "Interactions" timeline
minimalMode: Boolean,
@ -65,6 +63,19 @@ const Notifications = {
loading () {
return this.$store.state.statuses.notifications.loading
},
noHeading () {
const { layoutType } = this.$store.state.interface
console.log(layoutType)
return layoutType === 'mobile'
},
teleportTarget () {
const { layoutType } = this.$store.state.interface
const map = {
wide: '#notifs-column',
mobile: '#mobile-notifications'
}
return map[layoutType] || '#notifs-sidebar'
},
notificationsToDisplay () {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
},

View file

@ -1,69 +1,71 @@
<template>
<div
:class="{ minimal: minimalMode }"
class="Notifications"
>
<div :class="mainClass">
<div
v-if="!noHeading"
class="panel-heading"
>
<div class="title">
{{ $t('notifications.notifications') }}
<span
v-if="unseenCount"
class="badge badge-notification unseen-count"
>{{ unseenCount }}</span>
</div>
<button
v-if="unseenCount"
class="button-default read-button"
@click.prevent="markAsSeen"
>
{{ $t('notifications.read') }}
</button>
<NotificationFilters />
</div>
<div class="panel-body">
<teleport :to="teleportTarget">
<div
:class="{ minimal: minimalMode }"
class="Notifications"
>
<div :class="mainClass">
<div
v-for="notification in notificationsToDisplay"
:key="notification.id"
class="notification"
:class="{&quot;unseen&quot;: !minimalMode && !notification.seen}"
v-if="!noHeading"
class="notifications-heading panel-heading -sticky"
>
<div class="notification-overlay" />
<notification :notification="notification" />
</div>
</div>
<div class="panel-footer notifications-footer">
<div
v-if="bottomedOut"
class="new-status-notification text-center faint"
>
{{ $t('notifications.no_more_notifications') }}
</div>
<button
v-else-if="!loading"
class="button-unstyled -link -fullwidth"
@click.prevent="fetchOlderNotifications()"
>
<div class="new-status-notification text-center">
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
<div class="title">
{{ $t('notifications.notifications') }}
<span
v-if="unseenCount"
class="badge badge-notification unseen-count"
>{{ unseenCount }}</span>
</div>
<button
v-if="unseenCount"
class="button-default read-button"
@click.prevent="markAsSeen"
>
{{ $t('notifications.read') }}
</button>
<NotificationFilters />
</div>
<div class="panel-body">
<div
v-for="notification in notificationsToDisplay"
:key="notification.id"
class="notification"
:class="{&quot;unseen&quot;: !minimalMode && !notification.seen}"
>
<div class="notification-overlay" />
<notification :notification="notification" />
</div>
</div>
<div class="panel-footer notifications-footer">
<div
v-if="bottomedOut"
class="new-status-notification text-center faint"
>
{{ $t('notifications.no_more_notifications') }}
</div>
<button
v-else-if="!loading"
class="button-unstyled -link -fullwidth"
@click.prevent="fetchOlderNotifications()"
>
<div class="new-status-notification text-center">
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
</div>
</button>
<div
v-else
class="new-status-notification text-center"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
</button>
<div
v-else
class="new-status-notification text-center"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
</div>
</div>
</div>
</teleport>
</template>
<script src="./notifications.js"></script>

View file

@ -91,6 +91,9 @@
flex-direction: column;
margin-top: 0.6em;
max-width: 18rem;
> * {
min-width: 0;
}
}
.form-group {

View file

@ -149,5 +149,30 @@
}
}
.button-default.dropdown-item {
&,
i[class*=icon-] {
color: $fallback--text;
color: var(--btnText, $fallback--text);
}
&:active {
background-color: $fallback--lightBg;
background-color: var(--selectedMenuPopover, $fallback--lightBg);
color: $fallback--link;
color: var(--selectedMenuPopoverText, $fallback--link);
}
&:disabled {
color: $fallback--text;
color: var(--btnDisabledText, $fallback--text);
}
&.toggled {
color: $fallback--text;
color: var(--btnToggledText, $fallback--text);
}
}
}
</style>

View file

@ -271,7 +271,10 @@ $validations-cRed: #f04124;
.container {
display: flex;
flex-direction: row;
//margin-bottom: 1em;
> * {
min-width: 0;
}
}
.terms-of-service {

View file

@ -2,6 +2,18 @@
.settings-modal {
overflow: hidden;
.setting-list,
.option-list {
list-style-type: none;
padding-left: 2em;
li {
margin-bottom: 0.5em;
}
.suboptions {
margin-top: 0.3em
}
}
&.peek {
.settings-modal-panel {
/* Explanation:

View file

@ -98,7 +98,7 @@
.icon {
color: $fallback--text;
color: var(--text, $fallback--text);
color: var(--panelText, $fallback--text);
margin-right: 0.5em;
}

View file

@ -49,7 +49,7 @@ const SideDrawer = {
currentUser () {
return this.$store.state.users.currentUser
},
shout () { return this.$store.state.shout.channel.state === 'joined' },
shout () { return this.$store.state.shout.joined },
unseenNotifications () {
return unseenNotificationsFromStore(this.$store)
},

View file

@ -42,6 +42,10 @@
display: flex;
padding: var(--status-margin, $status-margin);
> * {
min-width: 0;
}
&.-repeat {
padding-top: 0;
}

View file

@ -64,7 +64,7 @@ const Timeline = {
if (this.blockingClicks) rootClasses = rootClasses.concat(['-blocked', '_misclick-prevention'])
return {
root: rootClasses,
header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading'] : []),
header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading', '-sticky'] : []),
body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []),
footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : [])
}
@ -89,7 +89,7 @@ const Timeline = {
const credentials = store.state.users.currentUser.credentials
const showImmediately = this.timeline.visibleStatuses.length === 0
window.addEventListener('scroll', this.handleScroll)
document.getElementById('content').addEventListener('scroll', this.handleScroll)
if (store.state.api.fetchers[this.timelineName]) { return false }
@ -111,7 +111,7 @@ const Timeline = {
setTimeout(this.determineVisibleStatuses, 250)
},
unmounted () {
window.removeEventListener('scroll', this.handleScroll)
document.getElementById('content').removeEventListener('scroll', this.handleScroll)
window.removeEventListener('keydown', this.handleShortKey)
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })

View file

@ -13,7 +13,6 @@
max-width: 100%;
flex-wrap: nowrap;
align-items: center;
position: relative;
.loadmore-button {
flex-shrink: 0;

View file

@ -8,7 +8,7 @@
:style="style"
class="background-image"
/>
<div class="panel-heading">
<div class="panel-heading -flexible-height">
<div class="user-info">
<div class="container">
<a
@ -289,6 +289,7 @@
.user-card {
position: relative;
z-index: 1;
&:hover {
--_still-image-img-visibility: visible;
@ -331,6 +332,7 @@
border-top-left-radius: calc(var(--panelRadius) - 1px);
border-top-right-radius: calc(var(--panelRadius) - 1px);
background-color: var(--profileBg);
z-index: -2;
&.hide-bio {
mask-size: 100% 40px;
@ -385,11 +387,16 @@
padding: 0 26px;
.container {
min-width: 0;
padding: 16px 0 6px;
display: flex;
align-items: flex-start;
max-height: 56px;
> * {
min-width: 0;
}
.Avatar {
--_avatarShadowBox: var(--avatarShadow);
--_avatarShadowFilter: var(--avatarShadowFilter);