aboutsummaryrefslogtreecommitdiff
path: root/mu4web/components.py
diff options
context:
space:
mode:
Diffstat (limited to 'mu4web/components.py')
-rw-r--r--mu4web/components.py131
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'}))