```space-lua function generate_calendar(calendarID, year, month) local month_names = { "Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December" } local day_names = {"Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"} -- A specified year and month will always display that calendar. if year == 0 and month == 0 then local wantedMonth = clientStore.get("calendar:" .. calendarID) editor.flashNotification(wantedMonth) -- Calculate year and month from the current date plus a month offset. if wantedMonth == nil then wantedMonth = tonumber(os.date("%Y")) * 12 + (tonumber(os.date("%m"))-1) end year = (wantedMonth - (wantedMonth % 12)) / 12 month = wantedMonth % 12 + 1 end local events = query[[ from index.tag "event" where _.at.match(string.format("^%d-%02d-", year, month)) order by _.at ]] local days = get_days_in_month(year, month) -- Get the first day of the month (1 = Sunday, 2 = Monday, ..., 7 = Saturday) local first_day = tonumber(os.date("%w", os.time{year=year, month=month, day=1})) + 1 -- Adjust the first day based on the week start setting first_day = first_day - 1 if first_day == 0 then first_day = 7 end local html = [[ ]] html = html .. "" for _, day_name in ipairs(day_names) do html = html .. "" end html = html .. "\n" local day = 1 local started = false for week = 1, 6 do html = html .. "" for weekday = 1, 7 do if not started and weekday == first_day then started = true end if started and day <= days then local cellcontent = "" cellcontent = day for _, e in pairs(events) do local datematch = e.at.match( string.format("^%d-%02d-%02d( +[0-9]+:[0-9]+)?", year, month, day)) if datematch and #datematch >= 2 and not datematch[2] then cellcontent = cellcontent .. string.format( [[
%s
]], e.ref, e.name ) elseif datematch and #datematch >= 2 and datematch[2] then cellcontent = cellcontent .. string.format( [[
%s
%s
]], e.ref, datematch[2], e.name ) end end -- Mark today's date if day == tonumber(os.date("%d")) and month == tonumber(os.date("%m")) and year == tonumber(os.date("%Y")) then html = html .. string.format([[]], cellcontent) else html = html .. string.format([[]], cellcontent) end day = day + 1 else html = html .. "" end end html = html .. "" if day > days then break end end html = html .. "
< Idag > ]] .. month_names[month] .. " " .. year .. [[
" .. day_name .. "
%s%s
" local tmpl = js.window.document.createElement("template") tmpl.innerHTML = html tmpl.content.querySelector('.month-prev').addEventListener('click', function() storeMonth(calendarID, year, month, -1) codeWidget.refreshAll() end) tmpl.content.querySelector('.month-next').addEventListener('click', function() storeMonth(calendarID, year, month, 1) codeWidget.refreshAll() end) tmpl.content.querySelector('.month-now').addEventListener('click', function() clientStore.delete("calendar:" .. calendarID) storeMonth(calendarID, tonumber(os.date("%Y")), tonumber(os.date("%m")), 0) codeWidget.refreshAll() end) return widget.new{ html = tmpl.content, display = "block", cssClasses = {"calendartable"} } end function storeMonth(calendarID, year, month, offset) local storedMonth = clientStore.get("calendar:" .. calendarID) if storedMonth == nil then storedMonth = year * 12 + month end clientStore.set("calendar:" .. calendarID, storedMonth + offset) codeWidget.refreshAll() end -- Local function to get the correct number of days in a month (handles leap years) local function get_days_in_month(year, month) local days_in_month = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} -- Check for leap year and adjust February if month == 2 and ((year % 4 == 0 and year % 100 ~= 0) or (year % 400 == 0)) then return 29 end return days_in_month[month] end ``` ```space-style .calendartable { border-collapse: collapse; table { border-color: var(--panel-border-color); border: 2px solid #444; border-collapse: collapse; th { width: 14%; padding: 3px; text-align: left; background-color: var(--panel-background-color); color: #852624; border: 1px solid #aaa; } td { width: 14%; padding: 3px; text-align: left; vertical-align: top; border: 1px solid #aaa; &.today { background-color: #f6f3c2; } } } a { text-decoration-line: none; color: var(--root-color); &.mark { text-decoration-line: none; color: var(--ui-accent-text-color); } } span.extramarker { background-color: yellow; } .event { background-color: #2ca05a; color: #fff; border-radius: 4px; padding: 4px 8px; margin-top: 8px; max-width: 200px; white-space: normal; overflow-wrap: normal !important; word-break: normal !important; &.all-day { background-color: #852624; } .time { font-weight: bold; } } .month-prev, .month-next, .month-now { cursor: pointer; } } ```