├── .gitignore ├── LICENSE ├── README.textile ├── background.html ├── chrome_ex_oauth.html ├── contentscript.js ├── fonts ├── pictos-web-webfont.eot ├── pictos-web-webfont.svg ├── pictos-web-webfont.ttf ├── pictos-web-webfont.woff └── pictos-web.ttf ├── images ├── googleIcon.png ├── michromeformat-logo-16.png ├── michromeformat-logo-48.png ├── michromeformat-logo.png └── microformats-logo-19.png ├── javascripts ├── application.js ├── background.js ├── exporters │ ├── export.js │ ├── hcalendar.export.js │ └── hcard.export.js ├── microformats │ ├── geo.js │ ├── hcalendar.js │ ├── hcard.js │ ├── hrecipe.js │ ├── hreview-aggregate.js │ ├── hreview.js │ ├── json2.min.js │ ├── microformat.js │ ├── microformats-coredefinition.min.js │ ├── microformats-hreviewdefinition.min.js │ ├── microformats-isodate.min.js │ └── microformats-shiv.min.js ├── popup.js └── utils │ ├── PerfectTime.js │ ├── chrome_ex_oauth.js │ ├── chrome_ex_oauth_init.js │ ├── chrome_ex_oauthsimple.js │ └── jquery.min.js ├── manifest.json ├── popup.html └── stylesheets └── popup.css /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Brian Ryckbost 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Michromeformats 2 | 3 | h2. What is it? 4 | 5 | It displays any microformats on the page and supports hCard, hCalendar, hReview, hReview-aggregate and hRecipe. 6 | 7 | hCards and hCalendars can be exported to your computer so you can add contacts to an address book or calendar. Or, connect to your Google account and import contacts to your Google Contacts or Google Calendar. 8 | 9 | Inspiration came from Remy Sharp's awesome "Microformats Bookmarklet":leftlogic.com/lounge/articles/microformats_bookmarklet, which got its inspiration from "Jon Hick's":http://www.hicksdesign.co.uk/journal/a-proposal-for-a-safari-microformats-plugin proposed Safari plugin. 10 | 11 | h3. What are microformats? 12 | 13 | Designed for humans first and machines second, microformats are a set of simple, open data formats built upon existing and widely adopted standards. "Learn more about microformats":http://microformats.org/about 14 | 15 | h2. Benefits 16 | 17 | * Download/Export data to *.vcf for cards or *.ics for events 18 | * Supports hCard, hCalendar, hReview, hReview-aggregate and hRecipe formats 19 | * Easily view and test your microformats while you're building out a site 20 | 21 | h3. Coming soon 22 | 23 | * Better handling of nested hCards 24 | * Firefox's Operator-like features? 25 | * Other format support: 26 | ** hProduct 27 | ** hAudio 28 | ** hResume 29 | ** hAtom 30 | ** better rel-tag support 31 | 32 | h3. Wild and crazy ideas 33 | 34 | * Integrate with other web services, for example 35 | * Add a context menu for downloading a card or event on the page 36 | 37 | h2. Thank You 38 | 39 | h3. Contributors 40 | 41 | "Haampie":https://github.com/haampie for providing the Google Contacts and Calendar integration. 42 | "ThomasArdal":https://github.com/ThomasArdal for providing the hreview-aggregate microformat. 43 | 44 | h3. Icons 45 | 46 | Some of the icons come from "Matthias Pfefferle's Microformat icon set":http://notizblog.org/projects/microformats-icons/. This extension wouldn't be *pretty* without these awesome little icons. The recipe icon comes from the MealKeeper set which can be found at "deviantart.com":http://shlyapnikova.deviantart.com/art/Icon-for-MealKeeper-s-soft-162435451 47 | -------------------------------------------------------------------------------- /background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /chrome_ex_oauth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OAuth Redirect Page 6 | 12 | 13 | 14 | 15 | 16 | 17 |

Redirecting to Google, fasten your seat belts...

