diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 61832566..3752e3c1 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -20,10 +20,12 @@
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index af47f032..fb8ffa30 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -8,13 +8,17 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes, faBell, - faBars + faBars, + faArrowUp, + faMinus } from '@fortawesome/free-solid-svg-icons' library.add( faTimes, faBell, - faBars + faBars, + faArrowUp, + faMinus ) const MobileNav = { @@ -25,12 +29,13 @@ const MobileNav = { }, data: () => ({ notificationsCloseGesture: undefined, - notificationsOpen: false + notificationsOpen: false, + notificationsAtTop: true }), created () { this.notificationsCloseGesture = GestureService.swipeGesture( GestureService.DIRECTION_RIGHT, - this.closeMobileNotifications, + () => this.closeMobileNotifications(true), 50 ) }, @@ -61,12 +66,14 @@ const MobileNav = { openMobileNotifications () { this.notificationsOpen = true }, - closeMobileNotifications () { + closeMobileNotifications (markRead) { if (this.notificationsOpen) { // make sure to mark notifs seen only when the notifs were open and not // from close-calls. this.notificationsOpen = false - this.markNotificationsAsSeen() + if (markRead) { + this.markNotificationsAsSeen() + } } }, notificationsTouchStart (e) { @@ -78,6 +85,9 @@ const MobileNav = { scrollToTop () { window.scrollTo(0, 0) }, + scrollMobileNotificationsToTop () { + this.$refs.mobileNotifications.scrollTo(0, 0) + }, logout () { this.$router.replace('/main/public') this.$store.dispatch('logout') @@ -87,6 +97,7 @@ const MobileNav = { this.$store.dispatch('markNotificationsAsSeen') }, onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) { + this.notificationsAtTop = scrollTop > 0 if (scrollTop + clientHeight >= scrollHeight) { this.$refs.notifications.fetchOlderNotifications() } diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index 9152879c..264bc713 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -48,19 +48,34 @@ >
{{ $t('notifications.notifications') }} - + +
@@ -165,6 +180,10 @@ box-shadow: 0px 0px 4px rgba(0,0,0,.6); box-shadow: var(--topBarShadow); + .spacer { + flex: 1; + } + .title { font-size: 1.3em; margin-left: 0.6em; diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 7373ca63..d628c380 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -12,7 +12,7 @@ @click="toggleCollapse" > @@ -143,12 +143,17 @@ border: none; } - .timelines-chevron { + .navigation-chevron { margin-left: 0.8em; margin-right: 0.8em; font-size: 1.1em; } + .timelines-chevron { + margin-left: 0.8em; + font-size: 1.1em; + } + .timelines-background { padding: 0 0 0 0.6em; background-color: $fallback--lightBg; diff --git a/src/components/navigation/navigation_pins.vue b/src/components/navigation/navigation_pins.vue index 5b3fa6f4..9cb4b536 100644 --- a/src/components/navigation/navigation_pins.vue +++ b/src/components/navigation/navigation_pins.vue @@ -39,10 +39,8 @@ height: 0.5em; width: 0.5em; position: absolute; - right: calc(50% - 0.25em); - top: calc(50% - 0.25em); - margin-left: 6px; - margin-top: -6px; + right: calc(50% - 0.75em); + top: calc(50% - 0.5em); background-color: $fallback--cRed; background-color: var(--badgeNotification, $fallback--cRed); } diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue index b0213167..1315b51a 100644 --- a/src/components/notifications/notification_filters.vue +++ b/src/components/notifications/notification_filters.vue @@ -109,22 +109,3 @@ export default { } } - - diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 0851f407..c3acd9e0 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -10,10 +10,12 @@ import { } from '../../services/notification_utils/notification_utils.js' import FaviconService from '../../services/favicon_service/favicon_service.js' import { library } from '@fortawesome/fontawesome-svg-core' -import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons' library.add( - faCircleNotch + faCircleNotch, + faArrowUp, + faMinus ) const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30 @@ -34,6 +36,7 @@ const Notifications = { }, data () { return { + showScrollTop: false, bottomedOut: false, // How many seen notifications to display in the list. The more there are, // the heavier the page becomes. This count is increased when loading @@ -90,8 +93,20 @@ const Notifications = { notificationsToDisplay () { return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount) }, + noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders }, ...mapGetters(['unreadChatCount']) }, + mounted () { + this.scrollerRef = this.$refs.root.closest('.column.-scrollable') + if (!this.scrollerRef) { + this.scrollerRef = this.$refs.root.closest('.mobile-notifications') + } + this.scrollerRef.addEventListener('scroll', this.updateScrollPosition) + }, + unmounted () { + if (!this.scrollerRef) return + this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition) + }, watch: { unseenCountTitle (count) { if (count > 0) { @@ -101,9 +116,29 @@ const Notifications = { FaviconService.clearFaviconBadge() this.$store.dispatch('setPageTitle', '') } + }, + teleportTarget () { + // handle scroller change + this.$nextTick(() => { + this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition) + this.scrollerRef = this.$refs.root.closest('.column.-scrollable') + if (!this.scrollerRef) { + this.scrollerRef = this.$refs.root.closest('.mobile-notifications') + } + this.scrollerRef.addEventListener('scroll', this.updateScrollPosition) + this.updateScrollPosition() + }) } }, methods: { + scrollToTop () { + const scrollable = this.scrollerRef + scrollable.scrollTo({ top: this.$refs.root.offsetTop }) + // this.$refs.root.scrollIntoView({ behavior: 'smooth', block: 'start' }) + }, + updateScrollPosition () { + this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop + }, markAsSeen () { this.$store.dispatch('markNotificationsAsSeen') this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index e778e27b..f9c90a7e 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -4,6 +4,7 @@ :to="teleportTarget" >
@@ -19,14 +20,34 @@ class="badge badge-notification unseen-count" >{{ unseenCount }}
+
+ +
- +
- - diff --git a/src/components/quick_view_settings/quick_view_settings.vue b/src/components/quick_view_settings/quick_view_settings.vue index 99b14a66..b0413cac 100644 --- a/src/components/quick_view_settings/quick_view_settings.vue +++ b/src/components/quick_view_settings/quick_view_settings.vue @@ -74,21 +74,3 @@ - - diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 8f6cae66..b7414610 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -1,4 +1,5 @@ import Status from '../status/status.vue' +import { mapState } from 'vuex' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' import Conversation from '../conversation/conversation.vue' import TimelineMenu from '../timeline_menu/timeline_menu.vue' @@ -6,11 +7,15 @@ import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings. import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue' import { debounce, throttle, keyBy } from 'lodash' import { library } from '@fortawesome/fontawesome-svg-core' -import { faCircleNotch, faCog } from '@fortawesome/free-solid-svg-icons' +import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons' library.add( faCircleNotch, - faCog + faCog, + faMinus, + faArrowUp, + faCirclePlus, + faCheck ) const Timeline = { @@ -29,6 +34,7 @@ const Timeline = { ], data () { return { + showScrollTop: false, paused: false, unfocused: false, bottomedOut: false, @@ -63,6 +69,13 @@ const Timeline = { return `${this.$t('timeline.show_new')} (${this.newStatusCount})` } }, + mobileLoadButtonString () { + if (this.timeline.flushMarker !== 0) { + return '+' + } else { + return this.newStatusCount > 99 ? '∞' : this.newStatusCount + } + }, classes () { let rootClasses = !this.embedded ? ['panel', 'panel-default'] : ['-nonpanel'] if (this.blockingClicks) rootClasses = rootClasses.concat(['-blocked', '_misclick-prevention']) @@ -87,7 +100,10 @@ const Timeline = { }, virtualScrollingEnabled () { return this.$store.getters.mergedConfig.virtualScrolling - } + }, + ...mapState({ + mobileLayout: state => state.interface.layoutType === 'mobile' + }) }, created () { const store = this.$store @@ -123,6 +139,9 @@ const Timeline = { this.$store.commit('setLoading', { timeline: this.timelineName, value: false }) }, methods: { + scrollToTop () { + window.scrollTo({ top: this.$el.offsetTop }) + }, stopBlockingClicks: debounce(function () { this.blockingClicks = false }, 1000), @@ -222,6 +241,7 @@ const Timeline = { } }, handleScroll: throttle(function (e) { + this.showScrollTop = this.$el.offsetTop < window.scrollY this.determineVisibleStatuses() this.scrollLoad(e) }, 200), diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss index 9e009fd3..c6fb1ca7 100644 --- a/src/components/timeline/timeline.scss +++ b/src/components/timeline/timeline.scss @@ -1,8 +1,35 @@ @import '../../_variables.scss'; .Timeline { - .loadmore-text { - opacity: 1; + .alert-dot { + border-radius: 100%; + height: 8px; + width: 8px; + position: absolute; + left: calc(50% - 4px); + top: calc(50% - 4px); + margin-left: 6px; + margin-top: -6px; + background-color: var(--badgeNeutral); + } + + .alert-badge { + font-size: 0.75em; + line-height: 1; + text-align: right; + border-radius: var(--tooltipRadius); + position: absolute; + left: calc(50% - 0.5em); + top: calc(50% - 0.4em); + padding: 0.2em; + margin-left: 0.7em; + margin-top: -1em; + background-color: var(--badgeNeutral); + color: var(--badgeNeutralText); + } + + .loadmore-button { + position: relative; } &.-blocked { diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index f842240b..8a036fd0 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -5,22 +5,74 @@ v-if="!embedded" :timeline-name="timelineName" /> -
- {{ $t('timeline.up_to_date') }} +
- - + + + +
button { + box-sizing: border-box; + padding: calc(1 * var(--panel-heading-height-padding)) 0; + height: 100%; + width: 100%; + text-align: center; + + svg { + font-size: 1.2em; + } + } + } + + .rightside-icon { + align-self: stretch; + text-align: center; + width: var(--__panel-heading-height); + margin-right: calc(-1 * var(--__panel-heading-gap)); + + svg { + font-size: 1.2em; + } + } } .panel-footer { diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js index c2983be7..dc7a5d89 100644 --- a/src/services/theme_data/pleromafe.js +++ b/src/services/theme_data/pleromafe.js @@ -709,6 +709,14 @@ export const SLOT_INHERITANCE = { textColor: 'bw' }, + badgeNeutral: '--cGreen', + badgeNeutralText: { + depends: ['text', 'badgeNeutral'], + layer: 'badge', + variant: 'badgeNeutral', + textColor: 'bw' + }, + chatBg: { depends: ['bg'] },