Compare commits

...

63 Commits

Author SHA1 Message Date
Peter J. Holzer 6923e6273a Merge thread-handling from mbox2threads into mbox2web 2020-04-12 23:08:10 +02:00
Peter J. Holzer 1ada1c3817 Add test program for threading algorithm 2020-04-05 16:21:46 +02:00
Peter J. Holzer 29b5288519 Clean up and include style sheets 2019-10-31 21:22:03 +01:00
Peter J. Holzer b86ae9cb3f Handle div and span elements 2019-06-18 23:14:18 +02:00
Peter J. Holzer c9a336e58f Handle cite attribute 2019-06-18 22:11:23 +02:00
Peter J. Holzer 589a217b1b Handle application/pgp-keys 2019-06-17 20:59:23 +02:00
Peter J. Holzer 3865c5f887 Handle message/delivery-status 2019-05-20 23:59:26 +02:00
Peter J. Holzer 35e8266695 Remove magic number from image attachment templates
I have no idea what 1000 was meant to signify.
2019-05-20 23:26:43 +02:00
Peter J. Holzer 00ad5f864e Fix charset handling for text/html parts 2019-05-20 23:25:49 +02:00
Peter J. Holzer bdb5842d75 Handle image/svg+xml attachments 2019-05-20 23:16:30 +02:00
Peter J. Holzer 5e96a73744 Tolerate decoding errors
Sometimes the charset is just wrong, or it uses a non-standard name. Try
to do something useful in these cases.
2019-05-20 23:06:08 +02:00
Peter J. Holzer b1dbb3d40c Implement format=flowed and fix charset handling for text/plain 2019-05-20 00:32:33 +02:00
Peter J. Holzer 647e93afd5 Implement attachment application/pgp-signature
A detached signature doesn't make much sense in an email, but may happen
because of improper quoting. Or somebody might really want to send a
signature separate from the signed file.
2019-05-12 23:17:17 +02:00
Peter J. Holzer 55a1a1df83 Implement attachment text/x-perl 2019-05-12 23:11:21 +02:00
Peter J. Holzer 10fc5e2f61 Implement attachment application/vnd.oasis.opendocument.text 2019-05-12 23:06:52 +02:00
Peter J. Holzer c9ccb38eb9 Implement attachment application/x-compressed-tar 2019-05-12 22:58:41 +02:00
Peter J. Holzer b5316d056e Implement attachment text/x-c++src 2019-05-12 22:51:45 +02:00
Peter J. Holzer 057eba197e Implement attachment text/x-patch 2019-05-12 22:48:05 +02:00
Peter J. Holzer 308a34e6ca Implement attachment application/x-sh 2019-05-12 22:41:35 +02:00
Peter J. Holzer 9b49c740c6 Implement attachment text/x-java 2019-05-12 22:37:24 +02:00
Peter J. Holzer 709c87658b Implement attachment text/x-python 2019-05-12 22:18:55 +02:00
Peter J. Holzer f3bdaae445 Implement inline image/jpeg 2019-05-12 22:12:12 +02:00
Peter J. Holzer f8f64d3506 Allow cid's in multipart/mixed 2019-05-12 22:06:51 +02:00
Peter J. Holzer c84c517f62 Pass extra parameter to children of multipart/alternative
A structure like this is quite common:

  ─><no description>                    [multipa/related, 7bit, 12K]
   ├─><no description>              [multipa/alternativ, 7bit, 9.0K]
   │ ├─><no description>      [text/plain, quoted, iso-8859-1, 3.7K]
   │ └─><no description>       [text/html, quoted, iso-8859-1, 4.9K]
   └─>2b0063.jpg                          [image/jpeg, base64, 3.3K]

