__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 getDisplayName 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 += \ '

' + postTime + ' ' + str(year) + '/' + \ str(monthNumber) + \ '/' + str(dayNumber) + '

' deletePostStr += '
' deletePostStr += '

' + \ translate['Delete this event'] + '

' postActor = getAltPath(actor, domainFull, callingDomain) deletePostStr += \ '
\n' deletePostStr += ' \n' deletePostStr += ' \n' deletePostStr += ' \n' deletePostStr += \ ' \n' deletePostStr += \ ' \n' deletePostStr += \ ' \n' deletePostStr += \ ' \n' deletePostStr += '
\n' deletePostStr += '
\n' deletePostStr += htmlFooter() return deletePostStr def _htmlCalendarDay(personCache: {}, 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 += '
\n' calendarStr += '\n' calendarStr += '\n' if dayEvents: for eventPost in dayEvents: eventTime = None eventDescription = None eventPlace = None postId = None senderName = '' senderActor = None eventIsPublic = False # 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 'public' in ev: if ev['public'] is True: eventIsPublic = True if ev.get('sender'): # get display name from sending actor if ev.get('sender'): senderActor = ev['sender'] dispName = \ getDisplayName(baseDir, senderActor, personCache) if dispName: senderName = \ '' + \ dispName + ': ' if ev.get('name'): eventDescription = ev['name'].strip() elif ev['type'] == 'Place': if ev.get('name'): eventPlace = ev['name'] # prepend a link to the sender of the calendar item if senderName and eventDescription: # if the sender is also mentioned within the event # description then this is a reminder senderActor2 = senderActor.replace('/users/', '/@') if senderActor not in eventDescription and \ senderActor2 not in eventDescription: eventDescription = senderName + eventDescription else: eventDescription = \ translate['Reminder'] + ': ' + eventDescription deleteButtonStr = '' if postId: deleteButtonStr = \ '\n' eventClass = 'calendar__day__event' if eventIsPublic: eventClass = 'calendar__day__event__public' if eventTime and eventDescription and eventPlace: calendarStr += \ '' + deleteButtonStr + '\n' elif eventTime and eventDescription and not eventPlace: calendarStr += \ '' + deleteButtonStr + '\n' elif not eventTime and eventDescription and not eventPlace: calendarStr += \ '' + deleteButtonStr + '\n' elif not eventTime and eventDescription and eventPlace: calendarStr += \ '' + \ '' + deleteButtonStr + '\n' elif eventTime and not eventDescription and eventPlace: calendarStr += \ '' + \ deleteButtonStr + '\n' calendarStr += '\n' calendarStr += '
\n' calendarStr += \ ' \n' calendarStr += \ '

' + str(dayNumber) + ' ' + monthName + \ '


' + str(year) + '\n' calendarStr += '
\n' + \
                    translate['Delete this event'] + ' |
' + eventTime + \ '' + \ '' + \ eventPlace + '
' + eventDescription + \ '
' + eventTime + \ '' + \ eventDescription + '
' + \ '' + \ eventDescription + '
' + \ eventPlace + '
' + eventDescription + \ '
' + eventTime + \ '' + \ '' + \ eventPlace + '
\n' calendarStr += htmlFooter() return calendarStr def htmlCalendar(personCache: {}, 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(personCache, 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 = '
\n' calendarStr += '\n' calendarStr += '\n' calendarStr += '\n' calendarStr += ' \n' calendarStr += ' \n' calendarStr += ' \n' calendarStr += ' \n' calendarStr += ' \n' calendarStr += ' \n' calendarStr += ' \n' calendarStr += '\n' calendarStr += '\n' calendarStr += '\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 += ' \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 = '' + \ str(dayOfMonth) + '' # accessibility menu links menuOptionStr = \ htmlHideFromScreenReader('📅') + ' ' + \ dayDescription navLinks[menuOptionStr] = url # there are events for this day if not isToday: calendarStr += \ ' \n' else: calendarStr += \ ' \n' else: # No events today if not isToday: calendarStr += \ ' \n' else: calendarStr += \ ' \n' else: calendarStr += ' \n' calendarStr += ' \n' calendarStr += '\n' calendarStr += '
\n' calendarStr += \ ' ' calendarStr += \ ' ' + translate['Previous month'] + \
        '\n' calendarStr += ' ' calendarStr += '

' + monthName + '

\n' calendarStr += \ ' ' calendarStr += \ ' ' + translate['Next month'] + \
        '\n' calendarStr += '
' + \ translate['Sun'] + '' + \ translate['Mon'] + '' + \ translate['Tue'] + '' + \ translate['Wed'] + '' + \ translate['Thu'] + '' + \ translate['Fri'] + '' + \ translate['Sat'] + '
' + \ dayLink + '' + \ dayLink + '' + \ str(dayOfMonth) + '' + str(dayOfMonth) + '
\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()