# HG changeset patch # User Paul Boddie # Date 1371249910 -7200 # Node ID 9f28df3c3edfb0628b85235006999fae2f9d1b8c # Parent ea518fa2d815f1ee15325b75dcd7c4924fe0effc Added support for traversing multipart/alternative updates and showing the constituent parts in the shared content list produced by the macro. diff -r ea518fa2d815 -r 9f28df3c3edf MoinShare.py --- a/MoinShare.py Fri Jun 14 18:01:52 2013 +0200 +++ b/MoinShare.py Sat Jun 15 00:45:10 2013 +0200 @@ -115,6 +115,13 @@ self.page = None + # Identification. + + self.path = [] + + def unique_id(self): + return "moinshare-tab-%s-%s" % (self.message_number, "-".join(map(str, self.path))) + def __cmp__(self, other): if self.updated is None and other.updated is not None: return 1 @@ -123,6 +130,20 @@ else: return cmp(self.updated, other.updated) + def copy(self, part_number=None): + update = Update() + update.title = self.title + update.link = self.link + update.updated = self.updated + update.fragment = self.fragment + update.preferred = self.preferred + update.message_number = self.message_number + update.page = self.page + update.path = self.path[:] + if part_number is not None: + update.path.append(part_number) + return update + # Update retrieval from pages. def getUpdatesFromPage(page, request): @@ -217,36 +238,56 @@ update.page = page update.message_number = n - # Determine whether the message has several representations. - - # For a single part, use it as the update content. - - if not message.is_multipart(): - charset = message.get_content_charset() - payload = message.get_payload(decode=True) - update.content = charset and unicode(payload, charset) or payload - update.content_type = message.get_content_type() - - # For a collection of related parts, use the first as the update content - # and assume that the formatter will reference the other parts. - - elif message.get_content_subtype() == "related": - main_part = message.get_payload()[0] - charset = main_part.get_content_charset() - payload = main_part.get_payload(decode=True) - update.content = charset and unicode(payload, charset) or payload - update.content_type = main_part.get_content_type() - - # Otherwise, just obtain the parts for separate display. - - else: - update.parts = message.get_payload() - update.content_type = message.get_content_type() + update.content, update.content_type, update.parts = getUpdateContentFromPart(message) updates.append(update) return updates +def getUpdateContentFromPart(part): + + """ + Return decoded content, the content type and any subparts in a tuple for a + given 'part'. + """ + + # Determine whether the part has several representations. + + # For a single part, use it as the update content. + + if not part.is_multipart(): + content, content_type = getPartContent(part) + return content, content_type, None + + # For a collection of related parts, use the first as the update content + # and assume that the formatter will reference the other parts. + + elif part.get_content_subtype() == "related": + main_part = part.get_payload()[0] + content, content_type = getPartContent(main_part) + return content, content_type, [main_part] + + # Otherwise, just obtain the parts for separate display. + + else: + return None, part.get_content_type(), part.get_payload() + +def getPartContent(part): + + "Decode the 'part', returning the decoded payload and the content type." + + charset = part.get_content_charset() + payload = part.get_payload(decode=True) + return (charset and unicode(payload, charset) or payload), part.get_content_type() + +def getUpdateFromPart(parent, part, part_number): + + "Using the 'parent' update, return an update object for the given 'part'." + + update = parent.copy(part_number) + update.content, update.content_type, update.parts = getUpdateContentFromPart(part) + return update + # Source management. def getUpdateSources(pagename, request): diff -r ea518fa2d815 -r 9f28df3c3edf css/moinshare.css --- a/css/moinshare.css Fri Jun 14 18:01:52 2013 +0200 +++ b/css/moinshare.css Sat Jun 15 00:45:10 2013 +0200 @@ -14,9 +14,46 @@ margin: 1em; border: 1px solid black; padding: 0.5em; + position: relative; } div.moinshare-date { margin: 0.5em; text-align: right; } + +/* Put the alternative part navigation inside the update container, overlaying + empty space belong to each of the parts. */ + +div.moinshare-alternatives { + position: absolute; + bottom: 0; + left: 0; + padding: 0.5em; +} + +div.moinshare-alternatives a { + background: #eee; + margin-right: 1em; + padding: 0.5em; +} + +div.moinshare-alternative { + padding-bottom: 3em; /* empty space for the navigation */ +} + +/* Always show primary parts. */ + +div.moinshare-alternative.moinshare-default { + display: block; +} + +/* Hide secondary parts unless selected. */ + +div.moinshare-alternative.moinshare-other:not(:target) { + display: none; +} + +div.moinshare-alternative.moinshare-other:target { + display: block; +} diff -r ea518fa2d815 -r 9f28df3c3edf macros/SharedContent.py --- a/macros/SharedContent.py Fri Jun 14 18:01:52 2013 +0200 +++ b/macros/SharedContent.py Sat Jun 15 00:45:10 2013 +0200 @@ -11,7 +11,8 @@ from MoinRemoteSupport import * from MoinSupport import parseMacroArguments, getParsersForContentType, formatText from MoinShare import getUpdateSources, getUpdatesFromPage, \ - getUpdatesFromStore, Update, get_make_parser + getUpdatesFromStore, getUpdateFromPart, \ + Update, get_make_parser from email.utils import parsedate import xml.dom.pulldom @@ -206,6 +207,119 @@ return (feed_type, channel_title, channel_link), feed_updates +# Update formatting. + +def getUpdatesForFormatting(update): + + "Get a list of updates for formatting given 'update'." + + updates = [] + + # Handle multipart/alternative. + + if update.parts: + for n, part in enumerate(update.parts): + update_part = getUpdateFromPart(update, part, n) + updates += getUpdatesForFormatting(update_part) + else: + updates.append(update) + + return updates + +def getFormattedUpdate(update, request, fmt): + + """ + Return the formatted form of the given 'update' using the given 'request' + and 'fmt'. + """ + + # NOTE: Some control over the HTML and XHTML should be exercised. + + if update.content: + if update.content_type == "text/html" and update.message_number is not None: + parsers = [get_make_parser(update.page, update.message_number)] + else: + parsers = getParsersForContentType(request.cfg, update.content_type) + + if parsers: + for parser_cls in parsers: + return formatText(update.content, request, fmt, parser_cls=parser_cls) + break + else: + return None + else: + return None + +def formatUpdate(update, request, fmt): + + "Format the given 'update' using the given 'request' and 'fmt'." + + result = [] + append = result.append + + updates = getUpdatesForFormatting(update) + single = len(updates) == 1 + + # Format some navigation tabs. + + if not single: + append(fmt.div(on=1, css_class="moinshare-alternatives")) + + first = True + + for update_part in updates: + append(fmt.url(1, "#%s" % update_part.unique_id())) + append(fmt.text(update_part.content_type)) + append(fmt.url(0)) + + first = False + + append(fmt.div(on=0)) + + # Format the content. + + first = True + + for update_part in updates: + + # Encapsulate each alternative if many exist. + + if not single: + css_class = first and "moinshare-default" or "moinshare-other" + append(fmt.div(on=1, css_class="moinshare-alternative %s" % css_class, id=update_part.unique_id())) + + # Include the content. + + append(formatUpdatePart(update_part, request, fmt)) + + if not single: + append(fmt.div(on=0)) + + first = False + + return "".join(result) + +def formatUpdatePart(update, request, fmt): + + "Format the given 'update' using the given 'request' and 'fmt'." + + _ = request.getText + + result = [] + append = result.append + + # Encapsulate the content. + + append(fmt.div(on=1, css_class="moinshare-content")) + text = getFormattedUpdate(update, request, fmt) + if text: + append(text) + else: + append(fmt.text(_("Update cannot be shown for content of type %s.") % update.content_type)) + append(fmt.div(on=0)) + + return "".join(result) + # The macro itself. def execute(macro, args): @@ -326,31 +440,16 @@ for update in updates: # Emit content where appropriate. - # NOTE: Some control over the HTML and XHTML should be exercised. if show_content: append(fmt.div(on=1, css_class="moinshare-update")) - append(fmt.div(on=1, css_class="moinshare-content")) - - # NOTE: Handle multipart/alternative. - if update.content: - if update.content_type == "text/html" and update.message_number is not None: - parsers = [get_make_parser(update.page, update.message_number)] - else: - parsers = getParsersForContentType(request.cfg, update.content_type) + append(formatUpdate(update, request, fmt)) - if parsers: - for parser_cls in parsers: - append(formatText(update.content, request, fmt, parser_cls=parser_cls)) - break - else: - append(fmt.text(_("Update cannot be shown for content of type %s.") % update.content_type)) - - append(fmt.div(on=0)) append(fmt.div(on=1, css_class="moinshare-date")) append(fmt.text(str(update.updated))) append(fmt.div(on=0)) + append(fmt.div(on=0)) # Or emit title and link information for items.