Here the main content of multipart/related isn't the html part, but a
multipart/alternative containing the html part and a text part. The html
part still needs access to the onther content of the multipart/related
part, so we need to pass this through.
2019-05-12 21:37:26 +02:00
Peter J. Holzer a44ff7ee4b Handle some more attachment types (some of them bogus) 2019-04-30 22:15:20 +02:00
Peter J. Holzer 8d78b2ec26 Handle RFC 2047 encoded headers 2019-04-30 21:55:21 +02:00
Peter J. Holzer 54290fb668 Handle application/x-shellscript attachments 2019-04-29 21:22:03 +02:00
Peter J. Holzer 2aadb830cd Handle application/x-bzip2 attachments 2019-04-29 21:18:00 +02:00
Peter J. Holzer 5294100b2c Handle multipart/related
For multipart related we need to be able to reference the other parts
from the root part by content-id, so we need to pass an argument with
the necessary information (imaginatively called "extra") to the render
function. Of course since this is called indirectly, every render
function needs to accept an extra argument, even if only
render_text_html uses it.
2019-03-31 23:48:57 +02:00
Peter J. Holzer 2a3e5622f3 Handle ms-tnef attachments
Like other attachments, just create a download link, don't try to parse
them.
2019-03-31 21:50:08 +02:00
Peter J. Holzer 865579655f Handle GIF attachments
Not sure whether providing a download link for attached images is the
right thing to do - we should probably just display them like inline
images. But we can always change that by a simple change of the
template.
2019-03-31 21:34:40 +02:00
Peter J. Holzer f543dbc5c8 Handle (old style) MS-Word attachments 2019-03-31 21:15:31 +02:00
Peter J. Holzer 08784b70fe Handle Perl attachments 2019-03-31 21:03:03 +02:00
Peter J. Holzer 0db91aaa5f Handle C attachments 2019-03-31 20:51:52 +02:00
Peter J. Holzer b2f56c1660 Handle underline in text/enriched 2019-03-17 22:42:44 +01:00
Peter J. Holzer 8acf092559 Fixed return values of all the new render functions
Oops. I should really check the output of the script, not just whether
it crashes in the expected place.
2019-03-17 22:30:27 +01:00
Peter J. Holzer 45848a73ae Handle multipart types that aren't 2019-03-17 22:24:17 +01:00
Peter J. Holzer b053ab454f Refactor render_body to handle each mime-type in a nested function
This replaces the giant elif cascade with a table lookup. It also allows
me to call one function from another.
2019-03-17 22:17:02 +01:00
Peter J. Holzer 035b4e1002 Limit width of text/enriched
60em is a bit more than 100 characters in the fixed-width font I use so
that should be an acceptable compromise between readability and avoiding
unintended line breaks.

The RFC says that output should use "the maximum available margins", but
that was clearly before large monitors became common.
2019-03-16 21:58:42 +01:00
Peter J. Holzer d5c5368bad Implement more text/enriched tags: nofill, param, ...
Nofill and param are handled specially by the parser. The parser now
also tolerates missing end tags.

Handle quite a few tags on output including some (like color and
fontfamily) that use a param.
2019-03-16 21:53:06 +01:00
Peter J. Holzer a997542cfe Handle image/gif (inline)
Inline images are very similar to attachments: We just want to store
them somewhere and refer to them. But we want to use a different element
(<img> instead of <a>) or more generally, a different template. So we
pass the disposition as an additional argument to save_part and use it
to construct the template name - which gives as a flurry of new
templates.
2019-03-10 23:27:30 +01:00
Peter J. Holzer df7fcb5777 Handle message/news 2019-03-10 22:47:10 +01:00
Peter J. Holzer d32c60a318 Handle plaintext and gzipped attachments 2019-03-04 21:49:46 +01:00
Peter J. Holzer 48fcf768ae "Handle" a detached pgp signature
Don't even ignore it (be sarcastic about it).
2019-03-04 21:23:03 +01:00
Peter J. Holzer 314ccaba48 Handle blockquote element and type attribute 2019-03-04 21:07:18 +01:00
Peter J. Holzer 77d2b87b1e Handle multipart/alternative and application/x-unknown-content-type-scpfile
Strange combination, but the first message with multipart/alternative
also contained a .scp file and not as an attachment.

