Adam Shaw 7 лет назад
Родитель
Сommit
0b9957703e

+ 11 - 7
demos/rrule.html

@@ -19,19 +19,23 @@
         right: 'month,agendaWeek,agendaDay,listMonth'
       },
       defaultDate: '2018-04-12',
-      navLinks: true, // can click day/week names to navigate views
-      businessHours: true, // display business hours
       editable: true,
       events: [
         {
-          title: 'cooool',
+          title: 'rrule event',
           rrule: {
-            dtstart: '2018-04-09',
-            until: '2018-04-30',
+            dtstart: '2018-04-09T13:00:00',
+            // until: '2018-05-01',
             freq: 'weekly'
-          }
+          },
+          duration: '02:00'
         }
-      ]
+      ],
+      eventClick: function(arg) {
+        if (confirm('delete event?')) {
+          arg.event.remove()
+        }
+      }
     });
 
     calendar.render();

+ 26 - 20
plugins/rrule/main.ts

@@ -1,5 +1,5 @@
 import { RRule, rrulestr } from 'rrule'
-import { registerRecurringType, ParsedRecurring, EventInput, refineProps, DateEnv, EventDef, DateRange, DateMarker, createDuration } from 'fullcalendar'
+import { registerRecurringType, ParsedRecurring, EventInput, refineProps, DateEnv, EventDef, DateRange, DateMarker, DateMarkerMeta, createDuration } from 'fullcalendar'
 
 interface RRuleParsedRecurring extends ParsedRecurring {
   typeData: RRule
@@ -15,13 +15,13 @@ registerRecurringType({
   parse(rawEvent: EventInput, leftoverProps: any, dateEnv: DateEnv): RRuleParsedRecurring | null {
     if (rawEvent.rrule != null) {
       let props = refineProps(rawEvent, EVENT_DEF_PROPS, {}, leftoverProps)
-      let rrule = parseRRule(props.rrule, dateEnv)
+      let parsed = parseRRule(props.rrule, dateEnv)
 
-      if (rrule) {
+      if (parsed) {
         return {
-          isAllDay: false, // TODO!!!
+          isAllDay: parsed.isAllDay,
           duration: props.duration,
-          typeData: rrule
+          typeData: parsed.rrule
         }
       }
     }
@@ -35,32 +35,38 @@ registerRecurringType({
 
 })
 
-function parseRRule(input, dateEnv: DateEnv): RRule | null {
+function parseRRule(input, dateEnv: DateEnv) {
 
   if (typeof input === 'string') {
-    return rrulestr(input)
+    return {
+      rrule: rrulestr(input),
+      isAllDay: false
+    }
 
   } else if (typeof input === 'object' && input) { // non-null object
-
-    let parseMarker = function(val) {
-      if (typeof val === 'string') {
-        let marker = dateEnv.createMarker(val)
-        if (marker) {
-          return marker
-        }
-      }
-      return val
-    }
+    let dtstartMeta: DateMarkerMeta
 
     let refined = refineProps(input, {
-      dtstart: parseMarker,
-      until: parseMarker,
+      dtstart: null,
+      until: null,
       freq: convertConstant,
       wkst: convertConstant,
       byweekday: convertConstants
     })
 
-    return new RRule(refined)
+    if (typeof refined.dtstart === 'string') {
+      dtstartMeta = dateEnv.createMarkerMeta(refined.dtstart)
+      refined.dtstart = dtstartMeta ? dtstartMeta.marker : null
+    }
+
+    if (typeof refined.until === 'string') {
+      refined.until = dateEnv.createMarker(refined.until)
+    }
+
+    return {
+      rrule: new RRule(refined),
+      isAllDay: dtstartMeta && dtstartMeta.isTimeUnspecified
+    }
   }
 
   return null

+ 7 - 5
src/component/DateComponent.ts

@@ -935,11 +935,13 @@ export default abstract class DateComponent extends Component {
 
       // fabricate an eventRange. important for mirror
       // TODO: make a separate utility for this?
-      let def = parseEventDef({
-        editable: false,
-        isAllDay: selection.isAllDay,
-        hasEnd: true
-      }, '', this.getCalendar())
+      let def = parseEventDef(
+        { editable: false },
+        '', // sourceId
+        selection.isAllDay,
+        true, // hasEnd
+        this.getCalendar()
+      )
       let eventRange = {
         def,
         ui: computeEventDefUi(def, {}, {}),

+ 7 - 1
src/datelib/env.ts

@@ -26,6 +26,12 @@ export interface DateEnvSettings {
 
 export type DateInput = Date | string | number | number[]
 
+export interface DateMarkerMeta {
+  marker: DateMarker
+  isTimeUnspecified: boolean
+  forcedTzo: number | null
+}
+
 
 export class DateEnv {
 
@@ -90,7 +96,7 @@ export class DateEnv {
     return this.timestampToMarker(new Date().valueOf())
   }
 
-  createMarkerMeta(input: DateInput) {
+  createMarkerMeta(input: DateInput): DateMarkerMeta {
 
     if (typeof input === 'string') {
       return this.parse(input)

+ 1 - 1
src/exports.ts

@@ -99,7 +99,7 @@ export {
   asRoughMinutes, asRoughSeconds, asRoughMs,
   wholeDivideDurations, greatestDurationDenominator
 } from './datelib/duration'
-export { DateEnv } from './datelib/env'
+export { DateEnv, DateMarkerMeta } from './datelib/env'
 export { defineLocale, getLocale, getLocaleCodes } from './datelib/locale'
 export {
   DateFormatter,

+ 7 - 3
src/interactions-external/ExternalElementDragging.ts

@@ -196,9 +196,13 @@ export default class ExternalElementDragging {
 // ----------------------------------------------------------------------------------------------------
 
 function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, calendar: Calendar): EventTuple {
-  let def = parseEventDef(dragMeta.leftoverProps, dragMeta.sourceId, calendar)
-  def.isAllDay = dateSpan.isAllDay
-  def.hasEnd = Boolean(dragMeta.duration)
+  let def = parseEventDef(
+    dragMeta.leftoverProps,
+    dragMeta.sourceId,
+    dateSpan.isAllDay,
+    Boolean(dragMeta.duration), // hasEnd
+    calendar
+  )
 
   let start = dateSpan.range.start
 

+ 114 - 107
src/structs/event.ts

@@ -115,45 +115,43 @@ let uid = 0
 
 
 export function parseEvent(raw: EventInput, sourceId: string, calendar: Calendar): EventTuple | null {
-  let leftovers0 = {} as any
-  let dateProps = pluckDateProps(raw, leftovers0)
-  let leftovers1 = {} as any
-  let def = parseEventDef(raw, sourceId, calendar, leftovers1)
-  let instance: EventInstance = null
-
-  if (dateProps.start !== null) {
-    let instanceRes = parseEventInstance(dateProps, def.defId, sourceId, calendar)
-
-    if (instanceRes) {
-      def.isAllDay = instanceRes.isAllDay
-      def.hasEnd = instanceRes.hasEnd
-      instance = instanceRes.instance
-    } else {
-      return null // TODO: give a warning
-    }
+  let leftovers0 = {}
+  let isAllDayDefault = computeIsAllDayDefault(sourceId, calendar)
+  let singleRes = parseSingle(raw, isAllDayDefault, calendar, leftovers0)
+
+  if (singleRes) {
+    let def = parseEventDef(leftovers0, sourceId, singleRes.isAllDay, singleRes.hasEnd, calendar)
+    let instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo)
+
+    return { def, instance }
+
   } else {
+    let leftovers1 = {}
     let recurringRes = parseRecurring(
-      leftovers0, // non-date props and other non-standard props
-      leftovers1, // dest
-      calendar.dateEnv
+      leftovers0, // raw, but with single-event stuff stripped out
+      calendar.dateEnv,
+      leftovers1 // the new leftovers
     )
 
     if (recurringRes) {
-      def.isAllDay = recurringRes.isAllDay
-      def.hasEnd = Boolean(recurringRes.duration)
-      def.recurringDef = {
+      let isAllDay =
+        (raw.isAllDay != null) ? Boolean(raw.isAllDay) : // need to get this from `raw` because already stripped out of `leftovers0`
+          (isAllDayDefault != null ? isAllDayDefault :
+            recurringRes.isAllDay) // fall back to the recurring date props LAST
+
+      let def = parseEventDef(leftovers1, sourceId, isAllDay, Boolean(recurringRes.duration), calendar)
+
+      def.recurringDef = { // TODO: more efficient way to do this
         typeId: recurringRes.typeId,
         typeData: recurringRes.typeData,
         duration: recurringRes.duration
       }
-    } else {
-      return null // TODO: give a warning
+
+      return { def, instance: null }
     }
   }
 
-  def.extendedProps = assignTo(leftovers1, def.extendedProps)
-
-  return { def, instance }
+  return null
 }
 
 
@@ -162,15 +160,15 @@ Will NOT populate extendedProps with the leftover properties.
 Will NOT populate date-related props.
 The EventNonDateInput has been normalized (id => publicId, etc).
 */
-export function parseEventDef(raw: EventNonDateInput, sourceId: string, calendar: Calendar, leftovers?: any): EventDef {
+export function parseEventDef(raw: EventNonDateInput, sourceId: string, isAllDay: boolean, hasEnd: boolean, calendar: Calendar): EventDef {
+  let leftovers = {}
   let def = pluckNonDateProps(raw, calendar, leftovers) as EventDef
 
   def.defId = String(uid++)
   def.sourceId = sourceId
-
-  if (!def.extendedProps) {
-    def.extendedProps = {}
-  }
+  def.isAllDay = isAllDay
+  def.hasEnd = hasEnd
+  def.extendedProps = assignTo(leftovers, def.extendedProps || {})
 
   // help out EventApi::extendedProps from having user modify props
   Object.freeze(def.extendedProps)
@@ -179,81 +177,48 @@ export function parseEventDef(raw: EventNonDateInput, sourceId: string, calendar
 }
 
 
-function pluckDateProps(raw: EventInput, leftovers: any) {
-  let props = refineProps(raw, DATE_PROPS, {}, leftovers)
-
-  props.start = (props.start !== null) ? props.start : props.date
-  delete props.date
-
-  return props
-}
-
-
-function pluckNonDateProps(raw: EventInput, calendar: Calendar, leftovers: any) {
-  let props = refineProps(raw, NON_DATE_PROPS, {}, leftovers)
-
-  props.publicId = props.id
-  props.classNames = props.classNames.concat(props.className)
-
-  if (props.constraint) {
-    props.constraint = normalizeConstraint(props.constraint, calendar)
-  }
-
-  if (props.startEditable == null) {
-    props.startEditable = props.editable
-  }
-
-  if (props.durationEditable == null) {
-    props.durationEditable = props.editable
-  }
-
-  if (!props.backgroundColor) {
-    props.backgroundColor = props.color
-  }
-
-  if (!props.borderColor) {
-    props.borderColor = props.color
+export function createEventInstance(
+  defId: string,
+  range: DateRange,
+  forcedStartTzo?: number,
+  forcedEndTzo?: number
+): EventInstance {
+  return {
+    instanceId: String(uid++),
+    defId,
+    range,
+    forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo,
+    forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo
   }
-
-  delete props.id
-  delete props.className
-  delete props.editable
-  delete props.color
-
-  return props
 }
 
 
-/*
-The EventDateInput has been normalized (date => start, etc).
-*/
-function parseEventInstance(dateProps: EventDateInput, defId: string, sourceId: string, calendar: Calendar) {
+function parseSingle(raw: EventInput, isAllDayDefault: boolean | null, calendar: Calendar, leftovers?) {
+  let props = pluckDateProps(raw, leftovers)
+  let isAllDay = props.isAllDay
   let startMeta
   let startMarker
   let hasEnd = false
   let endMeta = null
   let endMarker = null
 
-  startMeta = calendar.dateEnv.createMarkerMeta(dateProps.start)
+  startMeta = calendar.dateEnv.createMarkerMeta(props.start)
 
   if (!startMeta) {
     return null
   }
 
-  if (dateProps.end != null) {
-    endMeta = calendar.dateEnv.createMarkerMeta(dateProps.end)
+  if (props.end != null) {
+    endMeta = calendar.dateEnv.createMarkerMeta(props.end)
   }
 
-  let isAllDay = dateProps.isAllDay
-  if (isAllDay == null && sourceId) {
-    let source = calendar.state.eventSources[sourceId]
-    isAllDay = source.allDayDefault
-  }
-  if (isAllDay == null) {
-    isAllDay = calendar.opt('allDayDefault')
-  }
   if (isAllDay == null) {
-    isAllDay = startMeta.isTimeUnspecified && (!endMeta || endMeta.isTimeUnspecified)
+    if (isAllDayDefault != null) {
+      isAllDay = isAllDayDefault
+    } else {
+      // fall back to the date props LAST
+      isAllDay = startMeta.isTimeUnspecified && (!endMeta || endMeta.isTimeUnspecified)
+    }
   }
 
   startMarker = startMeta.marker
@@ -288,27 +253,69 @@ function parseEventInstance(dateProps: EventDateInput, defId: string, sourceId:
   return {
     isAllDay,
     hasEnd,
-    instance: createEventInstance(
-      defId,
-      { start: startMarker, end: endMarker },
-      startMeta.forcedTzo,
-      endMeta ? endMeta.forcedTzo : null
-    )
+    range: { start: startMarker, end: endMarker },
+    forcedStartTzo: startMeta.forcedTzo,
+    forcedEndTzo: endMeta ? endMeta.forcedTzo : null
   }
 }
 
 
-export function createEventInstance(
-  defId: string,
-  range: DateRange,
-  forcedStartTzo?: number,
-  forcedEndTzo?: number
-): EventInstance {
-  return {
-    instanceId: String(uid++),
-    defId,
-    range,
-    forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo,
-    forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo
+function pluckDateProps(raw: EventInput, leftovers: any) {
+  let props = refineProps(raw, DATE_PROPS, {}, leftovers)
+
+  props.start = (props.start !== null) ? props.start : props.date
+  delete props.date
+
+  return props
+}
+
+
+function pluckNonDateProps(raw: EventInput, calendar: Calendar, leftovers: any) {
+  let props = refineProps(raw, NON_DATE_PROPS, {}, leftovers)
+
+  props.publicId = props.id
+  props.classNames = props.classNames.concat(props.className)
+
+  if (props.constraint) {
+    props.constraint = normalizeConstraint(props.constraint, calendar)
+  }
+
+  if (props.startEditable == null) {
+    props.startEditable = props.editable
+  }
+
+  if (props.durationEditable == null) {
+    props.durationEditable = props.editable
+  }
+
+  if (!props.backgroundColor) {
+    props.backgroundColor = props.color
+  }
+
+  if (!props.borderColor) {
+    props.borderColor = props.color
   }
+
+  delete props.id
+  delete props.className
+  delete props.editable
+  delete props.color
+
+  return props
+}
+
+
+function computeIsAllDayDefault(sourceId: string, calendar: Calendar): boolean | null {
+  let res = null
+
+  if (sourceId) {
+    let source = calendar.state.eventSources[sourceId]
+    res = source.allDayDefault
+  }
+
+  if (res == null) {
+    res = calendar.opt('allDayDefault')
+  }
+
+  return res
 }

+ 12 - 6
src/structs/recurring-event.ts

@@ -2,14 +2,14 @@ import { EventInput, EventDef } from './event'
 import { DateRange } from '../datelib/date-range'
 import { DateEnv } from '../datelib/env'
 import { Duration } from '../datelib/duration'
-import { DateMarker } from '../datelib/marker'
+import { DateMarker, startOfDay } from '../datelib/marker'
 
 /*
 The plugin system for defining how a recurring event is expanded into individual instances.
 */
 
 export interface ParsedRecurring {
-  isAllDay: boolean
+  isAllDay: boolean // last fallback to be used
   duration: Duration | null // signals hasEnd
   typeData: any
 }
@@ -27,12 +27,12 @@ export function registerRecurringType(recurringType: RecurringType) {
 }
 
 
-export function parseRecurring(eventInput: EventInput, leftovers: any, dateEnv: DateEnv) {
+export function parseRecurring(eventInput: EventInput, dateEnv: DateEnv, leftovers: any) {
   for (let i = 0; i < recurringTypes.length; i++) {
     let parsed = recurringTypes[i].parse(eventInput, leftovers, dateEnv) as ParsedRecurring
 
     if (parsed) {
-      return {
+      return { // more efficient way to do this?
         isAllDay: parsed.isAllDay,
         duration: parsed.duration,
         typeData: parsed.typeData,
@@ -50,11 +50,17 @@ Event MUST have a recurringDef
 */
 export function expandRecurringRanges(eventDef: EventDef, framingRange: DateRange, dateEnv: DateEnv): DateMarker[] {
   let typeDef = recurringTypes[eventDef.recurringDef.typeId]
-
-  return typeDef.expand(
+  let markers = typeDef.expand(
     eventDef.recurringDef.typeData,
     eventDef,
     framingRange,
     dateEnv
   )
+
+  // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to
+  if (eventDef.isAllDay) {
+    markers = markers.map(startOfDay)
+  }
+
+  return markers
 }