feat: Agenda URL #hash scrolls to 'now' or specific day (#7772)
* feat: Agenda URL #now scroll to current event * fix: agendaData date offset for dev purposes * feat: scroll to day * fix: Showing correct hostname in agenda modify log * feat: mobile menu scroll to hash and general bug fixes * fix: agenda mobile menu formatting and Playwright selectors * fix: removing spurious ? mark * chore: removing redundant agenda time setter in favour of agenda settings panel for debugging * style: Update AgendaMobileBar.vue * style: Update AgendaScheduleList.vue --------- Co-authored-by: Nicolas Giard <github@ngpixel.com>
This commit is contained in:
parent
af21347b67
commit
bece8fd71b
|
@ -434,7 +434,7 @@ function reconnectScrollObservers () {
|
|||
scrollObserver.disconnect()
|
||||
visibleDays.length = 0
|
||||
for (const mDay of agendaStore.meetingDays) {
|
||||
const el = document.getElementById(`agenda-day-${mDay.slug}`)
|
||||
const el = document.getElementById(mDay.slug)
|
||||
el.dataset.dayId = mDay.slug.toString()
|
||||
el.dataset.dayTs = mDay.ts
|
||||
scrollObserver.observe(el)
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
|
||||
<script setup>
|
||||
import { computed, h } from 'vue'
|
||||
|
||||
import {
|
||||
NBadge,
|
||||
NDropdown,
|
||||
|
@ -51,21 +50,48 @@ const siteStore = useSiteStore()
|
|||
|
||||
// Meeting Days
|
||||
|
||||
function optionToLink(opts){
|
||||
const { key, label, icon } = opts
|
||||
|
||||
return {
|
||||
...opts,
|
||||
type: 'render',
|
||||
render: () => h(
|
||||
'a',
|
||||
{
|
||||
class: 'dropdown-link',
|
||||
'data-testid': 'mobile-link',
|
||||
href: `#${key}`
|
||||
},
|
||||
[
|
||||
h(
|
||||
'span',
|
||||
icon()
|
||||
),
|
||||
h(
|
||||
'span',
|
||||
label
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const jumpToDayOptions = computed(() => {
|
||||
const days = []
|
||||
if (agendaStore.isMeetingLive) {
|
||||
days.push({
|
||||
days.push(optionToLink({
|
||||
label: 'Jump to Now',
|
||||
key: 'now',
|
||||
icon: () => h('i', { class: 'bi bi-arrow-down-right-square text-red' })
|
||||
})
|
||||
}))
|
||||
}
|
||||
for (const day of agendaStore.meetingDays) {
|
||||
days.push({
|
||||
days.push(optionToLink({
|
||||
label: `Jump to ${day.label}`,
|
||||
key: day.slug,
|
||||
icon: () => h('i', { class: 'bi bi-arrow-down-right-square' })
|
||||
})
|
||||
}))
|
||||
}
|
||||
return days
|
||||
})
|
||||
|
@ -90,14 +116,13 @@ const downloadIcsOptions = [
|
|||
function jumpToDay (dayId) {
|
||||
if (dayId === 'now') {
|
||||
const lastEventId = agendaStore.findCurrentEventId()
|
||||
|
||||
if (lastEventId) {
|
||||
document.getElementById(`agenda-rowid-${lastEventId}`)?.scrollIntoView(true)
|
||||
} else {
|
||||
message.warning('There is no event happening right now.')
|
||||
}
|
||||
} else {
|
||||
document.getElementById(`agenda-day-${dayId}`)?.scrollIntoView(true)
|
||||
document.getElementById(dayId)?.scrollIntoView(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,4 +187,19 @@ function downloadIcs (key) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-link {
|
||||
display: flex;
|
||||
text-decoration:none;
|
||||
gap: 0.2rem 0.5rem;
|
||||
padding: 0.5em;
|
||||
color: var(--bs-body-color);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--bs-dark-bg-subtle);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
li.nav-item(v-for='day of agendaStore.meetingDays')
|
||||
a.nav-link(
|
||||
:class='agendaStore.dayIntersectId === day.slug ? `active` : ``'
|
||||
:href='`#slot-` + day.slug'
|
||||
:href='`#${day.slug}`'
|
||||
@click='scrollToDay(day.slug, $event)'
|
||||
)
|
||||
i.bi.bi-arrow-right-short.d-none.d-xxl-inline.me-2
|
||||
|
@ -109,7 +109,6 @@
|
|||
<script setup>
|
||||
import { computed, h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { DateTime } from 'luxon'
|
||||
import {
|
||||
NAffix,
|
||||
NBadge,
|
||||
|
@ -200,14 +199,11 @@ function pickerDiscard () {
|
|||
}
|
||||
}
|
||||
|
||||
function scrollToDay (dayId, ev) {
|
||||
ev.preventDefault()
|
||||
document.getElementById(`agenda-day-${dayId}`)?.scrollIntoView(true)
|
||||
function scrollToDay (daySlug, ev) {
|
||||
document.getElementById(daySlug)?.scrollIntoView(true)
|
||||
}
|
||||
|
||||
function scrollToNow (ev) {
|
||||
ev.preventDefault()
|
||||
|
||||
const lastEventId = agendaStore.findCurrentEventId()
|
||||
|
||||
if (lastEventId) {
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
)
|
||||
//- ROW - DAY HEADING -----------------------
|
||||
template(v-if='item.displayType === `day`')
|
||||
td(:id='`agenda-day-` + item.id', :colspan='pickerModeActive ? 6 : 5') {{item.date}}
|
||||
td(:id='item.slug', :colspan='pickerModeActive ? 6 : 5') {{item.date}}
|
||||
//- ROW - SESSION HEADING -------------------
|
||||
template(v-else-if='item.displayType === `session-head`')
|
||||
td.agenda-table-cell-check(v-if='pickerModeActive')
|
||||
|
@ -200,7 +200,7 @@ import {
|
|||
|
||||
import AgendaDetailsModal from './AgendaDetailsModal.vue'
|
||||
|
||||
import { useAgendaStore } from './store'
|
||||
import { useAgendaStore, daySlugPrefix, daySlug } from './store'
|
||||
import { useSiteStore } from '../shared/store'
|
||||
import { getUrl } from '../shared/urls'
|
||||
|
||||
|
@ -248,6 +248,7 @@ const meetingEvents = computed(() => {
|
|||
if (itemDate.toISODate() !== acc.lastDate) {
|
||||
acc.result.push({
|
||||
id: item.id,
|
||||
slug: daySlug(item),
|
||||
key: `day-${itemDate.toISODate()}`,
|
||||
displayType: 'day',
|
||||
date: itemDate.toLocaleString(DateTime.DATE_HUGE),
|
||||
|
@ -575,6 +576,30 @@ function recalculateRedLine () {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On page load when browser location hash contains '#now' or '#agenda-day-*' then scroll accordingly
|
||||
*/
|
||||
;(function scrollToHashInit() {
|
||||
if (!window.location.hash) {
|
||||
return
|
||||
}
|
||||
if (!(window.location.hash === "#now" || window.location.hash.startsWith(`#${daySlugPrefix}`))) {
|
||||
return
|
||||
}
|
||||
const unsubscribe = agendaStore.$subscribe((_mutation, agendaStoreState) => {
|
||||
if (agendaStoreState.schedule.length === 0) {
|
||||
return
|
||||
}
|
||||
unsubscribe() // we only need to scroll once, so unsubscribe from future updates
|
||||
if(window.location.hash === "#now") {
|
||||
const lastEventId = agendaStore.findCurrentEventId()
|
||||
document.getElementById(`agenda-rowid-${lastEventId}`)?.scrollIntoView(true)
|
||||
} else if(window.location.hash.startsWith(`#${daySlugPrefix}`)) {
|
||||
document.getElementById(window.location.hash.substring(1))?.scrollIntoView(true)
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
// MOUNTED
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
@ -121,7 +121,7 @@ export const useAgendaStore = defineStore('agenda', {
|
|||
meetingDays () {
|
||||
const siteStore = useSiteStore()
|
||||
return uniqBy(this.scheduleAdjusted, 'adjustedStartDate').sort().map(s => ({
|
||||
slug: s.id.toString(),
|
||||
slug: daySlug(s),
|
||||
ts: s.adjustedStartDate,
|
||||
label: siteStore.viewport < 1350 ? DateTime.fromISO(s.adjustedStartDate).toFormat('ccc LLL d') : DateTime.fromISO(s.adjustedStartDate).toLocaleString(DateTime.DATE_HUGE)
|
||||
}))
|
||||
|
@ -292,3 +292,8 @@ function findFirstConferenceUrl (txt) {
|
|||
} catch (err) { }
|
||||
return null
|
||||
}
|
||||
|
||||
export const daySlugPrefix = 'agenda-day-'
|
||||
export function daySlug(s) {
|
||||
return `${daySlugPrefix}${s.adjustedStartDate}` // eg 'agenda-day-2024-08-13'
|
||||
}
|
||||
|
|
|
@ -1431,7 +1431,7 @@ test.describe('past - small screens', () => {
|
|||
|
||||
// can open the jump to day dropdown
|
||||
await barBtnLocator.first().click()
|
||||
const jumpDayDdnLocator = page.locator('.n-dropdown-menu > .n-dropdown-option')
|
||||
const jumpDayDdnLocator = page.locator('.n-dropdown-menu [data-testid=mobile-link]')
|
||||
await expect(jumpDayDdnLocator).toHaveCount(7)
|
||||
for (let idx = 0; idx < 7; idx++) {
|
||||
const localDateTime = DateTime.fromISO(meetingData.meeting.startDate, { zone: meetingData.meeting.timezone })
|
||||
|
|
Loading…
Reference in a new issue