The template for multipart/alternative allows switching between the
alternatives.
2019-03-02 23:33:39 +01:00
Peter J. Holzer 090fd79c79 Add some more templates 2019-03-02 22:15:53 +01:00
Peter J. Holzer a737ce1760 Handle application/pgp 2019-03-02 12:24:56 +01:00
Peter J. Holzer 9f44375354 Save all attachments to separate files
For now we just save them unmodified and give them an extension that
will cause the web server to provide the correct content-type. This is
probably *not safe*: A user could send malicious html as an attachment
and the browser will interpret it when another user clicks on the link.
We might try to sanitize attachments (but you would normally expect an
attachment to be preserved) or to preserve the content-dispostion header
(but I don't think this is possible with just a static archive).
2019-03-02 12:10:01 +01:00
Peter J. Holzer 48ea68eaef Don't require <base> tag 2019-03-01 22:52:41 +01:00
Peter J. Holzer 7f5ffdde0b Handle <ol> and <u> tags 2019-03-01 22:52:07 +01:00
Peter J. Holzer 1a0ce4a967 Decode html parts correctly 2019-03-01 22:51:06 +01:00
Peter J. Holzer 5ee7c066ca Limit width of html parts 2019-03-01 22:49:55 +01:00
Peter J. Holzer 36a640857c Handle <ul> and <li> in html messages 2019-03-01 13:54:37 +01:00
Peter J. Holzer ea60f484a3 Handle PGP signed messages 2019-03-01 13:54:13 +01:00
Peter J. Holzer 02368a57f8 Handle application/octet-stream 2019-03-01 11:58:22 +01:00
Peter J. Holzer fbce052c69 Decode text/plain correctly 2019-03-01 11:57:55 +01:00
Peter J. Holzer 23a6ea4716 Wrap text/html and text/enriched in templates 2019-03-01 11:13:09 +01:00
Peter J. Holzer d1dc1db853 Handle some more html tags and attributes 2019-03-01 10:21:57 +01:00
Peter J. Holzer b5c979d5cb Add support for message/partial
This doesn't handle the case where total isn't present on all parts, but
I don't actually expect to encounter that (and if I do, it will crash
and I can fix it).
2019-02-28 17:17:44 +01:00
Peter J. Holzer d5e557e8e4 Make encoded message id html-safe
Exclude & and ' from list of allowed characters, so that the encoded
message id can be used unescaped.
2019-02-28 17:16:05 +01:00
Peter J. Holzer 341d5dd229 Add support for text/html and text/enriched 2019-02-28 09:30:47 +01:00
Peter J. Holzer b238c56edb Convert mbox files to standalone html files
No thread or date structure, just one isolated file per message.
Only text/plain and some multipart formats
2019-02-03 18:44:50 +01:00
52 changed files with 2173 additions and 0 deletions

328
mbox2threads Executable file
View File

