refactor: high-cohesion low-coupling cleanup across store and components
ical-io.js: - Import pad2/parseISODate from date.js (remove local duplicates) - Rename ICAL.Time helpers to icalTimeTo* for clarity useCalendar.js: - Delegate addEvent to createEvent() factory - Delegate splitEvent to splitRecurringEvent() pure function - Use cloneEvent() instead of JSON.parse(JSON.stringify()) - Add selectEvent() action (components no longer mutate state.selectedUid directly) Components: - EventList: use selectEvent action + toDate() from date.js - HeaderBar: destructure loadFile at top level (was re-calling useCalendar in handler) - EventDetail: import DOW from weekday.js (was inlining the array) - OccurrenceGrid: use toDate() from date.js
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { reactive, computed } from 'vue'
|
||||
import { parseICS, serializeICS } from '../lib/ical-io.js'
|
||||
import { organize as organizeEvents } from '../lib/organize.js'
|
||||
import { createEvent, cloneEvent } from '../lib/event-factory.js'
|
||||
import { splitRecurringEvent } from '../lib/split.js'
|
||||
|
||||
const MAX_HISTORY = 50
|
||||
|
||||
@@ -20,8 +22,8 @@ const state = reactive({
|
||||
// ------------------------------------------------------------------ //
|
||||
function snapshot() {
|
||||
return {
|
||||
meta: state.meta ? JSON.parse(JSON.stringify(state.meta)) : null,
|
||||
events: JSON.parse(JSON.stringify(state.events)),
|
||||
meta: state.meta ? cloneEvent(state.meta) : null,
|
||||
events: state.events.map(cloneEvent),
|
||||
selectedUid: state.selectedUid,
|
||||
}
|
||||
}
|
||||
@@ -33,8 +35,8 @@ function pushHistory() {
|
||||
}
|
||||
|
||||
function restore(snap) {
|
||||
state.meta = snap.meta ? JSON.parse(JSON.stringify(snap.meta)) : null
|
||||
state.events = JSON.parse(JSON.stringify(snap.events))
|
||||
state.meta = snap.meta ? cloneEvent(snap.meta) : null
|
||||
state.events = snap.events.map(cloneEvent)
|
||||
state.selectedUid = snap.selectedUid
|
||||
}
|
||||
|
||||
@@ -54,6 +56,10 @@ function loadFile(file) {
|
||||
})
|
||||
}
|
||||
|
||||
function selectEvent(uid) {
|
||||
state.selectedUid = uid
|
||||
}
|
||||
|
||||
function organize() {
|
||||
if (state.events.length === 0) return
|
||||
pushHistory()
|
||||
@@ -81,23 +87,7 @@ function deleteEvent(uid) {
|
||||
|
||||
function addEvent() {
|
||||
pushHistory()
|
||||
const today = new Date()
|
||||
const y = today.getFullYear()
|
||||
const m = String(today.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(today.getDate()).padStart(2, '0')
|
||||
const ev = {
|
||||
uid: crypto.randomUUID(),
|
||||
summary: '新日程',
|
||||
location: '',
|
||||
description: '',
|
||||
dtstartDate: `${y}-${m}-${d}`,
|
||||
dtstartTime: '09:00',
|
||||
dtendTime: '10:00',
|
||||
tzid: 'UTC',
|
||||
rrule: null,
|
||||
exdates: [],
|
||||
_raw: { dtstamp: new Date().toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z' },
|
||||
}
|
||||
const ev = createEvent()
|
||||
state.events.push(ev)
|
||||
state.selectedUid = ev.uid
|
||||
state.dirty = true
|
||||
@@ -107,34 +97,11 @@ function splitEvent(uid, splitDate) {
|
||||
const ev = state.events.find((e) => e.uid === uid)
|
||||
if (!ev || !ev.rrule) return
|
||||
|
||||
// Snap splitDate to the next grid point >= splitDate
|
||||
const step = ev.rrule.freq === 'WEEKLY' ? 7 * (ev.rrule.interval || 1) : (ev.rrule.interval || 1)
|
||||
const start = new Date(ev.dtstartDate + 'T00:00:00')
|
||||
let snap = new Date(splitDate + 'T00:00:00')
|
||||
// Advance snap to the next grid point on or after splitDate
|
||||
while (snap < start || ((snap - start) / (24 * 3600 * 1000)) % step !== 0) {
|
||||
if (snap < start) { snap = new Date(start); break }
|
||||
snap.setDate(snap.getDate() + 1)
|
||||
if (snap > new Date(ev.rrule.untilDate + 'T00:00:00')) return // out of range
|
||||
}
|
||||
const snapStr = `${snap.getFullYear()}-${String(snap.getMonth() + 1).padStart(2, '0')}-${String(snap.getDate()).padStart(2, '0')}`
|
||||
|
||||
// Part A: rrule.untilDate = day before snap (previous grid point)
|
||||
const prevGrid = new Date(snap)
|
||||
prevGrid.setDate(prevGrid.getDate() - step)
|
||||
const prevStr = `${prevGrid.getFullYear()}-${String(prevGrid.getMonth() + 1).padStart(2, '0')}-${String(prevGrid.getDate()).padStart(2, '0')}`
|
||||
const parts = splitRecurringEvent(ev, splitDate)
|
||||
if (!parts) return
|
||||
|
||||
pushHistory()
|
||||
|
||||
const partA = JSON.parse(JSON.stringify(ev))
|
||||
partA.rrule = { ...ev.rrule, untilDate: prevStr }
|
||||
partA.exdates = ev.exdates.filter((d) => d < splitDate)
|
||||
|
||||
const partB = JSON.parse(JSON.stringify(ev))
|
||||
partB.uid = crypto.randomUUID()
|
||||
partB.dtstartDate = snapStr
|
||||
partB.exdates = ev.exdates.filter((d) => d >= splitDate)
|
||||
|
||||
const [partA, partB] = parts
|
||||
const idx = state.events.findIndex((e) => e.uid === uid)
|
||||
state.events.splice(idx, 1, partA, partB)
|
||||
state.selectedUid = partB.uid
|
||||
@@ -199,6 +166,7 @@ export function useCalendar() {
|
||||
isLoaded,
|
||||
stats,
|
||||
loadFile,
|
||||
selectEvent,
|
||||
organize,
|
||||
updateEvent,
|
||||
deleteEvent,
|
||||
|
||||
Reference in New Issue
Block a user