changeset 389:2d782349b88a

server_side: display blog comments when you click on a main item header or title
author souliane <souliane@mailoo.org>
date Tue, 25 Feb 2014 17:50:47 +0100
parents 893451e35686
children 76583fab7ea0
files libervia.py libervia_server/__init__.py libervia_server/blog.py server_css/blog.css
diffstat 4 files changed, 144 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/libervia.py	Tue Feb 25 11:31:11 2014 +0100
+++ b/libervia.py	Tue Feb 25 17:50:47 2014 +0100
@@ -121,7 +121,7 @@
                          "getLastMblogs", "getMassiveLastMblogs", "getMblogComments", "getProfileJid",
                          "getHistory", "getPresenceStatus", "joinMUC", "mucLeave", "getRoomsJoined",
                          "inviteMUC", "launchTarotGame", "getTarotCardsPaths", "tarotGameReady",
-                         "tarotGamePlayCards", "launchRadioCollective",
+                         "tarotGamePlayCards", "launchRadioCollective", "getMblogs", "getMblogsWithComments",
                          "getWaitingSub", "subscription", "delContact", "updateContact", "getCard",
                          "getEntityData", "getParamsUI", "asyncGetParamA", "setParam", "launchAction",
                          "disconnect", "chatStateComposing", "getNewAccountDomain", "confirmationAnswer",
--- a/libervia_server/__init__.py	Tue Feb 25 11:31:11 2014 +0100
+++ b/libervia_server/__init__.py	Tue Feb 25 17:50:47 2014 +0100
@@ -280,6 +280,24 @@
         else:
             raise Exception("Invalid data")
 
+    def jsonrpc_getMblogs(self, publisher_jid, item_ids):
+        """Get specified microblogs posted by a contact
+        @param publisher_jid: jid of the publisher
+        @param item_ids: list of microblogs items IDs
+        @return list of microblog data (dict)"""
+        profile = ISATSession(self.session).profile
+        d = self.asyncBridgeCall("getGroupBlogs", publisher_jid, item_ids, profile)
+        return d
+
+    def jsonrpc_getMblogsWithComments(self, publisher_jid, item_ids):
+        """Get specified microblogs posted by a contact and their comments
+        @param publisher_jid: jid of the publisher
+        @param item_ids: list of microblogs items IDs
+        @return list of couple (microblog data, list of microblog data)"""
+        profile = ISATSession(self.session).profile
+        d = self.asyncBridgeCall("getGroupBlogsWithComments", publisher_jid, item_ids, profile)
+        return d
+
     def jsonrpc_getLastMblogs(self, publisher_jid, max_item):
         """Get last microblogs posted by a contact
         @param publisher_jid: jid of the publisher
--- a/libervia_server/blog.py	Tue Feb 25 11:31:11 2014 +0100
+++ b/libervia_server/blog.py	Tue Feb 25 17:50:47 2014 +0100
@@ -17,6 +17,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from sat.core.i18n import _
 from sat_frontends.tools.strings import addURLToText
 from libervia_server.html_tools import sanitizeHtml
 from twisted.internet import defer
@@ -25,6 +26,7 @@
 from twisted.words.protocols.jabber.jid import JID
 from datetime import datetime
 from constants import Const
+import uuid
 import re
 
 
@@ -61,11 +63,21 @@
                 def got_jid(pub_jid_s):
                     pub_jid = JID(pub_jid_s)
                     d2 = defer.Deferred()
-                    if len(request.postpath) > 1 and request.postpath[1] == 'atom.xml':
-                        d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None)
-                        self.host.bridge.getLastGroupBlogsAtom(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback)
-                    else:
-                        d2.addCallbacks(self._render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None)
+                    item_id = None
+                    if len(request.postpath) > 1:
+                        if request.postpath[1] == 'atom.xml':  # return the atom feed
+                            d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None)
+                            self.host.bridge.getLastGroupBlogsAtom(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback)
+                            return
+                        try:  # check if the given path is a valid UUID
+                            uuid.UUID(request.postpath[1])
+                            item_id = request.postpath[1]
+                        except ValueError:
+                            pass
+                    d2.addCallbacks(self.render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None)
+                    if item_id:  # display one message and its comments
+                        self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], 'libervia', d2.callback, d2.errback)
+                    else:  # display the last messages without comment
                         self.host.bridge.getLastGroupBlogs(pub_jid.userhost(), 10, 'libervia', d2.callback, d2.errback)
 
                 d1 = defer.Deferred()
@@ -74,21 +86,27 @@
 
                 return server.NOT_DONE_YET
 
-    def _render_html_blog(self, mblog_data, request, profile):
-        """Retrieve the blog banner or other user's specific stuff before actually rendering the static blog"""
+    def render_html_blog(self, mblog_data, request, profile):
+        """Retrieve the blog banner or other user's specific stuff before actually rendering the static blog
+        @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data)
+        @param request: HTTP request
+        @param profile
+        """
         def check_banner(banner):
             """Regexp from http://answers.oreilly.com/topic/280-how-to-validate-urls-with-regular-expressions/"""
             if re.match(r"^(https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+(/[\w-]+)*/[\w-]+\.(gif|png|jpg)$", banner):
                 style = {'banner': banner}
             else:
                 style = {}