@ -0,0 +1,328 @@
#!/usr/bin/python3
import datetime
import email.utils
import mailbox
import pdb
import re
import sys
def get_message_id(msg):
"""
Extract the message id from a message
Note that this assumes that there is (at least) one message id. If
this is not the case, it will raise an exception (currently an
IndexError, but we may use something more suitable in the future).
"""
match = re.search(r'<(.*?)>', msg["Message-ID"])
return match.group(1)
def encode_message_id(msgid):
encmsgid = re.sub('[^!"$(-.0-9:=@-z|~]', lambda x: "{%02x}" % (ord(x.group(0))), msgid)
return encmsgid
class Message:
def __init__(self, msgid, in_reply_to, references, date, mfrom, subject):
self.msgid = msgid
self.in_reply_to = in_reply_to
self.references = references
self.date = date
self.mfrom = mfrom
self.subject = subject
self.kids = False
if self.date.tzinfo is None:
# If timezone is missing, assume local time
self.date = self.date.astimezone()
def __repr__(self):
return (
self.msgid + " " +
self.date.strftime("%Y-%m-%d %H:%M:%S%z") +
" [" + ", ".join(self.references) + "]"
)
msg2thread = {}
class Thread:
def __init__(self):
self.messages = {}
self.threadid = None
def add_message(self, msg):
self.messages[msg.msgid] = msg
msg2thread[msg.msgid] = self
def merge_thread(self, other):
for msg in other.messages.values():
self.add_message(msg)
def __repr__(self):
if self.threadid:
s = self.threadid
else:
s = str(id(self))
if self.messages:
s += " {" + ", ".join(self.messages.keys()) + "}"
return s
def fixup_in_reply_tos(self):
# Fix up some problems with in_reply_to:
# Sometimes an in_reply_to refers to a message which isn't in the
# archive. Add a dummy message if this happens.
# Sometimes an in_reply_to refers to a message with a later date.
# In this case one of the two date headers must be wrong. We could try
# to analyze other headers (especially received), but for now we just
# assume that it is the referrer (although in the example I'm
# currently looking at it is the referree) and adjust that. We should
# preserve the original date header, though. Use separate sort_date and
# date?
missing = set()
for m in self.messages.values():
for r in m.in_reply_to:
if r not in self.messages:
missing.add(r)
for r in missing:
firstdate = sorted(self.messages.values(), key=lambda x: x.date)[0].date
missingdate = firstdate - datetime.timedelta(seconds=1)
self.add_message(
Message(r, [], [],
missingdate,
"unknown@invalid", "(not in archive)")
)
dates_ok = False
while not dates_ok:
dates_ok = True
for m in self.messages.values():
for r in m.in_reply_to:
rr = self.messages[r]
if rr.date >= m.date:
m.date = rr.date + datetime.timedelta(seconds=1)
dates_ok = False
def as_html(self):
self.fixup_in_reply_tos()
y = 0
x = 0
nodes = []
edges = []
lines = []
for m in sorted(self.messages.values(), key=lambda x: x.date):
# We have already fudged the in_reply_to field to always contain
# the latest reference(s), so we only need to consider that
if len(m.in_reply_to) == 0:
if y == 0:
# first message in thread
# Just add a node
nodes.append((x, y))
m.x = x
m.y = y
else:
# Not in reply to anything, but not the start of the thread
# either. This will happen if fixup_in_reply_tos adds more
# than one dummy message, but it might also happen if we
# use different criteria for matching threads (e.g. Subject
# or Thread-Index)
# Just start a new column to get out of the way
x += 1
nodes.append((x, y))
m.x = x
m.y = y
elif len(m.in_reply_to) == 1:
p = self.messages[m.in_reply_to[0]]
if p.kids:
# The parent already has kids, so we must move to the side
# to avoid running an edge through an existing kid. We
# could use a sophisticated algorithm to find the best
# position here, but I think it sufficient to just start a
# new column. This may waste some space (there might have
# been a suitable position in the existing columns, but it
# will avoid collisions and is very simple.
x += 1
m.x = x
m.y = y
else:
# Just put the new kid directly below the parent
m.x = p.x
m.y = y
nodes.append((m.x, m.y))
edges.append((p.x, p.y, m.x, m.y))
p.kids = True
else:
# Generic case with multiple references.
# I think this should always work well if we start a new
# column. There may be special cases where we can avoid it, not
# sure.
x += 1
m.x = x
m.y = y
nodes.append((m.x, m.y))
for r in m.in_reply_to:
p = self.messages[r]
edges.append((p.x, p.y, m.x, m.y))
lines.append((m.date, m.mfrom, m.subject))
y += 1
s = "<table class='thread'>"
s += "<tr>"
s += f"<td rowspan={y}>"
r = 4
fx = 16
fy = 32
s += f"<svg width={(x + 1) * fx} height={y * fy}>"
for e in edges:
if e[0] == e[2]:
s += f"<line x1={e[0] * fx + fx/2} y1={e[1] * fy + fy/2} x2={e[2] * fx + fx/2} y2={e[3] * fy + fy/2} stroke='black' />"
else:
if e[3] == e[1] + 1:
yc = (e[1] + e[2]) / 2
else:
yc = e[1] + 1
s += f"<path d='M {e[0] * fx + fx/2} {e[1] * fy + fy/2} Q {e[2] * fx + fx/2} {yc * fy + fy/2} {e[2] * fx + fx/2} {e[3] * fy + fy/2}' stroke='black' fill='none' />"
for n in nodes:
s += f"<circle cx={n[0] * fx + fx/2} cy={n[1] * fy + fy/2} r={r} />"
s += "</svg>"
s += "</td>"
# XXX - escape!
s += f"<td class='date'>{lines[0][0]}</td>"
s += f"<td class='from'>{lines[0][1]}</td>"
s += f"<td class='subject'>{lines[0][2]}</td>"
s += "</tr>"
for ln in lines[1:]:
s += "<tr>"
s += f"<td class='date'>{ln[0]}</td>"
s += f"<td class='from'>{ln[1]}</td>"
s += f"<td class='subject'>{ln[2]}</td>"
s += "</tr>"
s += "</table>"
return s
def add_message(msg):
mid = get_message_id(msg)
print("M", mid, file=sys.stderr)
encmid = encode_message_id(mid)
date = email.utils.parsedate_to_datetime(msg["Date"])
# In-Reply-To headers with more than one message-id are rare, but
# standard-conforming, and some MUAs (e.g., mutt) create them.
in_reply_to = msg["In-Reply-To"]
if in_reply_to:
if isinstance(in_reply_to, email.header.Header):
in_reply_to = in_reply_to.encode()
in_reply_to_msgids = re.findall(r'<(.*?)>', in_reply_to)
else:
in_reply_to_msgids = []
references = msg["References"]
if references:
references_msgids = re.findall(r'<(.*?)>', references)
else:
references_msgids = []
for msgid in in_reply_to_msgids:
if msgid not in references_msgids:
references_msgids.append(msgid)
if not in_reply_to_msgids and references_msgids:
in_reply_to_msgid = [references_msgids[-1]]
t = Thread()
t.add_message(
Message(
mid,
in_reply_to_msgids, references_msgids,
date,
msg["From"], msg["Subject"]))
for f in sys.argv[1:]:
print("F", f, file=sys.stderr)
mb = mailbox.mbox(f)
for m in mb:
add_message(m)
# Now I have a lot of 1 message threads
# Merge them
finished = False
while not finished:
finished = True
for msgid in list(msg2thread.keys()):
thread = msg2thread[msgid]
for msgid2 in list(thread.messages.keys()):
msg = thread.messages[msgid2]
for r in msg.references:
if r in thread.messages:
pass
else:
# references may contain non-existant messages, so
# be careful:
if r in msg2thread:
thread.merge_thread(msg2thread[r])
finished = False
thread_list = []
for thread in msg2thread.values():
if thread.threadid:
continue
messages = iter(thread.messages.values())
msg = next(messages)
thread.date = msg.date
thread.threadid = msg.msgid
for msg in messages:
if msg.date < thread.date:
thread.threadid = msg.msgid
thread.date = msg.date
thread_list.append(thread)
print("""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
table.thread {
border-collapse: collapse;
}
table.thread tr {
height: 32px;
font-size: 16px;
background-color: #EFE;
}
table.thread td {
overflow: hidden;
white-space: nowrap;
}
.timestamp {
width: 8em;
padding-right: 0.5em;
}
.from {
max-width: 8em;
padding-left: 0.5em;
padding-right: 0.5em;
}
.subject {
max-width: 10em;
padding-left: 0.5em;
}
</style>
</head>
<body>
""")
for thread in sorted(thread_list, key=lambda x: x.date):
print(thread.as_html())
# vim: tw=79

