diff options
Diffstat (limited to 'mu4web/components.py')
-rw-r--r-- | mu4web/components.py | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/mu4web/components.py b/mu4web/components.py new file mode 100644 index 0000000..a441390 --- /dev/null +++ b/mu4web/components.py @@ -0,0 +1,131 @@ +"""Various HTML "components".""" + +from email.headerregistry import Address +from .html_render import HTML +from typing import Any, cast, Optional +from urllib.parse import urlencode +from email.message import EmailMessage + + +def format_email(addr: Address) -> list[HTML]: + """Format an email address suitable for the headers of the message view.""" + mail_addr = f'{addr.username}@{addr.domain}' + return [addr.display_name, ' <', mailto(mail_addr), '>'] + + +def mailto(addr: str) -> HTML: + """Construct a mailto anchor element.""" + return ('a', {'href': f'mailto:{addr}'}, addr) + + +def header_format(key: str, value: Any) -> HTML: + """Format email headers to HTML.""" + if key in ['to', 'cc', 'bcc']: + return ('ul', *[('li', *format_email(addr)) + for addr in value.addresses]) + elif key == 'from': + return format_email(value.addresses[0]) + elif key == 'in-reply-to': + # type(value) == email.headerregistry._UnstructuredHeader + id = str(value).strip("<>") + return ['<', ('a', {'href': '?' + urlencode({'id': id})}, id), '>'] + else: + # assert type(value) == str, f"Weird value in header {value!r}" + return str(value) + + +def attachement_tree(id: str, + mail: EmailMessage, + idx: int = 0) -> tuple[HTML, int]: + """Construct a tree of all attachements for the given mail.""" + ct = mail.get_content_type() + fn = mail.get_filename() + + children = [] + _idx = idx + for child in mail.iter_parts(): + tree, idx = attachement_tree(id, cast(EmailMessage, child), idx + 1) + children.append(tree) + + content: HTML + if children: + content = ('ul', *children) + else: + content = [] + + if fn: + body = f'{ct} {fn}' + else: + body = str(ct) + download = {} + if mail.get_content_type() == 'application/octet-stream': + download['download'] = mail.get_filename() or '' + return ('li', ('a', {'data-idx': str(_idx), + 'href': '/part?' + urlencode({'id': id, + 'idx': _idx}), + **download, + }, body), content), idx + + +def login_page(returnto: Optional[str] = None) -> HTML: + """HTML form for the login page.""" + return ('form', {'action': '/login', 'method': 'POST', 'class': 'loginform'}, + ('label', {'for': 'username'}, 'Användarnamn'), + ('input', {'id': 'username', 'name': 'username', 'placeholder': 'Användarnamn'}), + ('label', {'for': 'password'}, 'Lösenord'), + ('input', {'id': 'password', 'name': 'password', + 'placeholder': 'Lösenord', + 'type': 'password'}), + ('div', + ('input', {'id': 'remember', 'name': 'remember', 'type': 'checkbox'}), + ('label', {'for': 'remember'}, 'Kom ihåg mig')), + ('input', {'type': 'hidden', + 'name': 'returnto', + 'value': returnto}) + if returnto else [], + ('input', {'type': 'submit', 'value': 'Logga in'}), + ) + + +def user_info(username: str) -> HTML: + """ + Return user info for top bar. + + Includes the users name, and a button for logging out. + """ + return [('span', username), + ('form', {'action': '/logout', 'method': 'POST'}, + ('input', {'type': 'submit', 'value': 'Logga ut'}))] + + +def login_prompt() -> HTML: + """Return link to the login page.""" + return ('a', {'href': '/login'}, 'Logga in') + + +def flashed_messages(messages: list[str] | list[tuple[str, str]]) -> HTML: + """Return Flasks flashed messages, formatted as a list.""" + return ('ul', {'class': 'flashes'}, + *[('li', msg) for msg in messages]) + + +def include_stylesheet(path: str) -> HTML: + """Return HTML for including a stylesheet inside the <head>.""" + return ('link', {'type': 'text/css', + 'rel': 'stylesheet', + 'href': path}) + + +def search_field(q: str) -> HTML: + """Build large search form for search page.""" + return ('form', {'id': 'searchform', + 'action': '/search', + 'method': 'GET'}, + ('label', {'for': 'search'}, + 'Sökförfrågan till Mu'), + ('input', {'id': 'search', + 'type': 'text', + 'placeholder': 'Sök...', + 'name': 'q', + 'value': q}), + ('input', {'type': 'Submit', 'value': 'Sök'})) |