__filename__ = "webapp_calendar.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.2.0" __maintainer__ = "Bob Mottram" __email__ = "bob@freedombone.net" __status__ = "Production" import os from datetime import datetime from datetime import date from shutil import copyfile from utils import getConfigParam from utils import getNicknameFromActor from utils import getDomainFromActor from utils import locatePost from utils import loadJson from utils import weekDayOfMonthStart from happening import getTodaysEvents from happening import getCalendarEvents from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlFooter from webapp_utils import getAltPath from webapp_utils import htmlHideFromScreenReader from webapp_utils import htmlKeyboardNavigation def htmlCalendarDeleteConfirm(cssCache: {}, translate: {}, baseDir: str, path: str, httpPrefix: str, domainFull: str, postId: str, postTime: str, year: int, monthNumber: int, dayNumber: int, callingDomain: str) -> str: """Shows a screen asking to confirm the deletion of a calendar event """ nickname = getNicknameFromActor(path) actor = httpPrefix + '://' + domainFull + '/users/' + nickname domain, port = getDomainFromActor(actor) messageId = actor + '/statuses/' + postId postFilename = locatePost(baseDir, nickname, domain, messageId) if not postFilename: return None postJsonObject = loadJson(postFilename) if not postJsonObject: return None if os.path.isfile(baseDir + '/img/delete-background.png'): if not os.path.isfile(baseDir + '/accounts/delete-background.png'): copyfile(baseDir + '/img/delete-background.png', baseDir + '/accounts/delete-background.png') deletePostStr = None cssFilename = baseDir + '/epicyon-profile.css' if os.path.isfile(baseDir + '/epicyon.css'): cssFilename = baseDir + '/epicyon.css' instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') deletePostStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) deletePostStr += \ '<center><h1>' + postTime + ' ' + str(year) + '/' + \ str(monthNumber) + \ '/' + str(dayNumber) + '</h1></center>' deletePostStr += '<center>' deletePostStr += ' <p class="followText">' + \ translate['Delete this event'] + '</p>' postActor = getAltPath(actor, domainFull, callingDomain) deletePostStr += \ ' <form method="POST" action="' + postActor + '/rmpost">\n' deletePostStr += ' <input type="hidden" name="year" value="' + \ str(year) + '">\n' deletePostStr += ' <input type="hidden" name="month" value="' + \ str(monthNumber) + '">\n' deletePostStr += ' <input type="hidden" name="day" value="' + \ str(dayNumber) + '">\n' deletePostStr += \ ' <input type="hidden" name="pageNumber" value="1">\n' deletePostStr += \ ' <input type="hidden" name="messageId" value="' + \ messageId + '">\n' deletePostStr += \ ' <button type="submit" class="button" name="submitYes">' + \ translate['Yes'] + '</button>\n' deletePostStr += \ ' <a href="' + actor + '/calendar?year=' + \ str(year) + '?month=' + \ str(monthNumber) + '"><button class="button">' + \ translate['No'] + '</button></a>\n' deletePostStr += ' </form>\n' deletePostStr += '</center>\n' deletePostStr += htmlFooter() return deletePostStr def _htmlCalendarDay(cssCache: {}, translate: {}, baseDir: str, path: str, year: int, monthNumber: int, dayNumber: int, nickname: str, domain: str, dayEvents: [], monthName: str, actor: str) -> str: """Show a day within the calendar """ accountDir = baseDir + '/accounts/' + nickname + '@' + domain calendarFile = accountDir + '/.newCalendar' if os.path.isfile(calendarFile): os.remove(calendarFile) cssFilename = baseDir + '/epicyon-calendar.css' if os.path.isfile(baseDir + '/calendar.css'): cssFilename = baseDir + '/calendar.css' calActor = actor if '/users/' in actor: calActor = '/users/' + actor.split('/users/')[1] instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') calendarStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) calendarStr += '<main><table class="calendar">\n' calendarStr += '<caption class="calendar__banner--month">\n' calendarStr += \ ' <a href="' + calActor + '/calendar?year=' + str(year) + \ '?month=' + str(monthNumber) + '">\n' calendarStr += \ ' <h1>' + str(dayNumber) + ' ' + monthName + \ '</h1></a><br><span class="year">' + str(year) + '</span>\n' calendarStr += '</caption>\n' calendarStr += '<tbody>\n' if dayEvents: for eventPost in dayEvents: eventTime = None eventDescription = None eventPlace = None postId = None # get the time place and description for ev in eventPost: if ev['type'] == 'Event': if ev.get('postId'): postId = ev['postId'] if ev.get('startTime'): eventDate = \ datetime.strptime(ev['startTime'], "%Y-%m-%dT%H:%M:%S%z") eventTime = eventDate.strftime("%H:%M").strip() if ev.get('name'): eventDescription = ev['name'].strip() elif ev['type'] == 'Place': if ev.get('name'): eventPlace = ev['name'] deleteButtonStr = '' if postId: deleteButtonStr = \ '<td class="calendar__day__icons"><a href="' + calActor + \ '/eventdelete?id=' + postId + '?year=' + str(year) + \ '?month=' + str(monthNumber) + '?day=' + str(dayNumber) + \ '?time=' + eventTime + \ '">\n<img class="calendardayicon" loading="lazy" alt="' + \ translate['Delete this event'] + ' |" title="' + \ translate['Delete this event'] + '" src="/' + \ 'icons/delete.png" /></a></td>\n' if eventTime and eventDescription and eventPlace: calendarStr += \ '<tr><td class="calendar__day__time"><b>' + eventTime + \ '</b></td><td class="calendar__day__event">' + \ '<span class="place">' + \ eventPlace + '</span><br>' + eventDescription + \ '</td>' + deleteButtonStr + '</tr>\n' elif eventTime and eventDescription and not eventPlace: calendarStr += \ '<tr><td class="calendar__day__time"><b>' + eventTime + \ '</b></td><td class="calendar__day__event">' + \ eventDescription + '</td>' + deleteButtonStr + '</tr>\n' elif not eventTime and eventDescription and not eventPlace: calendarStr += \ '<tr><td class="calendar__day__time">' + \ '</td><td class="calendar__day__event">' + \ eventDescription + '</td>' + deleteButtonStr + '</tr>\n' elif not eventTime and eventDescription and eventPlace: calendarStr += \ '<tr><td class="calendar__day__time"></td>' + \ '<td class="calendar__day__event"><span class="place">' + \ eventPlace + '</span><br>' + eventDescription + \ '</td>' + deleteButtonStr + '</tr>\n' elif eventTime and not eventDescription and eventPlace: calendarStr += \ '<tr><td class="calendar__day__time"><b>' + eventTime + \ '</b></td><td class="calendar__day__event">' + \ '<span class="place">' + \ eventPlace + '</span></td>' + \ deleteButtonStr + '</tr>\n' calendarStr += '</tbody>\n' calendarStr += '</table></main>\n' calendarStr += htmlFooter() return calendarStr def htmlCalendar(cssCache: {}, translate: {}, baseDir: str, path: str, httpPrefix: str, domainFull: str, textModeBanner: str) -> str: """Show the calendar for a person """ domain = domainFull if ':' in domainFull: domain = domainFull.split(':')[0] monthNumber = 0 dayNumber = None year = 1970 actor = httpPrefix + '://' + domainFull + path.replace('/calendar', '') if '?' in actor: first = True for p in actor.split('?'): if not first: if '=' in p: if p.split('=')[0] == 'year': numStr = p.split('=')[1] if numStr.isdigit(): year = int(numStr) elif p.split('=')[0] == 'month': numStr = p.split('=')[1] if numStr.isdigit(): monthNumber = int(numStr) elif p.split('=')[0] == 'day': numStr = p.split('=')[1] if numStr.isdigit(): dayNumber = int(numStr) first = False actor = actor.split('?')[0] currDate = datetime.now() if year == 1970 and monthNumber == 0: year = currDate.year monthNumber = currDate.month nickname = getNicknameFromActor(actor) if os.path.isfile(baseDir + '/img/calendar-background.png'): if not os.path.isfile(baseDir + '/accounts/calendar-background.png'): copyfile(baseDir + '/img/calendar-background.png', baseDir + '/accounts/calendar-background.png') months = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December') monthName = translate[months[monthNumber - 1]] if dayNumber: dayEvents = None events = \ getTodaysEvents(baseDir, nickname, domain, year, monthNumber, dayNumber) if events: if events.get(str(dayNumber)): dayEvents = events[str(dayNumber)] return _htmlCalendarDay(cssCache, translate, baseDir, path, year, monthNumber, dayNumber, nickname, domain, dayEvents, monthName, actor) events = \ getCalendarEvents(baseDir, nickname, domain, year, monthNumber) prevYear = year prevMonthNumber = monthNumber - 1 if prevMonthNumber < 1: prevMonthNumber = 12 prevYear = year - 1 nextYear = year nextMonthNumber = monthNumber + 1 if nextMonthNumber > 12: nextMonthNumber = 1 nextYear = year + 1 print('Calendar year=' + str(year) + ' month=' + str(monthNumber) + ' ' + str(weekDayOfMonthStart(monthNumber, year))) if monthNumber < 12: daysInMonth = \ (date(year, monthNumber + 1, 1) - date(year, monthNumber, 1)).days else: daysInMonth = \ (date(year + 1, 1, 1) - date(year, monthNumber, 1)).days # print('daysInMonth ' + str(monthNumber) + ': ' + str(daysInMonth)) cssFilename = baseDir + '/epicyon-calendar.css' if os.path.isfile(baseDir + '/calendar.css'): cssFilename = baseDir + '/calendar.css' calActor = actor if '/users/' in actor: calActor = '/users/' + actor.split('/users/')[1] instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') headerStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) # the main graphical calendar as a table calendarStr = '<main><table class="calendar">\n' calendarStr += '<caption class="calendar__banner--month">\n' calendarStr += \ ' <a href="' + calActor + '/calendar?year=' + str(prevYear) + \ '?month=' + str(prevMonthNumber) + '">' calendarStr += \ ' <img loading="lazy" alt="' + translate['Previous month'] + \ '" title="' + translate['Previous month'] + '" src="/icons' + \ '/prev.png" class="buttonprev"/></a>\n' calendarStr += ' <a href="' + calActor + '/inbox" title="' calendarStr += translate['Switch to timeline view'] + '">' calendarStr += ' <h1>' + monthName + '</h1></a>\n' calendarStr += \ ' <a href="' + calActor + '/calendar?year=' + str(nextYear) + \ '?month=' + str(nextMonthNumber) + '">' calendarStr += \ ' <img loading="lazy" alt="' + translate['Next month'] + \ '" title="' + translate['Next month'] + '" src="/icons' + \ '/prev.png" class="buttonnext"/></a>\n' calendarStr += '</caption>\n' calendarStr += '<thead>\n' calendarStr += '<tr>\n' calendarStr += ' <th scope="col" class="calendar__day__header">' + \ translate['Sun'] + '</th>\n' calendarStr += ' <th scope="col" class="calendar__day__header">' + \ translate['Mon'] + '</th>\n' calendarStr += ' <th scope="col" class="calendar__day__header">' + \ translate['Tue'] + '</th>\n' calendarStr += ' <th scope="col" class="calendar__day__header">' + \ translate['Wed'] + '</th>\n' calendarStr += ' <th scope="col" class="calendar__day__header">' + \ translate['Thu'] + '</th>\n' calendarStr += ' <th scope="col" class="calendar__day__header">' + \ translate['Fri'] + '</th>\n' calendarStr += ' <th scope="col" class="calendar__day__header">' + \ translate['Sat'] + '</th>\n' calendarStr += '</tr>\n' calendarStr += '</thead>\n' calendarStr += '<tbody>\n' # beginning of the links used for accessibility navLinks = {} timelineLinkStr = htmlHideFromScreenReader('🏠') + ' ' + \ translate['Switch to timeline view'] navLinks[timelineLinkStr] = calActor + '/inbox' dayOfMonth = 0 dow = weekDayOfMonthStart(monthNumber, year) for weekOfMonth in range(1, 7): if dayOfMonth == daysInMonth: continue calendarStr += ' <tr>\n' for dayNumber in range(1, 8): if (weekOfMonth > 1 and dayOfMonth < daysInMonth) or \ (weekOfMonth == 1 and dayNumber >= dow): dayOfMonth += 1 isToday = False if year == currDate.year: if currDate.month == monthNumber: if dayOfMonth == currDate.day: isToday = True if events.get(str(dayOfMonth)): url = calActor + '/calendar?year=' + \ str(year) + '?month=' + \ str(monthNumber) + '?day=' + str(dayOfMonth) dayDescription = monthName + ' ' + str(dayOfMonth) dayLink = '<a href="' + url + '" ' + \ 'title="' + dayDescription + '">' + \ str(dayOfMonth) + '</a>' # accessibility menu links menuOptionStr = \ htmlHideFromScreenReader('📅') + ' ' + \ dayDescription navLinks[menuOptionStr] = url # there are events for this day if not isToday: calendarStr += \ ' <td class="calendar__day__cell" ' + \ 'data-event="">' + \ dayLink + '</td>\n' else: calendarStr += \ ' <td class="calendar__day__cell" ' + \ 'data-today-event="">' + \ dayLink + '</td>\n' else: # No events today if not isToday: calendarStr += \ ' <td class="calendar__day__cell">' + \ str(dayOfMonth) + '</td>\n' else: calendarStr += \ ' <td class="calendar__day__cell" ' + \ 'data-today="">' + str(dayOfMonth) + '</td>\n' else: calendarStr += ' <td class="calendar__day__cell"></td>\n' calendarStr += ' </tr>\n' calendarStr += '</tbody>\n' calendarStr += '</table></main>\n' # end of the links used for accessibility nextMonthStr = \ htmlHideFromScreenReader('→') + ' ' + translate['Next month'] navLinks[nextMonthStr] = calActor + '/calendar?year=' + str(nextYear) + \ '?month=' + str(nextMonthNumber) prevMonthStr = \ htmlHideFromScreenReader('←') + ' ' + translate['Previous month'] navLinks[prevMonthStr] = calActor + '/calendar?year=' + str(prevYear) + \ '?month=' + str(prevMonthNumber) screenReaderCal = \ htmlKeyboardNavigation(textModeBanner, navLinks, monthName) return headerStr + screenReaderCal + calendarStr + htmlFooter()