quarta-feira, 4 de janeiro de 2012

Novidades: Threaded Comments sendo implementados no Blogger

Há tempos mostrei aqui um tweet de um dos funcionários do Blogger prometendo que um novo sistema de comentários estava a caminho. Creio eu que a implementação da nova interface e a integração com o Google+(rede social da Google) durante 2010 e 2011, devem ter atrasado os planos.

Exemplo qualquer de comentários aninhados 

O novo sistema de comentários é do tipo Threaded, ou aninhado. Para se ter uma idéia é só dar uma olhada em blogs da plataforma Wordpress.org ou B2Evolution. É um sistema dinâmico onde possui funções de mostrar/esconder e responder comentários, entre outros recursos. Muitos blogueiros utilizam sistemas de comentários independentes, tipo Disqus ou Intense Debate. Este último é um dos piores na minha sincera opinião, às vezes é lento, influindo na velocidade de carregamento do blog, dá uns bugs do tipo não aceitar o login e congela o navegador (Acontece comigo, juro! Eu não consigo comentar em blogs que possui o Intense Debate.)

O Marcos Lemos(@hordones) do Ferramentas Blog, há poucos dias chamou-me a atenção de novos códigos incluídos nos templates, o threaded_comment_css e o threaded_comment_js. Eu já os tinha vistos mas estava sem tempo para analisá-los.

Começo aqui com o código dos comentários aninhados (Threaded Comments) incluídos no template:

<b:includable id='threaded_comments' var='post'>
  <b:include name='threaded_comment_css'/>

  <div class='comments' id='comments'>
    <a name='comments'/>
    <h4>
      <b:if cond='data:post.numComments == 1'>
        1 <data:commentLabel/>:
      <b:else/>
        <data:post.numComments/> <data:commentLabelPlural/>:
      </b:if>
    </h4>

    <div class='comments-content'>
      <b:if cond='data:post.embedCommentForm'>
        <b:include data='post' name='threaded_comment_js'/>
      </b:if>
      <div id='comment-holder'>
         <data:post.commentHtml/>
      </div>
    </div>

    <p class='comment-footer'>
      <b:if cond='data:post.allowNewComments'>
        <b:include data='post' name='threaded-comment-form'/>
      <b:else/>
        <data:post.noNewCommentsText/>
      </b:if>
    </p>

    <b:if cond='data:showCmtPopup'>
      <div id='comment-popup'>
        <iframe allowtransparency='true' frameborder='0' id='comment-actions' name='comment-actions' scrolling='no'>
        </iframe>
      </div>
    </b:if>

    <div id='backlinks-container'>
    <div expr:id='data:widget.instanceId + &quot;_backlinks-container&quot;'>
       <b:if cond='data:post.showBacklinks'>
         <b:include data='post' name='backlinks'/>
       </b:if>
    </div>
    </div>
  </div>
</b:includable>

Aqui temos o mesmo formulário, cujas linhas são semelhantes ao que já existe. O engraçado é que ele mantem as mesmas medidas que adotei no outro. Para quem acha que eu bebi, há realmente 2 frames, o antigo abaixo de <b:includable id='comment-form' var='post'> e o novo conforme a seguir:

