silverbullet/Library/Widgets/Calendar.md
2025-09-09 23:46:11 +02:00

6.3 KiB


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)
    -- 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"))
    end
    year = (wantedMonth - (wantedMonth % 12)) / 12
    month = wantedMonth % 12
    if month == 0 then
      month = 1
    end
  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 = [[
    <table border='1'>
      <tr>
        <th colspan='7'>
          <span class="month-prev">&lt;</span>
          <span class="month-now">Idag</span>
          <span class="month-next">&gt;</span>
     ]] .. month_names[month] .. " " .. year .. [[ 
        </th>
      </tr>
  ]]
  
  html = html .. "<tr>"
  for _, day_name in ipairs(day_names) do
      html = html .. "<th>" .. day_name .. "</th>"
  end
  html = html .. "</tr>\n"

  local day = 1
  local started = false
  for week = 1, 6 do
      html = html .. "<tr>"
      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 >= 2 and not datematch[2] then
                    cellcontent = cellcontent .. string.format(
                      [[<a href="%s"><div class="event all-day">%s</div></a>]],
                      e.ref, e.name
                  )
                elseif #datematch >= 2 and datematch[2] then
                  cellcontent = cellcontent .. string.format(
                    [[
                      <a href="%s">
                        <div class="event">
                           <div class="time">%s</div>
                            <div>%s</div>
                        </div>
                      </a>
                    ]],
                    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([[<td class="today">%s</td>]], cellcontent)
              else
                  html = html .. string.format([[<td>%s</td>]], cellcontent)
              end

              day = day + 1
          else
              html = html .. "<td></td>"
          end
      end
      html = html .. "</tr>"
      if day > days then
          break
      end
  end

  html = html .. "</table>"

  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    
.calendartable {
  border-collapse: collapse; 
  /* width: 500px; */
}

.calendartable table {
  border-color: var(--panel-border-color);
  border: 2px solid #444;
  border-collapse: collapse;
}

.calendartable th {
  width: 14%;
  padding: 3px; 
  text-align: left;  
  background-color: var(--panel-background-color);
  color: #852624;
  border: 1px solid #aaa;
}


.calendartable td {
  width: 14%;
  padding: 3px; 
  text-align: left;
  vertical-align: top;
  border: 1px solid #aaa;
}

.calendartable td.today {
  background-color: #f6f3c2;
}

.calendartable a {
  text-decoration-line: none;
  color: var(--root-color);
}

.calendartable a.mark {
  text-decoration-line: none;
  color: var(--ui-accent-text-color);
}

.calendartable span.extramarker {
  background-color: yellow;
}

.calendartable .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;
}

.calendartable .event.all-day {
  background-color: #852624;
}

.calendartable .event .time {
  font-weight: bold;
}

.calendartable .month-prev,
.calendartable .month-next,
.calendartable .month-now {
  cursor: pointer;
}