1271
mbox2web Executable file

File diff suppressed because it is too large Load Diff

64
style/debug.css Normal file
View File

@ -0,0 +1,64 @@
.partouter {
border-style: solid;
border-width: 1px;
border-color: #0000FF;
}
.partinner {
border-style: dotted;
border-width: 1px;
border-color: #0000FF;
}
.partheader {
background-color: #0000FF;
color: #FFFFFF;
font-size: 70%;
}
th {
text-align: left;
}
.subject {
font-size: 1.2em;
color: #000088;
}
.text-enriched {
font-family: monospace;
white-space: pre-wrap;
max-width: 60em;
}
.signature {
font-size: 0.8rem;
}
.dubious {
background-color: #888800;
}
.partinner.html {
max-width: 40rem;
}
p.fixed {
font-family: monospace;
white-space: pre;
margin: 0.2em;
}
p.flowed {
font-family: monospace;
white-space: pre-wrap;
max-width: 60em;
margin: 0.2em;
}
.text_plain_flowed blockquote {
border-left: solid 0.2em #004;
padding-left: 0.8em;
margin: 0em;
background: #EEF;
}

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
application/ms-tnef
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
application/msword
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/octet-stream
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/pgp-keys
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/pgp-signature
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/vnd.oasis.opendocument.text
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/x-bzip2
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/x-compressed-tar
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/x-gzip
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/x-gzip
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/x-java-vm
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
application/x-perl
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
application/x-sh
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
application/x-shellscript
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
image/gif
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
image/png
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
image/png
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
image/svg+xml
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
text/html (attachment)
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
text/plain (attachment)
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
text/x-c++src
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
text/x-c
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
text/x-c
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
text/x-patch
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
text/x-perl
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
text/x-python
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
text/x-vcard
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
application/octet-stream
</div>
<div class="partinner">
<a href="{{url}}">{{name}}</a>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
application/pgp-signature
</div>
<div class="partinner">
(free floating signature - a blank cheque?)
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
<pre class="signature {{gpgstatus}}">{{gpgresult}}</pre>
</div>
<div class="partinner">
{{content}}
</div>
</div>

