Source for "NQS (Netvibes QuickShare) v1.1"

By Julien Carosi
Has 1 other script.


Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// NQS
// version 1.1
// 2007-05-31
// Copyright (c) 2007, Julien CAROSI (jcarosi@gmail.com)
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey Firefox extension : http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Click on install.
//
// --------------------------------------------------------------------
// ==UserScript==
// @name          NQS
// @namespace     http://userscripts.org/scripts/show/7573
// @description   Adds a QuickShare button to Netvibes, like the one found in Google Reader and allows you to share quickly items on delicious. Works with Netvibes Coriander.
// @include       http://www.netvibes.com/*
// ==/UserScript==
//
// --------------------------------------------------------------------
// This script allows you to share on delicious in one click an article you're reading on Netvibes.
// As it works with delicious, you do not need to publish a particular feed to share articles.
// You only need to supply one or several tags in order to group items on delicious.
//
// As articles are shared on delicious, you can, for instance, show together your netvibes shared articles and other bookmarks on your blog, using only one feed.
//
// --------------------------------------------------------------------
// Tested on Firefox 1.5 and 2.0
// --------------------------------------------------------------------
// Version 1.0 beta : initial release
// Version 1.1      : new : added support for Coriander "View site" function
//                    new : placed favicon.ico in source code
//                    new : added some code optimizations
//                    fixed : when viewing several articles using "Read all", correct article is shared
// Note : when clicking "Read all" in "view site" mode, the QS button only allows to share first article

// DOM related funcs
function getFirstElementMatchingClassName(root,tag,class)
{
  var elements=root.getElementsByTagName(tag); var i=0;
  while (elements[i] && !elements[i].className.match(class)) { i++; }
  return ((!elements[i]) ? null : (elements[i]));
}

function getElementsByClassName(root,tag,class)
{
  var elements = root.getElementsByTagName(tag);
  var results = new Array();
  for(var i=0; i<elements.length; i++) { if(elements[i].className.indexOf(class)>-1) { results.push(elements[i]); } }
  return (results);
}

function findParentNode(el,tag,class)
{
  el=el.parentNode;
  if (arguments.length==3)
  {
    // Find first element's parent node matching tag and className
    while (el.nodeName.toLowerCase()!='body' && (el.nodeName.toLowerCase()!=tag || (el.className!=class && el.className.indexOf(class+' ')==-1))) { el=el.parentNode; }
    return ((el.nodeName.toLowerCase()!='body') ? el : false);
  }
  else
  {
    // Find first element's parent node matching tag
    while (el.nodeName.toLowerCase()!='body' && el.nodeName.toLowerCase()!=tag) { el=el.parentNode; }
    return ((el.nodeName.toLowerCase()!='body') ? el : false);
  }	
}

function addStyles(css)
{
  var head=document.getElementsByTagName('head')[0];
  if (head)
  {
    var style=document.createElement('style');
    style.type='text/css';
    style.innerHTML=css;
    head.appendChild(style);
  }
}

function onAvailable(el,f,poll_interval,poll_timeout)
{
  // Wait for an object (identified by string or returned by a function) to be loaded in DOM, then fire a custom function
  this.poll_interval=poll_interval ? poll_interval : 500;
  this.poll_timeout=poll_timeout ? poll_timeout : 10000;
  this.obj=el;
  this.func=f;
  this.onAvailableID = window.setInterval(this.poll,this.poll_interval,this);
  this.timeoutID = window.setTimeout(this.timeout,this.poll_timeout,this);
}

onAvailable.prototype.poll = function(scope)
{
  if (typeof(scope.obj)=='string')
  {
    if (document.getElementById(scope.obj))
    {
        scope.stopPolling();
        scope.func();
    }
  }
  else if (typeof(scope.obj)=='function')
  {
    if (scope.obj())
    {
       scope.stopPolling();
       scope.func();
    }
  }
  else
  {
    alert('onAvailable : object must be a string or a function !');
  }
}

onAvailable.prototype.stopPolling = function()
{
  window.clearInterval(this.onAvailableID);
  window.clearTimeout(this.timeoutID);
}

onAvailable.prototype.timeout = function()
{
  if (this.obj=='string') { alert('object \''+this.obj+'\' not available !'); }
  else { alert('object not available !'); }
}

// Initialize NQS
// This tests ensures that the script is loading only for the main netvibes page, avoiding loading for instance for iframes on the same domain
if (window.location=='http://www.netvibes.com/') { var NQSInstance = new NQS(); }

