メタブ表示

    @@ -8,7 +8,11 @@ (function(){ //////////////////////////////////////////////////////////// - var doc = document, win = window, loc = location, con = console; + var win = this, + doc = this.document, + loc = this.location, + con = this.console; + var setTimeout = this.setTimeout; var DEBUG = true; @@ -18,73 +22,132 @@ var API_DOMAIN = "//b.hatena.ne.jp"; var ICON_DOMAIN = "http://cdn1.www.st-hatena.com/users/"; var JSONP_MAX_BYTES = 1800; + var LOADING_GIF = HATEB_DOMAIN + '/images/loading.gif'; //////////////////////////////////////////////////////////// var CSS = [ + '.metab-popup-locker {', + ' position: fixed;', + ' top: 0px;', + ' left: 0px;', + ' width: 100%;', + ' height: 100%;', + ' z-index: 9000;', + ' background: rgba(0,0,0,0.25);', + '}', + '.metab-popup {', + ' height: 90%;', + ' width: 600px;', + ' margin: 2.5% auto;', + ' padding: 10px;', + ' border-radius: 20px;', + ' overflow: auto;', + ' background: #F8F8F8;', + '}', '.metab-container {', - ' margin: 5px;', - ' padding: 5px;', - ' border: solid #9CF 1px;', + ' margin: 5px;', + ' padding: 5px;', + ' border: solid #9CF 1px;', ' border-radius: 10px;', '}', '.metab-container > .metab-title > .title{', - ' display: inline-block;', - ' margin: 0px 10px 0px 0px;', + ' display: inline-block;', + ' margin: 0px 10px 0px 0px;', ' font-size: 11px;', - ' color: #69F;', + ' color: #69F;', '}', '.metab-container > .metab-title > .metab-count{', - ' font-size: 10px;', - ' font-weight: 900;', - ' color: #F00;', + ' font-size: 10px;', + ' font-weight: 900;', + ' color: #F00;', ' background-color: #FCC;', - ' display: inline-block;', - ' text-decoration: none;', - ' line-height: 12px;', - ' padding: 2px;', - ' margin: 10px 0px;', + ' display: inline-block;', + ' text-decoration: none;', + ' line-height: 12px;', + ' padding: 2px;', + ' margin: 10px 0px;', '}', '.metab-container > .metab-title > .metab-count-detail{', - ' display: inline-block;', - ' margin: 0px 10px;', + ' display: inline-block;', + ' margin: 0px 10px;', ' font-size: 11px;', - ' color: rgb(153, 153, 153);', + ' color: rgb(153, 153, 153);', '}', '.metab-container > ul{', ' display: block;', ' margin: 0px;', ' padding: 0px;', '}', + '.metab-container > ul > li{', + ' margin: 0px;', + ' padding: 11px 0px 11px 30px;', + ' border-bottom: solid #CCC 1px;', + '}', '.metab-container > ul.comments > li{', - ' width: auto !important;', - ' margin: 0px;', - ' padding: 0px;', - ' display: block;', + ' width: auto !important;', + ' display: block;', ' list-style: none;', '}', '.metab-container > ul.no-comments > li{', - ' width: auto !important;', - ' margin: 0px 11px 0px 0px;', - ' padding: 0px;', - ' border: none;', + ' width: auto !important;', + ' margin: 0px 11px 0px 0px;', + ' border: none;', ' list-style: none;', - ' display: inline-block;', + ' display: inline-block;', + '}', + + '.metab-container > ul > li > span.head-part > a.profile-icon {', + ' margin-left: -30px;', + '}', + + '.metab-container > ul > li > span.head-part > a.profile-icon > img{', + ' width: 24px;', + ' height: 24px;', '}', - '.metab-container > ul > li > span.headPart > a.username{', + '.metab-container > ul > li > span.head-part > a.username{', + ' font-size: 12px;', ' display: inline-block;', - ' margin: 0px 11px 0px 0px;', + ' margin: 0px 11px;', '}', '.metab-container > ul > li > span.tags{', ' display: inline-block;', - ' margin: 0px 11px 0px 0px;', + ' margin: 0px 11px 0px 0px;', '}', '.metab-container > ul > li > span.comment{', + ' font-size: 12px;', + '}', + + '.metab-container .user-comment-meta{', + ' font-size: 11px;', + ' color: rgb(153,153,153);', + '}', + '.metab-container a{', + ' color: rgb(0, 143, 222);', + '}', + '.metab-container a.user-comment-link {', + ' background-image: url("/images/v3/comment-link.png");', + ' background-position: left center;', + ' background-repeat: no-repeat;', + ' background-size: 15px 15px;', + ' color: rgb(153, 153, 153);', ' font-size: 11px;', + ' font-weight: normal;', + ' margin: 0 8px 0 -1px;', + ' padding-left: 16px;', + ' text-decoration: none;', '}', ''].join("\n"); + //////////////////////////////////////////////////////////// + var PageType = ((function(pn){ + if(pn.match(/^\/entry\//)){ + if(pn.match(/^\/entry\/\d+\/comment\//)) return 'comment'; + return 'entry';} + return 'other'; + })(String(loc.pathname))); + //////////////////////////////////////////////////////////// @@ -106,11 +169,9 @@ var receiver = function receiver(cb){ var name = gensym(); - var func = function(){ + win[name] = function(){ delete win[name]; - cb.apply(this, arguments); - }; - win[name] = func; + cb.apply(this, arguments);}; return name;}; @@ -130,10 +191,10 @@ //////////////////////////////////////////////////////////// var withHatena = function(cb){(function(){ - if(typeof(Hatena) === 'undefined'){ + if(typeof(win.Hatena) === 'undefined'){ setTimeout(arguments.callee, 100); return;} - cb(Hatena);})();}; + cb(win.Hatena);})();}; var withStar = function(cb){ withHatena(function(Hatena){(function(){ @@ -150,24 +211,28 @@ cb(Hatena.Bookmark, Hatena);})();});}; //////////////////////////////////////////////////////////// - var PageEid = (function(){ - var ucl = doc.querySelector('a.user-comment-link'); - if(!ucl) return null; - return String(ucl.href).replace(/^\/entry\//,'').replace(/\/comments\/.*/,'');}); - - var domifyIcon = function(bm){ + var profileIcon = function(user){ var a = doc.createElement('a'); - a.href = HATEB_DOMAIN + '/' + bm.user + '/'; + a.href = HATEB_DOMAIN + '/' + user + '/'; a.target = '_blank'; - a.title = bm.user; + a.title = user; a.className = 'profile-icon'; var icon = doc.createElement('img'); icon.src = - ICON_DOMAIN + '/' + bm.user.substring(0,2) + '/' + bm.user + + ICON_DOMAIN + '/' + user.substring(0,2) + '/' + user + '/profile_l.gif'; - icon.alt = bm.user; + icon.alt = user; a.appendChild(icon); - return a;}; + return a; }; + + //////////////////////////////////////////////////////////// + var PageEid = (function(){ + var ucl = doc.querySelector('a.user-comment-link'); + if(!ucl) return null; + return String(ucl.href).replace(/^\/entry\//,'').replace(/\/comments\/.*/,'');}); + + var domifyIcon = function(bm){ + return profileIcon(bm.user);}; var domifyUsername = function(bm, entry){ var a = doc.createElement('a'); @@ -231,8 +296,7 @@ a.appendChild(doc.createTextNode(url)); R.appendChild(a); } - return R; - };})(); + return R;};})(); var domifyMeta = function(bm, entry){ var meta = doc.createElement('div'); @@ -285,7 +349,7 @@ var li = doc.createElement('li'); li.className = 'metab'; var headPart = doc.createElement('span'); - headPart.className = '.head-part'; + headPart.className = 'head-part'; headPart.appendChild(domifyIcon( bm, entry)); headPart.appendChild(domifyUsername(bm, entry)); li.appendChild(headPart); @@ -391,34 +455,106 @@ var node = slot.dom || (slot.dom = node = domifyEntry(entry,title)); return addBehaviors(node.cloneNode(true));}; - receivedMetab = function(uri, entry){ + receivedMetab = function(entry, uri, title){ var slot = entries[uri]; slot.entry = entry || false; - slot.cont.forEach(function(c){c(entry);});}; + slot.cont.forEach(function(c){c(entry, uri, title);});}; reserveMetab = function(uri, cont_, title_){ var slot = (entries[uri] || (entries[uri] = {entry: null, cont:[], req: false})); + var title = title_ || ''; var cont = cont_ instanceof Function ? cont_ : (function(){ var container = cont_; - var title = title_ || ''; + if(container.className == 'metab-popup'){ + var e = new Error(); + console.log(e.stack); + throw e; + } return function(entry){ container.appendChild(metabToDOM(entry, uri, title));};})(); if(slot.entry !== null){ - cont(slot.entry); + cont(slot.entry, uri, title); return; } slot.cont.push(cont || function(){}); if(slot.req) return; slot.req = true; jsonp(API_DOMAIN + '/entry/jsonlite/', {url: uri, - callback: function(entry){receivedMetab(uri, entry);}});};})(); + callback: function(entry){receivedMetab(entry, uri, title);}});};})(); var reserveUserMetab = function(user, cont_, title_){ var uri = HATEB_DOMAIN + '/' + user + '/'; var title = title_ || 'metab for id:'+user; reserveMetab(uri, cont_, title);}; + //////////////////////////////////////////////////////////// + var popupUserMetab = (function(){ + var popups = {}; + var locker = null; + var makeLocker = function(){ + var div = doc.createElement('div'); + div.className = 'metab-popup-locker'; + div.addEventListener('click', function(e){ + if(e.target !== div) return; + hidePopup();}); + return div;}; + var getLocker = function(){ return locker || (locker = makeLocker()); }; + + var lockPage, + unlockPage; + (function(){ + var html = null, body = null, + cssTextHtml = null, cssTextBody = null; + var init = function(){ + if(html) return; + html = doc.querySelector('html'); + body = doc.body; + cssTextHtml = html.style.cssText; + cssTextBody = body.style.cssText;}; + lockPage = function(){ + init(); + html.style.cssText = 'overflow: hidden;'; + body.style.cssText = 'overflow: hidden;';}; + unlockPage = function(){ + init(); + html.style.cssText = cssTextHtml; + body.style.cssText = cssTextBody;};})(); + + var showLocker = function(){ + var locker = getLocker(); + lockPage(); + doc.body.appendChild(locker); + return locker; }; + var hideLocker = function(){ + var locker = getLocker(); + unlockPage(); + doc.body.appendChild(locker); + return locker;; }; + + var makePopup = function(user){ + var popup = doc.createElement('div'); + popup.className = 'metab-popup'; + popup.appendChild(profileIcon(user)); + reserveUserMetab(user, function(entry, uri, title){ + var metabdom = metabToDOM(entry, uri, title); + if(!popup.querySelector('.metab-container')){ + popup.appendChild(metabdom);}}); + return popup; }; + + var showPopup = function(popup){ + hidePopup(); + showLocker().appendChild(popup);}; + + var hidePopup = function(){ + var locker = hideLocker(); + locker.innerHTML = ''; + locker.parentNode.removeChild(locker);}; + + return function(user){ + var popup = popups[user] || (popups[user]=makePopup(user)); + showPopup(popup); };})(); + //////////////////////////////////////////////////////////// @@ -428,40 +564,89 @@ style.appendChild(doc.createTextNode(CSS)); doc.querySelector('head').appendChild(style);}; + var showCommentMetab = function(n){ + if(PageType != 'entry') return; var nparent = n.parentNode; + if(nparent.className == 'metab-popup') return; var cl = nparent.querySelector('a.user-comment-link'); if(!cl) return; reserveMetab(cl.href, nparent, 'metab for this comment'); var user = n.href.match(/([^\/]+)\/$/)[1]; reserveUserMetab(user, nparent);}; - var showMetabs = function(){ - forEach(doc.querySelectorAll('a.profile-icon'), showCommentMetab);}; - var entryPage = function(){applyStyle(); - showMetabs();}; + //////////////////////////////////////////////////////////// + var updateStarBehavior = function(n){ + var a = n.parentNode; + if(String(a.tagName).toLowerCase() != 'a') return; + n.style.border = 'solid rgba(0,0,0,0.5) 1px'; + n.style.borderRadius = '6px'; + var user = (function(m){return m[1];})(a.href.match(/([^\/]*)\/?$/)); + reserveUserMetab(user, function(entry){ + if(entry){ + n.style.borderColor = 'rgba(255,0,0,0.5)'; + a.addEventListener('click', function(ev){ + ev.stopPropagation(); + ev.preventDefault(); + popupUserMetab(user);}, false);} + else{ + n.style.border = null;}});}; //////////////////////////////////////////////////////////// + var observeMutation = function(){ + var modify = function(n, selector, modifier){ + if(n.className == selector) setTimeout(function(){modifier(n);},100); + forEach(n.querySelectorAll(selector), function(n){ + setTimeout(function(){modifier(n);}, 100);});}; + + var mo = new win.MutationObserver(function(ms){ms.forEach(function(m){ + switch(m.type){ + case 'childList':{ + forEach(m.addedNodes, function(n){ + if(!n.tagName) return; + modify(n, '.profile-icon', showCommentMetab); + modify(n, '.hatena-star-star', updateStarBehavior);}); + break;} + case 'attributes':{ + var n = m.target, av; + switch(m.attributeName){ + case 'class':{ + switch(n.className){ + case 'profile-icon':{ + setTimeout(function(){showCommentMetab(n);},100); + break;} + case 'hatena-star-star':{ + updateStarBehavior(n); + break;} + default:{ + break;}} + break;} + default:{ + break;}} + break;} + default: { + break;}}});}); + mo.observe(doc.body, { + subtree: true, + childList: true, + attributes: true, + attributeFilter: ['class']});}; - var updateStarBehavior = function(){withStar(function(Star, Hatena){ - var starProto = Star.Star.prototype; - var orig = starProto.showName; - starProto.showName = function(){ - var self = this; - return orig.apply(self, arguments);};});}; //////////////////////////////////////////////////////////// - - if((function(pn){ - if(pn.match(/^\/entry\/\d+\/comment\//)) return true; - return false; - })(String(loc.pathname))) return; - entryPage(); + var main = function(){ + applyStyle(); + forEach(doc.querySelectorAll('a.profile-icon'), showCommentMetab); + forEach(doc.querySelectorAll('.hatena-star-star'), updateStarBehavior); + observeMutation(); + }; + + main(); -})(); +}).call(this); // Local Variables: // mode: hatena-let
  • /*
     * @title メタブ表示
     * @description メタブ表示
     * @include http://b.hatena.ne.jp/*
     * @license MIT License
     * @require 
     */
    (function(){
    
      ////////////////////////////////////////////////////////////
      var win = this,
          doc = this.document,
          loc = this.location,
          con = this.console;
      var setTimeout = this.setTimeout;
    
      var DEBUG        = true;
    
      ////////////////////////////////////////////////////////////
    
      var HATEB_DOMAIN = "http://b.hatena.ne.jp";
      var API_DOMAIN   = "//b.hatena.ne.jp";
      var ICON_DOMAIN  = "http://cdn1.www.st-hatena.com/users/";
      var JSONP_MAX_BYTES = 1800;
      var LOADING_GIF     = HATEB_DOMAIN + '/images/loading.gif';
    
      ////////////////////////////////////////////////////////////
      var CSS = [
        '.metab-popup-locker {',
        '  position:   fixed;',
        '  top:        0px;',
        '  left:       0px;',
        '  width:      100%;',
        '  height:     100%;',
        '  z-index:    9000;',
        '  background: rgba(0,0,0,0.25);',
        '}',
        '.metab-popup {',
        '  height:        90%;',
        '  width:         600px;',
        '  margin:        2.5% auto;',
        '  padding:       10px;',
        '  border-radius: 20px;',
        '  overflow:      auto;',
        '  background:    #F8F8F8;',
        '}',
        '.metab-container {',
        '  margin:        5px;',
        '  padding:       5px;',
        '  border: solid   #9CF 1px;',
        '  border-radius: 10px;',
        '}',
        '.metab-container > .metab-title > .title{',
        '  display:   inline-block;',
        '  margin:    0px 10px 0px 0px;',
        '  font-size: 11px;',
        '  color:     #69F;',
        '}',
        '.metab-container > .metab-title > .metab-count{',
        '  font-size:        10px;',
        '  font-weight:      900;',
        '  color:            #F00;',
        '  background-color: #FCC;',
        '  display:          inline-block;',
        '  text-decoration:  none;',
        '  line-height:      12px;',
        '  padding:          2px;',
        '  margin:           10px 0px;',
        '}',
        '.metab-container > .metab-title > .metab-count-detail{',
        '  display:   inline-block;',
        '  margin:    0px 10px;',
        '  font-size: 11px;',
        '  color:     rgb(153, 153, 153);',
        '}',
        '.metab-container > ul{',
        '  display: block;',
        '  margin: 0px;',
        '  padding: 0px;',
        '}',
        '.metab-container > ul > li{',
        '  margin:        0px;',
        '  padding:       11px 0px 11px 30px;',
        '  border-bottom: solid #CCC 1px;',
        '}',
        '.metab-container > ul.comments > li{',
        '  width:      auto !important;',
        '  display:    block;',
        '  list-style: none;',
        '}',
        '.metab-container > ul.no-comments > li{',
        ' width:      auto !important;',
        ' margin:     0px 11px 0px 0px;',
        ' border:     none;',
        ' list-style: none;',
        ' display:    inline-block;',
        '}',
    
        '.metab-container > ul > li > span.head-part > a.profile-icon {',
        '  margin-left: -30px;',
        '}',
    
        '.metab-container > ul > li > span.head-part >  a.profile-icon > img{',
        '  width:  24px;',
        '  height: 24px;',
        '}',
        '.metab-container > ul > li > span.head-part >  a.username{',
        '  font-size: 12px;',
        '  display: inline-block;',
        '  margin:  0px 11px;',
        '}',
    
        '.metab-container > ul > li >  span.tags{',
        '  display: inline-block;',
        '  margin:  0px 11px 0px 0px;',
        '}',
    
        '.metab-container > ul > li > span.comment{',
        '  font-size: 12px;',
        '}',
    
        '.metab-container .user-comment-meta{',
        '  font-size: 11px;',
        '  color: rgb(153,153,153);',
        '}',
        '.metab-container a{',
        '  color: rgb(0, 143, 222);',
        '}',
        '.metab-container a.user-comment-link {',
        '  background-image: url("/images/v3/comment-link.png");',
        '  background-position: left center;',
        '  background-repeat: no-repeat;',
        '  background-size: 15px 15px;',
        '  color: rgb(153, 153, 153);',
        '  font-size: 11px;',
        '  font-weight: normal;',
        '  margin: 0 8px 0 -1px;',
        '  padding-left: 16px;',
        '  text-decoration: none;',
        '}',
        ''].join("\n");
    
      ////////////////////////////////////////////////////////////
      var PageType = ((function(pn){
        if(pn.match(/^\/entry\//)){
          if(pn.match(/^\/entry\/\d+\/comment\//)) return 'comment';
          return 'entry';}
        return 'other';
      })(String(loc.pathname)));
    
    
      ////////////////////////////////////////////////////////////
      
      var p = function p() {
        if ( !DEBUG ) return;
        con.log.apply(con, arguments);};
    
    
      var forEach = function forEach(a,f){
        if(!a) return;
        for (var i=0,l=a.length;i<l;i++){
          if(f(a[i]) === false) break;}};
    
      var gensym = (function(){
        var rnd = function(){return Math.floor(Math.random() * (1<<30)).toString(16);};
        var cnt = 0;
        var prefix = 'metabsym_' + Date.now().toString(16) + '_' + rnd() + '_';
        return function gensym(){ return prefix + (cnt++).toString(16); };})();
    
      var receiver = function receiver(cb){
        var name = gensym();
        win[name] = function(){
          delete win[name];
          cb.apply(this, arguments);};
        return name;};
    
    
      var jsonp = function jsonp(url, opt){
        var query = '';
        for(var f in opt){
          var v = opt[f];
          if(v instanceof Function) v = receiver(v);
          query += '&' + encodeURIComponent(f) + '=' + encodeURIComponent(v);}
        url += query.replace(/^./, url.match(/\?/) ? '&' : '?');
    
        var script = doc.createElement('script');
        script.src = url;
        script.type = 'text/javascript';
        doc.querySelector('head').appendChild(script);};
    
      ////////////////////////////////////////////////////////////
    
      var withHatena = function(cb){(function(){
        if(typeof(win.Hatena) === 'undefined'){
          setTimeout(arguments.callee, 100);
          return;}
        cb(win.Hatena);})();};
    
      var withStar = function(cb){
        withHatena(function(Hatena){(function(){
          if(typeof(Hatena.Star) === 'undefined'){
            setTimeout(arguments.callee, 100);
            return;}
          cb(Hatena.Star, Hatena);})();});};
    
      var withBookmark = function(cb){
        withHatena(function(Hatena){(function(){
          if(typeof(Hatena.Bookmark) == 'undefined'){
            setTimeout(arguments.callee, 100);
            return;}
          cb(Hatena.Bookmark, Hatena);})();});};
    
      ////////////////////////////////////////////////////////////
      var profileIcon = function(user){
        var a  = doc.createElement('a');
        a.href      = HATEB_DOMAIN + '/' + user + '/';
        a.target    = '_blank';
        a.title     = user;
        a.className = 'profile-icon';
        var icon = doc.createElement('img');
        icon.src =
          ICON_DOMAIN + '/' + user.substring(0,2) + '/' + user +
          '/profile_l.gif';
        icon.alt = user;
        a.appendChild(icon);
        return a; };
    
      ////////////////////////////////////////////////////////////
      var PageEid = (function(){
        var ucl = doc.querySelector('a.user-comment-link');
        if(!ucl) return null;
        return String(ucl.href).replace(/^\/entry\//,'').replace(/\/comments\/.*/,'');});
    
      var domifyIcon = function(bm){
        return profileIcon(bm.user);};
    
      var domifyUsername = function(bm, entry){
        var a  = doc.createElement('a');
        var date    = String(bm.timestamp).replace(/ .+$/,'').replace(/\//g,'');
        a.href      = '/' + bm.user + '/' + date + '#bookmark-' + entry.eid ;
        a.target    = '_blank';
        a.title     = bm.user;
        a.className = 'username';
        a.appendChild(doc.createTextNode(bm.user));
        return a;};
    
      var domifyTags = function(bm){
        var tags = bm.tags || [];
        if(tags.length < 1) return doc.createDocumentFragment();
        var tagSpan = doc.createElement('span');
        tagSpan.className = 'tags';
        forEach(tags , function(tagName){
          var tagLink = doc.createElement('a');
          tagLink.className = 'user-tag';
          tagLink.href =
            HATEB_DOMAIN + '/' + bm.user + '/' + encodeURIComponent(tagName);
          tagLink.appendChild(doc.createTextNode(tagName));
          tagSpan.appendChild(tagLink);});
        return tagSpan;};
    
      var domifyComment = (function(){
    
        var reURL = (function(){
          var RE_HEX    = '%[a-fA-F0-9][a-fA-F0-9]';
          var RE_SCHEME = '[a-z]+:';
          var RE_CHAR   = '(?:[a-zA-Z0-9\-_\.]|' + RE_HEX + ')';
          var RE_STR    = RE_CHAR + '+';
          var RE_STR0   = RE_CHAR + '*';
          var RE_HOST   = RE_STR;
          var RE_PATH   = '(?:/(?:' + RE_STR + '/)*' + RE_STR0 + ')';
          var RE_QV     = RE_STR0 + '=' + RE_STR0;
          var RE_QUERY  = '(?:\\?(?:(?:' + RE_QV + '&)*'+RE_QV+')?|'+RE_QV+')';
          var RE_HASH   = '(?:#' + RE_STR0 + ')';
          var RE_URL    = 'https?://' + RE_HOST + RE_PATH +'?' +
                RE_QUERY + '?' + RE_HASH + '?';
          return  new RegExp('(.*?)(' + RE_URL + ')(.*)');
        })();
    
        return function(bm){
          var comment = bm.comment;
          var R = doc.createElement('span');
          R.className = 'comment';
          while(comment.length > 0){
            var match = comment.match(reURL);
            if(!match){
              if(comment.length > 0) R.appendChild(doc.createTextNode(comment));
              break;
            }
            var head = match[1];
            var url  = match[2];
            comment = match[3];
            if(head.length > 0) R.appendChild(doc.createTextNode(head));
            var a = doc.createElement('a');
            a.href = url;
            a.target = '_blank';
            a.appendChild(doc.createTextNode(url));
            R.appendChild(a);
          }
          return R;};})();
    
      var domifyMeta = function(bm, entry){
        var meta = doc.createElement('div');
        meta.className = 'user-comment-meta';
    
        var plink = doc.createElement('a');
        plink.className = 'user-comment-link';
        plink.href  = '/entry/' + entry.eid + '/comment/' + bm.user;
        plink.title = 'パーマリンク';
        plink.target = '_blank';
        plink.appendChild(doc.createTextNode('リンク'));
        meta.appendChild(plink);
    
        var tspan = doc.createElement('span');
        tspan.className = 'timestamp';
        tspan.appendChild(doc.createTextNode(bm.timestamp));
        meta.appendChild(tspan);
    
        var star = doc.createElement('span');
        star.className = 'hatena-bookmark-star';
        meta.appendChild(star);
        return meta;};
    
      var domifyCount = function(entry){
        var count = entry.count;
        var frgn = doc.createDocumentFragment();
        var a = doc.createElement('a');
        a.className = 'metab-count';
        a.href = entry.entry_url;
        a.target = "_blank";
        a.appendChild(doc.createTextNode(String(count) + " users"));
        frgn.appendChild(a);
        var bmc = (entry.bookmarks || []).length;
        if(count != bmc){
          var span = doc.createElement('span');
          span.className = 'metab-count-detail';
          span.appendChild(doc.createTextNode(String(bmc) + " comments + " +
                                              String(count - bmc)));
          frgn.appendChild(span);}
        return frgn;};
    
      var domifyBookmarks = function(entry){
        var node = doc.createDocumentFragment();
        if((entry.bookmarks || []).length < 1) return node;
        var comments   = doc.createElement('ul');
        comments.className = 'comments';
        var noComments = doc.createElement('ul');
        noComments.className = 'no-comments';
        forEach(entry.bookmarks, function(bm){
          var li = doc.createElement('li');
          li.className = 'metab';
          var headPart = doc.createElement('span');
          headPart.className = 'head-part';
          headPart.appendChild(domifyIcon(    bm, entry));
          headPart.appendChild(domifyUsername(bm, entry));
          li.appendChild(headPart);
          li.appendChild(domifyTags(    bm, entry));
          li.appendChild(domifyComment( bm, entry));
          li.appendChild(domifyMeta(    bm, entry));
          (String(bm.comment).length > 0 // || tags.length > 0
           ? comments : noComments).appendChild(li);});
        if(comments.firstChild)  node.appendChild(comments);
        if(noComments.firstChild) node.appendChild(noComments);
        return node;};
    
      var domifyTitle = function(entry, title){
        if(!(title && title.length > 0)) return doc.createDocumentFragment();
        var node = doc.createElement('div');
        node.className = 'metab-title';
        var tspan = doc.createElement('span');
        tspan.appendChild(doc.createTextNode(title));
        tspan.className = 'title';
        node.appendChild(tspan);
        node.appendChild(domifyCount(     entry));
        return node; };
    
      var domifyEntry = function(entry, title){
        if((entry || {count:0}).count < 1) return doc.createDocumentFragment();
        var node = doc.createElement('div');
        node.className = 'metab-container';
        node.appendChild(domifyTitle(entry, title));
        node.appendChild(domifyBookmarks( entry));
        return node;};
    
      var addStars = function(node){ withStar(function(Star, Hatena){
        var entries = [];
        forEach(node.querySelectorAll('li.metab') || [], function(li){
          var uri = li.querySelector('a.username').href;
          //        var uri = li.querySelector('a.user-comment-link').href;
          var sc = li.querySelector('.hatena-bookmark-star');
          var cc = doc.createElement('span');
          sc.parentNode.insertBefore(cc, sc);
          var earg = {
            entryNode:         li,
            uri:               uri,
            title:             Star.EntryLoader.scrapeTitle(li) || '',
            comment_container: cc,
            star_container:    sc};
          var e = new Star.Entry(earg);
          e.showButtons();
          entries.push(e);});
    
        //
        var receive = function(res){
          var entries_ = res.entries;
          var encodedUriToEntryInfoMap = {};
          if (!entries_) entries_ = [];
          forEach(entries_, function(entryInfo){
            if ( !entryInfo.uri ) return;
            var eURI = entryInfo.eURI;
            if ( !eURI ) eURI = entryInfo.eURI = encodeURIComponent( entryInfo.uri );
            encodedUriToEntryInfoMap[eURI] = entryInfo;});
          forEach(entries, function(e){
            if ( e.hasBoundToStarEntry() ) return;
            if ( !e.eURI ) e.eURI = encodeURIComponent(e.uri);
            var entryInfo = entryInfo = encodedUriToEntryInfoMap[e.eURI];
            if ( entryInfo ) e.bindStarEntry( entryInfo );
            if (typeof(e.can_comment) == 'undefined') e.setCanComment(res.can_comment);
            e.showStars();
            e.showCommentButton();});
          if (res.rks) {
            if (!Hatena.Visitor || typeof(Hatena.Visitor) == 'undefined') {
              Hatena.Visitor = {};}
            if (!Hatena.Visitor.RKS) {
              Hatena.Visitor.RKS = res.rks;}}
          Hatena.Star.User.RKS.ready(res.rks);};
    
        // getStarEntries
        (function(){
          if (!entries.length) return;
          var jsonpBase = Star.BaseURL.replace(
              /^http:/, Star.BaseURLProtocol) + 'entries.json?';
          var url = jsonpBase;
          for (var i = 0; i < entries.length; i++) {
            if (url.length > JSONP_MAX_BYTES) {
              jsonp(url, {callback: receive});
              url = jsonpBase;}
            url += 'uri=' + encodeURIComponent(entries[i].uri) + '&';}
          if (!Hatena.Visitor) url += 'timestamp=1';
          jsonp(url, {callback: receive});})();});};
    
      var addBehaviors = function(node){
        if(! node.tagName) return node;
        addStars(node);
        return node;};
    
    
      ////////////////////////////////////////////////////////////
      var metabToDOM, receivedMetab, reserveMetab;
    
      (function(){
        var entries = {};
    
        metabToDOM = function(entry, uri, title){
          var slot = entries[uri];
          var node = slot.dom || (slot.dom = node = domifyEntry(entry,title));
          return addBehaviors(node.cloneNode(true));};
    
        receivedMetab = function(entry, uri, title){
          var slot = entries[uri];
          slot.entry = entry || false;
          slot.cont.forEach(function(c){c(entry, uri, title);});};
    
        reserveMetab = function(uri, cont_, title_){
          var slot = (entries[uri] ||
                      (entries[uri] = {entry: null, cont:[], req: false}));
          var title = title_ || '';
          var cont = cont_ instanceof Function ? cont_ : (function(){
            var container = cont_;
            if(container.className == 'metab-popup'){
              var e = new Error();
              console.log(e.stack);
              throw e;
            }
            return function(entry){
              container.appendChild(metabToDOM(entry, uri, title));};})();
          if(slot.entry !== null){
            cont(slot.entry, uri, title);
            return; }
          slot.cont.push(cont || function(){});
          if(slot.req) return;
          slot.req = true;
          jsonp(API_DOMAIN + '/entry/jsonlite/',
                {url: uri,
                 callback: function(entry){receivedMetab(entry, uri, title);}});};})();
    
      var reserveUserMetab = function(user, cont_, title_){
        var uri = HATEB_DOMAIN + '/' + user + '/';
        var title = title_ ||  'metab for id:'+user;
        reserveMetab(uri, cont_, title);};
    
      ////////////////////////////////////////////////////////////
      var popupUserMetab = (function(){
        var popups = {};
        var locker = null;
        var makeLocker = function(){
          var div = doc.createElement('div');
          div.className = 'metab-popup-locker';
          div.addEventListener('click', function(e){
            if(e.target !== div) return;
            hidePopup();});
          return div;};
        var getLocker = function(){ return locker || (locker = makeLocker()); };
    
        var lockPage,
            unlockPage;
        (function(){
          var html = null,        body = null,
              cssTextHtml = null, cssTextBody = null;
          var init = function(){
            if(html) return;
            html = doc.querySelector('html');
            body = doc.body;
            cssTextHtml = html.style.cssText;
            cssTextBody = body.style.cssText;};
          lockPage = function(){
            init();
            html.style.cssText = 'overflow: hidden;';
            body.style.cssText = 'overflow: hidden;';};
          unlockPage = function(){
            init();
            html.style.cssText = cssTextHtml;
            body.style.cssText = cssTextBody;};})();
    
        var showLocker = function(){
          var locker = getLocker();
          lockPage();
          doc.body.appendChild(locker);
          return locker; };
        var hideLocker = function(){
          var locker = getLocker();
          unlockPage();
          doc.body.appendChild(locker);
          return locker;; };
    
        var makePopup = function(user){
          var popup = doc.createElement('div');
          popup.className = 'metab-popup';
          popup.appendChild(profileIcon(user));
          reserveUserMetab(user, function(entry, uri, title){
            var metabdom  = metabToDOM(entry, uri, title);
            if(!popup.querySelector('.metab-container')){
              popup.appendChild(metabdom);}});
          return popup; };
    
        var showPopup = function(popup){
          hidePopup();
          showLocker().appendChild(popup);};
    
        var hidePopup = function(){
          var locker = hideLocker();
          locker.innerHTML = '';
          locker.parentNode.removeChild(locker);};
    
        return function(user){
          var popup = popups[user] || (popups[user]=makePopup(user));
          showPopup(popup); };})();
        
    
      ////////////////////////////////////////////////////////////
    
      var applyStyle = function(){
        var style = doc.createElement('style');
        style.type = 'text/css';
        style.appendChild(doc.createTextNode(CSS));
        doc.querySelector('head').appendChild(style);};
    
    
      var showCommentMetab = function(n){
        if(PageType != 'entry') return;
        var nparent = n.parentNode;
        if(nparent.className == 'metab-popup') return;
        var cl = nparent.querySelector('a.user-comment-link');
        if(!cl) return;
        reserveMetab(cl.href, nparent, 'metab for this comment');
        var user = n.href.match(/([^\/]+)\/$/)[1];
        reserveUserMetab(user, nparent);};
    
    
      ////////////////////////////////////////////////////////////
      var updateStarBehavior = function(n){
        var a = n.parentNode;
        if(String(a.tagName).toLowerCase() != 'a') return;
        n.style.border = 'solid rgba(0,0,0,0.5) 1px';
        n.style.borderRadius = '6px';
        var user = (function(m){return m[1];})(a.href.match(/([^\/]*)\/?$/));
        reserveUserMetab(user, function(entry){
          if(entry){
            n.style.borderColor = 'rgba(255,0,0,0.5)';
            a.addEventListener('click', function(ev){
              ev.stopPropagation();
              ev.preventDefault();
              popupUserMetab(user);}, false);}
          else{
            n.style.border = null;}});};
    
      ////////////////////////////////////////////////////////////
      var observeMutation = function(){
        var modify = function(n, selector, modifier){
          if(n.className == selector) setTimeout(function(){modifier(n);},100);
          forEach(n.querySelectorAll(selector), function(n){
            setTimeout(function(){modifier(n);}, 100);});};
    
        var mo = new win.MutationObserver(function(ms){ms.forEach(function(m){
          switch(m.type){
          case 'childList':{
            forEach(m.addedNodes, function(n){
              if(!n.tagName) return;
              modify(n, '.profile-icon',     showCommentMetab);
              modify(n, '.hatena-star-star', updateStarBehavior);});
            break;}
          case 'attributes':{
            var n = m.target, av;
            switch(m.attributeName){
            case 'class':{
              switch(n.className){
              case 'profile-icon':{
                setTimeout(function(){showCommentMetab(n);},100);
                break;}
              case 'hatena-star-star':{
                updateStarBehavior(n);
                break;}
              default:{
                break;}}
              break;}
            default:{
              break;}}
            break;}
          default: {
            break;}}});});
        mo.observe(doc.body, {
          subtree: true,
          childList: true,
          attributes: true,
          attributeFilter: ['class']});};
    
    
    
      ////////////////////////////////////////////////////////////
      var main = function(){
        applyStyle();
        forEach(doc.querySelectorAll('a.profile-icon'),    showCommentMetab);
        forEach(doc.querySelectorAll('.hatena-star-star'), updateStarBehavior);
        observeMutation();
      };
    
    
      main();
    
    }).call(this);
    
    // Local Variables:
    // mode: hatena-let
    // End:
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2016/08/15 20:22:44 - 2016-08-15
  2. 2016/08/02 21:43:13 - 2016-08-02
  3. 2016/07/11 19:41:45 - 2016-07-11
  4. 2016/07/11 06:44:39 - 2016-07-11
  5. 2016/07/11 03:34:28 - 2016-07-11
  6. 2016/07/11 03:12:04 - 2016-07-11
  7. 2016/07/11 02:59:16 - 2016-07-11
  8. 2016/07/03 09:28:27 - 2016-07-03
  9. 2016/07/02 06:31:25 - 2016-07-02
  10. 2016/07/01 10:22:11 - 2016-07-01
  11. 2016/06/28 10:16:34 - 2016-06-28
  12. 2016/06/27 09:43:45 - 2016-06-27
  13. 2016/06/27 09:41:26 - 2016-06-27
  14. 2016/06/25 17:14:17 - 2016-06-25
  15. 2016/06/18 13:39:29 - 2016-06-18
  16. 2016/06/18 10:40:52 - 2016-06-18
  17. 2016/06/17 06:47:34 - 2016-06-17