View File

@ -0,0 +1,10 @@
<div class="partouter">
<div class="partheader">
application/x-unknown-content-type-scpfile
</div>
<div class="partinner">
<pre>
{{body}}
</pre>
</div>
</div>

View File

@ -0,0 +1,10 @@
<div class="partouter">
<div class="partheader">
message/delivery-status
</div>
<div class="partinner">
<pre>
{{body}}
</pre>
</div>
</div>

View File

@ -0,0 +1,11 @@
<div class="partouter">
<div class="partheader">
message/news
</div>
<div class="partinner">
{% for p in parts %}
{{p}}
{% endfor %}
</div>
</div>

View File

@ -0,0 +1,18 @@
<div class="partouter">
<div class="partheader">
message/rfc822
</div>
<div class="partinner">
<table>
<tr> <th>Message-Id </th> <td>{{message_id}} </td> </tr>
<tr> <th>Subject </th> <td>{{subject}} </td> </tr>
<tr> <th>From </th> <td>{{from}} </td> </tr>
<tr> <th>Date </th> <td>{{date}} </td> </tr>
</table>
{% for p in parts %}
{{p}}
{% endfor %}
</div>
</div>

View File

@ -0,0 +1,24 @@
<div class="partouter">
<div class="partheader">
multipart/alternative
</div>
<div class="partinner">
{% for type in types %}
<input id="sp{{prefix}}_{{loop.index}}" type="radio" name="g{{prefix}}">
<label for="sp{{prefix}}_{{loop.index}}">{{type}}</label>
<style>
#p{{prefix}}_{{loop.index}} {
display: none;
}
input#sp{{prefix}}_{{loop.index}}:checked ~ #p{{prefix}}_{{loop.index}} {
display: block;
}
</style>
{% endfor %}
{% for part in parts %}
<div id="p{{prefix}}_{{loop.index}}">
{{part}}
</div>
{% endfor %}
</div>
</div>

