Rewrite recurrence handling, much cleanup.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ref } from 'vue'
|
||||
import { addDays, differenceInWeeks } from 'date-fns'
|
||||
import { addDays, differenceInWeeks, isBefore, isAfter } from 'date-fns'
|
||||
import {
|
||||
toLocalString,
|
||||
fromLocalString,
|
||||
@@ -11,9 +11,8 @@ import {
|
||||
monthAbbr,
|
||||
lunarPhaseSymbol,
|
||||
MAX_YEAR,
|
||||
getOccurrenceIndex,
|
||||
getVirtualOccurrenceEndDate,
|
||||
} from '@/utils/date'
|
||||
import { buildDayEvents } from '@/utils/events'
|
||||
import { getHolidayForDate } from '@/utils/holidays'
|
||||
|
||||
/**
|
||||
@@ -54,81 +53,16 @@ export function createVirtualWeekManager({
|
||||
|
||||
function createWeek(virtualWeek) {
|
||||
const firstDay = getFirstDayForVirtualWeek(virtualWeek)
|
||||
const isoAnchor = addDays(firstDay, (4 - firstDay.getDay() + 7) % 7)
|
||||
const weekNumber = getISOWeek(isoAnchor)
|
||||
const thu = addDays(firstDay, (4 - firstDay.getDay() + 7) % 7)
|
||||
const weekNumber = getISOWeek(thu)
|
||||
const days = []
|
||||
let cur = new Date(firstDay)
|
||||
let hasFirst = false
|
||||
let monthToLabel = null
|
||||
let labelYear = null
|
||||
|
||||
const repeatingBases = []
|
||||
if (calendarStore.events) {
|
||||
for (const ev of calendarStore.events.values()) {
|
||||
if (ev.recur) repeatingBases.push(ev)
|
||||
}
|
||||
}
|
||||
|
||||
const collectEventsForDate = (dateStr, curDateObj) => {
|
||||
const storedEvents = []
|
||||
for (const ev of calendarStore.events.values()) {
|
||||
if (!ev.recur) {
|
||||
const evEnd = toLocalString(
|
||||
addDays(fromLocalString(ev.startDate, DEFAULT_TZ), (ev.days || 1) - 1),
|
||||
DEFAULT_TZ,
|
||||
)
|
||||
if (dateStr >= ev.startDate && dateStr <= evEnd) {
|
||||
storedEvents.push({ ...ev, endDate: evEnd })
|
||||
}
|
||||
}
|
||||
}
|
||||
const dayEvents = [...storedEvents]
|
||||
for (const base of repeatingBases) {
|
||||
const spanDays = (base.days || 1) - 1
|
||||
const currentDate = curDateObj
|
||||
const baseEnd = toLocalString(
|
||||
addDays(fromLocalString(base.startDate, DEFAULT_TZ), spanDays),
|
||||
DEFAULT_TZ,
|
||||
)
|
||||
for (let offset = 0; offset <= spanDays; offset++) {
|
||||
const candidateStart = addDays(currentDate, -offset)
|
||||
const candidateStartStr = toLocalString(candidateStart, DEFAULT_TZ)
|
||||
if (candidateStartStr === base.startDate) {
|
||||
if (dateStr >= base.startDate && dateStr <= baseEnd) {
|
||||
if (!dayEvents.some((ev) => ev.id === base.id)) {
|
||||
dayEvents.push({
|
||||
...base,
|
||||
endDate: baseEnd,
|
||||
_recurrenceIndex: 0,
|
||||
_baseId: base.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
const occurrenceIndex = getOccurrenceIndex(base, candidateStartStr, DEFAULT_TZ)
|
||||
if (occurrenceIndex === null) continue
|
||||
const virtualEndDate = getVirtualOccurrenceEndDate(base, candidateStartStr, DEFAULT_TZ)
|
||||
if (dateStr < candidateStartStr || dateStr > virtualEndDate) continue
|
||||
const virtualId = base.id + '_v_' + candidateStartStr
|
||||
if (!dayEvents.some((ev) => ev.id === virtualId)) {
|
||||
dayEvents.push({
|
||||
...base,
|
||||
id: virtualId,
|
||||
startDate: candidateStartStr,
|
||||
endDate: virtualEndDate,
|
||||
_recurrenceIndex: occurrenceIndex,
|
||||
_baseId: base.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return dayEvents
|
||||
}
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const dateStr = toLocalString(cur, DEFAULT_TZ)
|
||||
const dayEvents = collectEventsForDate(dateStr, fromLocalString(dateStr, DEFAULT_TZ))
|
||||
const events = buildDayEvents(calendarStore.events.values(), dateStr, DEFAULT_TZ)
|
||||
const dow = cur.getDay()
|
||||
const isFirst = cur.getDate() === 1
|
||||
if (isFirst) {
|
||||
@@ -137,10 +71,11 @@ export function createVirtualWeekManager({
|
||||
labelYear = cur.getFullYear()
|
||||
}
|
||||
let displayText = String(cur.getDate())
|
||||
if (isFirst) {
|
||||
if (cur.getMonth() === 0) displayText = cur.getFullYear()
|
||||
else displayText = monthAbbr[cur.getMonth()].slice(0, 3).toUpperCase()
|
||||
}
|
||||
if (isFirst)
|
||||
displayText =
|
||||
cur.getMonth() === 0
|
||||
? cur.getFullYear()
|
||||
: monthAbbr[cur.getMonth()].slice(0, 3).toUpperCase()
|
||||
let holiday = null
|
||||
if (calendarStore.config.holidays.enabled) {
|
||||
calendarStore._ensureHolidaysInitialized?.()
|
||||
@@ -157,36 +92,12 @@ export function createVirtualWeekManager({
|
||||
lunarPhase: lunarPhaseSymbol(cur),
|
||||
holiday,
|
||||
isHoliday: holiday !== null,
|
||||
isSelected:
|
||||
selection.value.startDate &&
|
||||
selection.value.dayCount > 0 &&
|
||||
dateStr >= selection.value.startDate &&
|
||||
dateStr <= addDaysStr(selection.value.startDate, selection.value.dayCount - 1),
|
||||
events: dayEvents,
|
||||
isSelected: isDateSelected(dateStr),
|
||||
events,
|
||||
})
|
||||
cur = addDays(cur, 1)
|
||||
}
|
||||
let monthLabel = null
|
||||
if (hasFirst && monthToLabel !== null) {
|
||||
if (labelYear && labelYear <= MAX_YEAR) {
|
||||
let weeksSpan = 0
|
||||
const d = addDays(cur, -1)
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const probe = addDays(cur, -1 + i * 7)
|
||||
d.setTime(probe.getTime())
|
||||
if (d.getMonth() === monthToLabel) weeksSpan++
|
||||
}
|
||||
const remainingWeeks = Math.max(1, maxVirtualWeek.value - virtualWeek + 1)
|
||||
weeksSpan = Math.max(1, Math.min(weeksSpan, remainingWeeks))
|
||||
const year = String(labelYear).slice(-2)
|
||||
monthLabel = {
|
||||
text: `${getLocalizedMonthName(monthToLabel)} '${year}`,
|
||||
month: monthToLabel,
|
||||
weeksSpan,
|
||||
monthClass: monthAbbr[monthToLabel],
|
||||
}
|
||||
}
|
||||
}
|
||||
const monthLabel = buildMonthLabel({ hasFirst, monthToLabel, labelYear, cur, virtualWeek })
|
||||
return {
|
||||
virtualWeek,
|
||||
weekNumber: pad(weekNumber),
|
||||
@@ -196,6 +107,36 @@ export function createVirtualWeekManager({
|
||||
}
|
||||
}
|
||||
|
||||
function isDateSelected(dateStr) {
|
||||
if (!selection.value.startDate || selection.value.dayCount <= 0) return false
|
||||
const startDateObj = fromLocalString(selection.value.startDate, DEFAULT_TZ)
|
||||
const endDateStr = addDaysStr(selection.value.startDate, selection.value.dayCount - 1)
|
||||
const endDateObj = fromLocalString(endDateStr, DEFAULT_TZ)
|
||||
const d = fromLocalString(dateStr, DEFAULT_TZ)
|
||||
return !isBefore(d, startDateObj) && !isAfter(d, endDateObj)
|
||||
}
|
||||
|
||||
function buildMonthLabel({ hasFirst, monthToLabel, labelYear, cur, virtualWeek }) {
|
||||
if (!hasFirst || monthToLabel === null) return null
|
||||
if (!labelYear || labelYear > MAX_YEAR) return null
|
||||
let weeksSpan = 0
|
||||
const d = addDays(cur, -1)
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const probe = addDays(cur, -1 + i * 7)
|
||||
d.setTime(probe.getTime())
|
||||
if (d.getMonth() === monthToLabel) weeksSpan++
|
||||
}
|
||||
const remainingWeeks = Math.max(1, maxVirtualWeek.value - virtualWeek + 1)
|
||||
weeksSpan = Math.max(1, Math.min(weeksSpan, remainingWeeks))
|
||||
const year = String(labelYear).slice(-2)
|
||||
return {
|
||||
text: `${getLocalizedMonthName(monthToLabel)} '${year}`,
|
||||
month: monthToLabel,
|
||||
weeksSpan,
|
||||
monthClass: monthAbbr[monthToLabel],
|
||||
}
|
||||
}
|
||||
|
||||
function internalWindowCalc() {
|
||||
const buffer = 6
|
||||
const currentScrollTop = viewport.value?.scrollTop ?? scrollTopRef?.value ?? 0
|
||||
@@ -299,10 +240,6 @@ export function createVirtualWeekManager({
|
||||
// Reflective update of only events inside currently visible weeks (keeps week objects stable)
|
||||
function refreshEvents(reason = 'events-refresh') {
|
||||
if (!visibleWeeks.value.length) return
|
||||
const repeatingBases = []
|
||||
if (calendarStore.events) {
|
||||
for (const ev of calendarStore.events.values()) if (ev.recur) repeatingBases.push(ev)
|
||||
}
|
||||
const selStart = selection.value.startDate
|
||||
const selCount = selection.value.dayCount
|
||||
const selEnd = selStart && selCount > 0 ? addDaysStr(selStart, selCount - 1) : null
|
||||
@@ -310,63 +247,13 @@ export function createVirtualWeekManager({
|
||||
for (const day of week.days) {
|
||||
const dateStr = day.date
|
||||
// Update selection flag
|
||||
if (selStart && selEnd) day.isSelected = dateStr >= selStart && dateStr <= selEnd
|
||||
else day.isSelected = false
|
||||
// Rebuild events list for this day
|
||||
const storedEvents = []
|
||||
for (const ev of calendarStore.events.values()) {
|
||||
if (!ev.recur) {
|
||||
const evEnd = toLocalString(
|
||||
addDays(fromLocalString(ev.startDate, DEFAULT_TZ), (ev.days || 1) - 1),
|
||||
DEFAULT_TZ,
|
||||
)
|
||||
if (dateStr >= ev.startDate && dateStr <= evEnd) {
|
||||
storedEvents.push({ ...ev, endDate: evEnd })
|
||||
}
|
||||
}
|
||||
}
|
||||
const dayEvents = [...storedEvents]
|
||||
for (const base of repeatingBases) {
|
||||
const spanDays = (base.days || 1) - 1
|
||||
const currentDate = fromLocalString(dateStr, DEFAULT_TZ)
|
||||
const baseEndStr = toLocalString(
|
||||
addDays(fromLocalString(base.startDate, DEFAULT_TZ), spanDays),
|
||||
DEFAULT_TZ,
|
||||
)
|
||||
for (let offset = 0; offset <= spanDays; offset++) {
|
||||
const candidateStart = addDays(currentDate, -offset)
|
||||
const candidateStartStr = toLocalString(candidateStart, DEFAULT_TZ)
|
||||
if (candidateStartStr === base.startDate) {
|
||||
if (dateStr >= base.startDate && dateStr <= baseEndStr) {
|
||||
if (!dayEvents.some((ev) => ev.id === base.id)) {
|
||||
dayEvents.push({
|
||||
...base,
|
||||
endDate: baseEndStr,
|
||||
_recurrenceIndex: 0,
|
||||
_baseId: base.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
const occurrenceIndex = getOccurrenceIndex(base, candidateStartStr, DEFAULT_TZ)
|
||||
if (occurrenceIndex === null) continue
|
||||
const virtualEndDate = getVirtualOccurrenceEndDate(base, candidateStartStr, DEFAULT_TZ)
|
||||
if (dateStr < candidateStartStr || dateStr > virtualEndDate) continue
|
||||
const virtualId = base.id + '_v_' + candidateStartStr
|
||||
if (!dayEvents.some((ev) => ev.id === virtualId)) {
|
||||
dayEvents.push({
|
||||
...base,
|
||||
id: virtualId,
|
||||
startDate: candidateStartStr,
|
||||
endDate: virtualEndDate,
|
||||
_recurrenceIndex: occurrenceIndex,
|
||||
_baseId: base.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
day.events = dayEvents
|
||||
if (selStart && selEnd) {
|
||||
const d = fromLocalString(dateStr, DEFAULT_TZ),
|
||||
s = fromLocalString(selStart, DEFAULT_TZ),
|
||||
e = fromLocalString(selEnd, DEFAULT_TZ)
|
||||
day.isSelected = !isBefore(d, s) && !isAfter(d, e)
|
||||
} else day.isSelected = false
|
||||
day.events = buildDayEvents(dateStr)
|
||||
}
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
|
||||
Reference in New Issue
Block a user