Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85830617
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
13 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/components/scroll_top_button/scroll_top_button.js b/src/components/scroll_top_button/scroll_top_button.js
index 41340dbad0..45e6923f9f 100644
--- a/src/components/scroll_top_button/scroll_top_button.js
+++ b/src/components/scroll_top_button/scroll_top_button.js
@@ -1,19 +1,19 @@
const ScrollTopButton = {
props: {
fast: {
type: Boolean,
required: false,
default: false,
},
},
inject: ['bodyScrollPosition'],
methods: {
scrollToTop() {
const speed = this.fast ? 'instant' : 'smooth'
- this.bodyScrollPosition.scrollTo({ top: 0, behavior: speed })
+ this.bodyScrollPosition.scrollToPriority({ top: 0, behavior: speed })
},
},
}
export default ScrollTopButton
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 0b3d16b4a3..2b5aec0b5f 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -1,269 +1,269 @@
import { debounce, throttle } from 'lodash-es'
import { storeToRefs } from 'pinia'
import {
computed,
inject,
nextTick,
onMounted,
onUnmounted,
ref,
toRefs,
useTemplateRef,
watch,
} from 'vue'
import { useI18n } from 'vue-i18n'
import Conversation from 'src/components/conversation/conversation.vue'
import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue'
import QuickViewSettings from 'src/components/quick_view_settings/quick_view_settings.vue'
import ScrollTopButton from 'src/components/scroll_top_button/scroll_top_button.vue'
import TimelineMenu from 'src/components/timeline_menu/timeline_menu.vue'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useTimelinesStore } from 'src/stores/timelines.js'
import { useDocumentFocus } from 'src/composables/useDocumentFocus.js'
import { useInterfaceSizes } from 'src/composables/useInterfaceSizes.js'
import { useVirtualScrolling } from 'src/composables/useVirtualScrolling.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faArrowUp,
faCheck,
faCircleNotch,
faCirclePlus,
faCog,
faMinus,
} from '@fortawesome/free-solid-svg-icons'
library.add(faCircleNotch, faCog, faMinus, faArrowUp, faCirclePlus, faCheck)
const Timeline = {
props: {
timelineRef: Object,
footerSlipgate: Object, // reference to an element where we should put our footer
embedded: Boolean,
skipPinned: Boolean,
hideEmpty: Boolean,
},
components: {
ScrollTopButton,
Conversation,
TimelineMenu,
QuickFilterSettings,
QuickViewSettings,
},
setup(props, ctx) {
const { t } = useI18n()
const { focused } = useDocumentFocus()
const unfocused = computed(() => !focused.value)
// Timeline
const { timelineRef } = toRefs(props)
const timeline = computed(() => useTimelinesStore()[timelineRef.value.name])
const { skipPinned } = toRefs(props)
const filteredVisibleStatuses = computed(() => {
return timeline.value.order
.filter((id) => timeline.value.visibleStatusIds.has(id))
.map((id) => useStatusesStore().allStatuses.get(id))
.filter(({ pinned }) => (skipPinned.value ? !pinned : true))
})
// Scroll position
const scroller = inject('bodyScrollPosition')
const { hasReachedTop, shouldLoadBottom } = scroller
// Virtual scrolling
const { fontSize, navbarSize } = useInterfaceSizes()
// Placeholder heights.
const mutedStatusHeight = computed(() => fontSize.value * 1.5)
const normalStatusHeight = computed(() => fontSize.value * 10)
const getPlaceholderHeight = (id) =>
filteredVisibleStatuses.value.find((item) => item.id === id)?.muted
? mutedStatusHeight
: normalStatusHeight
const body = useTemplateRef('timeline')
const offset = computed(() => {
if (embedded.value) {
// The fontsize after navbar is the little gap between navbar and content
return navbarSize.value + fontSize.value
} else {
return 0
}
})
const { heightChart, changeSuspendState, updateVirtualHeight } =
useVirtualScrolling({
name: 'Timeline',
enabled: ref(true),
list: filteredVisibleStatuses,
body,
offset,
scrollPositionInstance: scroller,
scrollCompensation: false,
getPlaceholderHeight,
})
// Counter
const count = computed(() => timeline.value.order.length)
const newStatusCount = computed(() => timeline.value.newStatusCount)
const showLoadButton = computed(
() => timeline.value.newStatusCount > 0 || timeline.value.reloadNeeded,
)
// Showing new
const paused = ref(false)
const { mergedConfig } = storeToRefs(useMergedConfigStore())
const unfocusedPause = computed(
() => unfocused.value && mergedConfig.value.pauseOnUnfocused,
)
const showNewAutomatically = computed(
() => useMergedConfigStore().mergedConfig.streaming,
)
watch(newStatusCount, (count) => {
if (!showNewAutomatically.value) return
if (count <= 0) return
// only 'stream' them when you're scrolled to the top
if (hasReachedTop.value && !paused.value && !unfocusedPause.value) {
showNewStatuses()
} else {
paused.value = true
}
})
const showNewStatuses = async () => {
if (timeline.value.reloadNeeded) {
useTimelinesStore().clearTimeline(timelineRef.value.name)
fetchOlderStatuses()
} else {
blockClicksTemporarily()
useTimelinesStore().showNewStatuses(timelineRef.value.name)
}
paused.value = false
await nextTick()
- scroller.scrollTo({ top: 0 })
+ scroller.scrollToPriority({ top: 0, behavior: 'smooth' })
}
const fetchOlderStatuses = throttle(() => {
timeline.value.fetcher.fetchOlder()
}, 1000)
// Timeline change
const timelineChange = (newTimeline, oldTimeline) => {
const sameName = newTimeline?.name === oldTimeline?.name
const sameArgument = newTimeline?.argument === oldTimeline?.argument
if (sameName && sameArgument) return
if (oldTimeline) {
useTimelinesStore().deactivate(oldTimeline.name)
}
if (newTimeline) {
useTimelinesStore().activate(newTimeline.name, newTimeline.argument)
}
}
watch(timelineRef, timelineChange, { immediate: true })
onUnmounted(() => {
timelineChange(null, timelineRef.value)
})
// Misclick prevention
const blockingClicks = ref(false)
const stopBlockingClicks = debounce(() => {
blockingClicks.value = false
}, 1000)
const blockClicksTemporarily = () => {
if (!blockingClicks.value) {
blockingClicks.value = true
}
stopBlockingClicks()
}
// Shortcuts
const handleShortKey = (e) => {
// Ignore when input fields are focused
if (['textarea', 'input'].includes(e.target.tagName.toLowerCase())) return
if (e.key === '.') showNewStatuses()
}
onMounted(() => {
window.addEventListener('keydown', handleShortKey)
})
onUnmounted(() => {
window.removeEventListener('keydown', handleShortKey)
})
// Scroll
watch(shouldLoadBottom, (value) => {
if (!value) return
fetchOlderStatuses()
})
// Misc UI things
const classes = computed(() => {
let rootClasses = !embedded.value
? ['panel', 'panel-default']
: ['-embedded']
if (blockingClicks.value)
rootClasses = rootClasses.concat(['-blocked', '_misclick-prevention'])
return {
root: rootClasses,
header: ['timeline-heading'].concat(
!embedded.value ? ['panel-heading', '-sticky'] : ['panel-body'],
),
body: ['timeline-body'].concat(
!embedded.value ? ['panel-body'] : ['panel-body'],
),
footer: ['timeline-footer'].concat(
!embedded.value ? ['panel-footer'] : ['panel-body'],
),
}
})
const loadButtonString = computed(() => {
if (timeline.value.reloadNeeded) {
return t('timeline.reload')
} else {
return `${t('timeline.show_new')} (${newStatusCount.value})`
}
})
const mobileLoadButtonString = computed(() => {
if (timeline.value.reloadNeeded) {
return '+'
} else {
return newStatusCount.value > 99 ? '∞' : newStatusCount.value
}
})
const { layoutType } = storeToRefs(useInterfaceStore())
const mobileLayout = computed(() => layoutType.value === 'mobile')
const { footerSlipgate, embedded, hideEmpty } = toRefs(props)
return {
timelineRef,
timeline,
filteredVisibleStatuses,
heightChart,
updateVirtualHeight,
changeSuspendState,
count,
showLoadButton,
showNewStatuses,
fetchOlderStatuses,
classes,
loadButtonString,
mobileLoadButtonString,
mobileLayout,
footerSlipgate,
embedded,
hideEmpty,
}
},
}
export default Timeline
diff --git a/src/composables/useBodyScroller.js b/src/composables/useBodyScroller.js
index 867c1e5321..d3b4a5bd6a 100644
--- a/src/composables/useBodyScroller.js
+++ b/src/composables/useBodyScroller.js
@@ -1,12 +1,12 @@
import { computed } from 'vue'
import { useClientRectSize } from 'src/composables/useClientRectSize.js'
export function useBodyScroller(windowScroller, windowSize) {
const element = computed(() => window.document.body)
- const { x, y, scrollBy } = windowScroller
+ const { x, y, scrollBy, scrollTo } = windowScroller
const { width: vWidth, height: vHeight } = windowSize
const { width: cWidth, height: cHeight } = useClientRectSize(element)
- return { x, y, vWidth, vHeight, cWidth, cHeight, scrollBy }
+ return { x, y, vWidth, vHeight, cWidth, cHeight, scrollBy, scrollTo }
}
diff --git a/src/composables/useScrollPosition.js b/src/composables/useScrollPosition.js
index aec92301cf..59b95da178 100644
--- a/src/composables/useScrollPosition.js
+++ b/src/composables/useScrollPosition.js
@@ -1,66 +1,77 @@
-import { computed } from 'vue'
+import { computed, ref } from 'vue'
export function useScrollPosition(scroller) {
- const { x, y, cWidth, cHeight, vWidth, vHeight, scrollBy } = scroller
+ const { x, y, cWidth, cHeight, vWidth, vHeight, scrollBy: realScrollBy, scrollTo: realScrollTo } = scroller
+ const priorityInProgress = ref(false)
const top = computed(() => y.value)
const bottom = computed(() => y.value + vHeight.value)
const left = computed(() => x.value)
const right = computed(() => x.value + vWidth.value)
const distanceToTop = top
const distanceToLeft = left
const distanceToBottom = computed(() => cHeight.value - bottom.value)
const distanceToRight = computed(() => cWidth.value - right.value)
const reachMargin = 15
const hasReachedTop = computed(() => distanceToTop.value < reachMargin)
const hasReachedBottom = computed(() => distanceToBottom.value < reachMargin)
const hasReachedLeft = computed(() => distanceToLeft.value < reachMargin)
const hasReachedRight = computed(() => distanceToRight.value < reachMargin)
const loadMargin = 750
const shouldLoadTop = computed(() => distanceToTop.value < loadMargin)
const shouldLoadBottom = computed(() => distanceToBottom.value < loadMargin)
const shouldLoadLeft = computed(() => distanceToLeft.value < loadMargin)
const shouldLoadRight = computed(() => distanceToRight.value < loadMargin)
- const scrollTo = (position = {}) => {
- const amount = {
- left: 0,
- top: 0,
- }
- if (position.left != null) {
- amount.left = position.left - left.value
- }
- if (position.top != null) {
- amount.top = position.top - top.value
- }
- scrollBy(amount.left, amount.top)
+ const scrollTo = async (...args) => {
+ if (priorityInProgress.value) return true
+ return await realScrollTo(...args)
+ }
+ const scrollBy = async (...args) => {
+ if (priorityInProgress.value) return true
+ return await realScrollBy(...args)
+ }
+
+ const scrollToPriority = async (...args) => {
+ priorityInProgress.value = true
+ const result = await realScrollTo(...args)
+ priorityInProgress.value = false
+ return result
+ }
+ const scrollByPriority = async (...args) => {
+ priorityInProgress.value = true
+ const result = await realScrollBy(...args)
+ priorityInProgress.value = false
+ return result
}
return {
top,
left,
right,
bottom,
cWidth,
cHeight,
vWidth,
vHeight,
hasReachedLeft,
hasReachedRight,
hasReachedTop,
hasReachedBottom,
shouldLoadLeft,
shouldLoadRight,
shouldLoadTop,
shouldLoadBottom,
scrollBy,
scrollTo,
+ scrollByPriority,
+ scrollToPriority,
}
}
diff --git a/src/composables/useWindowScroll.js b/src/composables/useWindowScroll.js
index 9f484bab05..1f2b8bd9d0 100644
--- a/src/composables/useWindowScroll.js
+++ b/src/composables/useWindowScroll.js
@@ -1,28 +1,24 @@
import { onMounted, onUnmounted, ref } from 'vue'
export function useWindowScroll() {
const x = ref(0)
const y = ref(0)
- const inProgress = ref(false)
const update = (e) => {
x.value = window.scrollX
y.value = window.scrollY
}
onMounted(() => {
window.addEventListener('scroll', update)
update()
})
onUnmounted(() => {
window.removeEventListener('scroll', update)
})
- const scrollBy = async (...args) => {
- inProgress.value = true
- await window.scrollBy(...args)
- inProgress.value = false
- }
+ const scrollBy = window.scrollBy
+ const scrollTo = window.scrollTo
- return { x, y, scrollBy, inProgress }
+ return { x, y, scrollBy, scrollTo }
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Nov 1, 5:38 AM (14 h, 26 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1802580
Default Alt Text
(13 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline