/** * share-on-delicious - an Ubiquity command for sharing bookmarks on * delicious.com * * l.m.orchard@pobox.com * http://decafbad.com/ * Share and Enjoy! * * This version is entirely ripped up: status bar delete, non-cookie authorization included. Bon appetit! */ var share_on_delicious_cmd = (function() { return { name: 'delicious', icon: 'http://delicious.com/favicon.ico', description: 'Share the current page as a bookmark on delicious.com', help: Select text on the page to use as notes, or enter your own text after the command word. You can also assign tags to the bookmark with the tagged modifier, and alter the bookmark default page title with the entitled modifier. Note that you must also already be logged in at delicious.com to use this command. .toXMLString(), homepage: 'http://decafbad.com', author: { name: 'Leslie Michael Orchard', email: 'l.m.orchard@pobox.com' }, license: 'MPL/GPL/LGPL', /** * Initialize the command package. Creates the command after doing * a last few bits of wiring. Called at the very end of the file. */ init: function() { this.takes = { quote: noun_arb_text }; this.modifiers = { quoted: noun_arb_text, tagged: noun_arb_text, // tagged: this.noun_type_tags, entitled: noun_arb_text }; CmdUtils.CreateCommand(this); return this; }, /** * Command configuration settings. */ config: { // Base URL for the delicious v1 API api_base: 'https://api.del.icio.us', // Domain and name of the delicious login session cookie. cookie_domain: '.delicious.com', cookie_name: '_user', // ID for the XUL element displaying bookmark info status_bar_id: 'ubiq-delicious-panel' }, // XML NS for XUL elements, used for the status bar panel. XUL_NS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", // Cache of URL info loaded up from Delicious urlinfo: {}, /** * Present a preview of the bookmark under construction during the course * of composing the command. */ preview: function(pblock, input_obj, mods) { var bm = this.extractBookmarkData(input_obj, mods); var user_cookie = this.getUserCookie(); var user_name = (user_cookie) ? user_cookie.split(' ')[0] : ''; var chars_left = (bm.extended) ? 1000 - bm.extended.length : 1000; var ns = { user_name: user_name, bm: bm, chars_left: chars_left, chars_over: (chars_left < 0) }; var tmpl; if (!bm.description) { tmpl = this.templates.preview_title_error; } else { tmpl = this.templates.preview_full; } pblock.innerHTML = this.renderE4XTemplate(tmpl, ns); }, /** * Attempt to use the delicious v1 API to post a bookmark using the * command input */ execute: function(input_obj, mods) { var bm = this.extractBookmarkData(input_obj, mods); var user_cookie = this.getUserCookie(); var user_name = (user_cookie) ? user_cookie.split(' ')[0] : ''; if (!bm.description) { displayMessage( "A title is required for bookmarks at delicious.com" ); return false; } if (bm.extended && bm.extended.length > 1000) { displayMessage( "The bookmark notes are " + Math.abs(1000-bm.extended.length) + " characters " + "too long." ); return false; } this.v1api( '/v1/posts/add', bm, function() { displayMessage('Bookmark "' + bm.description + '" ' + 'shared at delicious.com/' + user_name); }, function() { // TODO: more informative reporting on errors displayMessage('ERROR: Bookmark "' + bm.description + '" ' + ' NOT shared on delicious.com/' + user_name); } ); }, /** * A noun type for suggesting Delicious tags * * TODO: fetch and cache tags from user's account * TODO: use the tag suggestion call on the API */ noun_type_tags: { _name: 'tags', getTags: function() { return [ 'osx', 'apple', 'software', 'testing' ]; }, suggest: function(text, html) { var sugg_tags = this.getTags(); var curr_tags = (''+text).split(' '); var suggestions = []; if (curr_tags.length) { var last_tag = curr_tags.pop(); for (var i=0,tag; tag=sugg_tags[i]; i++) { if ( tag.indexOf( last_tag ) > -1 ) { var sugg_tags = curr_tags.join(' ') + ' ' + tag; suggestions.push( CmdUtils.makeSugg( sugg_tags, sugg_tags, sugg_tags ) ); } } } return suggestions; } }, /** * Fire off a Delicious V1 API request. */ v1api: function(path, data, success, error) { // Build a User-Agent string derived from the browser's, if none // previously defined. if (!this.user_agent) { var mediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]. getService(Components.interfaces.nsIWindowMediator); var win = mediator.getMostRecentWindow(null); this.user_agent = win.navigator.userAgent + ";Ubiquity-share-on-delicious"; } // Inject the user auth cookie into the API parameters. data['_user'] = this.getUserCookie(); jQuery.ajax({ type: 'POST', url: this.config.api_base + path, data: this.buildQueryString(data), beforeSend: function(req) { req.setRequestHeader("User-Agent", this.user_agent); }, success: success || function() { displayMessage('Delicious API call ' + path + ' succeeded.'); }, error: error || function() { displayMessage('ERROR: Delicious API call ' + path + ' failed!'); } }); }, /** * Given input data and modifiers, attempt to assemble data necessary to * post a bookmark. */ extractBookmarkData: function(input_obj, mods) { return { url: Application.activeWindow.activeTab.uri.spec, description: mods.entitled.text || context.focusedWindow.document.title, extended: input_obj.text + ( mods.quoted.text ? ' "' + mods.quoted.text + '"' : '' ), tags: mods.tagged.text }; }, /** * Dig up the Delicious login session cookie. */ getUserCookie: function() { var cookie_mgr = Components.classes["@mozilla.org/cookiemanager;1"] .getService(Components.interfaces.nsICookieManager); var iter = cookie_mgr.enumerator; while (iter.hasMoreElements()) { var cookie = iter.getNext(); if( cookie instanceof Components.interfaces.nsICookie && cookie.host.indexOf(this.config.cookie_domain) != -1 && cookie.name == this.config.cookie_name) { return decodeURIComponent(cookie.value); } } }, /** * Given an object, build a URL query string */ buildQueryString: function(data) { var qs = []; for (k in data) if (data[k]) qs.push( encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) ); return qs.join('&'); }, /** * Calculate an MD5 hash for a given string */ md5: function(str) { var Ci = Components.interfaces; var Cc = Components.classes; var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter'] .createInstance(Ci.nsIScriptableUnicodeConverter); converter.charset = 'UTF-8'; var result = {}; var data = converter.convertToByteArray(str, result); var ch = Cc["@mozilla.org/security/hash;1"] .createInstance(Ci.nsICryptoHash); ch.initWithString('md5'); ch.update(data, data.length); var hash = ch.finish(false); return [ ('0' + (hash.charCodeAt(i).toString(16)) ).slice(-2) for (i in hash) ].join(""); }, /** * Given an XML doc, render as a template. * * Since both JST and E4X use { and } functionally, [[ and ]] are * used as a convention for JST's syntax. */ renderE4XTemplate: function(xml, ns) { var tmpl = xml.toXMLString().replace(/\[\[/g,'{').replace(/\]\]/g, '}'); return CmdUtils.renderTemplate(tmpl, ns); }, /** * Templates for producing previews and other content. */ templates: { // Preview displayed when no user login found. preview_user_error:

No active user found - log in at delicious.com to use this command.

, // Preview displayed when title missing. preview_title_error:

A title is required for bookmarks on delicious.com

, // Preview showing full bookmark data. preview_full:

Share a bookmark at delicious.com/$[[user_name]]

$[[bm.description]] $[[bm.url]] [[if bm.extended]]
$[[bm.extended]]
$[[chars_left]] characters $[[ (chars_over) ? 'over' : 'remaining' ]]
[[/if]] [[if bm.tags]]
tags: $[[bm.tags]]
[[/if]]

}, /** * Original flat 4-color delicious logo. */ DEL_ICON_URL_OLD: .toString().replace(/\s/g,''), /** * Sexier gradient delicious logo. */ DEL_ICON_URL: .toString().replace(/\s/g,''), // I hate trailing commas. EOF:null }; })().init(); // Initialize the command, register handlers for startup and page load. // TODO: Find a way to roll all of this into init()? share_on_delicious_cmd.init(); function startup_share_on_delicious_cmd() { share_on_delicious_cmd.onStartup(); } function pageLoad_share_on_delicious_cmd(doc) { share_on_delicious_cmd.onPageLoad(doc); }