-            self.render_html_blog(mblog_data, style, request, profile)
+            self.__render_html_blog(mblog_data, style, request, profile)
         eb = lambda failure: self.render_error_blog(failure, request, profile)
         self.host.bridge.asyncGetParamA('Blog banner', 'Misc', 'value', Const.SERVER_SECURITY_LIMIT, profile, callback=check_banner, errback=eb)
 
-    def render_html_blog(self, mblog_data, style, request, profile):
-        """Actually rendering the static blog
-        @param mblog_data: list of microblog data
+    def __render_html_blog(self, mblog_data, style, request, profile):
+        """Actually render the static blog. If mblog_data is a list of dict, we are missing
+        the comments items so we just display the main items. If mblog_data is a list of couple,
+        each couple is associating a main item data with the list of its comments, so we render all.
+        @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data)
         @param style: dict defining the blog's rendering parameters
         @param request: the HTTP request
         @profile
@@ -96,43 +114,84 @@
         if not isinstance(style, dict):
             style = {}
         user = sanitizeHtml(profile).encode('utf-8')
+        root_url = '../' * len(request.postpath)
+        base_url = root_url + 'blog/' + user
         banner = style['banner'].encode('utf-8') if 'banner' in style else ''
         banner_elt = "<img src='%(banner)s' alt='%(user)s'/>" % {'user': user, 'banner': banner} if banner else user
         request.write("""
             <html>
             <head>
                 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-                <link rel="alternate" type="application/atom+xml" href="%(user)s/atom.xml"/>
-                <link rel="stylesheet" type="text/css" href="../css/blog.css" />
+                <link rel="alternate" type="application/atom+xml" href="%(base)s/atom.xml"/>
+                <link rel="stylesheet" type="text/css" href="%(root)scss/blog.css" />
                 <title>%(user)s's microblog</title>
             </head>
             <body>
-            <div class='mblog_title'>%(banner_elt)s</div>
-            """ % {'user': user, 'banner_elt': banner_elt})
-        mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry.get('published', 0))))
+            <div class="mblog_title"><a href="%(base)s">%(banner_elt)s</a></div>
+            """ % {'base': base_url,
+                   'root': root_url,
+                   'user': user,
+                   'banner_elt': banner_elt})
+        mblog_data = [(entry if isinstance(entry, tuple) else (entry, [])) for entry in mblog_data]
+        mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('published', 0))))
         for entry in mblog_data:
-            timestamp = float(entry.get('published', 0))
-            _datetime = datetime.fromtimestamp(timestamp)
-
-            def getText(key):
-                if ('%s_xhtml' % key) in entry:
-                    return entry['%s_xhtml' % key].encode('utf-8')
-                elif key in entry:
-                    processor = addURLToText if key.startswith('content') else sanitizeHtml
-                    return processor(entry[key]).encode('utf-8')
-                return ''
-
-            body = getText('content')
-            title = getText('title')
-            if title:
-                body = "<h1>%s</h1>\n%s" % (title, body)
-            request.write("""<div class='mblog_entry'><span class='mblog_timestamp'>%(date)s</span>
-                          <span class='mblog_content'>%(content)s</span></div>""" % {
-                          'date': _datetime,
-                          'content': body})
+            self.__render_html_entry(entry[0], base_url, request)
+            comments = sorted(entry[1], key=lambda entry: (float(entry.get('published', 0))))
+            for comment in comments:
+                self.__render_html_entry(comment, base_url, request)
         request.write('</body></html>')
         request.finish()
 
+    def __render_html_entry(self, entry, base_url, request):
+        """Render one microblog entry.
+        @param entry: the microblog entry
+        @param base_url: the base url of the blog
+        @param request: the HTTP request
+        """
+        timestamp = float(entry.get('published', 0))
+        datetime_ = datetime.fromtimestamp(timestamp)
+        is_comment = entry['type'] == 'comment'
+        if is_comment:
+            author = (_("comment from %s") % entry['author']).encode('utf-8')
+            item_link = ''
+        else:
+            author = '&nbsp;'
+            item_link = ("%(base)s/%(item_id)s" % {'base': base_url, 'item_id': entry['id']}).encode('utf-8')
+
+        def getText(key):
+            if ('%s_xhtml' % key) in entry:
+                return entry['%s_xhtml' % key].encode('utf-8')
+            elif key in entry:
+                processor = addURLToText if key.startswith('content') else sanitizeHtml
+                return processor(entry[key]).encode('utf-8')
+            return ''
+
+        def addMainItemLink(elem):
+            if not item_link or not elem:
+                return elem
+            return """<a href="%(link)s" class="item_link">%(elem)s</a>""" % {'link': item_link, 'elem': elem}
+
+        header = addMainItemLink("""<div class="mblog_header">
+                                      <div class="mblog_metadata">
+                                        <div class="mblog_author">%(author)s</div>
+                                        <div class="mblog_timestamp">%(date)s</div>
+                                      </div>
+                                    </div>""" % {'author': author, 'date': datetime_})
+
+        title = addMainItemLink(getText('title'))
+        body = getText('content')
+        if title:  # insert the title within the body
+            body = """<h1>%(title)s</h1>\n%(body)s""" % {'title': title, 'body': body}
+
+        request.write("""<div class="mblog_entry %(extra_style)s">
+                           %(header)s
+                           <span class="mblog_content">%(content)s</span>
+                         </div>""" %
+                         {'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '',
+                          'item_link': item_link,
+                          'header': header,
+                          'content': body})
+
     def render_atom_feed(self, feed, request):
         request.write(feed.encode('utf-8'))
         request.finish()
--- a/server_css/blog.css	Tue Feb 25 11:31:11 2014 +0100
+++ b/server_css/blog.css	Tue Feb 25 17:50:47 2014 +0100
@@ -16,8 +16,9 @@
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-.mblog_title {
+.mblog_title, .mblog_title a {
     text-align: center;
+    text-decoration: none;
     font-size: x-large;
     font-weight: bold;
     margin-bottom: 40px;
@@ -39,11 +40,28 @@
     color: rgb(51, 51, 51);
 }
 
-.mblog_timestamp {
-    display: block;
+.mblog_comment {
+}
+
+.mblog_header {
     font-size: small;
     border-bottom: 1px dashed LightGrey;
     color: gray;
+    display: table;
+    width: 100%;
+}
+
+.mblog_metadata {
+    display: table-row;
+    width: 100%;
+}
+
+.mblog_author {
+    display: table-cell;
+}
+
+.mblog_timestamp {
+    display: table-cell;
     text-align: right;
 }
 
@@ -52,10 +70,19 @@
 	padding-top: 5px;
 }
 
-h1, h2, h3, h4, h5, h6 {
+.item_link {
+    text-decoration: none;
+}
+
+.mblog_entry h1, h2, h3, h4, h5, h6 {
     border-bottom: 1px solid rgb(170, 170, 170);
 }
 
+.mblog_entry h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
+    text-decoration: none;
+    color: rgb(51, 51, 51);
+}
+
 img {
     max-width: 100%;
 }