18 | 19 | 20 | -------------------------------------------------------------------------------- /contentscript.js: -------------------------------------------------------------------------------- 1 | function discoverMicroformats() { 2 | var hcards = ufShiv.get('hCard', document.body)['microformats']['vcard']; 3 | var hcalendars = ufShiv.get('hCalendar', document.body)['microformats']['vevent']; 4 | var hreviews = ufShiv.get('hReview', document.body)['microformats']['hreview']; 5 | var hreviewaggs = HReviewAggregate.discover(); 6 | var hrecipes = HRecipe.discover(); 7 | var geos = ufShiv.get('geo', document.body)['microformats']['geo']; 8 | 9 | // convert objects into JSON so we can 10 | // pass the arrays to the background page 11 | for(i = 0; i < hcards.length; i++) { 12 | hcards[i] = JSON.stringify(hcards[i]); 13 | } 14 | for(i = 0; i < hcalendars.length; i++) { 15 | hcalendars[i] = JSON.stringify(hcalendars[i]); 16 | } 17 | for(i = 0; i < hreviews.length; i++) { 18 | var zz = JSON.stringify(hreviews[i]); 19 | hreviews[i] = zz; 20 | } 21 | for(i = 0; i < hreviewaggs.length; i++) { 22 | var yy = JSON.stringify(hreviewaggs[i]); 23 | hreviewaggs[i] = yy; 24 | } 25 | for(i = 0; i < hrecipes.length; i++) { 26 | hrecipes[i] = JSON.stringify(hrecipes[i]); 27 | } 28 | for(i = 0; i < geos.length; i++) { 29 | geos[i] = JSON.stringify(geos[i]); 30 | } 31 | 32 | chrome.extension.sendMessage({hcards: hcards, hcalendars: hcalendars, hreviews: hreviews, hreviewaggs: hreviewaggs, hrecipes: hrecipes, geos: geos}); 33 | } 34 | 35 | discoverMicroformats(); 36 | 37 | -------------------------------------------------------------------------------- /fonts/pictos-web-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryckbost/michromeformats/5a54680e2570c499deee48d5266dbee1ffb33292/fonts/pictos-web-webfont.eot -------------------------------------------------------------------------------- /fonts/pictos-web-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG webfont generated by Font Squirrel. 6 | Copyright : Drew Wilson wwwdrewwilsoncom 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /fonts/pictos-web-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryckbost/michromeformats/5a54680e2570c499deee48d5266dbee1ffb33292/fonts/pictos-web-webfont.ttf -------------------------------------------------------------------------------- /fonts/pictos-web-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryckbost/michromeformats/5a54680e2570c499deee48d5266dbee1ffb33292/fonts/pictos-web-webfont.woff -------------------------------------------------------------------------------- /fonts/pictos-web.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryckbost/michromeformats/5a54680e2570c499deee48d5266dbee1ffb33292/fonts/pictos-web.ttf -------------------------------------------------------------------------------- /images/googleIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryckbost/michromeformats/5a54680e2570c499deee48d5266dbee1ffb33292/images/googleIcon.png -------------------------------------------------------------------------------- /images/michromeformat-logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryckbost/michromeformats/5a54680e2570c499deee48d5266dbee1ffb33292/images/michromeformat-logo-16.png -------------------------------------------------------------------------------- /images/michromeformat-logo-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryckbost/michromeformats/5a54680e2570c499deee48d5266dbee1ffb33292/images/michromeformat-logo-48.png -------------------------------------------------------------------------------- /images/michromeformat-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryckbost/michromeformats/5a54680e2570c499deee48d5266dbee1ffb33292/images/michromeformat-logo.png -------------------------------------------------------------------------------- /images/microformats-logo-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryckbost/michromeformats/5a54680e2570c499deee48d5266dbee1ffb33292/images/microformats-logo-19.png -------------------------------------------------------------------------------- /javascripts/application.js: -------------------------------------------------------------------------------- 1 | function unique_geos(array) { 2 | var a = [], l = array.length; 3 | for(var i = 0; i < l; i++) { 4 | for(var j = i+1; j < l; j++) { 5 | if (array[i].latitude === array[j].latitude && array[i].longitude === array[j].longitude) { 6 | j = ++i; 7 | } 8 | } 9 | a.push(array[i]); 10 | } 11 | return a; 12 | } -------------------------------------------------------------------------------- /javascripts/background.js: -------------------------------------------------------------------------------- 1 | // Global accessor that the popup uses. 2 | var hcards = {}; 3 | var hcalendars = {}; 4 | var hreviews = {}; 5 | var hreviewaggs = {}; 6 | var hrecipes = {}; 7 | var geos = {}; 8 | 9 | // Set up OAuth for a secure connection between Google and this plugin 10 | // https://www.google.com/m8/feeds/ is for Google Contacts 11 | // https://www.google.com/calendar/feeds/ is for Google Calendar 12 | var SCOPE = 'https://www.google.com/m8/feeds/ https://www.google.com/calendar/feeds/'; 13 | 14 | var oauth = ChromeExOAuth.initBackgroundPage({ 15 | 'request_url': 'https://www.google.com/accounts/OAuthGetRequestToken', 16 | 'authorize_url': 'https://www.google.com/accounts/OAuthAuthorizeToken', 17 | 'access_url': 'https://www.google.com/accounts/OAuthGetAccessToken', 18 | 'consumer_key': 'anonymous', 19 | 'consumer_secret': 'anonymous', 20 | 'scope': SCOPE, 21 | 'app_name': 'Michromeformats' 22 | }); 23 | 24 | // Log out, can it be even simpler? 25 | function logout(){ 26 | oauth.clearTokens(); 27 | } 28 | 29 | chrome.extension.onMessage.addListener(function (request, sender, sendResponse) { 30 | var tabId = sender.tab.id; 31 | hcards[tabId] = request.hcards; 32 | hcalendars[tabId] = request.hcalendars; 33 | hreviews[tabId] = request.hreviews; 34 | hreviewaggs[tabId] = request.hreviewaggs; 35 | hrecipes[tabId] = request.hrecipes; 36 | geos[tabId] = request.geos; 37 | 38 | // convert JSON back to objects that the popup can use 39 | for (var i = 0; i < hcards[tabId].length; i++) { 40 | hcards[tabId][i] = JSON.parse(hcards[tabId][i]); 41 | } 42 | for (var j = 0; j < hcalendars[tabId].length; j++) { 43 | hcalendars[tabId][j] = JSON.parse(hcalendars[tabId][j]); 44 | } 45 | for (var k = 0; k < hreviews[tabId].length; k++) { 46 | hreviews[tabId][k] = JSON.parse(hreviews[tabId][k]); 47 | } 48 | for (var m = 0; m < hreviewaggs[tabId].length; m++) { 49 | hreviewaggs[tabId][m] = JSON.parse(hreviewaggs[tabId][m]); 50 | } 51 | for (var l = 0; l < hrecipes[tabId].length; l++) { 52 | hrecipes[tabId][l] = JSON.parse(hrecipes[tabId][l]); 53 | } 54 | for (var g = 0; g < geos[tabId].length; g++) { 55 | geos[tabId][g] = JSON.parse(geos[tabId][g]); 56 | } 57 | 58 | // display the icon in the address bar 59 | if (hcards[tabId].length > 0 || hcalendars[tabId].length > 0 || hreviews[tabId].length > 0 || hreviewaggs[tabId].length > 0 || hrecipes[tabId].length > 0 || geos[tabId].length > 0) { 60 | chrome.pageAction.show(tabId); 61 | } 62 | 63 | // call sendResponse with an empty object to allow the request to be cleaned up 64 | sendResponse({}); 65 | }); 66 | -------------------------------------------------------------------------------- /javascripts/exporters/export.js: -------------------------------------------------------------------------------- 1 | var Export = {}; -------------------------------------------------------------------------------- /javascripts/exporters/hcalendar.export.js: -------------------------------------------------------------------------------- 1 | function parseISO(isoString) { 2 | var d = isoString.match(/(\d{4})(-?(\d{2})(-?(\d{2})((T|\s)(\d{2}):?(\d{2})(:?(\d{2})([.]?(\d+))?)?(Z|(([+-])(\d{2}):?(\d{2}))?)?)?)?)?/); 3 | 4 | var theDate = new Date(d[1], 0, 1); 5 | 6 | // - 1: Because JS months are 0-11 7 | if (d[ 3]) { theDate.setMonth( d[ 3] - 1); } 8 | if (d[ 5]) { theDate.setDate( d[ 5]); } 9 | if (d[ 8]) { theDate.setHours( d[ 8]); } 10 | if (d[ 9]) { theDate.setMinutes(d[ 9]); } 11 | if (d[11]) { theDate.setSeconds(d[11]); } 12 | // Must be between 0 and 999), using Paul Sowden's method: http://delete.me.uk/2005/03/iso8601.html 13 | if (d[13]) { theDate.setMilliseconds(Number("0." + d[13]) * 1000); } 14 | var offset = 0; 15 | if (d[16]) { 16 | var offset = (Number(d[17])*60 + Number(d[18])) * 60; 17 | if (d[16] == "+") { offset *= -1; } 18 | } 19 | 20 | offset -= theDate.getTimezoneOffset() * 60; 21 | theDate.setTime(Number(theDate) + (offset * 1000)); 22 | return theDate; 23 | }; 24 | 25 | 26 | Export.HCalendar = { 27 | // 29 | // 31 | // Tennis with Beth 32 | // Meet for a quick lesson. 33 | // 34 | // 35 | // 36 | // 37 | // 38 | 39 | google: function(calendar, callback) { 40 | if (!calendar.summary || !calendar.dtstart) { 41 | throw new Error('Events must have a summary and a start date '); 42 | } 43 | 44 | // Make sure the user is logged in 45 | if (!oauth.hasToken()) { 46 | throw new Error('You must be logged in'); 47 | } 48 | 49 | // Create the XML document to send to Google 50 | var stdXML = "" 51 | + "" 52 | + ""; 53 | 54 | var sendURL = 'https://www.google.com/calendar/feeds/default/private/full'; 55 | 56 | var parser = new DOMParser(); 57 | 58 | // var xml can be used as a DOM document 59 | var xml = parser.parseFromString(stdXML, "text/xml"); 60 | var root = xml.firstChild; 61 | 62 | var title = xml.createElement('title'); 63 | title.appendChild(xml.createTextNode(calendar.summary)); 64 | root.appendChild(title); 65 | 66 | var desc = xml.createElement('content'); 67 | desc.appendChild(xml.createTextNode(calendar.description)); 68 | root.appendChild(desc); 69 | 70 | var transparency = xml.createElement('gd:transparency'); 71 | transparency.setAttribute("value", "http://schemas.google.com/g/2005#event.opaque"); 72 | root.appendChild(transparency); 73 | 74 | var status = xml.createElement('gd:eventStatus'); 75 | status.setAttribute("value","http://schemas.google.com/g/2005#event.confirmed"); 76 | root.appendChild(status); 77 | 78 | var where = xml.createElement('gd:where'); 79 | where.setAttribute('valueString', calendar.location); 80 | root.appendChild(where); 81 | 82 | var when = xml.createElement('gd:when'); 83 | when.setAttribute('startTime', parseISO(calendar.dtstart).toISOString()); 84 | if (calendar.dtend) { 85 | when.setAttribute('endTime', parseISO(calendar.dtend).toISOString()); 86 | } 87 | root.appendChild(when); 88 | 89 | var Serializer = new XMLSerializer(); 90 | 91 | // Now that everything is set, send it to Google Calendar! 92 | oauth.sendSignedRequest(sendURL, callback, { 93 | 'method': 'POST', 94 | 'headers': { 95 | 'GData-Version': '2.0', 96 | 'Content-Type': 'application/atom+xml' 97 | }, 98 | 'body': Serializer.serializeToString(xml) 99 | }); 100 | } 101 | }; -------------------------------------------------------------------------------- /javascripts/exporters/hcard.export.js: -------------------------------------------------------------------------------- 1 | Export.HCard = { 2 | 3 | /** 4 | * Will send an hCard to Google 5 | * For now it is quite hard to do, but I'm sure JSON will be supported one day 6 | * A typical XML structure for adding a contact to Google is: 7 | * 8 | * 9 | * 10 | * Elizabeth 11 | * Bennet 12 | * Elizabeth Bennet 13 | * 14 | * Notes 15 | * 16 | * 17 | * (206)555-1212 18 | * (206)555-1213 19 | * 20 | * Mountain View 21 | * 1600 Amphitheatre Pkwy 22 | * CA 23 | * 94043 24 | * United States 25 | * 26 | * 27 | */ 28 | google: function( card, callback ){ 29 | var i; 30 | 31 | if( !card.fn ){ 32 | throw new Error( 'hCard must have a name' ); 33 | } 34 | 35 | // Make sure the user is logged in 36 | if( !oauth.hasToken() ){ 37 | throw new Error( 'You must be logged in' ); 38 | } 39 | 40 | 41 | // Create the XML document to send to Google 42 | var stdXML = "" 43 | + "" 44 | + ""; 45 | var sendTo = 'https://www.google.com/m8/feeds/contacts/default/full'; 46 | var parser = new DOMParser(); 47 | 48 | // var xml can be used as a DOM document 49 | var xml = parser.parseFromString(stdXML, "text/xml"); 50 | var root = xml.firstChild; 51 | 52 | // Add name 53 | var elName = xml.createElement( 'gd:name' ); 54 | var elFullName = xml.createElement( 'gd:fullName' ); 55 | 56 | elName.appendChild( elFullName ); 57 | elFullName.appendChild( xml.createTextNode( card.fn ) ); 58 | 59 | root.appendChild( elName ); 60 | 61 | // Add email 62 | if( card.email && card.email.length > 0 ){ 63 | 64 | // Add all email addresses 65 | for( i=0; i 0 ){ 84 | for( i=0; i 0 ){ 100 | 101 | // Loop over each address 102 | for( i=0; i 'Dan Webb' 6 | // people[0].urlList => ['http://danwebb.net', 'http://eventwax.com'] 7 | // 8 | // TODO 9 | // 10 | // Fix _propFor to work with old safari 11 | // Find and use unit testing framework on microformats.org test cases 12 | // isue with hcard email? 13 | // More formats: HFeed, HEntry, HAtom, RelTag, XFN? 14 | 15 | Microformat = { 16 | define: function (name, spec) { 17 | var mf = function (node, data) { 18 | Microformat.extend(this, data); 19 | }; 20 | mf.container = name; 21 | mf.format = spec; 22 | mf.prototype = Microformat.Base; 23 | return Microformat.extend(mf, Microformat.SingletonMethods); 24 | }, 25 | SingletonMethods: { 26 | discover: function (context) { 27 | return Microformat.$$(this.container, context).map(function (node) { 28 | return new this(node, this._parse(this.format, node)); 29 | }, this); 30 | }, 31 | _parse: function (format, node) { 32 | var data = {}; 33 | this._process(data, format.one, node, true); 34 | this._process(data, format.many, node); 35 | return data; 36 | }, 37 | _process: function (data, format, context, firstOnly) { 38 | var selection, first; 39 | format = format || []; 40 | format.forEach(function (item) { 41 | if (typeof item == 'string') { 42 | selection = Microformat.$$(item, context); 43 | if (firstOnly && (first = selection[0])) { 44 | data[this._propFor(item)] = this._extractData(first, 'simple', data); 45 | } else if (selection.length > 0) { 46 | data[this._propFor(item)] = selection.map(function (node) { 47 | return this._extractData(node, 'simple', data); 48 | }, this); 49 | } 50 | } else { 51 | for (var cls in item) { 52 | selection = Microformat.$$(cls, context); 53 | if (firstOnly && (first = selection[0])) { 54 | data[this._propFor(cls)] = this._extractData(first, item[cls], data); 55 | } else if (selection.length > 0) { 56 | data[this._propFor(cls)] = selection.map(function (node) { 57 | return this._extractData(node, item[cls], data); 58 | }, this); 59 | } 60 | } 61 | } 62 | }, this); 63 | return data; 64 | }, 65 | _extractData: function (node, dataType, data) { 66 | if (dataType._parse) return dataType._parse(dataType.format, node); 67 | if (typeof dataType == 'function') return dataType.call(this, node, data); 68 | var values = Microformat.$$('value', node); 69 | if (values.length > 0) return this._extractClassValues(node, values); 70 | switch (dataType) { 71 | case 'simple': 72 | return this._extractSimple(node); 73 | case 'url': 74 | return this._extractURL(node); 75 | } 76 | return this._parse(dataType, node); 77 | }, 78 | _extractURL: function (node) { 79 | var href; 80 | switch (node.nodeName.toLowerCase()) { 81 | case 'img': 82 | href = node.src; 83 | break; 84 | case 'area': 85 | case 'a': 86 | href = node.href; 87 | break; 88 | case 'object': 89 | href = node.data; 90 | } 91 | var allowedSchemes = ['http://', 'https://', 'mailto:']; 92 | if (href) { 93 | var goodScheme = false; 94 | for (var i in allowedSchemes) { 95 | if (href.indexOf(allowedSchemes[i]) == 0) { 96 | goodScheme = true; 97 | break; 98 | } 99 | } 100 | if (href.indexOf('mailto:') == 0) { 101 | href = href.replace(/^mailto:/, '').replace(/\?.*$/, ''); 102 | } 103 | if (goodScheme == true) { 104 | return encodeURI(href); 105 | } 106 | } 107 | return ''; 108 | }, 109 | _extractSimple: function (node) { 110 | switch (node.nodeName.toLowerCase()) { 111 | case 'abbr': 112 | return this._coerce(node.title); 113 | case 'img': 114 | return this._coerce(node.alt); 115 | } 116 | return this._coerce(this._getText(node)); 117 | }, 118 | _extractClassValues: function (node, values) { 119 | var value = new String(values.map(function (value) { 120 | return this._extractSimple(value); 121 | }, this).join('')); 122 | var types = Microformat.$$('type', node); 123 | var t = types.map(function (type) { 124 | return this._extractSimple(type); 125 | }, this); 126 | value.types = t; 127 | return value; 128 | }, 129 | _getText: function (node) { 130 | if (node.textContent || node.textContent == "") return node.textContent; 131 | return node.childNodes.map(function (node) { 132 | if (node.nodeType == 3) return node.nodeValue; 133 | else return this._getText(node); 134 | }, this).join('').replace(/\s+/g, ' ').replace(/(^\s+)|(\s+)$/g, ''); 135 | }, 136 | _coerce: function (value) { 137 | var date, number; 138 | if (value == 'true') return true; 139 | if (value == 'false') return false; 140 | return String(value); 141 | }, 142 | _propFor: function (name) { 143 | this.__propCache = this.__propCache || {}; 144 | if (prop = this.__propCache[name]) return prop; 145 | return this.__propCache[name] = name; 146 | }, 147 | _handle: function (prop, item, data) { 148 | if (this.handlers[prop]) this.handlers[prop].call(this, item, data); 149 | } 150 | }, 151 | $$: function (className, context) { 152 | context = context || document; 153 | var nodeList; 154 | if (context == document || context.nodeType == 1) { 155 | if (typeof document.evaluate == 'function') { 156 | var xpath = document.evaluate(".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]", context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 157 | var els = []; 158 | for (var i = 0, l = xpath.snapshotLength; i < l; i++) 159 | els.push(xpath.snapshotItem(i)); 160 | return els; 161 | } else nodeList = context.getElementsByTagName('*'); 162 | } else nodeList = context; 163 | var re = new RegExp('(^|\\s)' + className + '(\\s|$)'); 164 | return Array.filter(nodeList, function (node) { 165 | return node.className.match(re); 166 | }); 167 | }, 168 | extend: function (dest, source) { 169 | for (var prop in source) dest[prop] = source[prop]; 170 | return dest; 171 | }, 172 | Base: {} 173 | }; -------------------------------------------------------------------------------- /javascripts/microformats/microformats-coredefinition.min.js: -------------------------------------------------------------------------------- 1 | if(ufShiv){(function(){var adr={className:"adr",properties:{type:{plural:true,values:["work","home","pref","postal","dom","intl","parcel"]},"post-office-box":{},"street-address":{plural:true},"extended-address":{plural:true},locality:{},region:{},"postal-code":{},"country-name":{}}};ufShiv.add("adr",adr);var hcard={className:"vcard",properties:{adr:{plural:true,datatype:"microformat",microformat:"adr"},agent:{plural:true,datatype:"microformat",microformat:"hCard"},bday:{datatype:"dateTime"},"class":{},category:{plural:true,datatype:"microformat",microformat:"tag",microformat_property:"tag"},email:{subproperties:{type:{plural:true,values:["internet","x400","pref"]},value:{datatype:"email",virtual:true}},plural:true},fn:{virtual:true,virtualGetter:function(context,mfnode){var givenName=context.getElementsByClassName(mfnode,"given-name");var additionalName=context.getElementsByClassName(mfnode,"additional-name");var familyName=context.getElementsByClassName(mfnode,"family-name");var fn="";if(context.getTextContent(givenName)!=undefined){fn+=givenName+" "}if(context.getTextContent(additionalName)!=undefined){fn+=additionalName+" "}if(context.getTextContent(familyName)!=undefined){fn+=familyName+" "}if(fn!==""){return fn.substring(0,fn.length-1)}else{return undefined}}},geo:{datatype:"microformat",microformat:"geo"},key:{plural:true},label:{plural:true},logo:{plural:true,datatype:"anyURI"},mailer:{plural:true},n:{subproperties:{"honorific-prefix":{plural:true},"given-name":{plural:true},"additional-name":{plural:true},"family-name":{plural:true},"honorific-suffix":{plural:true}},virtual:true,virtualGetter:function(context,mfnode){var fn=context.getMicroformatProperty(mfnode,"hCard","fn");var orgs=context.getMicroformatProperty(mfnode,"hCard","org");var given_name=[];var family_name=[];if(fn&&(!orgs||(orgs.length>1)||(fn!=orgs[0]["organization-name"]))){var fns=fn.split(" ");if(fns.length===2){if(fns[0].charAt(fns[0].length-1)==","){given_name[0]=fns[1];family_name[0]=fns[0].substr(0,fns[0].length-1)}else{if(fns[1].length==1){given_name[0]=fns[1];family_name[0]=fns[0]}else{if((fns[1].length==2)&&(fns[1].charAt(fns[1].length-1)==".")){given_name[0]=fns[1];family_name[0]=fns[0]}else{given_name[0]=fns[0];family_name[0]=fns[1]}}}return{"given-name":given_name,"family-name":family_name}}}return undefined}},nickname:{plural:true,virtual:true,virtualGetter:function(context,mfnode){var fn=context.getMicroformatProperty(mfnode,"hCard","fn");var orgs=context.getMicroformatProperty(mfnode,"hCard","org");var given_name;var family_name;if(fn&&(!orgs||(orgs.length)>1||(fn!=orgs[0]["organization-name"]))){var fns=fn.split(" ");if(fns.length===1){return[fns[0]]}}return undefined}},note:{plural:true,datatype:"HTML"},org:{subproperties:{"organization-name":{virtual:true},"organization-unit":{plural:true}},plural:true},photo:{plural:true,datatype:"anyURI"},rev:{datatype:"dateTime"},role:{plural:true},sequence:{},"sort-string":{},sound:{plural:true},title:{plural:true},tel:{subproperties:{type:{plural:true,values:["msg","home","work","pref","voice","fax","cell","video","pager","bbs","car","isdn","pcs"]},value:{datatype:"tel",virtual:true}},plural:true},tz:{},uid:{datatype:"anyURI"},url:{plural:true,datatype:"anyURI"}}};ufShiv.add("hCard",hcard);var hcalendar={className:"vevent",properties:{category:{plural:true,datatype:"microformat",microformat:"tag",microformat_property:"tag"},"class":{values:["public","private","confidential"]},description:{datatype:"HTML"},dtstart:{datatype:"dateTime"},dtend:{datatype:"dateTime",virtual:true,virtualGetter:function(context,mfnode){var dtends=context.getElementsByClassName(mfnode,"dtend");if(dtends.length===0){return undefined}var dtend=context.dateTimeGetter(dtends[0],mfnode,true);var dtstarts=context.getElementsByClassName(mfnode,"dtstart");if(dtstarts.length>0){var dtstart=context.dateTimeGetter(dtstarts[0],mfnode);if(dtstart.match("T")){return context.normalizeISO8601(dtstart.split("T")[0]+"T"+dtend)}}return undefined}},dtstamp:{datatype:"dateTime"},duration:{},geo:{datatype:"microformat",microformat:"geo"},location:{datatype:"microformat",microformat:"hCard"},status:{values:["tentative","confirmed","cancelled"]},summary:{},transp:{values:["opaque","transparent"]},uid:{datatype:"anyURI"},url:{datatype:"anyURI"},"last-modified":{datatype:"dateTime"},rrule:{subproperties:{interval:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"interval")}},freq:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"freq")}},bysecond:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"bysecond")}},byminute:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"byminute")}},byhour:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"byhour")}},bymonthday:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"bymonthday")}},byyearday:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"byyearday")}},byweekno:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"byweekno")}},bymonth:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"bymonth")}},byday:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"byday")}},until:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"until")}},count:{virtual:true,virtualGetter:function(context,mfnode){return context.hCalendar.properties.rrule.retrieve(mfnode,"count")}}},retrieve:function(mfnode,property){var value=ufShiv.internal.textGetter(mfnode);var rrule;rrule=value.split(";");for(var i=0;i0;i--){if(url_array[i]!==""){var tag=context.tag.validTagName(url_array[i].replace(/\+/g," "));if(tag){try{return decodeURIComponent(tag)}catch(ex){return unescape(tag)}}}}}return null}},link:{virtual:true,datatype:"anyURI"},text:{virtual:true}},validTagName:function(tag){var returnTag=tag;if(tag.indexOf("?")!=-1){if(tag.indexOf("?")===0){return false}else{returnTag=tag.substr(0,tag.indexOf("?"))}}if(tag.indexOf("#")!=-1){if(tag.indexOf("#")===0){return false}else{returnTag=tag.substr(0,tag.indexOf("#"))}}if(tag.indexOf(".html")!=-1){if(tag.indexOf(".html")==tag.length-5){return false}}return returnTag}};ufShiv.add("tag",tag);var xfn={altName:"xfn",attributeName:"rel",attributeValues:"contact acquaintance friend met co-worker colleague co-resident neighbor child parent sibling spouse kin muse crush date sweetheart me",properties:{contact:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"contact")}},acquaintance:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"acquaintance")}},friend:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"friend")}},met:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"met")}},"co-worker":{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"co-worker")}},colleague:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"colleague")}},"co-resident":{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"co-resident")}},neighbor:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"neighbor")}},child:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"child")}},parent:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"parent")}},sibling:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"sibling")}},spouse:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"spouse")}},kin:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"kin")}},muse:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"muse")}},crush:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"crush")}},date:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"date")}},sweetheart:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"sweetheart")}},me:{virtual:true,virtualGetter:function(context,propnode){return context.XFN.getXFN(propnode,"me")}},link:{virtual:true,datatype:"anyURI"},text:{virtual:true}},getXFN:function(propnode,relationship){var rel=propnode.getAttribute("rel");if(rel.match("(^|\\s)"+relationship+"(\\s|$)")){return true}return false}};ufShiv.add("XFN",xfn)})()}; -------------------------------------------------------------------------------- /javascripts/microformats/microformats-hreviewdefinition.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | hReview 3 | Copyright (C) 2010 Glenn Jones. All Rights Reserved. 4 | License: http://microformatshiv.com/license/ 5 | Version 0.2 6 | */ 7 | if(ufShiv){(function(){var hreview={className:"hreview",properties:{dtreviewed:{datatype:"dateTime"},description:{},item:{datatype:"custom",customGetter:function(context,propnode){var item;if(propnode.className.match("(^|\\s)vcard(\\s|$)")){item=context.getMicroformat(propnode,"hCard")}else{if(propnode.className.match("(^|\\s)vevent(\\s|$)")){item=context.getMicroformat(propnode,"hCalendar")}else{item={};var fns=context.getElementsByClassName(propnode,"fn");if(fns.length>0){item.fn=context.defaultGetter(fns[0])}var urls=context.getElementsByClassName(propnode,"url");if(urls.length>0){item.url=context.uriGetter(urls[0])}var photos=context.getElementsByClassName(propnode,"photo");if(photos.length>0){item.photo=context.uriGetter(photos[0])}}}for(var i in item){return item}return item}},rating:{datatype:"float"},best:{datatype:"float"},worst:{datatype:"float"},reviewer:{datatype:"microformat",microformat:"hCard"},summary:{},type:{types:["product","business","event","person","place","website","url"]},tag:{plural:true,rel:true,datatype:"microformat",microformat:"tag"},version:{}}};ufShiv.add("hReview",hreview)})()}; -------------------------------------------------------------------------------- /javascripts/microformats/microformats-isodate.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2010 Glenn Jones. All Rights Reserved. 3 | License: http://microformatshiv.com/license/ 4 | */ 5 | function dateFromISO8601(string){var dateArray=string.match(/(\d\d\d\d)(?:-?(\d\d[\d]*)(?:-?([\d]*)(?:[T ](\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(?:Z|(?:([-+])(\d\d)(?::?(\d\d))?)?)?)?)?)?/);if(dateArray[2]&&!dateArray[3]){var d=new Date("01/01/"+dateArray[1]);d.setDate(dateArray[2]);dateArray[2]=d.getMonth()+1;dateArray[3]=d.getDate()}var date=new Date(dateArray[1],0,1);date.time=false;if(dateArray[2]){date.setMonth(dateArray[2]-1)}if(dateArray[3]){date.setDate(dateArray[3])}if(dateArray[4]){date.setHours(dateArray[4]);date.time=true;if(dateArray[5]){date.setMinutes(dateArray[5]);if(dateArray[6]){date.setSeconds(dateArray[6]);if(dateArray[7]){date.setMilliseconds(Number("0."+dateArray[7])*1000)}}}}if(dateArray[8]){if(dateArray[8]=="-"){if(dateArray[9]&&dateArray[10]){date.setHours(date.getHours()+parseInt(dateArray[9],10));date.setMinutes(date.getMinutes()+parseInt(dateArray[10],10))}}else{if(dateArray[8]=="+"){if(dateArray[9]&&dateArray[10]){date.setHours(date.getHours()-parseInt(dateArray[9],10));date.setMinutes(date.getMinutes()-parseInt(dateArray[10],10))}}}if(dateArray[8]){var tzOffset=date.getTimezoneOffset();if(tzOffset<0){date.setMinutes(date.getMinutes()+tzOffset)}else{if(tzOffset>0){date.setMinutes(date.getMinutes()-tzOffset)}}}}return date}function iso8601FromDate(date,punctuation){var string=date.getFullYear().toString();if(punctuation){string+="-"}string+=(date.getMonth()+1).toString().replace(/\b(\d)\b/g,"0$1");if(punctuation){string+="-"}string+=date.getDate().toString().replace(/\b(\d)\b/g,"0$1");if(date.time){string+="T";string+=date.getHours().toString().replace(/\b(\d)\b/g,"0$1");if(punctuation){string+=":"}string+=date.getMinutes().toString().replace(/\b(\d)\b/g,"0$1");if(punctuation){string+=":"}string+=date.getSeconds().toString().replace(/\b(\d)\b/g,"0$1");if(date.getMilliseconds()>0){if(punctuation){string+="."}string+=date.getMilliseconds().toString()}}return string}; -------------------------------------------------------------------------------- /javascripts/microformats/microformats-shiv.min.js: -------------------------------------------------------------------------------- 1 | 2 | var ufShiv={version:0.2,get:function(name,element){var date=[];var nodes=[];var uf=this.internal[name];if(!uf||!element){return this.internal.pack({},"Sorry either the format or element was not found.",this.version)} 3 | if(uf.className){nodes=this.internal.getElementsByClassName(element,uf.className);if((nodes.length===0)&&uf.alternateClassName){var altClass=this.internal.getElementsByClassName(element,uf.alternateClassName);if(altClass.length>0){nodes.push(element)}}}else{if(uf.attributeValues){nodes=this.internal.getElementsByAttribute(element,uf.attributeName,uf.attributeValues)}} 4 | if(name=="hCard"){var items=[];for(var i=0;i0){var resultArray=[];for(var y=0;y0){return resultArray}}else{if(propobj.virtual){return this.getPropertyInternal(mfnode,null,propobj,propname,mfnode)}} 18 | return undefined},getPropertyInternal:function(propnode,parentnode,propobj,propname,mfnode){var result;if(propobj.subproperties){for(var subpropname in propobj.subproperties){var subpropnodes;var subpropobj=propobj.subproperties[subpropname];if(subpropobj.rel===true){subpropnodes=this.getElementsByAttribute(propnode,"rel",subpropname)}else{subpropnodes=this.getElementsByClassName(propnode,subpropname)} 19 | var resultArray=[];var subresult;for(var i=0;i0){result=result||{};if(subpropobj.plural){result[subpropname]=resultArray}else{result[subpropname]=resultArray[0]}}}} 22 | if(!parentnode||((result==undefined)&&propobj.subproperties)){if(propobj.virtual){if(propobj.virtualGetter){result=propobj.virtualGetter(this,mfnode||propnode)}else{result=this.datatypeHelper(propobj,propnode)}}}else{if(result==undefined){result=this.datatypeHelper(propobj,propnode,parentnode);if((result==undefined)&&!propobj.subproperties){if(propobj.virtual&&propobj.virtualGetter){result=propobj.virtualGetter(parentnode)}}}} 23 | return result},preProcessMicroformat:function(in_mfnode){var mfnode;if((in_mfnode.nodeName.toLowerCase()=="td")&&(in_mfnode.getAttribute("headers"))){mfnode=in_mfnode.cloneNode(true);mfnode.origNode=in_mfnode;var headers=in_mfnode.getAttribute("headers").split(" ");for(var i=0;i0){if(!mfnode.origNode){mfnode=in_mfnode.cloneNode(true);mfnode.origNode=in_mfnode} 25 | includes=this.getElementsByClassName(mfnode,"include");var includeId;var include_length=includes.length;for(var y=include_length-1;y>=0;y--){if(includes[y].nodeName.toLowerCase()=="a"){includeId=includes[y].getAttribute("href").substr(1)} 26 | if(includes[y].nodeName.toLowerCase()=="object"){includeId=includes[y].getAttribute("data").substr(1)} 27 | if(in_mfnode.ownerDocument.getElementById(includeId)){includes[y].parentNode.replaceChild(in_mfnode.ownerDocument.getElementById(includeId).cloneNode(true),includes[y])}}} 28 | return mfnode},defaultGetter:function(propnode,parentnode,datatype){function collapseWhitespace(instring){instring=instring.replace(/[\t\n\r ]+/g," ");if(instring.charAt(0)==" "){instring=instring.substring(1,instring.length)} 29 | if(instring.charAt(instring.length-1)==" "){instring=instring.substring(0,instring.length-1)} 30 | return instring} 31 | if((propnode.nodeName.toLowerCase()=="abbr")&&(propnode.getAttribute("title"))){return propnode.getAttribute("title")}else{if((propnode.nodeName.toLowerCase()=="time")&&(propnode.getAttribute("datetime"))){return propnode.getAttribute("datetime")}else{if((propnode.nodeName.toLowerCase()=="img")&&(propnode.getAttribute("alt"))){return propnode.getAttribute("alt")}else{if((propnode.nodeName.toLowerCase()=="area")&&(propnode.getAttribute("alt"))){return propnode.getAttribute("alt")}else{if((propnode.nodeName.toLowerCase()=="textarea")||(propnode.nodeName.toLowerCase()=="select")||(propnode.nodeName.toLowerCase()=="input")){return propnode.value}else{var valueTitles=this.getElementsByClassName(propnode,"value-title");for(var i=valueTitles.length-1;i>=0;i--){if(valueTitles[i].parentNode!=propnode){valueTitles.splice(i,1)}} 32 | if(valueTitles.length>0){var valueTitle="";for(var j=0;j=0;x--){if(values[x].parentNode!=propnode){values.splice(x,1)}} 35 | if(values.length>0){var value="";for(var z=0;z0){return s}}}}}} 40 | return undefined},dateTimeGetter:function(propnode,parentnode,raw){function parseTime(time){if(time.match("am")||time.match("a.m.")){time=time.replace("am","");time=time.replace("a.m.","");var times=time.split(":");if(times[0]=="12"){times[0]="00"} 41 | if(times[0].length==1){times[0]="0"+times[0]} 42 | if(times.length>1){time=times.join(":")}else{time=times[0]+":00:00"} 43 | if(times.length==2){time+=":00"}} 44 | if(time.match("pm")||time.match("p.m.")){time=time.replace("pm","");time=time.replace("p.m.","");times=time.split(":");if(times[0]<12){times[0]=parseInt(times[0],10)+12} 45 | if(times[0].length==1){times[0]="0"+times[0]} 46 | if(times.length>1){time=times.join(":")}else{time=times[0]+":00:00"} 47 | if(times.length==2){time+=":00"}} 48 | return time} 49 | var valueTitles=this.getElementsByClassName(propnode,"value-title");if(valueTitles.length>0){var time="";var date="";var value="";var offset="";for(var i=0;i=0;z--){if(values[z].parentNode!=propnode){values.splice(z,1)}} 53 | if(values.length>0){var time="";var date="";var value="";var offset="";for(var y=0;y0){return unescape(propnode[pairs[name]].substring(protocol.length,propnode[pairs[name]].indexOf("?")))}else{return unescape(propnode[pairs[name]].substring(protocol.length))}}} 63 | if(this.matchClass(propnode,"value")){return this.textGetter(parentnode,parentnode)}else{if(!parentnode&&(this.getElementsByClassName(propnode,"type").length>0)){var tempNode=propnode.cloneNode(true);var typeNodes=this.getElementsByClassName(tempNode,"type");for(var i=0;i0){return unescape(mailto.substring("mailto:".length,mailto.indexOf("?")))}else{return unescape(mailto.substring("mailto:".length))}}else{if(this.matchClass(propnode,"value")){return this.textGetter(parentnode,parentnode)}else{if(!parentnode&&(this.getElementsByClassName(propnode,"type").length>0)){var tempNode=propnode.cloneNode(true);var typeNodes=this.getElementsByClassName(tempNode,"type");for(var i=0;i-1){dateString+="Z"} 75 | return dateString},datatypeHelper:function(prop,node,parentnode){var result;var datatype=prop.datatype;switch(datatype){case"dateTime":result=this.dateTimeGetter(node,parentnode);break;case"anyURI":result=this.uriGetter(node,parentnode);break;case"email":result=this.emailGetter(node,parentnode);break;case"tel":result=this.telGetter(node,parentnode);break;case"HTML":result=this.htmlGetter(node,parentnode);break;case"float":var asText=this.textGetter(node,parentnode);if(!isNaN(asText)){result=parseFloat(asText)} 76 | break;case"custom":result=prop.customGetter(this,node,parentnode);break;case"microformat":try{result=this.getMicroformat(node,prop.microformat)}catch(ex){if(ex!="Node is not a microformat ("+prop.microformat+")"){break}} 77 | if(result!=undefined&&this.hasProperties(result)){if(prop.microformat_property){result=result[prop.microformat_property]} 78 | break} 79 | default:result=this.textGetter(node,parentnode);break} 80 | if(prop.values&&(result!=undefined)){var validType=false;for(var value in prop.values){if(result.toLowerCase()==prop.values[value]){result=result.toLowerCase();validType=true;break}} 81 | if(!validType){return undefined}} 82 | return result},getElementsByClassName:function(rootNode,className){var returnElements=[];if(rootNode.getElementsByClassName){var col=rootNode.getElementsByClassName(className);for(var i=0;i' 43 | + '
' 44 | + '

' 45 | + hcard.fn 46 | + '

' 47 | + '
    ' 48 | + '
    ' 49 | + '' 50 | + 'c' 51 | + '' 52 | + '' 53 | + '~' 54 | + '' 55 | + '
    ' 56 | + '
    ' 57 | + ''); 58 | 59 | // fn is required so only output the vcard if it's "valid" 60 | if (hcard.fn) { 61 | 62 | // When the Google icon is clicked, the hcard will be added to Google Contacts 63 | $('.submithcard', html).click(function(){ 64 | bgPage.Export.HCard.google(hcard, function(txt, response){ 65 | if (response.status == 201) { 66 | $('.submithcard span', html).first().replaceWith('3'); 67 | } else { 68 | console.log('FAILED'); 69 | } 70 | }); 71 | return false; 72 | }); 73 | 74 | $("#hcards").append(html); 75 | 76 | if (hcard.org) { 77 | $.each(hcard.org, function(index, org) { 78 | $('#hcard-'+i+' ul').append('
  • '+ org['organization-name'] +'
  • '); 79 | }); 80 | } 81 | 82 | if (hcard.role) { 83 | $.each(hcard.role, function(index, role) { 84 | $('#hcard-'+i+' ul').append('
  • '+ role +'
  • '); 85 | }); 86 | } 87 | 88 | if (hcard.nickname) { 89 | $('#hcard-'+i+' ul').append('
  • Nickname: '+ hcard.nickname[0] +'
  • '); 90 | } 91 | 92 | if (hcard.email) { 93 | $.each(hcard.email, function(index, email) { 94 | $('#hcard-'+i+' ul').append('
  • '+ email['value'] +'
  • '); 95 | }); 96 | } 97 | 98 | if (hcard.url) { 99 | $.each(hcard.url, function(index, url) { 100 | $('#hcard-'+i+' ul').append('
  • '+ url + '
  • '); 101 | }); 102 | } 103 | 104 | if (hcard.tel) { 105 | $.each(hcard.tel, function(index, tel) { 106 | var type = tel.type ? tel.type.toString() + ': ' : ''; 107 | $('#hcard-'+i+' ul').append('
  • '+ type + tel['value'] +'
  • '); 108 | }); 109 | } 110 | 111 | if (hcard.adr) { 112 | var street = hcard.adr[0]["street-address"] ? hcard.adr[0]["street-address"] : ''; 113 | var locality = hcard.adr[0].locality ? hcard.adr[0].locality : ''; 114 | var region = hcard.adr[0].region ? hcard.adr[0].region : ''; 115 | var country = hcard.adr[0]["country-name"] ? hcard.adr[0]["country-name"] : ''; 116 | var postalCode = hcard.adr[0]["postal-code"] ? hcard.adr[0]["postal-code"] : ''; 117 | var mapString = [street, locality, region, country, postalCode]; 118 | 119 | $('#hcard-'+i+' ul').append('
  • '+ street +'
    '+ locality +', '+ region +' '+ country +' '+ postalCode +'
  • '); 120 | } 121 | 122 | if (hcard.photo) { 123 | $.each(hcard.photo, function(index, url) { 124 | $('#hcard-'+i+' ul').append('
  • '); 125 | }); 126 | } 127 | } 128 | }); 129 | 130 | $.each(hcalendars, function(j, hcalendar) { 131 | var hcalHtml = $('
  • ' 132 | + '
    ' 133 | + '

    ' 134 | + hcalendar.summary 135 | + '

    ' 136 | + '
      ' 137 | + '
    • '+ $.fn.strftime($.fn.parseISO(hcalendar.dtstart), '%b %d, %Y at %i:%M%P') +'
    • ' 138 | + '
    ' 139 | + '
    ' 140 | + '' 141 | + 'c' 142 | + '' 143 | + '' 144 | + '~' 145 | + '' 146 | + '' 148 | + '
  • '); 149 | 150 | $("#hcalendars").append(hcalHtml); 151 | 152 | // Handle submit to Google link 153 | $('.submitvevent', hcalHtml).click(function(){ 154 | bgPage.Export.HCalendar.google(hcalendar, function(txt, response){ 155 | if (response.status == 201) { 156 | $('.submitvevent span', hcalHtml).first().replaceWith('3'); 157 | } else { 158 | console.log('FAILED'); 159 | } 160 | }); 161 | return false; 162 | }); 163 | 164 | if (hcalendar.dtend) 165 | $('#hcal-'+j+' ul .start').append(' — '+ $.fn.strftime($.fn.parseISO(hcalendar.dtend), '%b %d, %Y at %i:%M%P')); 166 | 167 | if (hcalendar.location) { 168 | var location = hcalendar.location.adr[0]; 169 | var street = location["street-address"][0] ? location["street-address"][0] : ''; 170 | var locality = location.locality ? location.locality : ''; 171 | var region = location.region ? location.region : ''; 172 | var country = location["country-name"] ? location["country-name"] : ''; 173 | var postalCode = location["postal-code"] ? location["postal-code"] : ''; 174 | var mapString = [street, locality, region, country, postalCode]; 175 | 176 | $('#hcal-'+j+' ul').append('
  • '+hcalendar.location.fn+'
  • '); 177 | } 178 | }); 179 | 180 | $.each(hreviews, function(k, hreview) { 181 | var hreviewHTML = $('
  • ' + (hreview.item.fn || hreview.item.url || hreview.item.photo_url) + '

    • '); 182 | 183 | $("#hreviews").append(hreviewHTML); 184 | 185 | if (hreview.summary) 186 | $('#hreview-'+k+' ul').append('
    • ' + hreview.summary + '
    • '); 187 | 188 | if (hreview.description) 189 | $('#hreview-'+k+' ul').append('
    • '+ hreview.description +'
    • '); 190 | 191 | if (hreview.dtreviewed) 192 | $('#hreview-'+k+' ul').append('
    • Reviewed: '+ hreview.dtreviewed +'
    • '); 193 | 194 | if (hreview.rating) 195 | $('#hreview-'+k+' ul').append('
    • Rating: '+ hreview.rating +'
    • '); 196 | }); 197 | 198 | $.each(hreviewaggs, function(m, hreviewagg) { 199 | var hreviewaggHTML = $('
    • ' + (hreviewagg.count || hreviewagg.item.fn || hreviewagg.item.url || hreviewagg.item.photo_url) + '

      • '); 200 | 201 | $("#hreviewaggs").append(hreviewaggHTML); 202 | 203 | if (hreviewagg.summary) 204 | $('#hreviewagg-'+m+' ul').append('
      • '+ hreviewagg.summary +'
      • '); 205 | 206 | if (hreviewagg.rating) 207 | $('#hreviewagg-'+m+' ul').append('
      • Rating: '+ hreviewagg.rating +'
      • '); 208 | 209 | if (hreviewagg.count) 210 | $('#hreviewagg-'+m+' ul').append('
      • Count: ' + hreviewagg.count + '
      • '); 211 | 212 | if (hreviewagg.votes) 213 | $('#hreviewagg-'+m+' ul').append('
      • Votes: '+ hreviewagg.votes +'
      • '); 214 | }); 215 | 216 | $.each(hrecipes, function(l, hrecipe) { 217 | var hrecipeHTML = $('
      • '+ hrecipe.fn +'

        • Ingredients
        • '); 218 | 219 | $("#hrecipes").append(hrecipeHTML); 220 | 221 | $.each(hrecipe.ingredient, function(m, ingredient) { 222 | $('#hrecipe-'+l+' .ingredients').append('
        • '+ ingredient +'
        • '); 223 | }); 224 | 225 | if (hrecipe.instructions) 226 | $('#hrecipe-'+l+' .other').append('
        • Directions
        • '+ hrecipe.instructions +'
        • '); 227 | 228 | if (hrecipe.duration) 229 | $('#hrecipe-'+l+' .other').append('
        • Total time: '+ hrecipe.duration[0] +'
        • '); 230 | 231 | if (hrecipe.yield) 232 | $('#hrecipe-'+l+' .other').append('
        • Yield: '+ hrecipe.yield +'
        • '); 233 | 234 | if (hrecipe.author) 235 | $('#hrecipe-'+l+' .other').append('
        • From: '+ hrecipe.author +'
        • '); 236 | 237 | }); 238 | 239 | $.each(unique_geos(geos), function(l, geo) { 240 | var geoHTML = $('
        • '+ geo.latitude +', '+ geo.longitude +'

          • '); 241 | 242 | $("#geos").append(geoHTML); 243 | 244 | var mapString = [geo.latitude, geo.longitude]; 245 | $('#geo-'+l+' ul').append('
          • '); 246 | }); 247 | }); -------------------------------------------------------------------------------- /javascripts/utils/PerfectTime.js: -------------------------------------------------------------------------------- 1 | /* 2 | Original implimentation by Why The Lucky Stiff 3 | , described at: 4 | 5 | http://redhanded.hobix.com/inspect/showingPerfectTime.html 6 | 7 | Modified to fit in a single, unobtrusive javascript 8 | class by Mike West 9 | 10 | I'm not sure what the original license chosen for this 11 | code was. I'm assuming it's liberal enough, and this 12 | class is released under the same license, whatever that 13 | turns out to be. 14 | 15 | Heavily refactored by Daniel Morrison 16 | http://github.com/collectiveidea/perfecttime 17 | */ 18 | 19 | /* PerfectTime */ 20 | $(function(){ 21 | var _defaultFormat = '%d %b %Y at %H:%M'; 22 | 23 | $.fn.perfectTime = function(format) { 24 | return this.each( function() { 25 | var fmt = (format)?format:_defaultFormat; 26 | var newDate = $.fn.parseISO($(this).attr('title')); 27 | $(this).html($.fn.strftime(newDate, fmt)); 28 | }); 29 | } 30 | 31 | var isoRegEx = /(\d{4})(-?(\d{2})(-?(\d{2})((T|\s)(\d{2}):?(\d{2})(:?(\d{2})([.]?(\d+))?)?(Z|(([+-])(\d{2}):?(\d{2}))?)?)?)?)?/; 32 | 33 | $.fn.parseISO = function(isoString) { 34 | // Parse ISO 8601 type times (e.g. hCalendar) 35 | // based on Paul Sowden's method, tweaked to match up 36 | // with 'real world' hCalendar usage: 37 | // 38 | // http://delete.me.uk/2005/03/iso8601.html 39 | // 40 | var d = isoString.match(isoRegEx); 41 | 42 | var theDate = new Date(d[1], 0, 1); 43 | 44 | // - 1: Because JS months are 0-11 45 | if (d[ 3]) { theDate.setMonth( d[ 3] - 1); } 46 | if (d[ 5]) { theDate.setDate( d[ 5]); } 47 | if (d[ 8]) { theDate.setHours( d[ 8]); } 48 | if (d[ 9]) { theDate.setMinutes(d[ 9]); } 49 | if (d[11]) { theDate.setSeconds(d[11]); } 50 | // Must be between 0 and 999), using Paul Sowden's method: http://delete.me.uk/2005/03/iso8601.html 51 | if (d[13]) { theDate.setMilliseconds(Number("0." + d[13]) * 1000); } 52 | var offset = 0; 53 | if (d[16]) { 54 | var offset = (Number(d[17])*60 + Number(d[18])) * 60; 55 | if (d[16] == "+") { offset *= -1; } 56 | } 57 | 58 | offset -= theDate.getTimezoneOffset() * 60; 59 | theDate.setTime(Number(theDate) + (offset * 1000)); 60 | return theDate; 61 | }; 62 | 63 | var strftime_funks = { 64 | zeropad: 65 | function( n ){ return n>9 ? n : '0'+n; }, 66 | a: function(t) { return ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][t.getDay()] }, 67 | A: function(t) { return ['Sunday','Monday','Tuedsay','Wednesday','Thursday','Friday','Saturday'][t.getDay()] }, 68 | b: function(t) { return ['Jan','Feb','Mar','Apr','May','Jun', 'Jul','Aug','Sep','Oct','Nov','Dec'][t.getMonth()] }, 69 | B: function(t) { return ['January','February','March','April','May','June', 'July','August', 'September','October','November','December'][t.getMonth()] }, 70 | c: function(t) { return t.toString() }, 71 | d: function(t) { return this.zeropad(t.getDate()) }, 72 | D: function(t) { return t.getDate() }, 73 | H: function(t) { return this.zeropad(t.getHours()) }, 74 | i: function(t) { return (t.getHours() + 12) % 12 }, 75 | I: function(t) { return this.zeropad((t.getHours() + 12) % 12) }, 76 | l: function(t) { return (t.getHours() + 12) % 12 }, 77 | m: function(t) { return this.zeropad(t.getMonth()+1) }, // month-1 78 | M: function(t) { return this.zeropad(t.getMinutes()) }, 79 | p: function(t) { return this.H(t) < 12 ? 'AM' : 'PM'; }, 80 | P: function(t) { return this.H(t) < 12 ? 'am' : 'pm'; }, 81 | S: function(t) { return this.zeropad(t.getSeconds()) }, 82 | w: function(t) { return t.getDay() }, // 0..6 == sun..sat 83 | y: function(t) { return this.zeropad(this.Y(t) % 100); }, 84 | Y: function(t) { return t.getFullYear() }, 85 | Z: function(t) { 86 | if (t.getTimezoneOffset() > 0) { 87 | return "-" + this.zeropad(t.getTimezoneOffset()/60) + "00"; 88 | } else { 89 | return "+" + this.zeropad(Math.abs(t.getTimezoneOffset())/60) + "00"; 90 | } 91 | }, 92 | '%': function(t) { return '%' } 93 | }; 94 | $.fn.strftime = function(theDate, format) { 95 | for (var s in strftime_funks) { 96 | if (s.length == 1) { 97 | format = format.replace('%' + s, strftime_funks[s](theDate)); 98 | } 99 | } 100 | return format; 101 | } 102 | 103 | }); -------------------------------------------------------------------------------- /javascripts/utils/chrome_ex_oauth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2010 The Chromium Authors. All rights reserved. Use of this 3 | * source code is governed by a BSD-style license that can be found in the 4 | * LICENSE file. 5 | */ 6 | 7 | /** 8 | * Constructor - no need to invoke directly, call initBackgroundPage instead. 9 | * @constructor 10 | * @param {String} url_request_token The OAuth request token URL. 11 | * @param {String} url_auth_token The OAuth authorize token URL. 12 | * @param {String} url_access_token The OAuth access token URL. 13 | * @param {String} consumer_key The OAuth consumer key. 14 | * @param {String} consumer_secret The OAuth consumer secret. 15 | * @param {String} oauth_scope The OAuth scope parameter. 16 | * @param {Object} opt_args Optional arguments. Recognized parameters: 17 | * "app_name" {String} Name of the current application 18 | * "callback_page" {String} If you renamed chrome_ex_oauth.html, the name 19 | * this file was renamed to. 20 | */ 21 | function ChromeExOAuth(url_request_token, url_auth_token, url_access_token, 22 | consumer_key, consumer_secret, oauth_scope, opt_args) { 23 | this.url_request_token = url_request_token; 24 | this.url_auth_token = url_auth_token; 25 | this.url_access_token = url_access_token; 26 | this.consumer_key = consumer_key; 27 | this.consumer_secret = consumer_secret; 28 | this.oauth_scope = oauth_scope; 29 | this.app_name = opt_args && opt_args['app_name'] || 30 | "ChromeExOAuth Library"; 31 | this.key_token = "oauth_token"; 32 | this.key_token_secret = "oauth_token_secret"; 33 | this.callback_page = opt_args && opt_args['callback_page'] || 34 | "chrome_ex_oauth.html"; 35 | this.auth_params = {}; 36 | if (opt_args && opt_args['auth_params']) { 37 | for (key in opt_args['auth_params']) { 38 | if (opt_args['auth_params'].hasOwnProperty(key)) { 39 | this.auth_params[key] = opt_args['auth_params'][key]; 40 | } 41 | } 42 | } 43 | }; 44 | 45 | /******************************************************************************* 46 | * PUBLIC API METHODS 47 | * Call these from your background page. 48 | ******************************************************************************/ 49 | 50 | /** 51 | * Initializes the OAuth helper from the background page. You must call this 52 | * before attempting to make any OAuth calls. 53 | * @param {Object} oauth_config Configuration parameters in a JavaScript object. 54 | * The following parameters are recognized: 55 | * "request_url" {String} OAuth request token URL. 56 | * "authorize_url" {String} OAuth authorize token URL. 57 | * "access_url" {String} OAuth access token URL. 58 | * "consumer_key" {String} OAuth consumer key. 59 | * "consumer_secret" {String} OAuth consumer secret. 60 | * "scope" {String} OAuth access scope. 61 | * "app_name" {String} Application name. 62 | * "auth_params" {Object} Additional parameters to pass to the 63 | * Authorization token URL. For an example, 'hd', 'hl', 'btmpl': 64 | * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth 65 | * @return {ChromeExOAuth} An initialized ChromeExOAuth object. 66 | */ 67 | ChromeExOAuth.initBackgroundPage = function(oauth_config) { 68 | window.chromeExOAuthConfig = oauth_config; 69 | window.chromeExOAuth = ChromeExOAuth.fromConfig(oauth_config); 70 | window.chromeExOAuthRedirectStarted = false; 71 | window.chromeExOAuthRequestingAccess = false; 72 | 73 | var url_match = chrome.extension.getURL(window.chromeExOAuth.callback_page); 74 | var tabs = {}; 75 | chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { 76 | if (changeInfo.url && 77 | changeInfo.url.substr(0, url_match.length) === url_match && 78 | changeInfo.url != tabs[tabId] && 79 | window.chromeExOAuthRequestingAccess == false) { 80 | chrome.tabs.create({ 'url' : changeInfo.url }, function(tab) { 81 | tabs[tab.id] = tab.url; 82 | chrome.tabs.remove(tabId); 83 | }); 84 | } 85 | }); 86 | 87 | return window.chromeExOAuth; 88 | }; 89 | 90 | /** 91 | * Authorizes the current user with the configued API. You must call this 92 | * before calling sendSignedRequest. 93 | * @param {Function} callback A function to call once an access token has 94 | * been obtained. This callback will be passed the following arguments: 95 | * token {String} The OAuth access token. 96 | * secret {String} The OAuth access token secret. 97 | */ 98 | ChromeExOAuth.prototype.authorize = function(callback) { 99 | if (this.hasToken()) { 100 | callback(this.getToken(), this.getTokenSecret()); 101 | } else { 102 | window.chromeExOAuthOnAuthorize = function(token, secret) { 103 | callback(token, secret); 104 | }; 105 | chrome.tabs.create({ 'url' :chrome.extension.getURL(this.callback_page) }); 106 | } 107 | }; 108 | 109 | /** 110 | * Clears any OAuth tokens stored for this configuration. Effectively a 111 | * "logout" of the configured OAuth API. 112 | */ 113 | ChromeExOAuth.prototype.clearTokens = function() { 114 | delete localStorage[this.key_token + encodeURI(this.oauth_scope)]; 115 | delete localStorage[this.key_token_secret + encodeURI(this.oauth_scope)]; 116 | }; 117 | 118 | /** 119 | * Returns whether a token is currently stored for this configuration. 120 | * Effectively a check to see whether the current user is "logged in" to 121 | * the configured OAuth API. 122 | * @return {Boolean} True if an access token exists. 123 | */ 124 | ChromeExOAuth.prototype.hasToken = function() { 125 | return !!this.getToken(); 126 | }; 127 | 128 | /** 129 | * Makes an OAuth-signed HTTP request with the currently authorized tokens. 130 | * @param {String} url The URL to send the request to. Querystring parameters 131 | * should be omitted. 132 | * @param {Function} callback A function to be called once the request is 133 | * completed. This callback will be passed the following arguments: 134 | * responseText {String} The text response. 135 | * xhr {XMLHttpRequest} The XMLHttpRequest object which was used to 136 | * send the request. Useful if you need to check response status 137 | * code, etc. 138 | * @param {Object} opt_params Additional parameters to configure the request. 139 | * The following parameters are accepted: 140 | * "method" {String} The HTTP method to use. Defaults to "GET". 141 | * "body" {String} A request body to send. Defaults to null. 142 | * "parameters" {Object} Query parameters to include in the request. 143 | * "headers" {Object} Additional headers to include in the request. 144 | */ 145 | ChromeExOAuth.prototype.sendSignedRequest = function(url, callback, 146 | opt_params) { 147 | var method = opt_params && opt_params['method'] || 'GET'; 148 | var body = opt_params && opt_params['body'] || null; 149 | var params = opt_params && opt_params['parameters'] || {}; 150 | var headers = opt_params && opt_params['headers'] || {}; 151 | 152 | var signedUrl = this.signURL(url, method, params); 153 | 154 | ChromeExOAuth.sendRequest(method, signedUrl, headers, body, function (xhr) { 155 | if (xhr.readyState == 4) { 156 | callback(xhr.responseText, xhr); 157 | } 158 | }); 159 | }; 160 | 161 | /** 162 | * Adds the required OAuth parameters to the given url and returns the 163 | * result. Useful if you need a signed url but don't want to make an XHR 164 | * request. 165 | * @param {String} method The http method to use. 166 | * @param {String} url The base url of the resource you are querying. 167 | * @param {Object} opt_params Query parameters to include in the request. 168 | * @return {String} The base url plus any query params plus any OAuth params. 169 | */ 170 | ChromeExOAuth.prototype.signURL = function(url, method, opt_params) { 171 | var token = this.getToken(); 172 | var secret = this.getTokenSecret(); 173 | if (!token || !secret) { 174 | throw new Error("No oauth token or token secret"); 175 | } 176 | 177 | var params = opt_params || {}; 178 | 179 | var result = OAuthSimple().sign({ 180 | action : method, 181 | path : url, 182 | parameters : params, 183 | signatures: { 184 | consumer_key : this.consumer_key, 185 | shared_secret : this.consumer_secret, 186 | oauth_secret : secret, 187 | oauth_token: token 188 | } 189 | }); 190 | 191 | return result.signed_url; 192 | }; 193 | 194 | /** 195 | * Generates the Authorization header based on the oauth parameters. 196 | * @param {String} url The base url of the resource you are querying. 197 | * @param {Object} opt_params Query parameters to include in the request. 198 | * @return {String} An Authorization header containing the oauth_* params. 199 | */ 200 | ChromeExOAuth.prototype.getAuthorizationHeader = function(url, method, 201 | opt_params) { 202 | var token = this.getToken(); 203 | var secret = this.getTokenSecret(); 204 | if (!token || !secret) { 205 | throw new Error("No oauth token or token secret"); 206 | } 207 | 208 | var params = opt_params || {}; 209 | 210 | return OAuthSimple().getHeaderString({ 211 | action: method, 212 | path : url, 213 | parameters : params, 214 | signatures: { 215 | consumer_key : this.consumer_key, 216 | shared_secret : this.consumer_secret, 217 | oauth_secret : secret, 218 | oauth_token: token 219 | } 220 | }); 221 | }; 222 | 223 | /******************************************************************************* 224 | * PRIVATE API METHODS 225 | * Used by the library. There should be no need to call these methods directly. 226 | ******************************************************************************/ 227 | 228 | /** 229 | * Creates a new ChromeExOAuth object from the supplied configuration object. 230 | * @param {Object} oauth_config Configuration parameters in a JavaScript object. 231 | * The following parameters are recognized: 232 | * "request_url" {String} OAuth request token URL. 233 | * "authorize_url" {String} OAuth authorize token URL. 234 | * "access_url" {String} OAuth access token URL. 235 | * "consumer_key" {String} OAuth consumer key. 236 | * "consumer_secret" {String} OAuth consumer secret. 237 | * "scope" {String} OAuth access scope. 238 | * "app_name" {String} Application name. 239 | * "auth_params" {Object} Additional parameters to pass to the 240 | * Authorization token URL. For an example, 'hd', 'hl', 'btmpl': 241 | * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth 242 | * @return {ChromeExOAuth} An initialized ChromeExOAuth object. 243 | */ 244 | ChromeExOAuth.fromConfig = function(oauth_config) { 245 | return new ChromeExOAuth( 246 | oauth_config['request_url'], 247 | oauth_config['authorize_url'], 248 | oauth_config['access_url'], 249 | oauth_config['consumer_key'], 250 | oauth_config['consumer_secret'], 251 | oauth_config['scope'], 252 | { 253 | 'app_name' : oauth_config['app_name'], 254 | 'auth_params' : oauth_config['auth_params'] 255 | } 256 | ); 257 | }; 258 | 259 | /** 260 | * Initializes chrome_ex_oauth.html and redirects the page if needed to start 261 | * the OAuth flow. Once an access token is obtained, this function closes 262 | * chrome_ex_oauth.html. 263 | */ 264 | ChromeExOAuth.initCallbackPage = function() { 265 | var background_page = chrome.extension.getBackgroundPage(); 266 | var oauth_config = background_page.chromeExOAuthConfig; 267 | var oauth = ChromeExOAuth.fromConfig(oauth_config); 268 | background_page.chromeExOAuthRedirectStarted = true; 269 | oauth.initOAuthFlow(function (token, secret) { 270 | background_page.chromeExOAuthOnAuthorize(token, secret); 271 | background_page.chromeExOAuthRedirectStarted = false; 272 | chrome.tabs.getSelected(null, function (tab) { 273 | chrome.tabs.remove(tab.id); 274 | }); 275 | }); 276 | }; 277 | 278 | /** 279 | * Sends an HTTP request. Convenience wrapper for XMLHttpRequest calls. 280 | * @param {String} method The HTTP method to use. 281 | * @param {String} url The URL to send the request to. 282 | * @param {Object} headers Optional request headers in key/value format. 283 | * @param {String} body Optional body content. 284 | * @param {Function} callback Function to call when the XMLHttpRequest's 285 | * ready state changes. See documentation for XMLHttpRequest's 286 | * onreadystatechange handler for more information. 287 | */ 288 | ChromeExOAuth.sendRequest = function(method, url, headers, body, callback) { 289 | var xhr = new XMLHttpRequest(); 290 | xhr.onreadystatechange = function(data) { 291 | callback(xhr, data); 292 | }; 293 | xhr.open(method, url, true); 294 | if (headers) { 295 | for (var header in headers) { 296 | if (headers.hasOwnProperty(header)) { 297 | xhr.setRequestHeader(header, headers[header]); 298 | } 299 | } 300 | } 301 | xhr.send(body); 302 | }; 303 | 304 | /** 305 | * Decodes a URL-encoded string into key/value pairs. 306 | * @param {String} encoded An URL-encoded string. 307 | * @return {Object} An object representing the decoded key/value pairs found 308 | * in the encoded string. 309 | */ 310 | ChromeExOAuth.formDecode = function(encoded) { 311 | var params = encoded.split("&"); 312 | var decoded = {}; 313 | for (var i = 0, param; param = params[i]; i++) { 314 | var keyval = param.split("="); 315 | if (keyval.length == 2) { 316 | var key = ChromeExOAuth.fromRfc3986(keyval[0]); 317 | var val = ChromeExOAuth.fromRfc3986(keyval[1]); 318 | decoded[key] = val; 319 | } 320 | } 321 | return decoded; 322 | }; 323 | 324 | /** 325 | * Returns the current window's querystring decoded into key/value pairs. 326 | * @return {Object} A object representing any key/value pairs found in the 327 | * current window's querystring. 328 | */ 329 | ChromeExOAuth.getQueryStringParams = function() { 330 | var urlparts = window.location.href.split("?"); 331 | if (urlparts.length >= 2) { 332 | var querystring = urlparts.slice(1).join("?"); 333 | return ChromeExOAuth.formDecode(querystring); 334 | } 335 | return {}; 336 | }; 337 | 338 | /** 339 | * Binds a function call to a specific object. This function will also take 340 | * a variable number of additional arguments which will be prepended to the 341 | * arguments passed to the bound function when it is called. 342 | * @param {Function} func The function to bind. 343 | * @param {Object} obj The object to bind to the function's "this". 344 | * @return {Function} A closure that will call the bound function. 345 | */ 346 | ChromeExOAuth.bind = function(func, obj) { 347 | var newargs = Array.prototype.slice.call(arguments).slice(2); 348 | return function() { 349 | var combinedargs = newargs.concat(Array.prototype.slice.call(arguments)); 350 | func.apply(obj, combinedargs); 351 | }; 352 | }; 353 | 354 | /** 355 | * Encodes a value according to the RFC3986 specification. 356 | * @param {String} val The string to encode. 357 | */ 358 | ChromeExOAuth.toRfc3986 = function(val){ 359 | return encodeURIComponent(val) 360 | .replace(/\!/g, "%21") 361 | .replace(/\*/g, "%2A") 362 | .replace(/'/g, "%27") 363 | .replace(/\(/g, "%28") 364 | .replace(/\)/g, "%29"); 365 | }; 366 | 367 | /** 368 | * Decodes a string that has been encoded according to RFC3986. 369 | * @param {String} val The string to decode. 370 | */ 371 | ChromeExOAuth.fromRfc3986 = function(val){ 372 | var tmp = val 373 | .replace(/%21/g, "!") 374 | .replace(/%2A/g, "*") 375 | .replace(/%27/g, "'") 376 | .replace(/%28/g, "(") 377 | .replace(/%29/g, ")"); 378 | return decodeURIComponent(tmp); 379 | }; 380 | 381 | /** 382 | * Adds a key/value parameter to the supplied URL. 383 | * @param {String} url An URL which may or may not contain querystring values. 384 | * @param {String} key A key 385 | * @param {String} value A value 386 | * @return {String} The URL with URL-encoded versions of the key and value 387 | * appended, prefixing them with "&" or "?" as needed. 388 | */ 389 | ChromeExOAuth.addURLParam = function(url, key, value) { 390 | var sep = (url.indexOf('?') >= 0) ? "&" : "?"; 391 | return url + sep + 392 | ChromeExOAuth.toRfc3986(key) + "=" + ChromeExOAuth.toRfc3986(value); 393 | }; 394 | 395 | /** 396 | * Stores an OAuth token for the configured scope. 397 | * @param {String} token The token to store. 398 | */ 399 | ChromeExOAuth.prototype.setToken = function(token) { 400 | localStorage[this.key_token + encodeURI(this.oauth_scope)] = token; 401 | }; 402 | 403 | /** 404 | * Retrieves any stored token for the configured scope. 405 | * @return {String} The stored token. 406 | */ 407 | ChromeExOAuth.prototype.getToken = function() { 408 | return localStorage[this.key_token + encodeURI(this.oauth_scope)]; 409 | }; 410 | 411 | /** 412 | * Stores an OAuth token secret for the configured scope. 413 | * @param {String} secret The secret to store. 414 | */ 415 | ChromeExOAuth.prototype.setTokenSecret = function(secret) { 416 | localStorage[this.key_token_secret + encodeURI(this.oauth_scope)] = secret; 417 | }; 418 | 419 | /** 420 | * Retrieves any stored secret for the configured scope. 421 | * @return {String} The stored secret. 422 | */ 423 | ChromeExOAuth.prototype.getTokenSecret = function() { 424 | return localStorage[this.key_token_secret + encodeURI(this.oauth_scope)]; 425 | }; 426 | 427 | /** 428 | * Starts an OAuth authorization flow for the current page. If a token exists, 429 | * no redirect is needed and the supplied callback is called immediately. 430 | * If this method detects that a redirect has finished, it grabs the 431 | * appropriate OAuth parameters from the URL and attempts to retrieve an 432 | * access token. If no token exists and no redirect has happened, then 433 | * an access token is requested and the page is ultimately redirected. 434 | * @param {Function} callback The function to call once the flow has finished. 435 | * This callback will be passed the following arguments: 436 | * token {String} The OAuth access token. 437 | * secret {String} The OAuth access token secret. 438 | */ 439 | ChromeExOAuth.prototype.initOAuthFlow = function(callback) { 440 | if (!this.hasToken()) { 441 | var params = ChromeExOAuth.getQueryStringParams(); 442 | if (params['chromeexoauthcallback'] == 'true') { 443 | var oauth_token = params['oauth_token']; 444 | var oauth_verifier = params['oauth_verifier']; 445 | this.getAccessToken(oauth_token, oauth_verifier, callback); 446 | } else { 447 | var request_params = { 448 | 'url_callback_param' : 'chromeexoauthcallback' 449 | }; 450 | this.getRequestToken(function(url) { 451 | window.location.href = url; 452 | }, request_params); 453 | } 454 | } else { 455 | callback(this.getToken(), this.getTokenSecret()); 456 | } 457 | }; 458 | 459 | /** 460 | * Requests an OAuth request token. 461 | * @param {Function} callback Function to call once the authorize URL is 462 | * calculated. This callback will be passed the following arguments: 463 | * url {String} The URL the user must be redirected to in order to 464 | * approve the token. 465 | * @param {Object} opt_args Optional arguments. The following parameters 466 | * are accepted: 467 | * "url_callback" {String} The URL the OAuth provider will redirect to. 468 | * "url_callback_param" {String} A parameter to include in the callback 469 | * URL in order to indicate to this library that a redirect has 470 | * taken place. 471 | */ 472 | ChromeExOAuth.prototype.getRequestToken = function(callback, opt_args) { 473 | if (typeof callback !== "function") { 474 | throw new Error("Specified callback must be a function."); 475 | } 476 | var url = opt_args && opt_args['url_callback'] || 477 | window && window.top && window.top.location && 478 | window.top.location.href; 479 | 480 | var url_param = opt_args && opt_args['url_callback_param'] || 481 | "chromeexoauthcallback"; 482 | var url_callback = ChromeExOAuth.addURLParam(url, url_param, "true"); 483 | 484 | var result = OAuthSimple().sign({ 485 | path : this.url_request_token, 486 | parameters: { 487 | "xoauth_displayname" : this.app_name, 488 | "scope" : this.oauth_scope, 489 | "oauth_callback" : url_callback 490 | }, 491 | signatures: { 492 | consumer_key : this.consumer_key, 493 | shared_secret : this.consumer_secret 494 | } 495 | }); 496 | var onToken = ChromeExOAuth.bind(this.onRequestToken, this, callback); 497 | ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken); 498 | }; 499 | 500 | /** 501 | * Called when a request token has been returned. Stores the request token 502 | * secret for later use and sends the authorization url to the supplied 503 | * callback (for redirecting the user). 504 | * @param {Function} callback Function to call once the authorize URL is 505 | * calculated. This callback will be passed the following arguments: 506 | * url {String} The URL the user must be redirected to in order to 507 | * approve the token. 508 | * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the 509 | * request token. 510 | */ 511 | ChromeExOAuth.prototype.onRequestToken = function(callback, xhr) { 512 | if (xhr.readyState == 4) { 513 | if (xhr.status == 200) { 514 | var params = ChromeExOAuth.formDecode(xhr.responseText); 515 | var token = params['oauth_token']; 516 | this.setTokenSecret(params['oauth_token_secret']); 517 | var url = ChromeExOAuth.addURLParam(this.url_auth_token, 518 | "oauth_token", token); 519 | for (var key in this.auth_params) { 520 | if (this.auth_params.hasOwnProperty(key)) { 521 | url = ChromeExOAuth.addURLParam(url, key, this.auth_params[key]); 522 | } 523 | } 524 | callback(url); 525 | } else { 526 | throw new Error("Fetching request token failed. Status " + xhr.status); 527 | } 528 | } 529 | }; 530 | 531 | /** 532 | * Requests an OAuth access token. 533 | * @param {String} oauth_token The OAuth request token. 534 | * @param {String} oauth_verifier The OAuth token verifier. 535 | * @param {Function} callback The function to call once the token is obtained. 536 | * This callback will be passed the following arguments: 537 | * token {String} The OAuth access token. 538 | * secret {String} The OAuth access token secret. 539 | */ 540 | ChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier, 541 | callback) { 542 | if (typeof callback !== "function") { 543 | throw new Error("Specified callback must be a function."); 544 | } 545 | var bg = chrome.extension.getBackgroundPage(); 546 | if (bg.chromeExOAuthRequestingAccess == false) { 547 | bg.chromeExOAuthRequestingAccess = true; 548 | 549 | var result = OAuthSimple().sign({ 550 | path : this.url_access_token, 551 | parameters: { 552 | "oauth_token" : oauth_token, 553 | "oauth_verifier" : oauth_verifier 554 | }, 555 | signatures: { 556 | consumer_key : this.consumer_key, 557 | shared_secret : this.consumer_secret, 558 | oauth_secret : this.getTokenSecret(this.oauth_scope) 559 | } 560 | }); 561 | 562 | var onToken = ChromeExOAuth.bind(this.onAccessToken, this, callback); 563 | ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken); 564 | } 565 | }; 566 | 567 | /** 568 | * Called when an access token has been returned. Stores the access token and 569 | * access token secret for later use and sends them to the supplied callback. 570 | * @param {Function} callback The function to call once the token is obtained. 571 | * This callback will be passed the following arguments: 572 | * token {String} The OAuth access token. 573 | * secret {String} The OAuth access token secret. 574 | * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the 575 | * access token. 576 | */ 577 | ChromeExOAuth.prototype.onAccessToken = function(callback, xhr) { 578 | if (xhr.readyState == 4) { 579 | var bg = chrome.extension.getBackgroundPage(); 580 | if (xhr.status == 200) { 581 | var params = ChromeExOAuth.formDecode(xhr.responseText); 582 | var token = params["oauth_token"]; 583 | var secret = params["oauth_token_secret"]; 584 | this.setToken(token); 585 | this.setTokenSecret(secret); 586 | bg.chromeExOAuthRequestingAccess = false; 587 | callback(token, secret); 588 | } else { 589 | bg.chromeExOAuthRequestingAccess = false; 590 | throw new Error("Fetching access token failed with status " + xhr.status); 591 | } 592 | } 593 | }; 594 | 595 | -------------------------------------------------------------------------------- /javascripts/utils/chrome_ex_oauth_init.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | ChromeExOAuth.initCallbackPage(); 3 | }; 4 | -------------------------------------------------------------------------------- /javascripts/utils/chrome_ex_oauthsimple.js: -------------------------------------------------------------------------------- 1 | /* OAuthSimple 2 | * A simpler version of OAuth 3 | * 4 | * author: jr conlin 5 | * mail: src@anticipatr.com 6 | * copyright: unitedHeroes.net 7 | * version: 1.0 8 | * url: http://unitedHeroes.net/OAuthSimple 9 | * 10 | * Copyright (c) 2009, unitedHeroes.net 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without 14 | * modification, are permitted provided that the following conditions are met: 15 | * * Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * * Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * * Neither the name of the unitedHeroes.net nor the 21 | * names of its contributors may be used to endorse or promote products 22 | * derived from this software without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY 25 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | * DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY 28 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | var OAuthSimple; 36 | 37 | if (OAuthSimple === undefined) 38 | { 39 | /* Simple OAuth 40 | * 41 | * This class only builds the OAuth elements, it does not do the actual 42 | * transmission or reception of the tokens. It does not validate elements 43 | * of the token. It is for client use only. 44 | * 45 | * api_key is the API key, also known as the OAuth consumer key 46 | * shared_secret is the shared secret (duh). 47 | * 48 | * Both the api_key and shared_secret are generally provided by the site 49 | * offering OAuth services. You need to specify them at object creation 50 | * because nobody ing uses OAuth without that minimal set of 51 | * signatures. 52 | * 53 | * If you want to use the higher order security that comes from the 54 | * OAuth token (sorry, I don't provide the functions to fetch that because 55 | * sites aren't horribly consistent about how they offer that), you need to 56 | * pass those in either with .setTokensAndSecrets() or as an argument to the 57 | * .sign() or .getHeaderString() functions. 58 | * 59 | * Example: 60 | 61 | var oauthObject = OAuthSimple().sign({path:'http://example.com/rest/', 62 | parameters: 'foo=bar&gorp=banana', 63 | signatures:{ 64 | api_key:'12345abcd', 65 | shared_secret:'xyz-5309' 66 | }}); 67 | document.getElementById('someLink').href=oauthObject.signed_url; 68 | 69 | * 70 | * that will sign as a "GET" using "SHA1-MAC" the url. If you need more than 71 | * that, read on, McDuff. 72 | */ 73 | 74 | /** OAuthSimple creator 75 | * 76 | * Create an instance of OAuthSimple 77 | * 78 | * @param api_key {string} The API Key (sometimes referred to as the consumer key) This value is usually supplied by the site you wish to use. 79 | * @param shared_secret (string) The shared secret. This value is also usually provided by the site you wish to use. 80 | */ 81 | OAuthSimple = function (consumer_key,shared_secret) 82 | { 83 | /* if (api_key == undefined) 84 | throw("Missing argument: api_key (oauth_consumer_key) for OAuthSimple. This is usually provided by the hosting site."); 85 | if (shared_secret == undefined) 86 | throw("Missing argument: shared_secret (shared secret) for OAuthSimple. This is usually provided by the hosting site."); 87 | */ this._secrets={}; 88 | this._parameters={}; 89 | 90 | // General configuration options. 91 | if (consumer_key !== undefined) { 92 | this._secrets['consumer_key'] = consumer_key; 93 | } 94 | if (shared_secret !== undefined) { 95 | this._secrets['shared_secret'] = shared_secret; 96 | } 97 | this._default_signature_method= "HMAC-SHA1"; 98 | this._action = "GET"; 99 | this._nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 100 | 101 | 102 | this.reset = function() { 103 | this._parameters={}; 104 | this._path=undefined; 105 | return this; 106 | }; 107 | 108 | /** set the parameters either from a hash or a string 109 | * 110 | * @param {string,object} List of parameters for the call, this can either be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash) 111 | */ 112 | this.setParameters = function (parameters) { 113 | if (parameters === undefined) { 114 | parameters = {}; 115 | } 116 | if (typeof(parameters) == 'string') { 117 | parameters=this._parseParameterString(parameters); 118 | } 119 | this._parameters = parameters; 120 | if (this._parameters['oauth_nonce'] === undefined) { 121 | this._getNonce(); 122 | } 123 | if (this._parameters['oauth_timestamp'] === undefined) { 124 | this._getTimestamp(); 125 | } 126 | if (this._parameters['oauth_method'] === undefined) { 127 | this.setSignatureMethod(); 128 | } 129 | if (this._parameters['oauth_consumer_key'] === undefined) { 130 | this._getApiKey(); 131 | } 132 | if(this._parameters['oauth_token'] === undefined) { 133 | this._getAccessToken(); 134 | } 135 | 136 | return this; 137 | }; 138 | 139 | /** convienence method for setParameters 140 | * 141 | * @param parameters {string,object} See .setParameters 142 | */ 143 | this.setQueryString = function (parameters) { 144 | return this.setParameters(parameters); 145 | }; 146 | 147 | /** Set the target URL (does not include the parameters) 148 | * 149 | * @param path {string} the fully qualified URI (excluding query arguments) (e.g "http://example.org/foo") 150 | */ 151 | this.setURL = function (path) { 152 | if (path == '') { 153 | throw ('No path specified for OAuthSimple.setURL'); 154 | } 155 | this._path = path; 156 | return this; 157 | }; 158 | 159 | /** convienence method for setURL 160 | * 161 | * @param path {string} see .setURL 162 | */ 163 | this.setPath = function(path){ 164 | return this.setURL(path); 165 | }; 166 | 167 | /** set the "action" for the url, (e.g. GET,POST, DELETE, etc.) 168 | * 169 | * @param action {string} HTTP Action word. 170 | */ 171 | this.setAction = function(action) { 172 | if (action === undefined) { 173 | action="GET"; 174 | } 175 | action = action.toUpperCase(); 176 | if (action.match('[^A-Z]')) { 177 | throw ('Invalid action specified for OAuthSimple.setAction'); 178 | } 179 | this._action = action; 180 | return this; 181 | }; 182 | 183 | /** set the signatures (as well as validate the ones you have) 184 | * 185 | * @param signatures {object} object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token: oauth_secret:} 186 | */ 187 | this.setTokensAndSecrets = function(signatures) { 188 | if (signatures) 189 | { 190 | for (var i in signatures) { 191 | this._secrets[i] = signatures[i]; 192 | } 193 | } 194 | // Aliases 195 | if (this._secrets['api_key']) { 196 | this._secrets.consumer_key = this._secrets.api_key; 197 | } 198 | if (this._secrets['access_token']) { 199 | this._secrets.oauth_token = this._secrets.access_token; 200 | } 201 | if (this._secrets['access_secret']) { 202 | this._secrets.oauth_secret = this._secrets.access_secret; 203 | } 204 | // Gauntlet 205 | if (this._secrets.consumer_key === undefined) { 206 | throw('Missing required consumer_key in OAuthSimple.setTokensAndSecrets'); 207 | } 208 | if (this._secrets.shared_secret === undefined) { 209 | throw('Missing required shared_secret in OAuthSimple.setTokensAndSecrets'); 210 | } 211 | if ((this._secrets.oauth_token !== undefined) && (this._secrets.oauth_secret === undefined)) { 212 | throw('Missing oauth_secret for supplied oauth_token in OAuthSimple.setTokensAndSecrets'); 213 | } 214 | return this; 215 | }; 216 | 217 | /** set the signature method (currently only Plaintext or SHA-MAC1) 218 | * 219 | * @param method {string} Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now) 220 | */ 221 | this.setSignatureMethod = function(method) { 222 | if (method === undefined) { 223 | method = this._default_signature_method; 224 | } 225 | //TODO: accept things other than PlainText or SHA-MAC1 226 | if (method.toUpperCase().match(/(PLAINTEXT|HMAC-SHA1)/) === undefined) { 227 | throw ('Unknown signing method specified for OAuthSimple.setSignatureMethod'); 228 | } 229 | this._parameters['oauth_signature_method']= method.toUpperCase(); 230 | return this; 231 | }; 232 | 233 | /** sign the request 234 | * 235 | * note: all arguments are optional, provided you've set them using the 236 | * other helper functions. 237 | * 238 | * @param args {object} hash of arguments for the call 239 | * {action:, path:, parameters:, method:, signatures:} 240 | * all arguments are optional. 241 | */ 242 | this.sign = function (args) { 243 | if (args === undefined) { 244 | args = {}; 245 | } 246 | // Set any given parameters 247 | if(args['action'] !== undefined) { 248 | this.setAction(args['action']); 249 | } 250 | if (args['path'] !== undefined) { 251 | this.setPath(args['path']); 252 | } 253 | if (args['method'] !== undefined) { 254 | this.setSignatureMethod(args['method']); 255 | } 256 | this.setTokensAndSecrets(args['signatures']); 257 | if (args['parameters'] !== undefined){ 258 | this.setParameters(args['parameters']); 259 | } 260 | // check the parameters 261 | var normParams = this._normalizedParameters(); 262 | this._parameters['oauth_signature']=this._generateSignature(normParams); 263 | return { 264 | parameters: this._parameters, 265 | signature: this._oauthEscape(this._parameters['oauth_signature']), 266 | signed_url: this._path + '?' + this._normalizedParameters(), 267 | header: this.getHeaderString() 268 | }; 269 | }; 270 | 271 | /** Return a formatted "header" string 272 | * 273 | * NOTE: This doesn't set the "Authorization: " prefix, which is required. 274 | * I don't set it because various set header functions prefer different 275 | * ways to do that. 276 | * 277 | * @param args {object} see .sign 278 | */ 279 | this.getHeaderString = function(args) { 280 | if (this._parameters['oauth_signature'] === undefined) { 281 | this.sign(args); 282 | } 283 | 284 | var result = 'OAuth '; 285 | for (var pName in this._parameters) 286 | { 287 | if (!pName.match(/^oauth/)) { 288 | continue; 289 | } 290 | if ((this._parameters[pName]) instanceof Array) 291 | { 292 | var pLength = this._parameters[pName].length; 293 | for (var j=0;j>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d); 398 | } 399 | 400 | 401 | this._normalizedParameters = function() { 402 | var elements = new Array(); 403 | var paramNames = []; 404 | var ra =0; 405 | for (var paramName in this._parameters) 406 | { 407 | if (ra++ > 1000) { 408 | throw('runaway 1'); 409 | } 410 | paramNames.unshift(paramName); 411 | } 412 | paramNames = paramNames.sort(); 413 | pLen = paramNames.length; 414 | for (var i=0;i 1000) { 427 | throw('runaway 1'); 428 | } 429 | elements.push(this._oauthEscape(paramName) + '=' + 430 | this._oauthEscape(sorted[j])); 431 | } 432 | continue; 433 | } 434 | elements.push(this._oauthEscape(paramName) + '=' + 435 | this._oauthEscape(this._parameters[paramName])); 436 | } 437 | return elements.join('&'); 438 | }; 439 | 440 | this._generateSignature = function() { 441 | 442 | var secretKey = this._oauthEscape(this._secrets.shared_secret)+'&'+ 443 | this._oauthEscape(this._secrets.oauth_secret); 444 | if (this._parameters['oauth_signature_method'] == 'PLAINTEXT') 445 | { 446 | return secretKey; 447 | } 448 | if (this._parameters['oauth_signature_method'] == 'HMAC-SHA1') 449 | { 450 | var sigString = this._oauthEscape(this._action)+'&'+this._oauthEscape(this._path)+'&'+this._oauthEscape(this._normalizedParameters()); 451 | return this.b64_hmac_sha1(secretKey,sigString); 452 | } 453 | return null; 454 | }; 455 | 456 | return this; 457 | }; 458 | } 459 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Microformats", 3 | "version": "0.4.13", 4 | "manifest_version": 2, 5 | "description": "Shows microformats on the page. Supports hCard, hCalendar, hReview, hReview-aggregate, hRecipe and geo.", 6 | "page_action": { 7 | "default_icon": "images/microformats-logo-19.png", 8 | "default_title" : "Click to view the microformats…", 9 | "default_popup": "popup.html" 10 | }, 11 | "content_scripts": [ { 12 | "js": [ 13 | "javascripts/utils/jquery.min.js", 14 | "javascripts/microformats/microformat.js", 15 | "javascripts/microformats/hreview-aggregate.js", 16 | "javascripts/microformats/hrecipe.js", 17 | "javascripts/microformats/microformats-shiv.min.js", 18 | "javascripts/microformats/microformats-coredefinition.min.js", 19 | "javascripts/microformats/microformats-hreviewdefinition.min.js", 20 | "javascripts/microformats/microformats-isodate.min.js", 21 | "javascripts/microformats/json2.min.js", 22 | "contentscript.js" 23 | ], 24 | "matches": [ "http://*/*", "https://*/*", "file://*/*" ] 25 | } ], 26 | "icons" : { 27 | "48" : "images/michromeformat-logo-48.png", 28 | "128" : "images/michromeformat-logo.png" 29 | }, 30 | "permissions": [ "tabs", "http://*/*", "https://*/*" ], 31 | "background": { 32 | "page": "background.html", 33 | "persistent": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
            15 |
            16 | 17 |
            You are not connected to Google, connect now.
            18 |
            19 | 23 |
              24 |
            25 | 26 |
              27 |
            28 | 29 |
              30 |
            31 | 32 |
              33 |
            34 | 35 |
              36 |
            37 | 38 |
              39 |
            40 |
            41 | 42 | -------------------------------------------------------------------------------- /stylesheets/popup.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'PictosRegular'; 3 | src: url('/fonts/pictos-web-webfont.eot'); 4 | src: url('/fonts/pictos-web-webfont.woff') format('woff'), 5 | url('/fonts/pictos-web-webfont.ttf') format('truetype'), 6 | url('/fonts/pictos-web-webfont.svg#PictosRegular') format('svg'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | body { 12 | margin: 15px 10px; 13 | padding: 0; 14 | width: 420px; 15 | font-family: 'Open Sans', sans-serif; 16 | color: #3b3b3b; 17 | overflow-x: hidden; 18 | } 19 | #header { 20 | overflow: hidden; 21 | } 22 | #icon { 23 | display: block; 24 | float: left; 25 | } 26 | #header h1 { 27 | margin-left: 51px; 28 | margin-top: 16px; 29 | font-size: 17px; 30 | color: rgb(0, 141, 43); 31 | } 32 | .loggedout .login { 33 | display: none; 34 | } 35 | .loggedin .logout { 36 | display: none; 37 | } 38 | ul { 39 | list-style-type: none; 40 | padding: 0; 41 | } 42 | h1 { 43 | font-size: 20px; 44 | font-weight: 700; 45 | margin: 0; 46 | } 47 | .hcard, .hcalendar, .hreview, .hreviewagg, .hrecipe, .geo { 48 | padding: 10px 0 5px 10px; 49 | overflow: hidden; 50 | border-bottom: 1px solid #ccc; 51 | } 52 | .hcard h1 a, .hcalendar h1 a { 53 | float: right; 54 | } 55 | .photo { 56 | height: 50px; 57 | display: block; 58 | } 59 | .vevent:hover, .vcard:hover { 60 | cursor: pointer; 61 | } 62 | .details ul li { 63 | font-size: 15px; 64 | font-weight: 300; 65 | margin-bottom: 7px; 66 | } 67 | .details { 68 | overflow: hidden; 69 | } 70 | .details h1 { 71 | white-space: nowrap; 72 | width: 100%; 73 | overflow: hidden; 74 | text-overflow: ellipsis; 75 | margin-bottom: 5px; 76 | } 77 | p { 78 | font-size: 12px; 79 | } 80 | .static-map { 81 | display: block; 82 | margin-top: 7px; 83 | border: 1px solid #888; 84 | } 85 | .googleconnection, .googleconnection a { 86 | color: #999; 87 | font-size: 12px; 88 | overflow: hidden; 89 | } 90 | .googleconnection div { 91 | float: right; 92 | background: #fff url(../images/googleIcon.png) no-repeat 0 0; 93 | padding-left: 20px; 94 | height: 16px; 95 | } 96 | .submitvevent, .submithcard { 97 | margin-right: 10px; 98 | } 99 | .webfont { 100 | font: normal 28px Pictos, 'PictosRegular'; 101 | } 102 | .download { 103 | float: right; 104 | } 105 | .download a { 106 | display: inline-block; 107 | text-decoration: none; 108 | color: #676767; 109 | } 110 | .download a span { 111 | height: 28px; 112 | line-height: 28px; 113 | display: inline-block; 114 | } 115 | .download a:hover { 116 | -webkit-mask-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0,0,0,0.5)), color-stop(100%, rgba(0,0,0,1)), to(rgba(0,0,0,1))); 117 | } --------------------------------------------------------------------------------