Compare commits

...

93 Commits

Author SHA1 Message Date
Henry Jameson 4d720974c3 minor fixes 2022-04-28 16:36:53 +03:00
Henry Jameson 23c5627639 sort and optimize panel.scss 2022-04-28 10:57:06 +03:00
Henry Jameson 5775abb25c fix panel footer colors 2022-04-28 10:52:41 +03:00
Henry Jameson e5be00dae1 fix mobile version 2022-04-26 18:55:21 +03:00
Henry Jameson cc84485650 improvements to chats view 2022-04-26 18:50:22 +03:00
Henry Jameson 4e4c96dfaf fix for notices being glued together 2022-04-26 18:15:12 +03:00
Henry Jameson f0f56bf148 more cleanup/fixes, gap b/w panels and topbar is closer to original now 2022-04-26 18:12:58 +03:00
Henry Jameson 49db16318b more fixes for shoutbox 2022-04-26 18:12:45 +03:00
Henry Jameson 079e289260 improvements and cleanup to input components 2022-04-26 17:31:26 +03:00
Henry Jameson a96367139c oops 2022-04-26 00:12:24 +03:00
Henry Jameson 1860bb354f make panel height property private since it relies on font size 2022-04-26 00:10:51 +03:00
Henry Jameson deece57584 better formatting 2022-04-26 00:10:33 +03:00
Henry Jameson 318c62c852 reduce height of post form by default 2022-04-26 00:08:55 +03:00
Henry Jameson 2a6f42fef3 unified how panel-footer works between regular timelines and user timeline 2022-04-25 23:50:22 +03:00
Henry Jameson f2b75a075d switching to center because baseline looks extremely off 2022-04-22 00:28:58 +03:00
Henry Jameson cd021bc6df cleanup & more fixes for alignments and sizing 2022-04-21 19:57:00 +03:00
Henry Jameson d4bd64a5f5 fix panel headers on mobile 2022-04-21 00:39:54 +03:00
Henry Jameson ed80ecfdbd cleanup 2022-04-21 00:27:48 +03:00
Henry Jameson 491449c418 undo mistake 2022-04-21 00:21:46 +03:00
Henry Jameson 33d92f9b16 relative units in line-height + some cleanup 2022-04-20 23:55:34 +03:00
Henry Jameson 7f513fe46f more em-sized fonts 2022-04-20 23:44:33 +03:00
Henry Jameson 58a975e8df cleanup and fixes 2022-04-20 23:22:51 +03:00
Henry Jameson 076c52788b fixes for mobile 2022-04-20 20:43:10 +03:00
Henry Jameson 80b1ccb267 restore old chat inputbox behavior 2022-04-20 20:40:10 +03:00
Henry Jameson e388dbc168 Merge branch 'fix-chat-errors' into threecolumn
* fix-chat-errors:
  fix some chat errors/warnings that sometimes happen
  Fix incorrect close of a status popover when clicking Expand inside it
  fix tests
  fix hashtags by explicitly putting attributes
2022-04-20 20:23:34 +03:00
Henry Jameson be8098e8ec fix chat 2022-04-20 20:16:41 +03:00
Henry Jameson 06f58f5349 i forgor 2022-04-20 20:03:18 +03:00
Henry Jameson 85c515b395 more fixes for timeline headers 2022-04-20 19:57:01 +03:00
Henry Jameson 631b8b93a4 tons of fixes mainly aimed at panel headings 2022-04-20 19:44:49 +03:00
Henry Jameson aef6d52951 fix logoLeft not working at all 2022-04-20 01:25:30 +03:00
Henry Jameson d87a1fda59 fix scrollbars in dropdowns 2022-04-20 00:47:09 +03:00
Henry Jameson 9ae8ff0098 fix scrollbars in panel headers 2022-04-20 00:46:57 +03:00
Henry Jameson bb29218a6c fixate conversation heading 2022-04-19 23:06:57 +03:00
Henry Jameson b25c0c6f02 fix overflow that sometimes happens in sidebar due to too wide file
upload element
2022-04-19 22:22:16 +03:00
Henry Jameson 28556f7c27 localization updates 2022-04-12 22:01:04 +03:00
Henry Jameson 4b050c7fa5 properly ignore sticky elements when calculating offset because they
technically are following main scroll
2022-04-12 21:49:14 +03:00
Henry Jameson 3d37b9d8e1 unified layout-setting code and made an option to control or disable
third column behavior
2022-04-12 21:18:06 +03:00
Henry Jameson b37932fdf4 fix main scrollbar on chrome 2022-04-12 17:27:34 +03:00
Henry Jameson 08ad2923a5 fix chats button alignment 2022-04-12 17:17:52 +03:00
Henry Jameson e5eca8a554 reduce chats jumpiness 2022-04-12 17:03:29 +03:00
Henry Jameson 0db756b4a5 fix post form/dropzone for good (?) 2022-04-12 01:22:31 +03:00
Henry Jameson f4447eb3a0 deal with browsers that don't support hiding scrollbars (somewhat) 2022-04-12 00:19:29 +03:00
Henry Jameson 2e10c1b0a3 enforce scroll to avoid jumpiness 2022-04-11 23:54:44 +03:00
Henry Jameson e744775ce8 fix dropdown menus 2022-04-11 23:49:46 +03:00
Henry Jameson 647b8e5f6b more fixes for z-indexes in panels 2022-04-11 23:34:56 +03:00
Henry Jameson 4f617a7293 fix for chat shadow 2022-04-11 23:30:41 +03:00
Henry Jameson df18a74adc fixes for z-indexes 2022-04-11 23:18:46 +03:00
Henry Jameson 2f1f1455aa fix notifications scrolling the main column 2022-04-11 16:45:16 +03:00
Henry Jameson e5f0f95356 fix dropzone 2022-04-11 00:20:27 +03:00
Henry Jameson 21f7cb39a8 cleanup 2022-04-10 22:50:33 +03:00
Henry Jameson 5d203e93ae improvements to relative panel sizing 2022-04-10 22:37:08 +03:00
Henry Jameson fe8b5b62ae fix chromium crashing on my machine :hyperjoy: LUL 2022-04-10 22:23:07 +03:00
Henry Jameson f27226b55c improvements to relative font sizes 2022-04-10 22:09:46 +03:00
Henry Jameson 666498e7b7 fix main post form having hidden emoji picker 2022-04-10 21:56:54 +03:00
Henry Jameson aa07b219c9 fix horizontal scroll, make hovered column render stuff on top of other
ones (popups, tooltips etc)
2022-04-10 21:32:29 +03:00
Henry Jameson 3f65c8b1d6 Merge remote-tracking branch 'origin/develop' into threecolumn
* origin/develop:
  Fix image cropper not closing correctly
2022-04-10 20:42:25 +03:00
Henry Jameson 62572c3204 remove margins from underlay since it causes horizontal scroll on
smaller screens
2022-04-10 20:41:36 +03:00
Henry Jameson 3f9d9dd6bd underlay improvements 2022-04-10 20:27:09 +03:00
Henry Jameson 3e1b40ce29 fix new post button not working day1, minor stylistic fixes 2022-04-10 20:18:54 +03:00
Henry Jameson 0f28c28760 layout fixes 2022-04-10 19:29:10 +03:00
Henry Jameson d9f8091ce6 fix extra padding in thread view 2022-04-10 19:29:06 +03:00
Henry Jameson 5b664f464d chat fixes 2022-04-10 19:29:06 +03:00
Henry Jameson 7426417a52 more fixes 2022-04-10 18:44:22 +03:00
Henry Jameson a035fa4e6c fix timeline scroll-to-top 2022-04-10 18:44:03 +03:00
Henry Jameson 4c2301bc9f fix scrollable columns 2022-04-10 18:43:52 +03:00
Henry Jameson 77505fa7c9 revert layout back to scrollable body 2022-04-10 17:48:02 +03:00
Henry Jameson 2df48b1643 revert timeline changes 2022-04-10 17:47:54 +03:00
Henry Jameson ce9fd04865 Merge remote-tracking branch 'origin/develop' into threecolumn
* origin/develop:
  Fix tab switcher not working when some tabs hidden
  Fix mobile nav link text colour
  Fix shrug text in muted status
2022-04-10 14:54:03 +03:00
Henry Jameson 684650e14f restore margins for inline convos 2022-04-10 14:47:47 +03:00
Henry Jameson 0ff34f8a5a clean up and organize CSS, use ems for widths, use vars more 2022-04-10 14:23:30 +03:00
Henry Jameson 4750d9bb4c fix notifs' shadow peeking in when closed on mobile 2022-04-10 14:23:03 +03:00
Henry Jameson 5b47856329 fix scrollbars for real now 2022-04-08 13:34:20 +03:00
Henry Jameson 282e6812b3 fix chats, boost popover's z-index some more 2022-04-08 13:31:29 +03:00
Henry Jameson 6109fab14e cleanup, fix some things, try to disable scrollbars on mobile 2022-04-08 10:17:23 +03:00
Henry Jameson c71cf72eea fix mobile view 2022-04-07 16:39:39 +03:00
Henry Jameson 51c996bd7e boost z-index of popover 2022-04-07 16:18:39 +03:00
Henry Jameson 4007ddbf32 oops 2022-04-07 16:10:46 +03:00
Henry Jameson 6b67c095bf make scrollbars use button roundness 2022-04-07 16:07:47 +03:00
Henry Jameson b8b7ae8564 fancy scrollbars 2022-04-07 16:04:22 +03:00
Henry Jameson b5ded67c06 options to enable scrollbars and disable sticky headers 2022-04-07 15:11:23 +03:00
Henry Jameson d3d219f15d fix interactions page 2022-04-07 14:37:16 +03:00
Henry Jameson 8d623ab1ea fix some scroll issues 2022-04-07 11:36:14 +03:00
Henry Jameson ab7490e49b Merge remote-tracking branch 'origin/develop' into threecolumn
* origin/develop:
  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
  fix i18n for good??
  force runtime build of i18n
  fix CSP by compiling the i18n templates as well
