epicyon/webapp_calendar.py

468 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

__filename__ = "webapp_calendar.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Calendar"
import os
from datetime import datetime
from datetime import date
from utils import get_display_name
from utils import get_config_param
from utils import get_nickname_from_actor
from utils import get_domain_from_actor
from utils import locate_post
from utils import load_json
from utils import week_day_of_month_start
from utils import get_alt_path
from utils import remove_domain_port
from utils import acct_dir
from utils import local_actor_url
from utils import replace_users_with_at
from happening import getTodaysEvents
from happening import getCalendarEvents
from webapp_utils import setCustomBackground
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
from webapp_utils import htmlHideFromScreenReader
from webapp_utils import htmlKeyboardNavigation
def htmlCalendarDeleteConfirm(css_cache: {}, translate: {}, base_dir: str,
path: str, http_prefix: str,
domain_full: str, post_id: str, postTime: str,
year: int, monthNumber: int,
dayNumber: int, calling_domain: str) -> str:
"""Shows a screen asking to confirm the deletion of a calendar event
"""
nickname = get_nickname_from_actor(path)
actor = local_actor_url(http_prefix, nickname, domain_full)
domain, port = get_domain_from_actor(actor)
messageId = actor + '/statuses/' + post_id
post_filename = locate_post(base_dir, nickname, domain, messageId)
if not post_filename:
return None
post_json_object = load_json(post_filename)
if not post_json_object:
return None
delete_postStr = None
cssFilename = base_dir + '/epicyon-profile.css'
if os.path.isfile(base_dir + '/epicyon.css'):
cssFilename = base_dir + '/epicyon.css'
instanceTitle = \
get_config_param(base_dir, 'instanceTitle')
delete_postStr = \
htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None)
delete_postStr += \
'<center><h1>' + postTime + ' ' + str(year) + '/' + \
str(monthNumber) + \
'/' + str(dayNumber) + '</h1></center>'
delete_postStr += '<center>'
delete_postStr += ' <p class="followText">' + \
translate['Delete this event'] + '</p>'
postActor = get_alt_path(actor, domain_full, calling_domain)
delete_postStr += \
' <form method="POST" action="' + postActor + '/rmpost">\n'
delete_postStr += ' <input type="hidden" name="year" value="' + \
str(year) + '">\n'
delete_postStr += ' <input type="hidden" name="month" value="' + \
str(monthNumber) + '">\n'
delete_postStr += ' <input type="hidden" name="day" value="' + \
str(dayNumber) + '">\n'
delete_postStr += \
' <input type="hidden" name="pageNumber" value="1">\n'
delete_postStr += \
' <input type="hidden" name="messageId" value="' + \
messageId + '">\n'
delete_postStr += \
' <button type="submit" class="button" name="submitYes">' + \
translate['Yes'] + '</button>\n'
delete_postStr += \
' <a href="' + actor + '/calendar?year=' + \
str(year) + '?month=' + \
str(monthNumber) + '"><button class="button">' + \
translate['No'] + '</button></a>\n'
delete_postStr += ' </form>\n'
delete_postStr += '</center>\n'
delete_postStr += htmlFooter()
return delete_postStr
def _htmlCalendarDay(person_cache: {}, css_cache: {}, translate: {},
base_dir: 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 = acct_dir(base_dir, nickname, domain)
calendarFile = accountDir + '/.newCalendar'
if os.path.isfile(calendarFile):
try:
os.remove(calendarFile)
except OSError:
print('EX: _htmlCalendarDay unable to delete ' + calendarFile)
cssFilename = base_dir + '/epicyon-calendar.css'
if os.path.isfile(base_dir + '/calendar.css'):
cssFilename = base_dir + '/calendar.css'
calActor = actor
if '/users/' in actor:
calActor = '/users/' + actor.split('/users/')[1]
instanceTitle = get_config_param(base_dir, 'instanceTitle')
calendarStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None)
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
post_id = None
senderName = ''
senderActor = None
eventIsPublic = False
# get the time place and description
for ev in eventPost:
if ev['type'] == 'Event':
if ev.get('post_id'):
post_id = ev['post_id']
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 = \
get_display_name(base_dir, senderActor,
person_cache)
if dispName:
senderName = \
'<a href="' + senderActor + '">' + \
dispName + '</a>: '
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 = replace_users_with_at(senderActor)
if senderActor not in eventDescription and \
senderActor2 not in eventDescription:
eventDescription = senderName + eventDescription
else:
eventDescription = \
translate['Reminder'] + ': ' + eventDescription
deleteButtonStr = ''
if post_id:
deleteButtonStr = \
'<td class="calendar__day__icons"><a href="' + calActor + \
'/eventdelete?eventid=' + post_id + \
'?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'
eventClass = 'calendar__day__event'
calItemClass = 'calItem'
if eventIsPublic:
eventClass = 'calendar__day__event__public'
calItemClass = 'calItemPublic'
if eventTime and eventDescription and eventPlace:
calendarStr += \
'<tr class="' + calItemClass + '">' + \
'<td class="calendar__day__time"><b>' + eventTime + \
'</b></td><td class="' + eventClass + '">' + \
'<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 + '">' + \
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 + '">' + \
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">' + \
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 + '">' + \
'<span class="place">' + \
eventPlace + '</span></td>' + \
deleteButtonStr + '</tr>\n'
calendarStr += '</tbody>\n'
calendarStr += '</table></main>\n'
calendarStr += htmlFooter()
return calendarStr
def htmlCalendar(person_cache: {}, css_cache: {}, translate: {},
base_dir: str, path: str,
http_prefix: str, domain_full: str,
text_mode_banner: str, accessKeys: {}) -> str:
"""Show the calendar for a person
"""
domain = remove_domain_port(domain_full)
monthNumber = 0
dayNumber = None
year = 1970
actor = http_prefix + '://' + domain_full + 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 = get_nickname_from_actor(actor)
setCustomBackground(base_dir, 'calendar-background', 'calendar-background')
months = (
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
)
monthName = translate[months[monthNumber - 1]]
if dayNumber:
dayEvents = None
events = \
getTodaysEvents(base_dir, nickname, domain,
year, monthNumber, dayNumber)
if events:
if events.get(str(dayNumber)):
dayEvents = events[str(dayNumber)]
return _htmlCalendarDay(person_cache, css_cache,
translate, base_dir, path,
year, monthNumber, dayNumber,
nickname, domain, dayEvents,
monthName, actor)
events = \
getCalendarEvents(base_dir, 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(week_day_of_month_start(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 = base_dir + '/epicyon-calendar.css'
if os.path.isfile(base_dir + '/calendar.css'):
cssFilename = base_dir + '/calendar.css'
calActor = actor
if '/users/' in actor:
calActor = '/users/' + actor.split('/users/')[1]
instanceTitle = \
get_config_param(base_dir, 'instanceTitle')
headerStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None)
# 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) + '" ' + \
'accesskey="' + accessKeys['Page up'] + '">'
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'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">'
calendarStr += ' <h1>' + monthName + '</h1></a>\n'
calendarStr += \
' <a href="' + calActor + '/calendar?year=' + str(nextYear) + \
'?month=' + str(nextMonthNumber) + '" ' + \
'accesskey="' + accessKeys['Page down'] + '">'
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'
days = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
for d in days:
calendarStr += ' <th scope="col" class="calendar__day__header">' + \
translate[d] + '</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 = week_day_of_month_start(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)
navAccessKeys = {
}
screenReaderCal = \
htmlKeyboardNavigation(text_mode_banner, navLinks, navAccessKeys,
monthName)
newEventStr = \
'<br><center>\n<p>\n' + \
'<a href="' + calActor + '/newreminder"> ' + \
translate['Add to the calendar'] + '</a>\n</p>\n</center>\n'
calStr = \
headerStr + screenReaderCal + calendarStr + newEventStr + htmlFooter()
return calStr