mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
Merge pull request #444 from ThatOneCalculator/feat/calendar-events
feat: show calendar events in calendar
This commit is contained in:
135
Bin/calendar-events.py
Executable file
135
Bin/calendar-events.py
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
import gi
|
||||
|
||||
gi.require_version('EDataServer', '1.2')
|
||||
gi.require_version('ECal', '2.0')
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from gi.repository import ECal, EDataServer
|
||||
|
||||
start_time = int(sys.argv[1])
|
||||
end_time = int(sys.argv[2])
|
||||
|
||||
print(f"Starting with time range: {start_time} to {end_time}", file=sys.stderr)
|
||||
|
||||
all_events = []
|
||||
|
||||
def safe_get_time(ical_time):
|
||||
"""Safely get time from ICalTime object"""
|
||||
if not ical_time:
|
||||
return None
|
||||
|
||||
try:
|
||||
year = ical_time.get_year()
|
||||
month = ical_time.get_month()
|
||||
day = ical_time.get_day()
|
||||
|
||||
if year < 1970 or year > 2100 or month < 1 or month > 12 or day < 1 or day > 31:
|
||||
return None
|
||||
|
||||
if ical_time.is_date():
|
||||
local_struct = time.struct_time((year, month, day, 0, 0, 0, 0, 0, -1))
|
||||
return int(time.mktime(local_struct))
|
||||
|
||||
hour = ical_time.get_hour()
|
||||
minute = ical_time.get_minute()
|
||||
second = ical_time.get_second()
|
||||
|
||||
dt = datetime(year, month, day, hour, minute, second, tzinfo=timezone.utc)
|
||||
return int(dt.timestamp())
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
print("Getting registry...", file=sys.stderr)
|
||||
registry = EDataServer.SourceRegistry.new_sync(None)
|
||||
print("Registry obtained", file=sys.stderr)
|
||||
|
||||
sources = registry.list_sources(EDataServer.SOURCE_EXTENSION_CALENDAR)
|
||||
print(f"Found {len(sources)} calendar sources", file=sys.stderr)
|
||||
|
||||
for source in sources:
|
||||
if not source.get_enabled():
|
||||
print(f"Skipping disabled calendar: {source.get_display_name()}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
calendar_name = source.get_display_name()
|
||||
print(f"\nProcessing calendar: {calendar_name}", file=sys.stderr)
|
||||
|
||||
try:
|
||||
print(f" Connecting to {calendar_name}...", file=sys.stderr)
|
||||
client = ECal.Client.connect_sync(
|
||||
source,
|
||||
ECal.ClientSourceType.EVENTS,
|
||||
30,
|
||||
None
|
||||
)
|
||||
print(f" Connected to {calendar_name}", file=sys.stderr)
|
||||
|
||||
start_dt = datetime.fromtimestamp(start_time, tz=timezone.utc)
|
||||
end_dt = datetime.fromtimestamp(end_time, tz=timezone.utc)
|
||||
|
||||
start_str = start_dt.strftime("%Y%m%dT%H%M%SZ")
|
||||
end_str = end_dt.strftime("%Y%m%dT%H%M%SZ")
|
||||
|
||||
query = f'(occur-in-time-range? (make-time "{start_str}") (make-time "{end_str}"))'
|
||||
print(f" Query: {query}", file=sys.stderr)
|
||||
|
||||
print(f" Getting object list for {calendar_name}...", file=sys.stderr)
|
||||
success, ical_objects = client.get_object_list_sync(query, None)
|
||||
print(f" Got object list for {calendar_name}: success={success}, count={len(ical_objects) if ical_objects else 0}", file=sys.stderr)
|
||||
|
||||
if not success or not ical_objects:
|
||||
print(f" No events found in {calendar_name}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
print(f" Processing {len(ical_objects)} events from {calendar_name}...", file=sys.stderr)
|
||||
for idx, ical_obj in enumerate(ical_objects):
|
||||
try:
|
||||
if hasattr(ical_obj, 'get_summary'):
|
||||
comp = ical_obj
|
||||
else:
|
||||
comp = ECal.Component.new_from_string(ical_obj)
|
||||
|
||||
if not comp:
|
||||
continue
|
||||
|
||||
summary = comp.get_summary() or "(No title)"
|
||||
|
||||
start_timestamp = safe_get_time(comp.get_dtstart())
|
||||
if start_timestamp is None:
|
||||
continue
|
||||
|
||||
end_timestamp = safe_get_time(comp.get_dtend())
|
||||
if end_timestamp is None or end_timestamp == start_timestamp:
|
||||
end_timestamp = start_timestamp + 3600
|
||||
|
||||
location = comp.get_location() or ""
|
||||
description = comp.get_description() or ""
|
||||
|
||||
all_events.append({
|
||||
'summary': summary,
|
||||
'start': start_timestamp,
|
||||
'end': end_timestamp,
|
||||
'location': location,
|
||||
'description': description,
|
||||
'calendar': calendar_name
|
||||
})
|
||||
|
||||
if (idx + 1) % 10 == 0:
|
||||
print(f" Processed {idx + 1} events from {calendar_name}...", file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f" Error processing event {idx} in {calendar_name}: {e}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
print(f" Finished processing {calendar_name}, found {len([e for e in all_events if e['calendar'] == calendar_name])} events", file=sys.stderr)
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error for {calendar_name}: {e}", file=sys.stderr)
|
||||
|
||||
print(f"\nSorting {len(all_events)} total events...", file=sys.stderr)
|
||||
all_events.sort(key=lambda x: x['start'])
|
||||
print("Done! Outputting JSON...", file=sys.stderr)
|
||||
print(json.dumps(all_events))
|
||||
11
Bin/check-calendar.py
Executable file
11
Bin/check-calendar.py
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import gi
|
||||
|
||||
gi.require_version('EDataServer', '1.2')
|
||||
gi.require_version('ECal', '2.0')
|
||||
|
||||
try:
|
||||
from gi.repository import ECal, EDataServer
|
||||
print("available")
|
||||
except ImportError as e:
|
||||
print(f"unavailable: {e}")
|
||||
21
Bin/list-calendars.py
Executable file
21
Bin/list-calendars.py
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
import gi
|
||||
|
||||
gi.require_version('EDataServer', '1.2')
|
||||
import json
|
||||
|
||||
from gi.repository import EDataServer
|
||||
|
||||
registry = EDataServer.SourceRegistry.new_sync(None)
|
||||
sources = registry.list_sources(EDataServer.SOURCE_EXTENSION_CALENDAR)
|
||||
|
||||
calendars = []
|
||||
for source in sources:
|
||||
if source.get_enabled():
|
||||
calendars.append({
|
||||
'uid': source.get_uid(),
|
||||
'name': source.get_display_name(),
|
||||
'enabled': True
|
||||
})
|
||||
|
||||
print(json.dumps(calendars))
|
||||
@@ -10,6 +10,7 @@ import qs.Widgets
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
readonly property var now: Time.date
|
||||
|
||||
preferredWidth: (Settings.data.location.showWeekNumberInCalendar ? 400 : 380) * Style.uiScaleRatio
|
||||
@@ -347,6 +348,14 @@ NPanel {
|
||||
grid.year = newDate.getFullYear()
|
||||
grid.month = newDate.getMonth()
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth()
|
||||
const now = new Date()
|
||||
const monthStart = new Date(grid.year, grid.month, 1)
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0)
|
||||
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)))
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)))
|
||||
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30)
|
||||
}
|
||||
}
|
||||
NIconButton {
|
||||
@@ -355,6 +364,7 @@ NPanel {
|
||||
grid.month = Time.date.getMonth()
|
||||
grid.year = Time.date.getFullYear()
|
||||
content.isCurrentMonth = true
|
||||
CalendarService.loadEvents()
|
||||
}
|
||||
}
|
||||
NIconButton {
|
||||
@@ -364,6 +374,14 @@ NPanel {
|
||||
grid.year = newDate.getFullYear()
|
||||
grid.month = newDate.getMonth()
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth()
|
||||
const now = new Date()
|
||||
const monthStart = new Date(grid.year, grid.month, 1)
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0)
|
||||
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)))
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)))
|
||||
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,6 +423,71 @@ NPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 0
|
||||
|
||||
// Helper function to check if a date has events
|
||||
function hasEventsOnDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return false
|
||||
|
||||
const targetDate = new Date(year, month, day)
|
||||
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000
|
||||
const targetEnd = targetStart + 86400 // +24 hours
|
||||
|
||||
return CalendarService.events.some(event => {
|
||||
// Check if event starts or overlaps with this day
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd)
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to get events for a specific date
|
||||
function getEventsForDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return []
|
||||
|
||||
const targetDate = new Date(year, month, day)
|
||||
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000)
|
||||
const targetEnd = targetStart + 86400 // +24 hours
|
||||
|
||||
return CalendarService.events.filter(event => {
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd)
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to check if an event is all-day
|
||||
function isAllDayEvent(event) {
|
||||
const duration = event.end - event.start
|
||||
const startDate = new Date(event.start * 1000)
|
||||
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0
|
||||
return duration === 86400 && isAtMidnight
|
||||
}
|
||||
|
||||
// Helper function to check if an event is multi-day
|
||||
function isMultiDayEvent(event) {
|
||||
if (isAllDayEvent(event)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const startDate = new Date(event.start * 1000)
|
||||
const endDate = new Date(event.end * 1000)
|
||||
|
||||
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
|
||||
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())
|
||||
|
||||
return startDateOnly.getTime() !== endDateOnly.getTime()
|
||||
}
|
||||
|
||||
// Helper function to get color for a specific event
|
||||
function getEventColor(event, isToday) {
|
||||
if (isMultiDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mTertiary
|
||||
} else if (isAllDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mSecondary
|
||||
} else {
|
||||
return isToday ? Color.mOnSecondary : Color.mPrimary
|
||||
}
|
||||
}
|
||||
|
||||
// Column of week numbers
|
||||
ColumnLayout {
|
||||
visible: Settings.data.location.showWeekNumberInCalendar
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
||||
@@ -475,6 +558,55 @@ NPanel {
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium
|
||||
}
|
||||
|
||||
// Event indicator dots
|
||||
Row {
|
||||
visible: parent.parent.parent.parent.parent.hasEventsOnDate(model.year, model.month, model.day)
|
||||
spacing: 2 * scaling
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.marginXS * scaling
|
||||
|
||||
readonly property int currentYear: model.year
|
||||
readonly property int currentMonth: model.month
|
||||
readonly property int currentDay: model.day
|
||||
readonly property bool isToday: model.today
|
||||
|
||||
Repeater {
|
||||
model: parent.parent.parent.parent.parent.parent.getEventsForDate(parent.currentYear, parent.currentMonth, parent.currentDay)
|
||||
|
||||
Rectangle {
|
||||
width: 4 * scaling
|
||||
height: 4 * scaling
|
||||
radius: 2 * scaling
|
||||
color: parent.parent.parent.parent.parent.parent.getEventColor(modelData, model.today)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
const events = parent.parent.parent.parent.parent.getEventsForDate(model.year, model.month, model.day)
|
||||
if (events.length > 0) {
|
||||
const summaries = events.map(e => e.summary).join('\n')
|
||||
TooltipService.show(Screen, parent, summaries)
|
||||
TooltipService.updateText(summaries)
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
const dateWithSlashes = `${model.month.toString().padStart(2, '0')}/${model.day.toString().padStart(2, '0')}/${model.year.toString().substring(2)}`
|
||||
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes])
|
||||
}
|
||||
|
||||
onExited: {
|
||||
TooltipService.hide()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
|
||||
250
Services/CalendarService.qml
Normal file
250
Services/CalendarService.qml
Normal file
@@ -0,0 +1,250 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Core state
|
||||
property var events: ([])
|
||||
property bool loading: false
|
||||
property bool available: false
|
||||
property string lastError: ""
|
||||
property var calendars: ([])
|
||||
|
||||
// Persistent cache
|
||||
property string cacheFile: Settings.cacheDir + "calendar.json"
|
||||
|
||||
// Python scripts
|
||||
readonly property string checkCalendarAvailableScript: Quickshell.shellDir + '/Bin/check-calendar.py'
|
||||
readonly property string listCalendarsScript: Quickshell.shellDir + '/Bin/list-calendars.py'
|
||||
readonly property string calendarEventsScript: Quickshell.shellDir + '/Bin/calendar-events.py'
|
||||
|
||||
// Cache file handling
|
||||
FileView {
|
||||
id: cacheFileView
|
||||
path: root.cacheFile
|
||||
printErrors: false
|
||||
|
||||
JsonAdapter {
|
||||
id: cacheAdapter
|
||||
property var cachedEvents: ([])
|
||||
property var cachedCalendars: ([])
|
||||
property string lastUpdate: ""
|
||||
}
|
||||
|
||||
onLoadFailed: {
|
||||
cacheAdapter.cachedEvents = ([])
|
||||
cacheAdapter.cachedCalendars = ([])
|
||||
cacheAdapter.lastUpdate = ""
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
loadFromCache()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.log("Calendar", "Service initialized")
|
||||
loadFromCache()
|
||||
checkAvailability()
|
||||
}
|
||||
|
||||
// Save cache with debounce
|
||||
Timer {
|
||||
id: saveDebounce
|
||||
interval: 1000
|
||||
onTriggered: cacheFileView.writeAdapter()
|
||||
}
|
||||
|
||||
function saveCache() {
|
||||
saveDebounce.restart()
|
||||
}
|
||||
|
||||
// Load events and calendars from cache
|
||||
function loadFromCache() {
|
||||
if (cacheAdapter.cachedEvents && cacheAdapter.cachedEvents.length > 0) {
|
||||
root.events = cacheAdapter.cachedEvents
|
||||
Logger.log("Calendar", `Loaded ${cacheAdapter.cachedEvents.length} cached event(s)`)
|
||||
}
|
||||
|
||||
if (cacheAdapter.cachedCalendars && cacheAdapter.cachedCalendars.length > 0) {
|
||||
root.calendars = cacheAdapter.cachedCalendars
|
||||
Logger.log("Calendar", `Loaded ${cacheAdapter.cachedCalendars.length} cached calendar(s)`)
|
||||
}
|
||||
|
||||
if (cacheAdapter.lastUpdate) {
|
||||
Logger.log("Calendar", `Cache last updated: ${cacheAdapter.lastUpdate}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-refresh timer (every 5 minutes)
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: 300000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: loadEvents()
|
||||
}
|
||||
|
||||
// Core functions
|
||||
function checkAvailability() {
|
||||
availabilityCheckProcess.running = true
|
||||
}
|
||||
|
||||
function loadCalendars() {
|
||||
listCalendarsProcess.running = true
|
||||
}
|
||||
|
||||
function loadEvents(daysAhead = 31, daysBehind = 14) {
|
||||
if (loading)
|
||||
return
|
||||
|
||||
loading = true
|
||||
lastError = ""
|
||||
|
||||
const now = new Date()
|
||||
const startDate = new Date(now.getTime() - (daysBehind * 24 * 60 * 60 * 1000))
|
||||
const endDate = new Date(now.getTime() + (daysAhead * 24 * 60 * 60 * 1000))
|
||||
|
||||
loadEventsProcess.startTime = Math.floor(startDate.getTime() / 1000)
|
||||
loadEventsProcess.endTime = Math.floor(endDate.getTime() / 1000)
|
||||
loadEventsProcess.running = true
|
||||
|
||||
Logger.log("Calendar", `Loading events (${daysBehind} days behind, ${daysAhead} days ahead): ${startDate.toLocaleDateString()} to ${endDate.toLocaleDateString()}`)
|
||||
}
|
||||
|
||||
// Helper to format date/time
|
||||
function formatDateTime(timestamp) {
|
||||
const date = new Date(timestamp * 1000)
|
||||
return Qt.formatDateTime(date, "yyyy-MM-dd hh:mm")
|
||||
}
|
||||
|
||||
// Process to check for evolution-data-server libraries
|
||||
Process {
|
||||
id: availabilityCheckProcess
|
||||
running: false
|
||||
command: ["sh", "-c", "command -v python3 >/dev/null 2>&1 && python3 " + root.checkCalendarAvailableScript + " || echo 'unavailable: python3 not installed'"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const result = text.trim()
|
||||
root.available = result === "available"
|
||||
|
||||
if (root.available) {
|
||||
Logger.log("Calendar", "EDS libraries available")
|
||||
loadCalendars()
|
||||
} else {
|
||||
Logger.warn("Calendar", "EDS libraries not available: " + result)
|
||||
root.lastError = "Evolution Data Server libraries not installed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
Logger.warn("Calendar", "Availability check error: " + text)
|
||||
root.available = false
|
||||
root.lastError = "Failed to check library availability"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process to list available calendars
|
||||
Process {
|
||||
id: listCalendarsProcess
|
||||
running: false
|
||||
command: ["python3", root.listCalendarsScript]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const result = JSON.parse(text.trim())
|
||||
root.calendars = result
|
||||
cacheAdapter.cachedCalendars = result
|
||||
saveCache()
|
||||
|
||||
Logger.log("Calendar", `Found ${result.length} calendar(s)`)
|
||||
|
||||
// Auto-load events after discovering calendars
|
||||
// Only load if we have calendars and no cached events
|
||||
if (result.length > 0 && root.events.length === 0) {
|
||||
loadEvents()
|
||||
} else if (result.length > 0) {
|
||||
// If we already have cached events, load in background
|
||||
loadEvents()
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.warn("Calendar", "Failed to parse calendars: " + e)
|
||||
root.lastError = "Failed to parse calendar list"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text.trim()) {
|
||||
Logger.warn("Calendar", "List calendars error: " + text)
|
||||
root.lastError = text.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process to load events
|
||||
Process {
|
||||
id: loadEventsProcess
|
||||
running: false
|
||||
property int startTime: 0
|
||||
property int endTime: 0
|
||||
|
||||
command: ["python3", root.calendarEventsScript, startTime.toString(), endTime.toString()]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.loading = false
|
||||
|
||||
try {
|
||||
const result = JSON.parse(text.trim())
|
||||
root.events = result
|
||||
cacheAdapter.cachedEvents = result
|
||||
cacheAdapter.lastUpdate = new Date().toISOString()
|
||||
saveCache()
|
||||
|
||||
Logger.log("Calendar", `Loaded ${result.length} event(s)`)
|
||||
} catch (e) {
|
||||
Logger.warn("Calendar", "Failed to parse events: " + e)
|
||||
root.lastError = "Failed to parse events"
|
||||
|
||||
// Fall back to cached events if available
|
||||
if (cacheAdapter.cachedEvents.length > 0) {
|
||||
root.events = cacheAdapter.cachedEvents
|
||||
Logger.log("Calendar", "Using cached events")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.loading = false
|
||||
|
||||
if (text.trim()) {
|
||||
Logger.warn("Calendar", "Load events error: " + text)
|
||||
root.lastError = text.trim()
|
||||
|
||||
// Fall back to cached events if available
|
||||
if (cacheAdapter.cachedEvents.length > 0) {
|
||||
root.events = cacheAdapter.cachedEvents
|
||||
Logger.log("Calendar", "Using cached events due to error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user