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 UserPanel from './components/user_panel/user_panel.vue'
import NavPanel from './components/nav_panel/nav_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 InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue' import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_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 GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { windowWidth, windowHeight } from './services/window_utils/window_utils' import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { defineAsyncComponent } from 'vue'
export default { export default {
name: 'app', name: 'app',
components: { components: {
UserPanel, UserPanel,
NavPanel, NavPanel,
Notifications, Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
InstanceSpecificPanel, InstanceSpecificPanel,
FeaturesPanel, FeaturesPanel,
WhoToFollowPanel, WhoToFollowPanel,
@ -50,6 +50,16 @@ export default {
window.removeEventListener('resize', this.updateMobileState) window.removeEventListener('resize', this.updateMobileState)
}, },
computed: { 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 }, currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image }, userBackground () { return this.currentUser.background_image },
instanceBackground () { instanceBackground () {
@ -72,31 +82,38 @@ export default {
!this.$store.getters.mergedConfig.hideISP && !this.$store.getters.mergedConfig.hideISP &&
this.$store.state.instance.instanceSpecificPanelContent 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 }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
shoutboxPosition () { shoutboxPosition () {
return this.$store.getters.mergedConfig.showNewPostButton || false return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
}, },
hideShoutbox () { hideShoutbox () {
return this.$store.getters.mergedConfig.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 }, privateMode () { return this.$store.state.instance.private },
sidebarAlign () { reverseLayout () {
return { const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
'order': this.$store.getters.mergedConfig.sidebarRight ? 99 : 0 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']) ...mapGetters(['mergedConfig'])
}, },
methods: { methods: {
updateMobileState () { updateMobileState () {
const mobileLayout = windowWidth() <= 800 this.$store.dispatch('setLayoutWidth', windowWidth())
const layoutHeight = windowHeight() this.$store.dispatch('setLayoutHeight', windowHeight())
const changed = mobileLayout !== this.isMobileLayout
if (changed) {
this.$store.dispatch('setMobileLayout', mobileLayout)
}
this.$store.dispatch('setLayoutHeight', layoutHeight)
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

@ -173,7 +173,7 @@
margin: 8px; margin: 8px;
word-break: break-all; word-break: break-all;
h1 { h1 {
font-size: 14px; font-size: 1rem;
margin: 0px; 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 ChatTitle from '../chat_title/chat_title.vue'
import chatService from '../../services/chat_service/chat_service.js' import chatService from '../../services/chat_service/chat_service.js'
import { promiseInterval } from '../../services/promise_interval/promise_interval.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 { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faChevronDown, faChevronDown,
@ -20,7 +20,7 @@ library.add(
) )
const BOTTOMED_OUT_OFFSET = 10 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 SAFE_RESIZE_TIME_OFFSET = 100
const MARK_AS_READ_DELAY = 1500 const MARK_AS_READ_DELAY = 1500
const MAX_RETRIES = 10 const MAX_RETRIES = 10
@ -43,7 +43,6 @@ const Chat = {
}, },
created () { created () {
this.startFetching() this.startFetching()
window.addEventListener('resize', this.handleLayoutChange)
}, },
mounted () { mounted () {
window.addEventListener('scroll', this.handleScroll) window.addEventListener('scroll', this.handleScroll)
@ -52,15 +51,11 @@ const Chat = {
} }
this.$nextTick(() => { this.$nextTick(() => {
this.updateScrollableContainerHeight()
this.handleResize() this.handleResize()
}) })
this.setChatLayout()
}, },
unmounted () { unmounted () {
window.removeEventListener('scroll', this.handleScroll) window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleLayoutChange)
this.unsetChatLayout()
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false) if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.dispatch('clearCurrentChat') this.$store.dispatch('clearCurrentChat')
}, },
@ -96,8 +91,7 @@ const Chat = {
...mapState({ ...mapState({
backendInteractor: state => state.api.backendInteractor, backendInteractor: state => state.api.backendInteractor,
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus, mastoUserSocketStatus: state => state.api.mastoUserSocketStatus,
mobileLayout: state => state.interface.mobileLayout, mobileLayout: state => state.interface.layoutType === 'mobile',
layoutHeight: state => state.interface.layoutHeight,
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser
}) })
}, },
@ -115,9 +109,6 @@ const Chat = {
'$route': function () { '$route': function () {
this.startFetching() this.startFetching()
}, },
layoutHeight () {
this.handleResize({ expand: true })
},
mastoUserSocketStatus (newValue) { mastoUserSocketStatus (newValue) {
if (newValue === WSConnectionStatus.JOINED) { if (newValue === WSConnectionStatus.JOINED) {
this.fetchChat({ isFirstFetch: true }) this.fetchChat({ isFirstFetch: true })
@ -132,7 +123,6 @@ const Chat = {
onFilesDropped () { onFilesDropped () {
this.$nextTick(() => { this.$nextTick(() => {
this.handleResize() this.handleResize()
this.updateScrollableContainerHeight()
}) })
}, },
handleVisibilityChange () { 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. // Preserves the scroll position when OSK appears or the posting form changes its height.
handleResize (opts = {}) { handleResize (opts = {}) {
const { expand = false, delayed = false } = opts const { expand = false, delayed = false } = opts
@ -190,29 +144,21 @@ const Chat = {
} }
this.$nextTick(() => { this.$nextTick(() => {
this.updateScrollableContainerHeight() const { scrollHeight = undefined } = this.lastScrollPosition
this.lastScrollPosition = getScrollPosition()
const { offsetHeight = undefined } = this.lastScrollPosition const diff = this.lastScrollPosition.scrollHeight - scrollHeight
this.lastScrollPosition = getScrollPosition(this.$refs.scrollable) if (diff > 0 || (!this.bottomedOut() && expand)) {
const diff = this.lastScrollPosition.offsetHeight - offsetHeight
if (diff < 0 || (!this.bottomedOut() && expand)) {
this.$nextTick(() => { this.$nextTick(() => {
this.updateScrollableContainerHeight() window.scrollTo({ top: window.scrollY + diff })
this.$refs.scrollable.scrollTo({
top: this.$refs.scrollable.scrollTop - diff,
left: 0
})
}) })
} }
}) })
}, },
scrollDown (options = {}) { scrollDown (options = {}) {
const { behavior = 'auto', forceRead = false } = options const { behavior = 'auto', forceRead = false } = options
const scrollable = this.$refs.scrollable
if (!scrollable) { return }
this.$nextTick(() => { this.$nextTick(() => {
scrollable.scrollTo({ top: scrollable.scrollHeight, left: 0, behavior }) window.scrollTo({ top: document.documentElement.scrollHeight, behavior })
}) })
if (forceRead) { if (forceRead) {
this.readChat() this.readChat()
@ -228,11 +174,10 @@ const Chat = {
}) })
}, },
bottomedOut (offset) { bottomedOut (offset) {
return isBottomedOut(this.$refs.scrollable, offset) return isBottomedOut(offset)
}, },
reachedTop () { reachedTop () {
const scrollable = this.$refs.scrollable return window.scrollY <= 0
return scrollable && scrollable.scrollTop <= 0
}, },
cullOlderCheck () { cullOlderCheck () {
window.setTimeout(() => { window.setTimeout(() => {
@ -263,10 +208,9 @@ const Chat = {
} }
}, 200), }, 200),
handleScrollUp (positionBeforeLoading) { handleScrollUp (positionBeforeLoading) {
const positionAfterLoading = getScrollPosition(this.$refs.scrollable) const positionAfterLoading = getScrollPosition()
this.$refs.scrollable.scrollTo({ window.scrollTo({
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading), top: getNewTopPosition(positionBeforeLoading, positionAfterLoading)
left: 0
}) })
}, },
fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) { fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) {
@ -285,22 +229,18 @@ const Chat = {
chatService.clear(chatMessageService) chatService.clear(chatMessageService)
} }
const positionBeforeUpdate = getScrollPosition(this.$refs.scrollable) const positionBeforeUpdate = getScrollPosition()
this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => { this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
this.$nextTick(() => { this.$nextTick(() => {
if (fetchOlderMessages) { if (fetchOlderMessages) {
this.handleScrollUp(positionBeforeUpdate) this.handleScrollUp(positionBeforeUpdate)
} }
if (isFirstFetch) {
this.updateScrollableContainerHeight()
}
// In vertical screens, the first batch of fetched messages may not always take the // In vertical screens, the first batch of fetched messages may not always take the
// full height of the scrollable container. // full height of the scrollable container.
// If this is the case, we want to fetch the messages until 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. // 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 }) this.fetchChat({ maxId: this.currentChatMessageService.minId })
} }
}) })
@ -336,9 +276,6 @@ const Chat = {
this.handleResize() this.handleResize()
// When the posting form size changes because of a media attachment, we need an extra resize // 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. // to account for the potential delay in the DOM update.
setTimeout(() => {
this.updateScrollableContainerHeight()
}, SAFE_RESIZE_TIME_OFFSET)
this.scrollDown({ forceRead: true }) this.scrollDown({ forceRead: true })
}) })
}, },

View file

@ -1,28 +1,22 @@
.chat-view { .chat-view {
display: flex; display: flex;
height: calc(100vh - 60px); height: 100%;
width: 100%;
.chat-title {
// prevents chat header jumping on when the user avatar loads
height: 28px;
}
.chat-view-inner { .chat-view-inner {
height: auto; height: auto;
width: 100%; width: 100%;
overflow: visible; overflow: visible;
display: flex; display: flex;
margin: 0.5em 0.5em 0 0.5em;
} }
.chat-view-body { .chat-view-body {
box-sizing: border-box;
background-color: var(--chatBg, $fallback--bg); background-color: var(--chatBg, $fallback--bg);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
overflow: visible; overflow: visible;
min-height: 100%; min-height: calc(100vh - var(--navbar-height));
margin: 0 0 0 0; margin: 0 0 0 0;
border-radius: 10px 10px 0 0; border-radius: 10px 10px 0 0;
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0; border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
@ -32,11 +26,9 @@
} }
} }
.scrollable-message-list { .message-list {
padding: 0 0.8em; padding: 0 0.8em;
height: 100%; height: 100%;
overflow-y: scroll;
overflow-x: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -44,24 +36,21 @@
.footer { .footer {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
z-index: 1;
} }
.chat-view-heading { .chat-view-heading {
align-items: center; grid-template-columns: auto minmax(50%, 1fr);
justify-content: space-between;
top: 50px;
display: flex;
z-index: 2;
position: sticky;
overflow: hidden;
} }
.go-back-button { .go-back-button {
cursor: pointer;
width: 28px;
text-align: center; text-align: center;
padding: 0.6em; line-height: 1;
margin: -0.6em 0.6em -0.6em -0.6em; height: 100%;
align-self: start;
width: var(--__panel-heading-height-inner);
} }
.jump-to-bottom-button { .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">
<div class="chat-view-inner"> <div class="chat-view-inner">
<div <div
id="nav"
ref="inner" ref="inner"
class="panel-default panel chat-view-body" class="panel-default panel chat-view-body"
> >
<div <div
ref="header" ref="header"
class="panel-heading chat-view-heading mobile-hidden" class="panel-heading -sticky chat-view-heading"
> >
<a <button
class="go-back-button" class="button-unstyled go-back-button"
@click="goBack" @click="goBack"
> >
<FAIcon <FAIcon
size="lg" size="lg"
icon="chevron-left" icon="chevron-left"
/> />
</a> </button>
<div class="title text-center"> <div class="title text-center">
<ChatTitle <ChatTitle
:user="recipient" :user="recipient"
@ -27,10 +26,8 @@
</div> </div>
</div> </div>
<div <div
ref="scrollable" class="message-list"
class="scrollable-message-list"
:style="{ height: scrollableContainerHeight }" :style="{ height: scrollableContainerHeight }"
@scroll="handleScroll"
> >
<template v-if="!errorLoadingChat"> <template v-if="!errorLoadingChat">
<ChatMessage <ChatMessage

View file

@ -1,9 +1,9 @@
// Captures a scroll position // Captures a scroll position
export const getScrollPosition = (el) => { export const getScrollPosition = () => {
return { return {
scrollTop: el.scrollTop, scrollTop: window.scrollY,
scrollHeight: el.scrollHeight, scrollHeight: document.documentElement.scrollHeight,
offsetHeight: el.offsetHeight offsetHeight: window.innerHeight
} }
} }
@ -13,21 +13,12 @@ export const getNewTopPosition = (previousPosition, newPosition) => {
return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight) return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight)
} }
export const isBottomedOut = (el, offset = 0) => { export const isBottomedOut = (offset = 0) => {
if (!el) { return } const scrollHeight = window.scrollY + offset
const scrollHeight = el.scrollTop + offset const totalHeight = document.documentElement.scrollHeight - window.innerHeight
const totalHeight = el.scrollHeight - el.offsetHeight
return totalHeight <= scrollHeight 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. // Returns whether or not the scrollbar is visible.
export const isScrollable = (el) => { export const isScrollable = () => {
if (!el) return return document.documentElement.scrollHeight > window.innerHeight
return el.scrollHeight > el.clientHeight
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -58,16 +58,7 @@
background-color: var(--bg, $fallback--bg); background-color: var(--bg, $fallback--bg);
.dialog-modal-heading { .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 { .title {
margin-bottom: 0;
text-align: center; text-align: center;
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,6 @@
<nav <nav
id="nav" id="nav"
class="mobile-nav" class="mobile-nav"
:class="{ 'mobile-hidden': isChat }"
@click="scrollToTop()" @click="scrollToTop()"
> >
<div class="item"> <div class="item">
@ -51,7 +50,7 @@
<div <div
v-if="currentUser" v-if="currentUser"
class="mobile-notifications-drawer" class="mobile-notifications-drawer"
:class="{ 'closed': !notificationsOpen }" :class="{ '-closed': !notificationsOpen }"
@touchstart.stop="notificationsTouchStart" @touchstart.stop="notificationsTouchStart"
@touchmove.stop="notificationsTouchMove" @touchmove.stop="notificationsTouchMove"
> >
@ -69,12 +68,9 @@
</div> </div>
<div <div
class="mobile-notifications" class="mobile-notifications"
id="mobile-notifications"
@scroll="onScroll" @scroll="onScroll"
> >
<Notifications
ref="notifications"
:no-heading="true"
/>
</div> </div>
</div> </div>
<SideDrawer <SideDrawer
@ -92,12 +88,10 @@
.MobileNav { .MobileNav {
.mobile-nav { .mobile-nav {
display: grid; display: grid;
line-height: 50px; line-height: var(--navbar-height);
height: 50px;
grid-template-rows: 50px; grid-template-rows: 50px;
grid-template-columns: 2fr auto; grid-template-columns: 2fr auto;
width: 100%; width: 100%;
position: fixed;
box-sizing: border-box; box-sizing: border-box;
a { a {
color: var(--topBarLink, $fallback--link); color: var(--topBarLink, $fallback--link);
@ -156,8 +150,9 @@
z-index: 1001; z-index: 1001;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
&.closed { &.-closed {
transform: translateX(100%); transform: translateX(100%);
box-shadow: none;
} }
} }
@ -185,7 +180,7 @@
.mobile-notifications { .mobile-notifications {
margin-top: 50px; margin-top: 50px;
width: 100vw; width: 100vw;
height: calc(100vh - 50px); height: calc(100vh - var(--navbar-height));
overflow-x: hidden; overflow-x: hidden;
overflow-y: scroll; overflow-y: scroll;

View file

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

View file

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

View file

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

View file

@ -23,8 +23,6 @@ const Notifications = {
NotificationFilters NotificationFilters
}, },
props: { props: {
// Disables display of panel header
noHeading: Boolean,
// Disables panel styles, unread mark, potentially other notification-related actions // Disables panel styles, unread mark, potentially other notification-related actions
// meant for "Interactions" timeline // meant for "Interactions" timeline
minimalMode: Boolean, minimalMode: Boolean,
@ -65,6 +63,18 @@ const Notifications = {
loading () { loading () {
return this.$store.state.statuses.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 () { notificationsToDisplay () {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount) return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
}, },

View file

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

View file

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

View file

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

View file

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

View file

@ -486,7 +486,7 @@ const PostStatusForm = {
const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom'] const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom']
const bottomBottomPadding = pxStringToNumber(bottomBottomPaddingStr) 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') || this.$el.closest('.post-form-modal-view') ||
window window

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,18 @@
.settings-modal { .settings-modal {
overflow: hidden; overflow: hidden;
.setting-list,
.option-list {
list-style-type: none;
padding-left: 2em;
li {
margin-bottom: 0.5em;
}
.suboptions {
margin-top: 0.3em
}
}
&.peek { &.peek {
.settings-modal-panel { .settings-modal-panel {
/* Explanation: /* Explanation:
@ -42,7 +54,7 @@
overflow-y: hidden; overflow-y: hidden;
.btn { .btn {
min-height: 28px; min-height: 2em;
min-width: 10em; min-width: 10em;
padding: 0 2em; padding: 0 2em;
} }

View file

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

View file

@ -38,6 +38,11 @@ const GeneralTab = {
value: mode, value: mode,
label: this.$t(`settings.mention_link_display_${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: loopSilentAvailable:
// Firefox // Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||

View file

@ -60,6 +60,25 @@
{{ $t('settings.virtual_scrolling') }} {{ $t('settings.virtual_scrolling') }}
</BooleanSetting> </BooleanSetting>
</li> </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> <li>
<BooleanSetting <BooleanSetting
path="alwaysShowNewPostButton" path="alwaysShowNewPostButton"

View file

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

View file

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

View file

@ -245,25 +245,8 @@
border-color: var(--border, $fallback--border); 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 { .btn {
margin-left: 0;
padding: 0 1em;
min-width: 3em; min-width: 3em;
min-height: 30px;
} }
} }
} }
@ -342,7 +325,7 @@
.btn { .btn {
flex-grow: 1; flex-grow: 1;
min-height: 28px; min-height: 2em;
min-width: 0; min-width: 0;
max-width: 10em; max-width: 10em;
padding: 0; padding: 0;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,23 +9,22 @@
cursor: progress; cursor: progress;
} }
.timeline-heading { .conversation-heading {
max-width: 100%; top: calc(var(--__panel-heading-height) * 2);
flex-wrap: nowrap;
align-items: center;
position: relative;
.loadmore-button {
flex-shrink: 0;
}
.loadmore-text {
flex-shrink: 0;
line-height: 1em;
}
} }
.timeline-footer { &.-nonpanel {
border: none; .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> <template>
<div :class="[classes.root, 'Timeline']"> <div :class="['Timeline', classes.root]">
<div :class="classes.header"> <div :class="classes.header">
<TimelineMenu v-if="!embedded" /> <TimelineMenu v-if="!embedded" />
<button <button
@ -46,37 +46,39 @@
</div> </div>
</div> </div>
<div :class="classes.footer"> <div :class="classes.footer">
<div <teleport :to="footerSlipgate" :disabled="!embedded || !footerSlipgate">
v-if="count===0" <div
class="new-status-notification text-center faint" v-if="count===0"
> class="new-status-notification text-center faint"
{{ $t('timeline.no_statuses') }} >
</div> {{ $t('timeline.no_statuses') }}
<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') }}
</div> </div>
</button> <div
<div v-else-if="bottomedOut"
v-else class="new-status-notification text-center faint"
class="new-status-notification text-center" >
> {{ $t('timeline.no_more_statuses') }}
<FAIcon </div>
icon="circle-notch" <button
spin v-else-if="!timeline.loading"
size="lg" class="button-unstyled -link"
/> @click.prevent="fetchOlderStatuses()"
</div> >
<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>
</div> </div>
</template> </template>

View file

@ -12,8 +12,8 @@
@click="replyVisibilityAll = true" @click="replyVisibilityAll = true"
> >
<span <span
class="menu-checkbox" class="menu-checkbox -radio"
:class="{ 'menu-checkbox-radio': replyVisibilityAll }" :class="{ 'menu-checkbox-checked': replyVisibilityAll }"
/>{{ $t('settings.reply_visibility_all') }} />{{ $t('settings.reply_visibility_all') }}
</button> </button>
<button <button
@ -21,8 +21,8 @@
@click="replyVisibilityFollowing = true" @click="replyVisibilityFollowing = true"
> >
<span <span
class="menu-checkbox" class="menu-checkbox -radio"
:class="{ 'menu-checkbox-radio': replyVisibilityFollowing }" :class="{ 'menu-checkbox-checked': replyVisibilityFollowing }"
/>{{ $t('settings.reply_visibility_following_short') }} />{{ $t('settings.reply_visibility_following_short') }}
</button> </button>
<button <button
@ -30,8 +30,8 @@
@click="replyVisibilitySelf = true" @click="replyVisibilitySelf = true"
> >
<span <span
class="menu-checkbox" class="menu-checkbox -radio"
:class="{ 'menu-checkbox-radio': replyVisibilitySelf }" :class="{ 'menu-checkbox-checked': replyVisibilitySelf }"
/>{{ $t('settings.reply_visibility_self_short') }} />{{ $t('settings.reply_visibility_self_short') }}
</button> </button>
<div <div
@ -96,15 +96,14 @@
align-self: stretch; align-self: stretch;
> button { > button {
font-size: 1.2em;
padding-left: 0.7em;
padding-right: 0.2em;
line-height: 100%; line-height: 100%;
height: 100%; height: 100%;
} width: var(--__panel-heading-height-inner);
text-align: center;
.dropdown-item { svg {
margin: 0; 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" :style="style"
class="background-image" class="background-image"
/> />
<div class="panel-heading"> <div class="panel-heading -flexible-height">
<div class="user-info"> <div class="user-info">
<div class="container"> <div class="container">
<a <a
@ -284,320 +284,4 @@
<script src="./user_card.js"></script> <script src="./user_card.js"></script>
<style lang="scss"> <style lang="scss" src="./user_card.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>

View file

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

View file

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

View file

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

View file

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

View file

@ -76,17 +76,6 @@
min-height: 20vh; min-height: 20vh;
max-height: 80vh; 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 { .panel-body {
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
@ -98,7 +87,7 @@
&-left { &-left {
padding: 1.1em 0.7em 0.7em; padding: 1.1em 0.7em 0.7em;
line-height: 1.4em; line-height: var(--post-line-height);
box-sizing: border-box; box-sizing: border-box;
> div { > div {

View file

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

View file

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

View file

@ -367,7 +367,7 @@
"max_thumbnails": "Maximum amount of thumbnails per post (empty = no limit)", "max_thumbnails": "Maximum amount of thumbnails per post (empty = no limit)",
"hide_isp": "Hide instance-specific panel", "hide_isp": "Hide instance-specific panel",
"hide_shoutbox": "Hide instance shoutbox", "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", "always_show_post_button": "Always show floating New Post button",
"hide_wallpaper": "Hide instance wallpaper", "hide_wallpaper": "Hide instance wallpaper",
"preload_images": "Preload images", "preload_images": "Preload images",
@ -481,6 +481,12 @@
"subject_line_noop": "Do not copy", "subject_line_noop": "Do not copy",
"conversation_display": "Conversation display style", "conversation_display": "Conversation display style",
"conversation_display_tree": "Tree-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_advanced": "Allow more flexible navigation in tree view",
"tree_fade_ancestors": "Display ancestors of the current status in faint text", "tree_fade_ancestors": "Display ancestors of the current status in faint text",
"conversation_display_linear": "Linear-style", "conversation_display_linear": "Linear-style",

View file

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

View file

@ -13,7 +13,7 @@ const defaultState = {
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
) )
}, },
mobileLayout: false, layoutType: 'normal',
globalNotices: [], globalNotices: [],
layoutHeight: 0, layoutHeight: 0,
lastTimeline: null lastTimeline: null
@ -36,8 +36,8 @@ const interfaceMod = {
setNotificationPermission (state, permission) { setNotificationPermission (state, permission) {
state.notificationPermission = permission state.notificationPermission = permission
}, },
setMobileLayout (state, value) { setLayoutType (state, value) {
state.mobileLayout = value state.layoutType = value
}, },
closeSettingsModal (state) { closeSettingsModal (state) {
state.settingsModalState = 'hidden' state.settingsModalState = 'hidden'
@ -72,6 +72,9 @@ const interfaceMod = {
setLayoutHeight (state, value) { setLayoutHeight (state, value) {
state.layoutHeight = value state.layoutHeight = value
}, },
setLayoutWidth (state, value) {
state.layoutWidth = value
},
setLastTimeline (state, value) { setLastTimeline (state, value) {
state.lastTimeline = value state.lastTimeline = value
} }
@ -86,9 +89,6 @@ const interfaceMod = {
setNotificationPermission ({ commit }, permission) { setNotificationPermission ({ commit }, permission) {
commit('setNotificationPermission', permission) commit('setNotificationPermission', permission)
}, },
setMobileLayout ({ commit }, value) {
commit('setMobileLayout', value)
},
closeSettingsModal ({ commit }) { closeSettingsModal ({ commit }) {
commit('closeSettingsModal') commit('closeSettingsModal')
}, },
@ -133,6 +133,24 @@ const interfaceMod = {
setLayoutHeight ({ commit }, value) { setLayoutHeight ({ commit }, value) {
commit('setLayoutHeight', 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) { setLastTimeline ({ commit }, value) {
commit('setLastTimeline', 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 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) return findOffset(child.offsetParent, parent, result, false)
} else { } else {
if (parent !== window) { if (parent !== window) {

View file

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