function NQS()
{
  // Check if GM_xmlhttpRequest is available
  if (!GM_xmlhttpRequest)
  {
    alert('Please upgrade to the latest version of Greasemonkey.');
    return;
  }
    
  this.init = function()
  {
    // Retrieve delicious settings from GM user config. The script is disabled if one of the settings are not supplied.
    if (!this.delicious.settings.get()) { return; }
       
    // Initialize user menu
    this.initUserMenu();
    
    // Initialize the netvibes related part of the script
    this.netvibes.init();
  };
 
  this.initUserMenu = function()
  {
    var th=this;
    GM_registerMenuCommand("Enter delicious login",function() { th.delicious.settings.changeLogin(); });
    GM_registerMenuCommand("Enter delicious quicksharing tag(s)",function() { th.delicious.settings.changeTags(); });
  };
 
  this.netvibes =
  {
    nqs : this,
    feedReaderContentFrame : null,
    icon : 'data:image/x-icon,%00%00%01%00%01%00%10%10%00%00%00%00%00%00h%05%00%00%16%00%00%00(%00%00%00%10%00%00%00%20%00%00%00%01%00%08%00%00%00%00%00%40%01%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%FF%FF%FF%00%FF%00%00%00%DD%DD%DD%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%03%03%03%03%03%03%03%00%00%00%00%00%00%00%00%03%03%03%03%03%03%03%03%00%00%00%00%00%00%00%00%03%03%03%03%03%03%03%03%00%00%00%00%00%00%00%00%03%03%03%03%03%03%03%03%00%00%00%00%00%00%00%00%03%03%03%03%03%03%03%03%00%00%00%00%00%00%00%00%03%03%03%03%03%03%03%03%00%00%00%00%00%00%00%00%03%03%03%03%03%03%03%03%00%00%00%00%00%00%00%00%03%03%03%03%03%03%03%03%01%01%01%01%01%01%01%01%02%02%02%02%02%02%02%02%01%01%01%01%01%01%01%01%02%02%02%02%02%02%02%02%01%01%01%01%01%01%01%01%02%02%02%02%02%02%02%02%01%01%01%01%01%01%01%01%02%02%02%02%02%02%02%02%01%01%01%01%01%01%01%01%02%02%02%02%02%02%02%02%01%01%01%01%01%01%01%01%02%02%02%02%02%02%02%02%01%01%01%01%01%01%01%01%02%02%02%02%02%02%02%02%01%01%01%01%01%01%01%01%02%02%02%02%02%02%02%02%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00',
    
    init : function()
    {
      // Add needed css styles
      addStyles(".QSButton { margin-left: 10px; vertical-align: middle; } .QSButton:hover { cursor: pointer;} #NetvibesQuickSharePanel { z-index: 99999; position: fixed; top: 0; right: 0; width: 80px; height: 25px; line-height: 25px; padding: 0 0 0 5px; background-color: #FF0000; color: #000000; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; font-weight: bold; }");

      // Attaches an interval that waits for Netvibes App to be available
      var th=this;
      var onAppAvailable = new onAvailable('application',function() { th.attach() },500,10000);
    },

    attach : function()
    {
      var th=this;
      unsafeWindow.App.FeedReader.watch('isOpen',function(id,oldval,newval) { return (th.openStateChange(id,oldval,newval)); } );
    },

    openStateChange : function(id,oldval,newval)
    {
      if (newval)
      {
        // Needed for closure below
        var th=this;
      
        // Will be used several times, save it
        this.feedReaderContentFrame=document.getElementById('feedReaderContentFrame');
        
        // assign addQuickShareButton() to headlines on the left, attaches to the div and uses event bubbling in order for netvibes' code to fire first
        var headlines=getElementsByClassName(document.getElementById('feedReaderHeadlinesFrame'),'div','headline');
        for (var i=0;i<headlines.length;i++) { headlines[i].addEventListener('click',function() { th.addQuickShareButtonWhenArticleAvailable(); },false); }
        
        // Catch clicks on "view site" button
        document.getElementById('btShowPage').addEventListener('click',function() { th.catchViewSite(); },false);
        
        // Assign addQuickShareButton() to "Read All" button
        document.getElementById('FeedreaderReadAll').parentNode.addEventListener('click',function() { th.addQuickShareButtonWhenArticleAvailable(); },false);

        // Adds the QuickShare button to the main headline's title
        this.addQuickShareButtonWhenArticleAvailable();
      }
      // Prevents watch from changing the value of isOpen
      return newval;
    },

    addQuickShareButtonWhenArticleAvailable : function()
    {
      // Wait for article to be loaded in DOM, then add QS button.
      var th=this;
      var contentInsideFrame=getFirstElementMatchingClassName(this.feedReaderContentFrame,'div','contentInsideFrame');
      var onArticleAvailable = new onAvailable(function() { return(getElementsByClassName(contentInsideFrame,'div','title')); },function() { th.addQuickShareButtons(); },10,3000);        
    },
    
    addQuickShareButtons : function()
    {
    	// Add QS button to article title, and to feed reader if in "view site" mode.
        this.addQuickShareButtonToTitle();
        
        if (this.inViewSiteMode())
        {
          // Reader initially in "view site" mode
          this.feedReaderShowQSButton();
        }
    },
    
    addQuickShareButtonToTitle : function()
    {
      // Adds share button(s) to headlines' title
      var contentInsideFrame=getFirstElementMatchingClassName(this.feedReaderContentFrame,'div','contentInsideFrame');
      var headlines=getElementsByClassName(contentInsideFrame,'div','title');
      for (var i=0;i<headlines.length;i++) { this.addQSButton(headlines[i].getElementsByTagName('h2')[0]); }
    },
    
    feedReaderAddQSButton : function()
    {
      var header=document.getElementById('feedReaderFrame').getElementsByTagName('h2')[0];
      var button=this.addQSButton(header);
      button.id='feedReaderQSButton';
    },
    
    feedReaderShowQSButton : function()
    {
      var button=document.getElementById('feedReaderQSButton');
      if (button) { button.style.visibility='visible'; }
      else { this.feedReaderAddQSButton(); }
    },
    
    feedReaderHideQSButton : function()
    {
      document.getElementById('feedReaderQSButton').style.visibility='hidden';
    },
    
    catchViewSite : function()
    {
      // Catch clicks on "view site"
      if (this.inViewSiteMode()) { this.feedReaderShowQSButton(); }
      else { this.feedReaderHideQSButton(); }
    },

    inViewSiteMode : function()
    {
      // Returns true if in "View site" mode
      return (this.feedReaderContentFrame.getElementsByTagName('iframe').length>0);
    },
    
    addQSButton : function(el)
    {
      // Adds a quickshare button to an element
      var th=this;
      var button=document.createElement('img');
      button.setAttribute('src',this.icon);
      button.setAttribute('alt','QS!');
      button.className='QSButton';
      el.appendChild(button);
      button.addEventListener('click',function(e) { th.quickShare(e); },true);
      return button;          
    },
    
    quickShare : function(e)
    {
      // Share article on delicious
      if (this.inViewSiteMode()) { var h2=this.feedReaderContentFrame.getElementsByTagName('h2')[0]; }
      else { var h2=findParentNode(e.target,'h2'); }
      
      var a=h2.getElementsByTagName('a')[1];
      var title=a.innerHTML;
      var url=a.getAttribute('href');
      this.nqs.delicious.share.share({url: url,title: title});
    },
      
    initTimeout : function(scope)
    {
      window.clearInterval(scope.onAvailableID);
      alert("Could not attach GM script to Netvibes app !");
    }
  };   
   
  this.delicious =
  {
    settings :
    {
      login : null,
      defaultTags : '.quickshared',

      get : function()
      {
        return (this.getLogin() && this.getTags());
      },
      
      getLogin : function()
      {
        this.login=GM_getValue('login','');
        // Prompt for delicious login, if needed
        if (!this.validateSetting(this.login)) { return (this.promptForLogin()); }
        else { return true; }
      },
 
      promptForLogin : function()
      {
        var login=window.prompt("First launch, enter your delicious login, please.");
        if (!this.validateSetting(login)) { alert("NetvibesQuickShare will be disabled until you supply your delicious login."); return false; }
        else { this.setLogin(login); return true; }
      },
      
      changeLogin : function()
      {
        var login=window.prompt("Enter your delicious login, please.",this.login);
        if (!login) { alert('Ok, keeping previous login'); }
        else if (!this.validateSetting(login)) { alert('Invalid login, keeping previous one.'); }
        else { this.setLogin(login); }
      },
 
      setLogin : function(login)
      {
        this.login=login;  
        GM_setValue('login',this.login);  
        alert('Delicious login set to : '+this.login);    
      },
      
      getTags : function()
      {
        this.tags=GM_getValue('tags','');
        // Prompt for delicious tags, if needed
        if (!this.validateSetting(this.tags)) { return (this.promptForTags()); }
        else { return true; }
      },
 
      promptForTags : function()
      {
        var tags=window.prompt("First launch, enter your desired delicious quicksharing tag(s), please.",this.defaultTags);
        if (!this.validateSetting(tags)) { alert("NetvibesQuickShare will be disabled until you supply at least one tag for quicksharing."); return false; }
        else { this.setTags(tags); return true; }
      },
      
      changeTags : function()
      {
        var tags=window.prompt("Enter your desired delicious quicksharing tag(s), please.",this.tags);
        if (!tags) { alert('Ok, keeping previous tag(s)'); }
        else if (!this.validateSetting(tags)) { alert('Invalid tag(s), keeping previous one(s).'); }
        else { this.setTags(tags); }
      },
 
      setTags : function(tags)
      {
        this.tags=tags;  
        GM_setValue('tags',this.tags);  
        alert('Delicious quicksharing tag(s) set to : '+this.tags);    
      },

      validateSetting : function(setting)
      {
        return (typeof(setting)=='string' && setting.length>0);
      }
    },

    share :
    {
      urlPrefix : 'http://del.icio.us',
      
      share : function(params)
      {
        this.delicious.infos.show('Sharing...');
        this.formLoad(params);
      },
      
      formLoad : function(params)
      {    
        // Retrieve delicious submit form in order to have the key, so the use of delicious API, and thus of a proxy is not needed
        var th=this;
        var request =
        {
          url : this.urlPrefix+'/'+this.delicious.settings.login+'?v=2&jump=close&tags='+encodeURIComponent(this.delicious.settings.tags)+'&url='+encodeURIComponent(params.url)+'&title='+encodeURIComponent(params.title),
          method : 'GET',
          headers : {'User-Agent' : navigator.userAgent},
          onload : function(response) { th.formLoaded(response); },
          onerror : function(response) { th.formLoadError(response); }
        };
        GM_xmlhttpRequest(request);
      },
 
      formLoaded : function(response)
      {
        if (response.status==200)
        {
          // Import response into the DOM
          var el=document.createElement('div');
          el.innerHTML=response.responseText;
          var form=el.getElementsByTagName('form')[0];
    
          if (form.getAttribute('action').indexOf('login')>-1)
          {
            // Delicious invites the user to log in : the login exists but user is not logged in with it
            this.loginError();
          }
          else
          {
            // Retrieve form infos
            var url=form.getAttribute('action');
            var inputs=form.getElementsByTagName('input');
        
            // Uncheck "private"
            var i=0; while (inputs[i] && inputs[i].getAttribute('name')!='private' && i<inputs.length) { i++; }; if (i<inputs.length) { inputs[i].value=''; }
 
            // Submit the form via XMLHttpRequest
            this.submit({url: url, inputs: inputs});
          }
        }
        else
    {
      // If 404, login doesn't exist
      this.loginError();
    }
      },
 
      formLoadError : function(response)
      {
        // Display status
        this.delicious.infos.hide();
        alert('Error while trying to share this article, please try again ! (Error code '+response.status+')');
      },
 
      submit : function(params)
      {
        // Submit infos to delicious
        var th=this;
        var formdata=new Array();
        for (var i=0;i<params.inputs.length;i++) { formdata.push(params.inputs[i].getAttribute('name')+'='+params.inputs[i].value); }

        var request =
        {
          url : this.urlPrefix+params.url,
          method : 'POST',
          headers : {'User-Agent': navigator.userAgent, 'Content-Type': 'application/x-www-form-urlencoded'},
          data : formdata.join('&'),
          onload : function(response) { th.submitLoaded(response); },
          onerror : function(response) { th.submitError(response); }
        };
        GM_xmlhttpRequest(request);    
      },
      
      submitLoaded : function(response)
      {
        if (response.status==200)
        {
          this.delicious.infos.show('Shared !',true);
        }
        else
        {
      alert('Error while trying to bookmark the article on delicious ! (Error code '+response.status+')');
    }
      },
 
      submitError : function(response)
      {
        this.delicious.infos.hide();
        alert('Error while trying to bookmark the article on delicious ! (Error code '+response.status+')');
      },
      
      loginError : function()
      {
        this.delicious.infos.hide();
        alert('Error ! Please check that you\'re correctly logged in delicious with login : '+this.delicious.settings.login+'. If not, change login in menu.');          
      }
    },
    
    infos :
    {
      show : function(msg,timeout)
      {
        // Create panel if not already in DOM
        if (!this.panel.el)
        {
          // Insert it in DOM
          var div=document.createElement('div');
          div.setAttribute('id',this.panel.id);
    
          // Set its style
          document.getElementsByTagName('body')[0].appendChild(div);
          this.panel.el=document.getElementById(this.panel.id);
        }
    
        // Set panel content
        this.panel.el.innerHTML=msg;
    
        // Check if the panel should be removed automatically after a specified timeout
        if (arguments.length==2 && timeout)
        {
          // Waits for timeout, then hides the panel
          var th=this;
          window.setTimeout(function() { th.hide(); },this.panel.showTimeout);
        }
      },
     
      hide : function()
      {
        this.panel.el.parentNode.removeChild(this.panel.el);
        this.panel.el=null;
      },
      
      panel :
      {
        el : null,      
        id : 'NetvibesQuickSharePanel',
        showTimeout : 1500
      }
    }
  };
 
  this.delicious.share.delicious = this.delicious;
  this.init();
}