epicyon/webapp_calendar.py

459 lines
19 KiB
Python
Raw Normal View History

2020-11-09 20:15:17 +00:00
__filename__ = "webapp_calendar.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
2021-01-26 10:07:42 +00:00
__version__ = "1.2.0"
2020-11-09 20:15:17 +00:00
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
2021-06-15 15:08:12 +00:00
__module_group__ = "Calendar"
2020-11-09 20:15:17 +00:00
import os
from datetime import datetime
from datetime import date
from shutil import copyfile
2021-03-06 18:12:43 +00:00
from utils import getDisplayName
2021-01-11 19:46:21 +00:00
from utils import getConfigParam
2020-11-09 20:15:17 +00:00
from utils import getNicknameFromActor
from utils import getDomainFromActor
from utils import locatePost
from utils import loadJson
from utils import weekDayOfMonthStart
2021-06-26 11:16:41 +00:00
from utils import getAltPath
2021-06-26 14:21:24 +00:00
from utils import removeDomainPort
2021-07-13 21:59:53 +00:00
from utils import acctDir
2020-11-09 20:15:17 +00:00
from happening import getTodaysEvents
from happening import getCalendarEvents
2020-11-12 17:05:38 +00:00
from webapp_utils import htmlHeaderWithExternalStyle
2020-11-09 20:15:17 +00:00
from webapp_utils import htmlFooter
2021-02-12 15:13:07 +00:00
from webapp_utils import htmlHideFromScreenReader
from webapp_utils import htmlKeyboardNavigation
2020-11-09 20:15:17 +00:00
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'
2021-01-11 19:46:21 +00:00
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
deletePostStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
2020-11-12 17:05:38 +00:00
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()
2020-11-09 20:15:17 +00:00
return deletePostStr
2021-03-06 18:12:43 +00:00
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:
2020-11-09 20:15:17 +00:00
"""Show a day within the calendar
"""
2021-07-13 21:59:53 +00:00
accountDir = acctDir(baseDir, nickname, domain)
2020-11-09 20:15:17 +00:00
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]
2021-07-05 19:35:57 +00:00
instanceTitle = getConfigParam(baseDir, 'instanceTitle')
2021-01-11 19:46:21 +00:00
calendarStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
2020-11-09 20:15:17 +00:00
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
2021-03-06 18:38:36 +00:00
senderName = ''
2021-03-06 19:04:41 +00:00
senderActor = None
2021-03-07 15:38:10 +00:00
eventIsPublic = False
2020-11-09 20:15:17 +00:00
# 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
2021-03-06 18:38:36 +00:00
if ev.get('sender'):
2021-03-06 18:20:10 +00:00
# get display name from sending actor
2021-03-06 18:38:36 +00:00
if ev.get('sender'):
senderActor = ev['sender']
2021-03-06 18:44:33 +00:00
dispName = \
2021-03-06 18:38:36 +00:00
getDisplayName(baseDir, senderActor,
2021-03-06 18:44:33 +00:00
personCache)
if dispName:
senderName = \
'<a href="' + senderActor + '">' + \
dispName + '</a>: '
2021-03-06 18:38:36 +00:00
if ev.get('name'):
2021-03-06 19:04:41 +00:00
eventDescription = ev['name'].strip()
2020-11-09 20:15:17 +00:00
elif ev['type'] == 'Place':
if ev.get('name'):
eventPlace = ev['name']
2021-03-06 18:48:16 +00:00
# prepend a link to the sender of the calendar item
2021-03-06 18:38:36 +00:00
if senderName and eventDescription:
2021-03-06 19:41:39 +00:00
# if the sender is also mentioned within the event
# description then this is a reminder
2021-03-06 19:04:41 +00:00
senderActor2 = senderActor.replace('/users/', '/@')
if senderActor not in eventDescription and \
senderActor2 not in eventDescription:
eventDescription = senderName + eventDescription
else:
eventDescription = \
translate['Reminder'] + ': ' + eventDescription
2021-03-06 18:48:16 +00:00
2020-11-09 20:15:17 +00:00
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="/' + \
2020-12-09 13:08:26 +00:00
'icons/delete.png" /></a></td>\n'
2020-11-09 20:15:17 +00:00
eventClass = 'calendar__day__event'
calItemClass = 'calItem'
if eventIsPublic:
eventClass = 'calendar__day__event__public'
calItemClass = 'calItemPublic'
2020-11-09 20:15:17 +00:00
if eventTime and eventDescription and eventPlace:
calendarStr += \
'<tr class="' + calItemClass + '">' + \
'<td class="calendar__day__time"><b>' + eventTime + \
'</b></td><td class="' + eventClass + '">' + \
2020-11-09 20:15:17 +00:00
'<span class="place">' + \
eventPlace + '</span><br>' + eventDescription + \
'</td>' + deleteButtonStr + '</tr>\n'
elif eventTime and eventDescription and not eventPlace:
calendarStr += \
'<tr class="' + calItemClass + '">' + \
'<td class="calendar__day__time"><b>' + eventTime + \
'</b></td><td class="' + eventClass + '">' + \
2020-11-09 20:15:17 +00:00
eventDescription + '</td>' + deleteButtonStr + '</tr>\n'
elif not eventTime and eventDescription and not eventPlace:
calendarStr += \
'<tr class="' + calItemClass + '">' + \
'<td class="calendar__day__time">' + \
'</td><td class="' + eventClass + '">' + \
2020-11-09 20:15:17 +00:00
eventDescription + '</td>' + deleteButtonStr + '</tr>\n'
elif not eventTime and eventDescription and eventPlace:
calendarStr += \
'<tr class="' + calItemClass + '">' + \
'<td class="calendar__day__time"></td>' + \
'<td class="' + eventClass + '"><span class="place">' + \
2020-11-09 20:15:17 +00:00
eventPlace + '</span><br>' + eventDescription + \
'</td>' + deleteButtonStr + '</tr>\n'
elif eventTime and not eventDescription and eventPlace:
calendarStr += \
'<tr class="' + calItemClass + '">' + \
'<td class="calendar__day__time"><b>' + eventTime + \
'</b></td><td class="' + eventClass + '">' + \
2020-11-09 20:15:17 +00:00
'<span class="place">' + \
eventPlace + '</span></td>' + \
deleteButtonStr + '</tr>\n'
calendarStr += '</tbody>\n'
calendarStr += '</table></main>\n'
calendarStr += htmlFooter()
return calendarStr
2021-03-06 18:12:43 +00:00
def htmlCalendar(personCache: {}, cssCache: {}, translate: {},
2020-11-09 20:15:17 +00:00
baseDir: str, path: str,
2021-02-12 15:13:07 +00:00
httpPrefix: str, domainFull: str,
2021-04-23 19:42:35 +00:00
textModeBanner: str, accessKeys: {}) -> str:
2020-11-09 20:15:17 +00:00
"""Show the calendar for a person
"""
domain = removeDomainPort(domainFull)
2020-11-09 20:15:17 +00:00
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)]
2021-03-06 18:12:43 +00:00
return _htmlCalendarDay(personCache, cssCache,
translate, baseDir, path,
year, monthNumber, dayNumber,
nickname, domain, dayEvents,
monthName, actor)
2020-11-09 20:15:17 +00:00
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]
2021-01-11 19:46:21 +00:00
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
2021-02-12 15:13:07 +00:00
headerStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
# the main graphical calendar as a table
calendarStr = '<main><table class="calendar">\n'
2020-11-09 20:15:17 +00:00
calendarStr += '<caption class="calendar__banner--month">\n'
calendarStr += \
' <a href="' + calActor + '/calendar?year=' + str(prevYear) + \
2021-04-23 19:42:35 +00:00
'?month=' + str(prevMonthNumber) + '" ' + \
'accesskey="' + accessKeys['Page up'] + '">'
2020-11-09 20:15:17 +00:00
calendarStr += \
' <img loading="lazy" alt="' + translate['Previous month'] + \
2020-12-09 13:08:26 +00:00
'" title="' + translate['Previous month'] + '" src="/icons' + \
2020-11-09 20:15:17 +00:00
'/prev.png" class="buttonprev"/></a>\n'
calendarStr += ' <a href="' + calActor + '/inbox" title="'
2021-04-23 19:42:35 +00:00
calendarStr += translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">'
2020-11-09 20:15:17 +00:00
calendarStr += ' <h1>' + monthName + '</h1></a>\n'
calendarStr += \
' <a href="' + calActor + '/calendar?year=' + str(nextYear) + \
2021-04-23 19:42:35 +00:00
'?month=' + str(nextMonthNumber) + '" ' + \
'accesskey="' + accessKeys['Page down'] + '">'
2020-11-09 20:15:17 +00:00
calendarStr += \
' <img loading="lazy" alt="' + translate['Next month'] + \
2020-12-09 13:08:26 +00:00
'" title="' + translate['Next month'] + '" src="/icons' + \
2020-11-09 20:15:17 +00:00
'/prev.png" class="buttonnext"/></a>\n'
calendarStr += '</caption>\n'
calendarStr += '<thead>\n'
calendarStr += '<tr>\n'
2021-07-05 19:35:57 +00:00
days = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
for d in days:
calendarStr += ' <th scope="col" class="calendar__day__header">' + \
translate[d] + '</th>\n'
2020-11-09 20:15:17 +00:00
calendarStr += '</tr>\n'
calendarStr += '</thead>\n'
calendarStr += '<tbody>\n'
2021-02-12 15:13:07 +00:00
# beginning of the links used for accessibility
navLinks = {}
timelineLinkStr = htmlHideFromScreenReader('🏠') + ' ' + \
translate['Switch to timeline view']
navLinks[timelineLinkStr] = calActor + '/inbox'
2020-11-09 20:15:17 +00:00
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)
2021-02-12 10:33:10 +00:00
dayDescription = monthName + ' ' + str(dayOfMonth)
dayLink = '<a href="' + url + '" ' + \
'title="' + dayDescription + '">' + \
2020-11-09 20:15:17 +00:00
str(dayOfMonth) + '</a>'
2021-02-12 15:13:07 +00:00
# accessibility menu links
menuOptionStr = \
htmlHideFromScreenReader('📅') + ' ' + \
dayDescription
navLinks[menuOptionStr] = url
2020-11-09 20:15:17 +00:00
# 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'
2021-02-12 15:13:07 +00:00
# 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)
2021-04-22 11:51:19 +00:00
navAccessKeys = {
}
2021-02-12 15:28:11 +00:00
screenReaderCal = \
2021-04-22 11:51:19 +00:00
htmlKeyboardNavigation(textModeBanner, navLinks, navAccessKeys,
monthName)
2021-02-12 15:13:07 +00:00
return headerStr + screenReaderCal + calendarStr + htmlFooter()