2022-04-07 10:33:20 +03:00
Henry Jameson 6a319154d9 teleport bread 2022-04-05 19:22:15 +03:00
Henry Jameson 4a068483ed wide mode initial implementation + cleanup 2022-04-05 18:38:05 +03:00
Henry Jameson 9e5037c715 make chatlist header sticky 2022-04-05 17:44:40 +03:00
Henry Jameson 835feea163 fix random error that sometimes occurs 2022-04-05 17:44:30 +03:00
Henry Jameson 52649bdabc cleanup & code splitting 2022-04-05 17:15:45 +03:00
Henry Jameson 0a606c2720 fix chat loading endlessly 2022-04-05 17:11:50 +03:00
Henry Jameson cfa8edf2c0 chats work and look a bit better 2022-04-05 13:19:12 +03:00
Henry Jameson e7ac0e5d68 fix main column having wild widths 2022-04-05 13:03:03 +03:00
Henry Jameson 71863061c6 fixed tons of stuff, at least it looks normalish on desktop 2022-04-04 19:41:09 +03:00
Henry Jameson 0e83ced25b refactored how main app layout works 2022-04-04 09:42:52 +03:00
73 changed files with 1426 additions and 1375 deletions

View File

@ -1,6 +1,5 @@
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 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'
@ -16,13 +15,14 @@ import PostStatusModal from './components/post_status_modal/post_status_modal.vu
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex'
import { defineAsyncComponent } from 'vue'
export default {
name: 'app',
components: {
UserPanel,
NavPanel,
Notifications,
Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
@ -50,6 +50,16 @@ export default {
window.removeEventListener('resize', this.updateMobileState)
},
computed: {
classes () {
return [
{
'-reverse': this.reverseLayout,
'-no-sticky-headers': this.noSticky,
'-has-new-post-button': this.newPostButtonShown
},
'-' + this.layoutType
]
},
currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image },
instanceBackground () {
@ -72,31 +82,38 @@ export default {
!this.$store.getters.mergedConfig.hideISP &&
this.$store.state.instance.instanceSpecificPanelContent
},
isChats () {
return this.$route.name === 'chat' || this.$route.name === 'chats'
},
newPostButtonShown () {
if (this.isChats) return false
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
shoutboxPosition () {
return this.$store.getters.mergedConfig.showNewPostButton || false
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
},
hideShoutbox () {
return this.$store.getters.mergedConfig.hideShoutbox
},
isMobileLayout () { return this.$store.state.interface.mobileLayout },
layoutType () { return this.$store.state.interface.layoutType },
privateMode () { return this.$store.state.instance.private },
sidebarAlign () {
return {
'order': this.$store.getters.mergedConfig.sidebarRight ? 99 : 0
reverseLayout () {
const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
if (this.layoutType !== 'wide') {
return reverseSetting
} else {
return thirdColumnMode === 'notifications' ? reverseSetting : !reverseSetting
}
},
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
...mapGetters(['mergedConfig'])
},
methods: {
updateMobileState () {
const mobileLayout = windowWidth() <= 800
const layoutHeight = windowHeight()
const changed = mobileLayout !== this.isMobileLayout
if (changed) {
this.$store.dispatch('setMobileLayout', mobileLayout)
}
this.$store.dispatch('setLayoutHeight', layoutHeight)
this.$store.dispatch('setLayoutWidth', windowWidth())
this.$store.dispatch('setLayoutHeight', windowHeight())
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,33 +7,26 @@
id="app_bg_wrapper"
class="app-bg-wrapper"
/>
<MobileNav v-if="isMobileLayout" />
<MobileNav v-if="layoutType === 'mobile'" />
<DesktopNav v-else />
<div class="app-bg-wrapper app-container-wrapper" />
<notifications v-if="currentUser" />
<div
id="content"
class="container underlay"
class="app-layout container"
:class="classes"
>
<div
class="sidebar-flexer mobile-hidden"
:style="sidebarAlign"
>
<div class="sidebar-bounds">
<div class="sidebar-scroller">
<div class="sidebar">
<user-panel />
<div v-if="!isMobileLayout">
<nav-panel />
<instance-specific-panel v-if="showInstanceSpecificPanel" />
<features-panel v-if="!currentUser && showFeaturesPanel" />
<who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
<notifications v-if="currentUser" />
</div>
</div>
</div>
</div>
<div class="underlay"/>
<div id="sidebar" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }">
<user-panel />
<template v-if="layoutType !== 'mobile'">
<nav-panel />
<instance-specific-panel v-if="showInstanceSpecificPanel" />
<features-panel v-if="!currentUser && showFeaturesPanel" />
<who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
<div id="notifs-sidebar" />
</template>
</div>
<div class="main">
<div id="main-scroller" class="column main" :class="{ '-full-height': isChats }">
<div
v-if="!currentUser"
class="login-hint panel panel-default"
@ -47,13 +40,14 @@
</div>
<router-view />
</div>
<media-modal />
<div id="notifs-column" class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }"/>
</div>
<media-modal />
<shout-panel
v-if="currentUser && shout && !hideShoutbox"
:floating="true"
class="floating-shout mobile-hidden"
:class="{ 'left': shoutboxPosition }"
:class="{ '-left': shoutboxPosition }"
/>
<MobilePostStatusButton />
<UserReportingModal />

View File

@ -8,7 +8,7 @@ import App from '../App.vue'
import routes from './routes'
import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { windowWidth } from '../services/window_utils/window_utils'
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
@ -332,8 +332,8 @@ const checkOAuthToken = async ({ store }) => {
}
const afterStoreSetup = async ({ store, i18n }) => {
const width = windowWidth()
store.dispatch('setMobileLayout', width <= 800)
store.dispatch('setLayoutWidth', windowWidth())
store.dispatch('setLayoutHeight', windowHeight())
FaviconService.initFaviconService()

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

@ -74,10 +74,6 @@
<style lang="scss">
@import '../../_variables.scss';
.AccountActions {
button.dropdown-item {
margin-left: 0;
}
.ellipsis-button {
width: 2.5em;
margin: -0.5em 0;

View File

@ -173,7 +173,7 @@
margin: 8px;
word-break: break-all;
h1 {
font-size: 14px;
font-size: 1rem;
margin: 0px;
}
}

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,
@ -20,7 +20,7 @@ library.add(
)
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
@ -43,7 +43,6 @@ const Chat = {
},
created () {
this.startFetching()
window.addEventListener('resize', this.handleLayoutChange)
},
mounted () {
window.addEventListener('scroll', this.handleScroll)
@ -52,15 +51,11 @@ const Chat = {
}
this.$nextTick(() => {
this.updateScrollableContainerHeight()
this.handleResize()
})
this.setChatLayout()
},
unmounted () {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleLayoutChange)
this.unsetChatLayout()
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.dispatch('clearCurrentChat')
},
@ -96,8 +91,7 @@ const Chat = {
...mapState({
backendInteractor: state => state.api.backendInteractor,
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus,
mobileLayout: state => state.interface.mobileLayout,
layoutHeight: state => state.interface.layoutHeight,
mobileLayout: state => state.interface.layoutType === 'mobile',
currentUser: state => state.users.currentUser
})
},
@ -115,9 +109,6 @@ const Chat = {
'$route': function () {
this.startFetching()
},
layoutHeight () {
this.handleResize({ expand: true })
},
mastoUserSocketStatus (newValue) {
if (newValue === WSConnectionStatus.JOINED) {
this.fetchChat({ isFirstFetch: true })
@ -132,7 +123,6 @@ const Chat = {
onFilesDropped () {
this.$nextTick(() => {
this.handleResize()
this.updateScrollableContainerHeight()
})
},
handleVisibilityChange () {
@ -142,42 +132,6 @@ const Chat = {
}
})
},
setChatLayout () {
// This is a hacky way to adjust the global layout to the mobile chat (without modifying the rest of the app).
// This layout prevents empty spaces from being visible at the bottom
// of the chat on iOS Safari (`safe-area-inset`) when
// - the on-screen keyboard appears and the user starts typing
// - the user selects the text inside the input area
// - the user selects and deletes the text that is multiple lines long
// TODO: unify the chat layout with the global layout.
let html = document.querySelector('html')
if (html) {
html.classList.add('chat-layout')
}
this.$nextTick(() => {
this.updateScrollableContainerHeight()
})
},
unsetChatLayout () {
let html = document.querySelector('html')
if (html) {
html.classList.remove('chat-layout')
}
},
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,29 +144,21 @@ const Chat = {
}
this.$nextTick(() => {
this.updateScrollableContainerHeight()
const { scrollHeight = undefined } = this.lastScrollPosition
this.lastScrollPosition = getScrollPosition()
const { offsetHeight = undefined } = this.lastScrollPosition
this.lastScrollPosition = getScrollPosition(this.$refs.scrollable)
const diff = this.lastScrollPosition.offsetHeight - offsetHeight
if (diff < 0 || (!this.bottomedOut() && expand)) {
const diff = this.lastScrollPosition.scrollHeight - scrollHeight
if (diff > 0 || (!this.bottomedOut() && expand)) {
this.$nextTick(() => {
this.updateScrollableContainerHeight()
this.$refs.scrollable.scrollTo({
top: this.$refs.scrollable.scrollTop - diff,
left: 0
})
window.scrollTo({ top: window.scrollY + diff })
})
}
})
},
scrollDown (options = {}) {
const { behavior = 'auto', forceRead = false } = options
const scrollable = this.$refs.scrollable
if (!scrollable) { return }
this.$nextTick(() => {
scrollable.scrollTo({ top: scrollable.scrollHeight, left: 0, behavior })
window.scrollTo({ top: document.documentElement.scrollHeight, behavior })
})
if (forceRead) {
this.readChat()
@ -228,11 +174,10 @@ const Chat = {
})
},
bottomedOut (offset) {
return isBottomedOut(this.$refs.scrollable, offset)
return isBottomedOut(offset)
},
reachedTop () {
const scrollable = this.$refs.scrollable
return scrollable && scrollable.scrollTop <= 0
return window.scrollY <= 0
},
cullOlderCheck () {
window.setTimeout(() => {
@ -263,10 +208,9 @@ const Chat = {
}
}, 200),
handleScrollUp (positionBeforeLoading) {
const positionAfterLoading = getScrollPosition(this.$refs.scrollable)
this.$refs.scrollable.scrollTo({
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
left: 0
const positionAfterLoading = getScrollPosition()
window.scrollTo({
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading)
})
},
fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) {
@ -285,22 +229,18 @@ const Chat = {
chatService.clear(chatMessageService)
}
const positionBeforeUpdate = getScrollPosition(this.$refs.scrollable)
const positionBeforeUpdate = getScrollPosition()
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() && messages.length > 0) {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
}
})
@ -336,9 +276,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,28 +1,22 @@
.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 {
box-sizing: border-box;
background-color: var(--chatBg, $fallback--bg);
display: flex;
flex-direction: column;
width: 100%;
overflow: visible;
min-height: 100%;
min-height: calc(100vh - var(--navbar-height));
margin: 0 0 0 0;
border-radius: 10px 10px 0 0;
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
@ -32,11 +26,9 @@
}
}
.scrollable-message-list {
.message-list {
padding: 0 0.8em;
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
display: flex;
flex-direction: column;
}
@ -44,24 +36,21 @@
.footer {
position: sticky;
bottom: 0;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
z-index: 1;
}
.chat-view-heading {
align-items: center;
justify-content: space-between;
top: 50px;
display: flex;
z-index: 2;
position: sticky;
overflow: hidden;
grid-template-columns: auto minmax(50%, 1fr);
}
.go-back-button {
cursor: pointer;
width: 28px;
text-align: center;
padding: 0.6em;
margin: -0.6em 0.6em -0.6em -0.6em;
line-height: 1;
height: 100%;
align-self: start;
width: var(--__panel-heading-height-inner);
}
.jump-to-bottom-button {
@ -115,56 +104,4 @@
}
}
}
@media all and (max-width: 800px) {
height: 100%;
overflow: hidden;
.chat-view-inner {
overflow: hidden;
height: 100%;
margin-top: 0;
margin-left: 0;
margin-right: 0;
}
.chat-view-body {
display: flex;
min-height: auto;
overflow: hidden;
height: 100%;
margin: 0;
border-radius: 0;
}
.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 {
display: unset;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.footer {
position: sticky;
bottom: auto;
}
}
}

View File

@ -2,23 +2,22 @@
<div class="chat-view">
<div class="chat-view-inner">
<div
id="nav"
ref="inner"
class="panel-default panel chat-view-body"
>
<div
ref="header"
class="panel-heading chat-view-heading mobile-hidden"
class="panel-heading -sticky chat-view-heading"
>
<a
class="go-back-button"
<button
class="button-unstyled go-back-button"
@click="goBack"
>
<FAIcon
size="lg"
icon="chevron-left"
/>
</a>
</button>
<div class="title text-center">
<ChatTitle
:user="recipient"
@ -27,10 +26,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

@ -1,9 +1,9 @@
// Captures a scroll position
export const getScrollPosition = (el) => {
export const getScrollPosition = () => {
return {
scrollTop: el.scrollTop,
scrollHeight: el.scrollHeight,
offsetHeight: el.offsetHeight
scrollTop: window.scrollY,
scrollHeight: document.documentElement.scrollHeight,
offsetHeight: window.innerHeight
}
}
@ -13,21 +13,12 @@ export const getNewTopPosition = (previousPosition, newPosition) => {
return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight)
}
export const isBottomedOut = (el, offset = 0) => {
if (!el) { return }
const scrollHeight = el.scrollTop + offset
const totalHeight = el.scrollHeight - el.offsetHeight
export const isBottomedOut = (offset = 0) => {
const scrollHeight = window.scrollY + offset
const totalHeight = document.documentElement.scrollHeight - window.innerHeight
return totalHeight <= scrollHeight
}
// Height of the scrollable container. The dynamic height is needed to ensure the mobile browser panel doesn't overlap or hide the posting form.
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
export const isScrollable = () => {
return document.documentElement.scrollHeight > window.innerHeight
}

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

@ -43,7 +43,7 @@
white-space: nowrap;
overflow: hidden;
flex-shrink: 1;
line-height: 1.4em;
line-height: var(--post-line-height);
}
.chat-preview {
@ -82,7 +82,7 @@
}
.time-wrapper {
line-height: 1.4em;
line-height: var(--post-line-height);
}
.chat-preview-body {

View File

@ -22,10 +22,10 @@
}
.go-back-button {
cursor: pointer;
width: 28px;
text-align: center;
padding: 0.6em;
margin: -0.6em 0.6em -0.6em -0.6em;
line-height: 1;
height: 100%;
align-self: start;
width: var(--__panel-heading-height-inner);
}
}

View File

@ -1,21 +1,20 @@
<template>
<div
id="nav"
class="panel-default panel chat-new"
>
<div
ref="header"
class="panel-heading"
>
<a
class="go-back-button"
<button
class="button-unstyled go-back-button"
@click="goBack"
>
<FAIcon
size="lg"
icon="chevron-left"
/>
</a>
</button>
</div>
<div class="input-wrap">
<div class="input-search">

View File

@ -4,19 +4,19 @@
:title="title"
>
<router-link
class="avatar-container"
v-if="withAvatar && user"
:to="getUserProfileLink(user)"
>
<UserAvatar
class="titlebar-avatar"
:user="user"
width="23px"
height="23px"
/>
</router-link>
<RichContent
v-if="user"
class="username"
:title="'@'+user.screen_name_ui"
:title="'@'+(user && user.screen_name_ui)"
:html="htmlTitle"
:emoji="user.emoji || []"
/>
@ -33,7 +33,6 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
align-items: center;
--emoji-size: 14px;
@ -46,11 +45,15 @@
overflow: hidden;
}
.Avatar {
width: 23px;
height: 23px;
margin-right: 0.5em;
.avatar-container {
align-self: center;
line-height: 1;
}
.titlebar-avatar {
margin-right: 0.5em;
height: 1.5em;
width: 1.5em;
border-radius: $fallback--avatarAltRadius;
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);

View File

@ -7,7 +7,7 @@
>
<div
v-if="isExpanded"
class="panel-heading conversation-heading"
class="panel-heading conversation-heading -sticky"
>
<span class="title"> {{ $t('timeline.conversation') }} </span>
<button
@ -223,6 +223,7 @@
--text: var(--faint);
color: var(--text);
}
.thread-ancestor-dive-box {
padding-left: var(--status-margin, $status-margin);
border-bottom-width: 1px;
@ -250,6 +251,7 @@
.thread-ancestor-has-other-replies .conversation-status,
.thread-ancestor:last-child .conversation-status,
.thread-ancestor:last-child .thread-ancestor-dive-box,
&:last-child .conversation-status,
&.-expanded .thread-tree .conversation-status {
border-bottom: none;
}
@ -270,5 +272,9 @@
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
border-bottom: 1px solid var(--border, $fallback--border);
}
&.-expanded.status-fadein {
margin: calc(var(--status-margin, $status-margin) / 2);
}
}
</style>

View File

@ -1,9 +1,7 @@
@import '../../_variables.scss';
.DesktopNav {
height: 50px;
width: 100%;
position: fixed;
a {
color: var(--topBarLink, $fallback--link);
@ -11,7 +9,7 @@
.inner-nav {
display: grid;
grid-template-rows: 50px;
grid-template-rows: var(--navbar-height);
grid-template-columns: 2fr auto 2fr;
grid-template-areas: "sitename logo actions";
box-sizing: border-box;
@ -20,7 +18,7 @@
max-width: 980px;
}
&.-logoLeft {
&.-logoLeft .inner-nav {
grid-template-columns: auto 2fr 2fr;
grid-template-areas: "logo sitename actions";
}
@ -77,7 +75,7 @@
img {
display: inline-block;
height: 50px;
height: var(--navbar-height);
}
}
@ -103,8 +101,8 @@
.item {
flex: 1;
line-height: 50px;
height: 50px;
line-height: var(--navbar-height);
height: var(--navbar-height);
overflow: hidden;
display: flex;
flex-wrap: wrap;

View File

@ -58,16 +58,7 @@
background-color: var(--bg, $fallback--bg);
.dialog-modal-heading {
padding: .5em .5em;
margin-right: auto;
margin-bottom: 0;
white-space: nowrap;
color: var(--panelText);
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
.title {
margin-bottom: 0;
text-align: center;
}
}

View File

@ -78,7 +78,7 @@
top: 0;
right: 0;
margin: .2em .25em;
font-size: 16px;
font-size: 1.3em;
cursor: pointer;
line-height: 24px;

View File

@ -7,7 +7,7 @@
right: 0;
left: 0;
margin: 0 !important;
z-index: 1;
z-index: 100;
background-color: $fallback--bg;
background-color: var(--popover, $fallback--bg);
color: $fallback--link;
@ -73,12 +73,13 @@
&-item {
padding: 0 7px;
cursor: pointer;
font-size: 24px;
font-size: 1.85em;
&.disabled {
opacity: 0.5;
pointer-events: none;
}
&.active {
border-bottom: 4px solid;
@ -151,9 +152,10 @@
justify-content: left;
&-title {
font-size: 12px;
font-size: 0.85em;
width: 100%;
margin: 0;
&.disabled {
display: none;
}

View File

@ -44,20 +44,18 @@
max-width: calc(100% - 3em);
display: flex;
padding-left: 1.5em;
line-height: 2em;
line-height: 2;
margin-bottom: 0.5em;
.notice-message {
flex: 1 1 100%;
}
i {
flex: 0 0;
width: 1.5em;
cursor: pointer;
}
}
.global-error {
background-color: var(--alertPopupError, $fallback--cRed);
color: var(--alertPopupErrorText, $fallback--text);
.svg-inline--fa {
color: var(--alertPopupErrorText, $fallback--text);
}
@ -66,6 +64,7 @@
.global-warning {
background-color: var(--alertPopupWarning, $fallback--cOrange);
color: var(--alertPopupWarningText, $fallback--text);
.svg-inline--fa {
color: var(--alertPopupWarningText, $fallback--text);
}

View File

@ -63,7 +63,7 @@
}
.card-host {
font-size: 12px;
font-size: 0.85em;
}
.card-description {

View File

@ -101,7 +101,7 @@
padding: 0.6em;
.btn {
min-height: 28px;
min-height: 2em;
width: 10em;
}

View File

@ -121,7 +121,7 @@ $modal-view-button-icon-width: 3em;
$modal-view-button-icon-margin: 0.5em;
.modal-view.media-modal-view {
z-index: 1001;
z-index: 9000;
flex-direction: column;
.modal-view-button-arrow,
@ -234,7 +234,7 @@ $modal-view-button-icon-margin: 0.5em;
position: absolute;
height: $modal-view-button-icon-height;
width: $modal-view-button-icon-width;
font-size: 14px;
font-size: 1rem;
line-height: $modal-view-button-icon-height;
color: #FFF;
text-align: center;

View File

@ -19,7 +19,7 @@
v-if="uploadReady"
:disabled="disabled"
type="file"
style="position: fixed; top: -100em"
style="position: fixed; top: -100em; max-width: 0; max-height: 0"
multiple="true"
@change="change"
>
@ -32,6 +32,6 @@
@import '../../_variables.scss';
.media-upload {
cursor: pointer;
cursor: pointer; // We use <label> for interactivity... i wonder if it's fine
}
</style>

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

@ -5,7 +5,6 @@
<nav
id="nav"
class="mobile-nav"
:class="{ 'mobile-hidden': isChat }"
@click="scrollToTop()"
>
<div class="item">
@ -51,7 +50,7 @@
<div
v-if="currentUser"
class="mobile-notifications-drawer"
:class="{ 'closed': !notificationsOpen }"
:class="{ '-closed': !notificationsOpen }"
@touchstart.stop="notificationsTouchStart"
@touchmove.stop="notificationsTouchMove"
>
@ -69,12 +68,9 @@
</div>
<div
class="mobile-notifications"
id="mobile-notifications"
@scroll="onScroll"
>
<Notifications
ref="notifications"
:no-heading="true"
/>
</div>
</div>
<SideDrawer
@ -92,12 +88,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;
a {
color: var(--topBarLink, $fallback--link);
@ -156,8 +150,9 @@
z-index: 1001;
-webkit-overflow-scrolling: touch;
&.closed {
&.-closed {
transform: translateX(100%);
box-shadow: none;
}
}
@ -185,7 +180,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

@ -45,7 +45,7 @@ const MobilePostStatusButton = {
return this.autohideFloatingPostButton && (this.hidden || this.inputActive)
},
isPersistent () {
return !!this.$store.getters.mergedConfig.showNewPostButton
return !!this.$store.getters.mergedConfig.alwaysShowNewPostButton
},
autohideFloatingPostButton () {
return !!this.$store.getters.mergedConfig.autohideFloatingPostButton

View File

@ -35,7 +35,7 @@ export default {
<style lang="scss">
.modal-view {
z-index: 1000;
z-index: 2000;
position: fixed;
top: 0;
left: 0;

View File

@ -64,7 +64,7 @@
</div>
</template>
<template v-slot:trigger>
<button class="button-unstyled">
<button class="filter-trigger-button button-unstyled">
<FAIcon icon="filter" />
</button>
</template>
@ -107,15 +107,14 @@ export default {
align-self: stretch;
> button {
font-size: 1.2em;
padding-left: 0.7em;
padding-right: 0.2em;
line-height: 100%;
height: 100%;
}
width: var(--__panel-heading-height-inner);
text-align: center;
.dropdown-item {
margin: 0;
svg {
font-size: 1.2em;
}
}
}

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,18 @@ const Notifications = {
loading () {
return this.$store.state.statuses.notifications.loading
},
noHeading () {
const { layoutType } = this.$store.state.interface
return this.minimalMode || 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

@ -11,10 +11,6 @@
color: var(--text, $fallback--text);
}
.notifications-footer {
border: none;
}
.notification {
position: relative;
@ -47,6 +43,10 @@
}
}
&:last-child .Notification {
border-bottom: none;
}
.non-mention {
display: flex;
flex: 1;
@ -113,13 +113,13 @@
}
.emoji-reaction-emoji {
font-size: 16px;
font-size: 1.3em;
}
.notification-details {
min-width: 0px;
min-width: 0;
word-wrap: break-word;
line-height:18px;
line-height: var(--post-line-height);
position: relative;
overflow: hidden;
width: 100%;
@ -142,7 +142,7 @@
}
.timeago {
margin-right: .2em;
margin-right: 0.2em;
}
.status-content {
@ -155,7 +155,8 @@
margin: 0 0 0.3em;
padding: 0;
font-size: 1em;
line-height:20px;
line-height: 1.5;
small {
font-weight: lighter;
}

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 :disabled="minimalMode" :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">
<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,14 +91,18 @@
flex-direction: column;
margin-top: 0.6em;
max-width: 18rem;
> * {
min-width: 0;
}
}
.form-group {
display: flex;
flex-direction: column;
margin-bottom: 1em;
padding: 0.3em 0.0em 0.3em;
line-height: 24px;
padding: 0.3em 0;
line-height: 1.85em;
}
.error {
@ -110,7 +114,7 @@
.alert {
padding: 0.5em;
margin: 0.3em 0.0em 1em;
margin: 0.3em 0 1em;
}
.password-reset-required {

View File

@ -5,7 +5,7 @@
>
<button
ref="trigger"
class="button-unstyled -fullwidth popover-trigger-button"
class="button-unstyled popover-trigger-button"
type="button"
@click="onClick"
>
@ -37,7 +37,7 @@
}
.popover {
z-index: 8;
z-index: 500;
position: absolute;
min-width: 0;
}
@ -45,8 +45,19 @@
.popover-default {
transition: opacity 0.3s;
box-shadow: 1px 1px 4px rgba(0,0,0,.6);
box-shadow: var(--panelShadow);
&:after {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 3;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
box-shadow: var(--panelShadow);
pointer-events: none;
}
border-radius: $fallback--btnRadius;
border-radius: var(--btnRadius, $fallback--btnRadius);
@ -65,11 +76,11 @@
.dropdown-menu {
display: block;
padding: .5rem 0;
font-size: 1rem;
font-size: 1em;
text-align: left;
list-style: none;
max-width: 100vw;
z-index: 10;
z-index: 200;
white-space: nowrap;
.dropdown-divider {
@ -82,9 +93,9 @@
.dropdown-item {
line-height: 21px;
overflow: auto;
overflow: hidden;
display: block;
padding: .5em 0.75em;
padding: 0.5em 0.75em;
clear: both;
font-weight: 400;
text-align: inherit;
@ -110,14 +121,15 @@
&:active, &:hover {
background-color: $fallback--lightBg;
background-color: var(--selectedMenuPopover, $fallback--lightBg);
color: $fallback--link;
color: var(--selectedMenuPopoverText, $fallback--link);
box-shadow: none;
--btnText: var(--selectedMenuPopoverText, $fallback--link);
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
svg {
color: var(--selectedMenuPopoverIcon, $fallback--icon);
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
}
}
@ -142,9 +154,13 @@
content: '✓';
}
&.menu-checkbox-radio::after {
font-size: 2em;
content: '•';
&.-radio {
border-radius: 9999px;
&.menu-checkbox-checked::after {
font-size: 2em;
content: '•';
}
}
}

View File

@ -486,7 +486,7 @@ const PostStatusForm = {
const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom']
const bottomBottomPadding = pxStringToNumber(bottomBottomPaddingStr)
const scrollerRef = this.$el.closest('.sidebar-scroller') ||
const scrollerRef = this.$el.closest('.column.-scrollable') ||
this.$el.closest('.post-form-modal-view') ||
window

View File

@ -8,15 +8,6 @@
@submit.prevent
@dragover.prevent="fileDrag"
>
<div
v-show="showDropIcon !== 'hide'"
:style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
class="drop-indicator"
@dragleave="fileDragStop"
@drop.stop="fileDrop"
>
<FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" />
</div>
<div class="form-group">
<i18n-t
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
@ -277,6 +268,15 @@
{{ $t('post_status.post') }}
</button>
</div>
<div
v-show="showDropIcon !== 'hide'"
:style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
class="drop-indicator"
@dragleave="fileDragStop"
@drop.stop="fileDrop"
>
<FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" />
</div>
<div
v-if="error"
class="alert error"
@ -336,7 +336,7 @@
display: flex;
justify-content: space-between;
padding: 0.5em;
height: 32px;
height: 2.5em;
button {
width: 10em;
@ -394,7 +394,6 @@
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
padding: 0.5em;
margin: 0;
line-height: 1.4em;
}
.text-format {
@ -408,13 +407,16 @@
display: flex;
justify-content: space-between;
padding-top: 5px;
align-items: baseline;
}
.media-upload-icon, .poll-icon, .emoji-icon {
font-size: 26px;
font-size: 1.85em;
line-height: 1.1;
flex: 1;
padding: 0 0.1em;
display: flex;
align-items: center;
&.selected, &:hover {
// needs to be specific to override icon default color
@ -441,21 +443,17 @@
// Order is not necessary but a good indicator
.media-upload-icon {
order: 1;
text-align: left;
justify-content: left;
}
.emoji-icon {
order: 2;
text-align: center;
justify-content: center;
}
.poll-icon {
order: 3;
text-align: right;
}
.poll-icon {
cursor: pointer;
justify-content: right;
}
.error {
@ -489,10 +487,6 @@
flex-direction: column;
}
.btn {
cursor: pointer;
}
.btn[disabled] {
cursor: not-allowed;
}
@ -508,26 +502,20 @@
display: flex;
flex-direction: column;
padding: 0.25em 0.5em 0.5em;
line-height:24px;
}
form textarea.form-cw {
line-height:16px;
resize: none;
overflow: hidden;
transition: min-height 200ms 100ms;
min-height: 1px;
line-height: 1.85;
}
.form-post-body {
height: 16px; // Only affects the empty-height
line-height: 16px;
resize: none;
// TODO: make a resizable textarea component?
box-sizing: content-box; // needed for easier computation of dynamic size
overflow: hidden;
transition: min-height 200ms 100ms;
padding-bottom: 1.75em;
min-height: 1px;
box-sizing: content-box;
// stock padding + 1 line of text (for counter)
padding-bottom: calc(var(--_padding) + var(--post-line-height) * 1em);
// two lines of text
height: calc(var(--post-line-height) * 1em);
min-height: calc(var(--post-line-height) * 1em);
resize: none;
&.scrollable-form {
overflow-y: auto;
@ -551,10 +539,6 @@
}
}
.btn {
cursor: pointer;
}
.btn[disabled] {
cursor: not-allowed;
}
@ -571,7 +555,6 @@
.drop-indicator {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
font-size: 5em;

View File

@ -101,7 +101,7 @@
cursor: pointer;
flex-basis: 20%;
line-height: 1.5em;
line-height: 1.5;
align-content: center;
&:hover {

View File

@ -271,7 +271,10 @@ $validations-cRed: #f04124;
.container {
display: flex;
flex-direction: row;
//margin-bottom: 1em;
> * {
min-width: 0;
}
}
.terms-of-service {
@ -294,8 +297,8 @@ $validations-cRed: #f04124;
.form-group {
display: flex;
flex-direction: column;
padding: 0.3em 0.0em 0.3em;
line-height:24px;
padding: 0.3em 0;
line-height: 2;
margin-bottom: 1em;
}
@ -315,7 +318,7 @@ $validations-cRed: #f04124;
text-align: left;
span {
font-size: 12px;
font-size: 0.85em;
}
}
@ -341,7 +344,7 @@ $validations-cRed: #f04124;
.btn {
margin-top: 0.6em;
height: 28px;
height: 2em;
}
.error {

View File

@ -32,7 +32,7 @@
.remote-button {
width: 100%;
min-height: 28px;
min-height: 2em;
}
}
</style>

View File

@ -39,10 +39,10 @@ label.Select {
padding: 0 2em 0 .2em;
font-family: sans-serif;
font-family: var(--inputFont, sans-serif);
font-size: 14px;
font-size: 1em;
width: 100%;
z-index: 1;
height: 28px;
height: 2em;
line-height: 16px;
}
@ -55,7 +55,7 @@ label.Select {
width: 0.875em;
color: $fallback--text;
color: var(--inputText, $fallback--text);
line-height: 28px;
line-height: 2;
z-index: 0;
pointer-events: none;
}

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:
@ -42,7 +54,7 @@
overflow-y: hidden;
.btn {
min-height: 28px;
min-height: 2em;
min-width: 10em;
padding: 0 2em;
}

View File

@ -11,22 +11,13 @@
{{ $t('settings.settings') }}
</span>
<transition name="fade">
<div v-if="currentSaveStateNotice">
<div
v-if="currentSaveStateNotice.error"
class="alert error"
@click.prevent
>
{{ $t('settings.saving_err') }}
</div>
<div
v-if="!currentSaveStateNotice.error"
class="alert transparent"
@click.prevent
>
{{ $t('settings.saving_ok') }}
</div>
<div
v-if="currentSaveStateNotice"
class="alert"
:class="{ transparent: !currentSaveStateNotice.error, error: currentSaveStateNotice.error}"
@click.prevent
>
{{ currentSaveStateNotice.error ? $t('settings.saving_err') : $t('settings.saving_ok') }}
</div>
</transition>
<button

View File

@ -38,6 +38,11 @@ const GeneralTab = {
value: mode,
label: this.$t(`settings.mention_link_display_${mode}`)
})),
thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({
key: mode,
value: mode,
label: this.$t(`settings.third_column_mode_${mode}`)
})),
loopSilentAvailable:
// Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||

View File

@ -60,6 +60,25 @@
{{ $t('settings.virtual_scrolling') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="disableStickyHeaders">
{{ $t('settings.disable_sticky_headers') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="showScrollbars">
{{ $t('settings.show_scrollbars') }}
</BooleanSetting>
</li>
<li>
<ChoiceSetting
id="thirdColumnMode"
path="thirdColumnMode"
:options="thirdColumnModeOptions"
>
{{ $t('settings.third_column_mode') }}
</ChoiceSetting>
</li>
<li>
<BooleanSetting
path="alwaysShowNewPostButton"

View File

@ -8,7 +8,7 @@
.bulk-actions {
text-align: right;
padding: 0 1em;
min-height: 28px;
min-height: 2em;
}
.bulk-action-button {

View File

@ -89,7 +89,7 @@
&-bulk-actions {
text-align: right;
padding: 0 1em;
min-height: 28px;
min-height: 2em;
button {
width: 10em;

View File

@ -245,25 +245,8 @@
border-color: var(--border, $fallback--border);
}
.panel-heading {
.badge, .alert, .btn, .faint {
margin-left: 1em;
white-space: nowrap;
}
.faint {
text-overflow: ellipsis;
min-width: 2em;
overflow-x: hidden;
}
.flex-spacer {
flex: 1;
}
}
.btn {
margin-left: 0;
padding: 0 1em;
min-width: 3em;
min-height: 30px;
}
}
}
@ -342,7 +325,7 @@
.btn {
flex-grow: 1;
min-height: 28px;
min-height: 2em;
min-width: 0;
max-width: 10em;
padding: 0;

View File

@ -57,7 +57,7 @@
>
<div class="panel panel-default">
<div
class="panel-heading stub timeline-heading shout-heading"
class="panel-heading -stub timeline-heading shout-heading"
@click.stop.prevent="togglePanel"
>
<div class="title">
@ -79,17 +79,17 @@
.floating-shout {
position: fixed;
bottom: 0px;
bottom: 0.5em;
z-index: 1000;
max-width: 25em;
}
.floating-shout.left {
left: 0px;
}
&.-left {
left: 0.5em;
}
.floating-shout:not(.left) {
right: 0px;
&:not(.-left) {
right: 0.5em;
}
}
.shout-panel {
@ -121,7 +121,7 @@
.shout-message {
display: flex;
padding: 0.2em 0.5em
padding: 0.2em 0.5em;
}
.shout-avatar {
@ -137,6 +137,7 @@
.shout-input {
display: flex;
textarea {
flex: 1;
margin: 0.6em;

View File

@ -42,6 +42,10 @@
display: flex;
padding: var(--status-margin, $status-margin);
> * {
min-width: 0;
}
&.-repeat {
padding-top: 0;
}
@ -78,7 +82,6 @@
.status-username {
white-space: nowrap;
font-size: 14px;
overflow: hidden;
max-width: 85%;
font-weight: bold;
@ -103,7 +106,7 @@
.heading-name-row {
display: flex;
justify-content: space-between;
line-height: 18px;
line-height: 1.3;
a {
display: inline-block;
@ -156,7 +159,7 @@
& .heading-reply-row {
position: relative;
align-content: baseline;
font-size: 12px;
font-size: 0.85em;
margin-top: 0.2em;
line-height: 130%;
max-width: 100%;
@ -224,8 +227,8 @@
.replies {
margin-top: 0.25em;
line-height: 18px;
font-size: 12px;
line-height: 1.3;
font-size: 0.85em;
display: flex;
flex-wrap: wrap;
@ -385,14 +388,14 @@
.stat-title {
color: var(--faint, $fallback--faint);
font-size: 12px;
font-size: 0.85em;
text-transform: uppercase;
position: relative;
}
.stat-number {
font-weight: bolder;
font-size: 16px;
font-size: 1.1em;
line-height: 1em;
}

View File

@ -19,7 +19,7 @@
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
line-height: 1.4em;
line-height: var(--post-line-height);
}
.summary {
@ -33,7 +33,7 @@
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
height: 1.4em;
height: 1.4;
}
}

View File

@ -58,10 +58,10 @@
zoom: var(--_still_image-label-scale, 1);
content: 'gif';
position: absolute;
line-height: 10px;
font-size: 10px;
top: 5px;
left: 5px;
line-height: 1;
font-size: 0.7em;
top: 0.5em;
left: 0.5em;
background: rgba(127, 127, 127, 0.5);
color: #fff;
display: block;

View File

@ -25,8 +25,9 @@
border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border);
}
.tab-wrapper {
height: 28px;
height: 2em;
&:not(.active)::after {
left: 0;

View File

@ -22,7 +22,8 @@ const Timeline = {
'embedded',
'count',
'pinnedStatusIds',
'inProfile'
'inProfile',
'footerSlipgate' // reference to an element where we should put our footer
],
data () {
return {
@ -60,11 +61,11 @@ const Timeline = {
}
},
classes () {
let rootClasses = !this.embedded ? ['panel', 'panel-default'] : []
let rootClasses = !this.embedded ? ['panel', 'panel-default'] : ['-nonpanel']
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'] : [])
}
@ -141,6 +142,7 @@ const Timeline = {
this.$store.commit('showNewStatuses', { timeline: this.timelineName })
this.paused = false
}
window.scrollTo({ top: 0 })
},
fetchOlderStatuses: throttle(function () {
const store = this.$store

View File

@ -9,23 +9,22 @@
cursor: progress;
}
.timeline-heading {
max-width: 100%;
flex-wrap: nowrap;
align-items: center;
position: relative;
.loadmore-button {
flex-shrink: 0;
}
.loadmore-text {
flex-shrink: 0;
line-height: 1em;
}
.conversation-heading {
top: calc(var(--__panel-heading-height) * 2);
}
.timeline-footer {
border: none;
&.-nonpanel {
.timeline-heading {
text-align: center;
line-height: 2.75em;
padding: 0 0.5em;
}
.timeline-heading {
.button-default, .alert {
line-height: 2em;
width: 100%;
}
}
}
}

View File

@ -1,5 +1,5 @@
<template>
<div :class="[classes.root, 'Timeline']">
<div :class="['Timeline', classes.root]">
<div :class="classes.header">
<TimelineMenu v-if="!embedded" />
<button
@ -46,37 +46,39 @@
</div>
</div>
<div :class="classes.footer">
<div
v-if="count===0"
class="new-status-notification text-center faint"
>
{{ $t('timeline.no_statuses') }}
</div>
<div
v-else-if="bottomedOut"
class="new-status-notification text-center faint"
>
{{ $t('timeline.no_more_statuses') }}
</div>
<button
v-else-if="!timeline.loading"
class="button-unstyled -link -fullwidth"
@click.prevent="fetchOlderStatuses()"
>
<div class="new-status-notification text-center">
{{ $t('timeline.load_older') }}
<teleport :to="footerSlipgate" :disabled="!embedded || !footerSlipgate">
<div
v-if="count===0"
class="new-status-notification text-center faint"
>
{{ $t('timeline.no_statuses') }}
</div>
</button>
<div
v-else
class="new-status-notification text-center"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
<div
v-else-if="bottomedOut"
class="new-status-notification text-center faint"
>
{{ $t('timeline.no_more_statuses') }}
</div>
<button
v-else-if="!timeline.loading"
class="button-unstyled -link"
@click.prevent="fetchOlderStatuses()"
>
<div class="new-status-notification text-center">
{{ $t('timeline.load_older') }}
</div>
</button>
<div
v-else
class="new-status-notification text-center"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
</teleport>
</div>
</div>
</template>

View File

@ -12,8 +12,8 @@
@click="replyVisibilityAll = true"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-radio': replyVisibilityAll }"
class="menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': replyVisibilityAll }"
/>{{ $t('settings.reply_visibility_all') }}
</button>
<button
@ -21,8 +21,8 @@
@click="replyVisibilityFollowing = true"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-radio': replyVisibilityFollowing }"
class="menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': replyVisibilityFollowing }"
/>{{ $t('settings.reply_visibility_following_short') }}
</button>
<button
@ -30,8 +30,8 @@
@click="replyVisibilitySelf = true"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-radio': replyVisibilitySelf }"
class="menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': replyVisibilitySelf }"
/>{{ $t('settings.reply_visibility_self_short') }}
</button>
<div
@ -96,15 +96,14 @@
align-self: stretch;
> button {
font-size: 1.2em;
padding-left: 0.7em;
padding-right: 0.2em;
line-height: 100%;
height: 100%;
}
width: var(--__panel-heading-height-inner);
text-align: center;
.dropdown-item {
margin: 0;
svg {
font-size: 1.2em;
}
}
}

View File

@ -0,0 +1,323 @@
@import '../../_variables.scss';
.user-card {
position: relative;
z-index: 1;
&:hover {
--_still-image-img-visibility: visible;
--_still-image-canvas-visibility: hidden;
--_still-image-label-visibility: hidden;
}
.panel-heading {
padding: .5em 0;
text-align: center;
box-shadow: none;
background: transparent;
flex-direction: column;
align-items: stretch;
// create new stacking context
position: relative;
}
.panel-body {
word-wrap: break-word;
border-bottom-right-radius: inherit;
border-bottom-left-radius: inherit;
// create new stacking context
position: relative;
}
.background-image {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
mask: linear-gradient(to top, white, transparent) bottom no-repeat,
linear-gradient(to top, white, white);
// Autoprefixer seem to ignore this one, and also syntax is different
-webkit-mask-composite: xor;
mask-composite: exclude;
background-size: cover;
mask-size: 100% 60%;
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;
}
}
&-bio {
text-align: center;
display: block;
line-height: 1.3;
padding: 1em;
margin: 0;
a {
color: $fallback--link;
color: var(--postLink, $fallback--link);
}
img {
object-fit: contain;
vertical-align: middle;
max-width: 100%;
max-height: 400px;
}
}
// 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);
}
}
.user-info {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
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);
--_avatarShadowInset: var(--avatarShadowInset);
flex: 1 0 100%;
width: 56px;
height: 56px;
object-fit: cover;
}
}
&-avatar-link {
position: relative;
cursor: pointer;
&-overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
opacity: 0;
transition: opacity .2s ease;
svg {
color: #FFF;
}
}
&:hover &-overlay {
opacity: 1;
}
}
.external-link-button, .edit-profile-button {
cursor: pointer;
width: 2.5em;
text-align: center;
margin: -0.5em 0;
padding: 0.5em 0;
&:not(:hover) .icon {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
}
.user-summary {
display: block;
margin-left: 0.6em;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 1 0;
// This is so that text doesn't get overlapped by avatar's shadow if it has
// big one
z-index: 1;
line-height: 2em;
--emoji-size: 1.7em;
.top-line,
.bottom-line {
display: flex;
}
}
.user-name {
text-overflow: ellipsis;
overflow: hidden;
flex: 1 1 auto;
margin-right: 1em;
font-size: 1.1em;
}
.bottom-line {
font-weight: light;
font-size: 1.1em;
align-items: baseline;
.lock-icon {
margin-left: 0.5em;
}
.user-screen-name {
min-width: 1px;
flex: 0 1 auto;
text-overflow: ellipsis;
overflow: hidden;
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
.dailyAvg {
min-width: 1px;
flex: 0 0 auto;
margin-left: 1em;
font-size: 0.7em;
color: $fallback--text;
color: var(--text, $fallback--text);
}
.user-role {
flex: none;
color: $fallback--text;
color: var(--alertNeutralText, $fallback--text);
background-color: $fallback--fg;
background-color: var(--alertNeutral, $fallback--fg);
}
}
.user-meta {
margin-bottom: .15em;
display: flex;
align-items: baseline;
line-height: 22px;
flex-wrap: wrap;
.following {
flex: 1 0 auto;
margin: 0;
margin-bottom: .25em;
text-align: left;
}
.highlighter {
flex: 0 1 auto;
display: flex;
flex-wrap: wrap;
margin-right: -.5em;
align-self: start;
.userHighlightCl {
padding: 2px 10px;
flex: 1 0 auto;
}
.userHighlightSel {
padding-top: 0;
padding-bottom: 0;
flex: 1 0 auto;
}
.userHighlightText {
width: 70px;
flex: 1 0 auto;
}
.userHighlightCl,
.userHighlightText,
.userHighlightSel {
vertical-align: top;
margin-right: .5em;
margin-bottom: .25em;
}
}
}
.user-interactions {
position: relative;
display: flex;
flex-flow: row wrap;
margin-right: -.75em;
> * {
margin: 0 .75em .6em 0;
white-space: nowrap;
min-width: 95px;
}
button {
margin: 0;
}
}
}
.sidebar .edit-profile-button {
display: none;
}
.user-counts {
display: flex;
line-height:16px;
padding: .5em 1.5em 0em 1.5em;
text-align: center;
justify-content: space-between;
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
flex-wrap: wrap;
}
.user-count {
flex: 1 0 auto;
padding: .5em 0 .5em 0;
margin: 0 .5em;
h5 {
font-size:1em;
font-weight: bolder;
margin: 0 0 0.25em;
}
a {
text-decoration: none;
}
}

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
@ -284,320 +284,4 @@
<script src="./user_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.user-card {
position: relative;
&:hover {
--_still-image-img-visibility: visible;
--_still-image-canvas-visibility: hidden;
--_still-image-label-visibility: hidden;
}
.panel-heading {
padding: .5em 0;
text-align: center;
box-shadow: none;
background: transparent;
flex-direction: column;
align-items: stretch;
// create new stacking context
position: relative;
}
.panel-body {
word-wrap: break-word;
border-bottom-right-radius: inherit;
border-bottom-left-radius: inherit;
// create new stacking context
position: relative;
}
.background-image {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
mask: linear-gradient(to top, white, transparent) bottom no-repeat,
linear-gradient(to top, white, white);
// Autoprefixed seem to ignore this one, and also syntax is different
-webkit-mask-composite: xor;
mask-composite: exclude;
background-size: cover;
mask-size: 100% 60%;
border-top-left-radius: calc(var(--panelRadius) - 1px);
border-top-right-radius: calc(var(--panelRadius) - 1px);
background-color: var(--profileBg);
&.hide-bio {
mask-size: 100% 40px;
}
}
&-bio {
text-align: center;
display: block;
line-height: 18px;
padding: 1em;
margin: 0;
a {
color: $fallback--link;
color: var(--postLink, $fallback--link);
}
img {
object-fit: contain;
vertical-align: middle;
max-width: 100%;
max-height: 400px;
}
}
// 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);
}
}
.user-info {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
padding: 0 26px;
.container {
padding: 16px 0 6px;
display: flex;
align-items: flex-start;
max-height: 56px;
.Avatar {
--_avatarShadowBox: var(--avatarShadow);
--_avatarShadowFilter: var(--avatarShadowFilter);
--_avatarShadowInset: var(--avatarShadowInset);
flex: 1 0 100%;
width: 56px;
height: 56px;
object-fit: cover;
}
}
&-avatar-link {
position: relative;
cursor: pointer;
&-overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
opacity: 0;
transition: opacity .2s ease;
svg {
color: #FFF;
}
}
&:hover &-overlay {
opacity: 1;
}
}
.external-link-button, .edit-profile-button {
cursor: pointer;
width: 2.5em;
text-align: center;
margin: -0.5em 0;
padding: 0.5em 0;
&:not(:hover) .icon {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
}
.user-summary {
display: block;
margin-left: 0.6em;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 1 0;
// This is so that text doesn't get overlapped by avatar's shadow if it has
// big one
z-index: 1;
.top-line {
display: flex;
}
}
.user-name {
text-overflow: ellipsis;
overflow: hidden;
flex: 1 1 auto;
margin-right: 1em;
font-size: 15px;
--emoji-size: 14px;
}
.bottom-line {
display: flex;
font-weight: light;
font-size: 15px;
.lock-icon {
margin-left: 0.5em;
}
.user-screen-name {
min-width: 1px;
flex: 0 1 auto;
text-overflow: ellipsis;
overflow: hidden;
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
.dailyAvg {
min-width: 1px;
flex: 0 0 auto;
margin-left: 1em;
font-size: 0.7em;
color: $fallback--text;
color: var(--text, $fallback--text);
}
.user-role {
flex: none;
color: $fallback--text;
color: var(--alertNeutralText, $fallback--text);
background-color: $fallback--fg;
background-color: var(--alertNeutral, $fallback--fg);
}
}
.user-meta {
margin-bottom: .15em;
display: flex;
align-items: baseline;
font-size: 14px;
line-height: 22px;
flex-wrap: wrap;
.following {
flex: 1 0 auto;
margin: 0;
margin-bottom: .25em;
text-align: left;
}
.highlighter {
flex: 0 1 auto;
display: flex;
flex-wrap: wrap;
margin-right: -.5em;
align-self: start;
.userHighlightCl {
padding: 2px 10px;
flex: 1 0 auto;
}
.userHighlightSel {
padding-top: 0;
padding-bottom: 0;
flex: 1 0 auto;
}
.userHighlightText {
width: 70px;
flex: 1 0 auto;
}
.userHighlightCl,
.userHighlightText,
.userHighlightSel {
vertical-align: top;
margin-right: .5em;
margin-bottom: .25em;
}
}
}
.user-interactions {
position: relative;
display: flex;
flex-flow: row wrap;
margin-right: -.75em;
> * {
margin: 0 .75em .6em 0;
white-space: nowrap;
min-width: 95px;
}
button {
margin: 0;
}
}
}
.sidebar .edit-profile-button {
display: none;
}
.user-counts {
display: flex;
line-height:16px;
padding: .5em 1.5em 0em 1.5em;
text-align: center;
justify-content: space-between;
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
flex-wrap: wrap;
}
.user-count {
flex: 1 0 auto;
padding: .5em 0 .5em 0;
margin: 0 .5em;
h5 {
font-size:1em;
font-weight: bolder;
margin: 0 0 0.25em;
}
a {
text-decoration: none;
}
}
</style>
<style lang="scss" src="./user_card.scss" />

View File

@ -73,7 +73,7 @@
}
.user-list-screen-name {
font-size: 9px;
font-size: 0.65em;
}
}
}

View File

@ -24,5 +24,6 @@
<style lang="scss">
.user-panel .signed-in {
overflow: visible;
z-index: 10;
}
</style>

View File

@ -39,7 +39,8 @@ const UserProfile = {
return {
error: false,
userId: null,
tab: defaultTabKey
tab: defaultTabKey,
footerRef: null
}
},
created () {
@ -78,6 +79,9 @@ const UserProfile = {
}
},
methods: {
setFooterRef (el) {
this.footerRef = el
},
load (userNameOrId) {
const startFetchingTimeline = (timeline, userId) => {
// Clear timeline only if load another user's profile

View File

@ -56,6 +56,7 @@
:user-id="userId"
:pinned-status-ids="user.pinnedStatusIds"
:in-profile="true"
:footerSlipgate="footerRef"
/>
<div
v-if="followsTabVisible"
@ -94,6 +95,7 @@
:timeline="media"
:user-id="userId"
:in-profile="true"
:footerSlipgate="footerRef"
/>
<Timeline
v-if="isUs"
@ -105,8 +107,10 @@
timeline-name="favorites"
:timeline="favorites"
:in-profile="true"
:footerSlipgate="footerRef"
/>
</tab-switcher>
<div class="panel-footer" :ref="setFooterRef"></div>
</div>
<div
v-else
@ -176,7 +180,7 @@
}
.user-profile-field-name, .user-profile-field-value {
line-height: 18px;
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
@ -192,24 +196,6 @@
align-items: middle;
padding: 2em;
}
.timeline-heading {
display: flex;
justify-content: center;
.loadmore-button, .alert {
flex: 1;
}
.loadmore-button {
height: 28px;
margin: 10px .6em;
}
.title, .loadmore-text {
display: none
}
}
}
.user-profile-placeholder {
.panel-body {

View File

@ -76,17 +76,6 @@
min-height: 20vh;
max-height: 80vh;
.panel-heading {
.title {
text-align: center;
// TODO: Consider making these as default of panel
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.panel-body {
display: flex;
flex-direction: column-reverse;
@ -98,7 +87,7 @@
&-left {
padding: 1.1em 0.7em 0.7em;
line-height: 1.4em;
line-height: var(--post-line-height);
box-sizing: border-box;
> div {

View File

@ -10,7 +10,7 @@
border-top-color: var(--border, $fallback--border);
.error {
font-size: 14px;
font-size: 1rem;
}
a {

View File

@ -4,7 +4,7 @@
text-align: center;
.error {
font-size: 14px;
font-size: 1rem;
}
}
}

View File

@ -367,7 +367,7 @@
"max_thumbnails": "Maximum amount of thumbnails per post (empty = no limit)",
"hide_isp": "Hide instance-specific panel",
"hide_shoutbox": "Hide instance shoutbox",
"right_sidebar": "Show sidebar on the right side",
"right_sidebar": "Reverse order of columns",
"always_show_post_button": "Always show floating New Post button",
"hide_wallpaper": "Hide instance wallpaper",
"preload_images": "Preload images",
@ -481,6 +481,12 @@
"subject_line_noop": "Do not copy",
"conversation_display": "Conversation display style",
"conversation_display_tree": "Tree-style",
"disable_sticky_headers": "Don't stick column headers to top of the screen",
"show_scrollbars": "Show side column's scrollbars",
"third_column_mode": "When there's enough space, show third column containing",
"third_column_mode_none": "Don't show third column at all",
"third_column_mode_notifications": "Notifications column",
"third_column_mode_postform": "Main post form and navigation",
"tree_advanced": "Allow more flexible navigation in tree view",
"tree_fade_ancestors": "Display ancestors of the current status in faint text",
"conversation_display_linear": "Linear-style",

View File

@ -46,6 +46,7 @@ export const defaultState = {
pauseOnUnfocused: true,
stopGifs: true,
replyVisibility: 'all',
thirdColumnMode: 'notifications',
notificationVisibility: {
follows: true,
mentions: true,
@ -73,6 +74,8 @@ export const defaultState = {
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: true,
disableStickyHeaders: false,
showScrollbars: false,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default
@ -163,6 +166,9 @@ const config = {
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
break
case 'thirdColumnMode':
dispatch('setLayoutWidth', undefined)
break
}
}
}

View File

@ -13,7 +13,7 @@ const defaultState = {
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
)
},
mobileLayout: false,
layoutType: 'normal',
globalNotices: [],
layoutHeight: 0,
lastTimeline: null
@ -36,8 +36,8 @@ const interfaceMod = {
setNotificationPermission (state, permission) {
state.notificationPermission = permission
},
setMobileLayout (state, value) {
state.mobileLayout = value
setLayoutType (state, value) {
state.layoutType = value
},
closeSettingsModal (state) {
state.settingsModalState = 'hidden'
@ -72,6 +72,9 @@ const interfaceMod = {
setLayoutHeight (state, value) {
state.layoutHeight = value
},
setLayoutWidth (state, value) {
state.layoutWidth = value
},
setLastTimeline (state, value) {
state.lastTimeline = value
}
@ -86,9 +89,6 @@ const interfaceMod = {
setNotificationPermission ({ commit }, permission) {
commit('setNotificationPermission', permission)
},
setMobileLayout ({ commit }, value) {
commit('setMobileLayout', value)
},
closeSettingsModal ({ commit }) {
commit('closeSettingsModal')
},
@ -133,6 +133,24 @@ const interfaceMod = {
setLayoutHeight ({ commit }, value) {
commit('setLayoutHeight', value)
},
// value is optional, assuming it was cached prior
setLayoutWidth ({ commit, state, rootGetters }, value) {
let width = value
if (value !== undefined) {
commit('setLayoutWidth', value)
} else {
width = state.layoutWidth
}
const mobileLayout = width <= 800
const normalOrMobile = mobileLayout ? 'mobile' : 'normal'
const { thirdColumnMode } = rootGetters.mergedConfig
if (thirdColumnMode === 'none') {
commit('setLayoutType', normalOrMobile)
} else {
const wideLayout = width >= 1300
commit('setLayoutType', wideLayout ? 'wide' : normalOrMobile)
}
},
setLastTimeline ({ commit }, value) {
commit('setLastTimeline', value)
}

198
src/panel.scss Normal file
View File

@ -0,0 +1,198 @@
.panel {
position: relative;
display: flex;
flex-direction: column;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
&::after,
& {
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
}
&::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 3;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
box-shadow: var(--panelShadow);
pointer-events: none;
}
}
.panel-body {
padding: var(--panel-body-padding, 0);
&:empty::before {
content: "¯\\_(ツ)_/¯"; // Could use words but it'd require translations
display: block;
margin: 1em;
text-align: center;
}
> p {
line-height: 1.3;
padding: 1em;
margin: 0;
}
}
.panel-heading,
.panel-footer {
--panel-heading-height-padding: 0.6em;
--__panel-heading-height: 3.2em;
--__panel-heading-height-inner: calc(var(--__panel-heading-height) - 2 * var(--panel-heading-height-padding));
position: relative;
box-sizing: border-box;
display: grid;
grid-auto-flow: column;
grid-template-columns: minmax(50%, 1fr);
grid-auto-columns: auto;
grid-column-gap: 0.5em;
flex: none;
background-size: cover;
padding: 0.6em;
height: var(--__panel-heading-height);
line-height: var(--__panel-heading-height-inner);
z-index: 2;
&.-flexible-height {
--__panel-heading-height: auto;
&::after,
&::before {
display: none;
}
}
&.-stub {
&,
&::after {
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
}
}
&.-sticky {
position: sticky;
top: var(--navbar-height);
}
&::after,
&::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
pointer-events: none;
}
.title {
font-size: 1.3em;
}
.alert {
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;
}
&:not(.-flexible-height) {
> .button-default,
> .alert {
height: var(--__panel-heading-height-inner);
min-height: 0;
box-sizing: border-box;
margin: 0;
min-width: 1px;
padding-top: 0;
padding-bottom: 0;
align-self: stretch;
}
}
}
// TODO Should refactor panels into separate component and utilize slots
.panel-heading {
border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
border-width: 0 0 1px 0;
align-items: start;
// panel theme
color: var(--panelText);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
&::after {
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
z-index: -2;
border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
box-shadow: var(--panelHeaderShadow);
}
a,
.-link {
color: $fallback--link;
color: var(--panelLink, $fallback--link);
}
.faint {
background-color: transparent;
color: $fallback--faint;
color: var(--panelFaint, $fallback--faint);
}
.faint-link {
color: $fallback--faint;
color: var(--faintLink, $fallback--faint);
}
&:not(.-flexible-height) {
> .button-default {
flex-shrink: 0;
&,
i[class*=icon-] {
color: $fallback--text;
color: var(--btnPanelText, $fallback--text);
}
&:active {
background-color: $fallback--fg;
background-color: var(--btnPressedPanel, $fallback--fg);
color: $fallback--text;
color: var(--btnPressedPanelText, $fallback--text);
}
&:disabled {
color: $fallback--text;
color: var(--btnDisabledPanelText, $fallback--text);
}
&.toggled {
color: $fallback--text;
color: var(--btnToggledPanelText, $fallback--text);
}
}
}
}
.panel-footer {
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
align-items: center;
border-width: 1px 0 0 0;
border-style: solid;
border-color: var(--border, $fallback--border);
}

View File

@ -9,7 +9,7 @@ export const findOffset = (child, parent, { top = 0, left = 0 } = {}, ignorePadd
result.left += ignorePadding ? 0 : leftPadding
}
if (child.offsetParent && (parent === window || parent.contains(child.offsetParent) || parent === child.offsetParent)) {
if (child.offsetParent && window.getComputedStyle(child.offsetParent).position !== 'sticky' && (parent === window || parent.contains(child.offsetParent) || parent === child.offsetParent)) {
return findOffset(child.offsetParent, parent, result, false)
} else {
if (parent !== window) {

View File

@ -13,10 +13,10 @@ export const applyTheme = (input) => {
const styleSheet = styleEl.sheet
styleSheet.toString()
styleSheet.insertRule(`body { ${rules.radii} }`, 'index-max')
styleSheet.insertRule(`body { ${rules.colors} }`, 'index-max')
styleSheet.insertRule(`body { ${rules.shadows} }`, 'index-max')
styleSheet.insertRule(`body { ${rules.fonts} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.radii} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.colors} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.fonts} }`, 'index-max')
body.classList.remove('hidden')
}