<b:includable id='threaded-comment-form' var='post'>
  <div class='comment-form'>
    <a name='comment-form'/>
    <b:if cond='data:mobile'>
      <p><data:blogCommentMessage/></p>
      <data:blogTeamBlogMessage/>
      <a expr:href='data:post.commentFormIframeSrc' id='comment-editor-src'/>
      <iframe allowtransparency='true' class='blogger-iframe-colorize blogger-comment-from-post' frameborder='0' height='410' id='comment-editor' name='comment-editor' src='' style='display: none' width='100%'/>
    <b:else/>
      <p><data:blogCommentMessage/></p>
      <data:blogTeamBlogMessage/>
      <a expr:href='data:post.commentFormIframeSrc' id='comment-editor-src'/>
      <iframe allowtransparency='true' class='blogger-iframe-colorize blogger-comment-from-post' frameborder='0' height='410' id='comment-editor' name='comment-editor' src='' width='100%'/>
    </b:if>
    <data:post.friendConnectJs/>
    <data:post.cmtfpIframe/>
    <script type='text/javascript'>
      BLOG_CMT_createIframe(&#39;<data:post.appRpcRelayPath/>&#39;, &#39;<data:post.communityId/>&#39;);
    </script>
  </div>
</b:includable>

Nesta outra seqüência, temos o CSS puro dos comentários aninhados:

<b:includable id='threaded_comment_css'>
  <style>
.comments {
  clear: both;
  margin-top: 10px;
  margin-bottom: 0px;
  line-height: 1em;
}
.comments .comments-content {
  font-size: 13px;
  margin-bottom: 16px;
}
.comments .comment .comment-actions a {
  padding-top: 5px;
  padding-right: 5px;
}
.comments .comment .comment-actions a:hover {
  text-decoration: underline;
}
.comments .comments-content .comment-thread ol {
  list-style-type: none;
  padding: 0;
  text-align: left;
}
.comments .comments-content .inline-thread {
  padding: 0.5em 1em;
}
.comments .comments-content .comment-thread {
  margin: 8px 0px;
}
.comments .comments-content .comment-thread:empty {
  display: none;
}
.comments .comments-content .comment-replies {
  margin-top: 1em;
  margin-left: 36px;
}
.comments .comments-content .comment {
  margin-bottom:16px;
  padding-bottom:8px;
}
.comments .comments-content .comment:first-child {
  padding-top:16px;
}
.comments .comments-content .comment:last-child {
  border-bottom:0;
  padding-bottom:0;
}
.comments .comments-content .comment-body {
  position:relative;
}
.comments .comments-content .user {
  font-style:normal;
  font-weight:bold;
}
.comments .comments-content .icon.blog-author {
  width: 18px;
  height: 18px;
  display: inline-block;
  margin: 0 0 -4px 6px;
}
.comments .comments-content .datetime {
  margin-left:6px;
}
.comments .comments-content .comment-header,
.comments .comments-content .comment-content {
  margin:0 0 8px;
}
.comments .comments-content .comment-content {
  text-align:justify;
}
.comments .comments-content .owner-actions {
  position:absolute;
  right:0;
  top:0;
}
.comments .comments-replybox {
  border: none;
  height: 250px;
  width: 100%;
}
.comments .comment-replybox-single {
  margin-top: 5px;
  margin-left: 48px;
}
.comments .comment-replybox-thread {
  margin-top: 5px;
}
.comments .comments-content .loadmore a {
  display: block;
  padding: 10px 16px;
  text-align: center;
}
.comments .thread-toggle {
  cursor: pointer;
  display: inline-block;
}
.comments .continue {
  cursor: pointer;
}
.comments .continue a {
  display: block;
  padding: 0.5em;
  font-weight: bold;
}
.comments .comments-content .loadmore {
  cursor: pointer;
  max-height: 3em;
  margin-top: 3em;
}
.comments .comments-content .loadmore.loaded {
  max-height: 0px;
  opacity: 0;
  overflow: hidden;
}
.comments .thread-chrome.thread-collapsed {
  display: none;
}
.comments .thread-toggle {
  display: inline-block;
}
.comments .thread-toggle .thread-arrow {
  display: inline-block;
  height: 6px;
  width: 7px;
  overflow: visible;
  margin: 0.3em;
  padding-right: 4px;
}
.comments .thread-expanded .thread-arrow {
  background: url(&quot;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAG0lEQVR42mNgwAfKy8v/48I4FeA0AacVDFQBAP9wJkE/KhUMAAAAAElFTkSuQmCC&quot;) no-repeat scroll 0 0 transparent;
}
.comments .thread-collapsed .thread-arrow {
  background: url(&quot;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAJUlEQVR42mNgAILy8vL/DLgASBKnApgkVgXIkhgKiNKJ005s4gDLbCZBiSxfygAAAABJRU5ErkJggg==&quot;) no-repeat scroll 0 0 transparent;
}
.comments .avatar-image-container {
  float: left;
  width: 36px;
  max-height: 36px;
  overflow: hidden;
}
.comments .avatar-image-container img {
  width: 36px;
}
.comments .comment-block {
  margin-left: 48px;
  position: relative;
}

/* Responsive styles. */
@media screen and (max-device-width: 480px) {
  .comments .comments-content .comment-replies {
    margin-left: 0;
  }
}
  </style>
</b:includable>

Abaixo temos o script dos comentários aninhados, feitos em json. Percorrendo as linhas, vemos os recursos de responder(repply), paginação (paginator), mostrar /esconder (more) entre outros:

<b:includable id='threaded_comment_js' var='post'>
  <script defer='defer' expr:src='data:post.commentSrc' type='text/javascript'/>

  <script type='text/javascript'>
    (function() {
      var items = <data:post.commentJso/>;
      var msgs = <data:post.commentMsgs/>;
      var postId = &#39;<data:post.id/>&#39;;
      var feed = &#39;<data:post.commentFeed/>&#39;;
      var authorName = &#39;<data:post.author/>&#39;;
      var authorUrl = &#39;<data:post.authorUrl/>&#39;;
      var blogId = &#39;<data:top.id/>&#39;;
      var baseUri = &#39;<data:post.commentBase/>&#39;;

// <![CDATA[
      feed += '?alt=json&v=2&orderby=published&reverse=false&max-results=50';
      var cursor = null;
      if (items && items.length > 0) {
        cursor = parseInt(items[items.length - 1].timestamp) + 1;
      }

      var bodyFromEntry = function(entry) {
        if (entry.gd$extendedProperty) {
          for (var k in entry.gd$extendedProperty) {
            if (entry.gd$extendedProperty[k].name == 'blogger.contentRemoved') {
              return '<span class="deleted-comment">' + entry.content.$t + '</span>';
            }
          }
        }
        return entry.content.$t;
      }

      var parse = function(data) {
        cursor = null;
        var comments = [];
        if (data && data.feed && data.feed.entry) {
          for (var i = 0, entry; entry = data.feed.entry[i]; i++) {
            var comment = {};
            // comment ID, parsed out of the original id format
            var id = /blog-(d+).post-(d+)/.exec(entry.id.$t);
            comment.id = id ? id[2] : null;
            comment.body = bodyFromEntry(entry);
            comment.timestamp = Date.parse(entry.published.$t) + '';
            if (entry.author && entry.author.constructor === Array) {
              var auth = entry.author[0];
              if (auth) {
                comment.author = {
                  name: (auth.name ? auth.name.$t : undefined),
                  profileUrl: (auth.uri ? auth.uri.$t : undefined),
                  avatarUrl: (auth.gd$image ? auth.gd$image.src : undefined)
                };
              }
            }
            if (entry.link) {
              if (entry.link[2]) {
                comment.link = comment.permalink = entry.link[2].href;
              }
              if (entry.link[3]) {
                var pid = /.*comments/default/(d+)?.*/.exec(entry.link[3].href);
                if (pid && pid[1]) {
                  comment.parentId = pid[1];
                }
              }
            }
            comment.deleteclass = 'item-control blog-admin';
            if (entry.gd$extendedProperty) {
              for (var k in entry.gd$extendedProperty) {
                console.log(entry.gd$extendedProperty[k].name + ' - ' + entry.gd$extendedProperty[k].value);
                if (entry.gd$extendedProperty[k].name == 'blogger.itemClass') {
                  comment.deleteclass += ' ' + entry.gd$extendedProperty[k].value;
                }
              }
            }
            comments.push(comment);
          }
        }
        return comments;
      };

      var paginator = function(callback) {
        if (hasMore()) {
          var url = feed;
          if (cursor) {
            url += '&published-min=' + new Date(cursor).toISOString();
          }
          window.bloggercomments = function(data) {
            var parsed = parse(data);
            cursor = parsed.length < 50 ? null
                : parseInt(parsed[parsed.length - 1].timestamp) + 1
            callback(parsed);
            window.bloggercomments = null;
          }
          url += '&callback=bloggercomments';
          var script = document.createElement('script');
          script.type = 'text/javascript';
          script.src = url;
          document.getElementsByTagName('head')[0].appendChild(script);
        }
      };
      var hasMore = function() {
        return !!cursor;
      };
      var getMeta = function(key, comment) {
        if ('iswriter' == key) {
          var matches = !!comment.author
              && comment.author.name == authorName
              && comment.author.profileUrl == authorUrl;
          return matches ? 'true' : '';
        } else if ('deletelink' == key) {
          return baseUri + '/delete-comment.g?blogID=' + blogId + '&postID=' + comment.id;
        } else if ('deleteclass' == key) {
          return comment.deleteclass;
        }
        return '';
      };

      var replybox = null;
      var replyUrlParts = null;
      var replyParent = undefined;

      var onReply = function(commentId, domId) {
        if (replybox == null) {
          // lazily cache replybox, and adjust to suit this style:
          replybox = document.getElementById('comment-editor');
          if (replybox != null) {
            replybox.height = '250px';
            replybox.style.display = 'block';
            replyUrlParts = replybox.src.split('#');
          }
        }
        if (replybox && (commentId !== replyParent)) {
          document.getElementById(domId).insertBefore(replybox, null);
          replybox.src = replyUrlParts[0]
              + (commentId ? '&parentID=' + commentId : '')
              + '#' + replyUrlParts[1];
          replyParent = commentId;
        }
      };

      var tok = 'comment-form_';
      var hash = window.location.hash || '';
      var startThread = hash.indexOf(tok) == 1 ? hash.substring(tok.length + 1) : undefined;

      // Configure commenting API:
      var configJso = {
        'maxDepth': 2
      };
      var provider = {
        'id': postId,
        'data': items,
        'loadNext': paginator,
        'hasMore': hasMore,
        'getMeta': getMeta,
        'onReply': onReply,
        'rendered': true,
        'initReplyThread': startThread,
        'config': configJso,
        'messages': msgs
      };

      var render = function() {
        if (window.goog && window.goog.comments) {
          var holder = document.getElementById('comment-holder');
          window.goog.comments.render(holder, provider);
        }
      };

      // render now, or queue to render when library loads:
      if (window.goog && window.goog.comments) {
        render();
      } else {
        window.goog = window.goog || {};
        window.goog.comments = window.goog.comments || {};
        window.goog.comments.loadQueue = window.goog.comments.loadQueue || [];
        window.goog.comments.loadQueue.push(render);
      }
    })();
// ]]>
  </script>
</b:includable>

A ativação desse novo sistema deve ocorrer neste ano (Espero!) e assim acabar com as reclamações de muitos usuários que odeiam o sistema atual. A única coisa que me deixou triste é que não há (ou se tem, eu não vi...) a separação dos anônimos de nome/url e possibilidade de comentar com outros logins (Twitter, Facebook, etc). Talvez estes deverão ser configurados no painel de controle. De qualquer maneira, esses códigos são a prova que haverá uma mudança. E quem viver, verá!

Artigos recentes

Linkem me:

Related Posts with Thumbnails
Google