View File

@ -0,0 +1,10 @@
<div class="partouter">
<div class="partheader">
multipart/digest
</div>
<div class="partinner">
{% for part in parts %}
{{part}}
{% endfor %}
</div>
</div>

View File

@ -0,0 +1,10 @@
<div class="partouter">
<div class="partheader">
multipart/mixed
</div>
<div class="partinner">
{% for part in parts %}
{{part}}
{% endfor %}
</div>
</div>

View File

@ -0,0 +1,10 @@
<div class="partouter">
<div class="partheader">
multipart/related
</div>
<div class="partinner">
{% for part in parts %}
{{part}}
{% endfor %}
</div>
</div>

View File

@ -0,0 +1,9 @@
<div class="partouter">
<div class="partheader">
<pre class="signature {{gpgstatus}}">{{gpgresult}}</pre>
</div>
<div class="partinner">
{{content}}
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
text/enriched
</div>
<div class="partinner">
{{body}}
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
text/html
</div>
<div class="partinner html">
{{body}}
</div>
</div>

View File

@ -0,0 +1,10 @@
<div class="partouter">
<div class="partheader">
text/plain
</div>
<div class="partinner">
<pre>
{{body}}
</pre>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
text/plain
</div>
<div class="partinner text_plain_flowed">
{{body}}
</div>
</div>

37
templates/calendar.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
{{list}}: {{subject}}
</title>
<link rel="stylesheet" href="../../style/debug.css">
</head>
<body>
<h1>{{list}} by date</h1>
<nav>
<ul>
</ul>
{% for y in cal | dictsort %}
<li>
{{y.0}}
<ul>
{% for m in y.1 | dictsort %}
{{m.0}}
<ul>
{% for t in m.1 %}
<li>
<a href="../thread/{{t.threadid}}/">{{t.subject}}</a>
</li>
{% endfor %}
</ul>
{% endfor %}
</ul>
</li>
{% endfor %}
</nav>
</body>
</html>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
image/gif
</div>
<div class="partinner">
<img src="{{url}}" alt="{{name}}">
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="partouter">
<div class="partheader">
image/jpeg
</div>
<div class="partinner">
<img src="{{url}}" alt="{{name}}">
</div>
</div>

24
templates/message.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
{{list}}: {{subject}}
</title>
<link rel="stylesheet" href="../../style/debug.css">
</head>
<body>
<h1>{{subject}}</h1>
<nav>
{{threadhtml}}
</nav>
<table>
<tr> <th>Message-Id </th> <td>{{message_id}} </td> </tr>
<tr> <th>From </th> <td>{{from}} </td> </tr>
<tr> <th>Date </th> <td>{{date}} </td> </tr>
</table>
{{bodyhtml}}
</body>
</html>

17
templates/message2.html Normal file
View File

@ -0,0 +1,17 @@
<div class="partouter">
<div class="partheader">
message
</div>
<div class="partinner">
<table>
<tr> <th>Newsgroups</th> <td>{{msg.newsgroups}} </td> </tr>
<tr> <th>Message-Id </th> <td>{{message_id}} </td> </tr>
<tr> <th>Subject </th> <td class="subject">{{subject}}</td> </tr>
<tr> <th>From </th> <td>{{from}} </td> </tr>
<tr> <th>Date </th> <td>{{date}} </td> </tr>
</table>
{{bodyhtml}}
</div>
</div>

17
templates/thread.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
{{list}}: {{subject}}
</title>
<link rel="stylesheet" href="../../style/debug.css">
</head>
<body>
<h1>{{subject}}</h1>
<nav>
{{threadhtml}}
</nav>
</body>
</html>