// ==UserScript== // @name Flickr AllSizes+ // @description Access all sizes for a Flickr photo. Copy the code, download the image, etc. // @author Premasagar Rose (http://premasagar.com/contact/) // @namespace http://premasagar.com // @identifier http://premasagar.com/dev/gm/flickrallsizesplus.user.js // @version 1.115 // @date 2007-01-23 // // @include http://www.flickr.com/photo_zoom.gne* // @include http://flickr.com/photo_zoom.gne* // @include http://www.flickr.com/photos/*/* // @include http://flickr.com/photos/*/* // // @exclude http://*flickr.com/photos/organize/* // @exclude http://*flickr.com/photos/friends/* // @exclude http://*flickr.com/photos/tags/* // // @exclude http://*flickr.com/photos/*/sets* // @exclude http://*flickr.com/photos/*/friends* // @exclude http://*flickr.com/photos/*/archives* // @exclude http://*flickr.com/photos/*/tags* // @exclude http://*flickr.com/photos/*/alltags* // @exclude http://*flickr.com/photos/*/multitags* // @exclude http://*flickr.com/photos/*/map* // @exclude http://*flickr.com/photos/*/favorites* // @exclude http://*flickr.com/photos/*/popular* // @exclude http://*flickr.com/photos/*/with* // ==/UserScript== /* INFO ==== Before installing this script, you'll need the Firefox browser (http://www.mozilla.org/firefox/) and the Greasemonkey extension (http://greasemonkey.mozdev.org) Discuss this script / report bugs / request features: http://www.flickr.com/groups/flickrhacks/discuss/72157594303798688/ Download the latest version: http://premasagar.com/dev/gm/flickrallsizesplus.user.js Released under the GPL license: http://www.gnu.org/copyleft/gpl.html SUMMARY ======= This script makes it easier to access all the sizes available for a photo, including the original size on non-Pro accounts. You can access the code for re-posting photos on Flickr discussion threads and other websites, as well as download images and more. To read the full documentation, click the "AllSizes" button on a photo's photopage and click on the 'Documentation' link. If you improve the script, please let me know... /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ (function() { // USERSCRIPT INFORMATION var UserScript = { id: 'flickrallsizesplus', title: 'Flickr AllSizes+', header: 'allSizes+', version: 1.115, date: new Date(2007, 0, 23), author: 'Premasagar', // Hours till next update check updateInterval: 24, // Api url to check for updates checkUpdateUrl: 'http://premasagar.com/api/checkupdates/', // Url for script discussion thread metaUrl: 'http://www.flickr.com/groups/flickrhacks/discuss/72157594303798688/', // Function to check for script updates checkForUpdates: function(){ // Does user want to check for updates? if (!Settings.getValue('checkforUpdates')) { return; } // Determine last time script was checked for updates var lastCheck = GM_getValue('lastUpdateCheck'); lastCheck = (typeof lastCheck != 'undefined') ? parseInt(lastCheck) : this.date.getTime(); // Get the current time var timeNow = new Date().getTime(); // Guard against weird stuff, like user changing system clock to the future if (lastCheck > timeNow) { lastCheck = timeNow; } // Is it time to check for updates? If not, return if (timeNow < (lastCheck + (this.updateInterval * 60 * 60 * 1000))) { return; } // Update the lastUpdateCheck local variable GM_setValue('lastUpdateCheck', timeNow.toString()); // +-+-+-+-+-+-+-+-+ // Api url for the update check var url = this.checkUpdateUrl + '?id=' + this.id + '&version=' + this.version + '&noCache=' + timeNow; var that = this; // Callback function for the AJAX request var callback = function(response){ // Bad response if (!response) { return; } else if (response.responseText == '') { return; } // Parse the response var parser = new DOMParser(); var dom = parser.parseFromString(response.responseText, "application/xml"); var rsp = dom.getElementsByTagName('rsp')[0]; // Our request failed! Return. if (rsp.getAttribute('stat') != 'ok') { return; } // Get userscript details var userscript = rsp.getElementsByTagName('userscript')[0]; var v = userscript.getAttribute('version'); var downloadUrl = userscript.getElementsByTagName('url')[0].textContent; var metaUrl = userscript.getElementsByTagName('url')[0].textContent; var changelog = userscript.getElementsByTagName('changelog')[0].textContent; // If there's no version update, then return if (v == that.version) { return; } // There's an update! Create a pop-up to tell the user var popUp = new PopUp('Update Available'); popUp.main.className = ''; var div = popUp.main.appendChild(cE('div')); div.className = 'indented'; var innerHTML = '

Script: ' + UserScript.title + '

'; innerHTML += '

Version ' + v + ' is now available.

'; innerHTML += '

» Install it!

'; if (changelog != '') { innerHTML += '

New Features

'; innerHTML += '

' + changelog + '

'; } div.innerHTML = innerHTML; popUp.open(); }; // Make the AJAX request ajaxRequest(url, callback); } }; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Check that latest version of Greasemonkey is installed if (!GM_xmlhttpRequest) { if (window.confirm('You do not have the latest version of Greasemonkey installed.\nWould you like to get it now?')) { window.location.href = 'http://greasemonkey.mozdev.org'; } return; } // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Initialise script for essential functions initScript(); /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // FLICKR REFERENCE OBJECT var Flickr = { // Urls imgRoot: 'http://static.flickr.com/', siteRoot: 'http://www.flickr.com/', // Flickr Regular Expressions regex: { photoSrc: /^(http:\/\/(farm\d{0,3}\.)?static\.flickr\.com\/(\d+)\/(\d+)_(\w+?)((_[stmb])?\.jpg|_o\.(jpg|gif|png))).*$/, photoPageUrl: /^((http:\/\/(www\.)?flickr\.com)?\/photos\/([\w@_-]+)\/(\d+))(\/.*)?$/, allSizesPageUrl: /^http:\/\/(www\.)?flickr\.com\/photo_zoom\.gne\?id=(\d+)&size=(sq?|[tmlo])$/, buddyiconSrc: /^(http:\/\/((farm\d{0,3}\.)?static.flickr.com\/(\d+)\/buddyicons\/([\w@_-]+)\.jpg(\?\d+)?|(www\.)?flickr\.com\/images\/buddyicon\.jpg\?([\w@_-]+)))$/, nsid: /^\d{8,12}@N\d{0,3}$/ } }; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // PHOTO OBJECT function Photo(id, secret, server){ this.id = id; this.secret = secret; this.server = server; this.init.init(this); } // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // STATIC FUNCTIONS ON PHOTO OBJECT Object.extend(Photo, { // Create Photo object from an image src fromSrc: function(src){ // Get parameters: id, secret, server, file extension for original size var params = src.replace(Flickr.regex.photoSrc, '$4,$5,$3,$8').split(","); // Create new Photo object var photo = new this(params[0], params[1], params[2]); // If the image is the original size, then set the original size file extension if (params[3] != '') { photo.sizes.o.extension = '.' + params[3]; } return photo; }, // Create Photo object from an image element fromImg: function(img){ // Create Photo object from the image src var photo = this.fromSrc(img.src); // Get the size suffix - e.g. '_s', '_t' var suffix = img.src.replace(Flickr.regex.photoSrc, '$6').slice(0,2); // For medium size if (suffix.slice(0,1) != '_') { suffix = ''; } // Determine which image size this is var size = $A(photo.sizes).getBy('suffix', suffix); if (size) { // Set the dimensions of this size and smaller sizes photo.sizes[size.id].setDimensions(img.width, img.height); photo.sizes.setDimensions(); } return photo; } }); // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // PHOTO OBJECT PROTOTYPE Photo.prototype = { // Init Object, for storing functions required at object initialisation init: new Init(), title: null, owner: null, // Get photo title. Add username to title, if user has requested it. getTitle: function(){ // Check title of page each time, in case owner/user changes it this.title = Page.getCurrentPage().getTitle(); // We don't yet know the title... if (!this.title) { return null; } // Replace blank titles with the user's setting for blank titles. var title = (this.title.length == 1 && this.title.charCodeAt(0) == 160) ? '' : this.title; if (title == '') { title = Settings.getValue('untitledTitle'); } // Check if we have the owner's username if (!this.owner) { return title; } if (!this.owner.username) { return title; } // Check the user's settings for adding the username to the title if ((this.isUser && Settings.getValue('addUsernameWhenYou')) || (!this.isUser && Settings.getValue('addUsernameToTitle')) ) { title += Settings.getValue('usernameFormat').replace(/\[user\]/g, this.owner.username); } // Convert any special characters to their HTML entities (e.g. &, <, ") return convertSpecialChars(title); }, // Get the url of the photopage for this photo getPhotopageUrl: function(){ // If we don't know the owner, then use the generic url if (!this.owner) { return Flickr.siteRoot + 'photo.gne?id=' + this.id; } // If we know the owner, then use the specific url else { return this.owner.getPhotosUrl() + this.id + '/'; } } }; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // PERSON OBJECT function Person(userUrl){ this.userUrl = userUrl; // Check if the userUrl is also the nsid if (userUrl.match(Flickr.regex.nsid)) { this.nsid = this.userUrl; } } // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // PERSON PROTOTYPE Person.prototype = { nsid: null, username: null, // Url for person's photostream getPhotosUrl: function(){ var userUrl = (this.userUrl) ? this.userUrl : this.nsid; return (userUrl) ? Flickr.siteRoot + 'photos/' + userUrl + '/' : null; }, // Url for person's profile getProfileUrl: function(){ var userUrl = (this.userUrl) ? this.userUrl : this.nsid; return (userUrl) ? Flickr.siteRoot + 'people/' + userUrl + '/' : null; }, // Url to send a FlickrMail getSendmailUrl: function(){ return (this.nsid) ? Flickr.siteRoot + 'messages_write.gne?to=' + this.nsid : null; } }; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // STATIC FUNCTIONS ON PERSON OBJECT Object.extend(Person, { getNsidFromBuddyiconSrc: function(src){ var nsid = src.replace(Flickr.regex.buddyiconSrc, '$5'); if (nsid == '') { nsid = src.replace(Flickr.regex.buddyiconSrc, '$8'); } return (nsid != '') ? nsid : null; }, // Create a Person object by supplying the photopage url fromPhotopageUrl: function(url){ var userUrl = url.replace(Flickr.regex.photoPageUrl, '$4'); return new this(userUrl); }, // Create a Person object by supplying their buddyicon image src fromBuddyiconSrc: function(src){ var nsid = Person.getNsidFromBuddyiconSrc(src); if (!nsid) { return null; } var person = new this(nsid); person.nsid = nsid; return person; } }); /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // PHOTO LICENCE OBJECT function License(id, name, url){ this.id = id; this.name = name; this.url = url; } // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // PHOTO LICENCE PROTOTYPE License.prototype = { // Get the icon to display for this license getIcon: function(){ if (this.id == 0) { return icon = cE('span', '\u00a9'); } // copyright symbol else if (this.id < Photo.licenses.length) { var icon = cE('img'); icon.src = EncodedImages.creativeCommonsIcon; return icon; } else { return null; } }, // Get an HTML anchor element to display the license icon and a link for further info getAnchor: function(){ var a = cE('a'); a.href = this.url; a.appendChild(this.getIcon()); a.appendChild(cTN(' ' + this.name)); return a; } }; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // EXTEND PHOTO OBJECT WITH STATIC LICENSE OBJECTS // (not to Photo prototype, because a photo only has one license at a time) Object.extend(Photo, { licenses: [ new License(0, 'All Rights Reserved', 'http://www.iusmentis.com/copyright/allrightsreserved/'), new License(1, 'Attribution-NonCommercial-ShareAlike', 'http://creativecommons.org/licenses/by-nc-sa/2.0/'), new License(2, 'Attribution-NonCommercial', 'http://creativecommons.org/licenses/by-nc/2.0/'), new License(3, 'Attribution-NonCommercial-NoDerivs', 'http://creativecommons.org/licenses/by-nc-nd/2.0/'), new License(4, 'Attribution', 'http://creativecommons.org/licenses/by/2.0/'), new License(5, 'Attribution-ShareAlike', 'http://creativecommons.org/licenses/by-sa/2.0/'), new License(6, 'Attribution-NoDerivs', 'http://creativecommons.org/licenses/by-nd/2.0/') ] }); /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // SIZE OBJECT function Size(initObj){ Object.extend(this, initObj); } // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // SIZE OBJECT PROTOTYPE Size.prototype = { extension:'.jpg', width: null, height: null, // Check if this size exists for this photo exists: function(){ // All sizes except large always exist if (this.id != 'l') { return true; } // LARGE SIZES // Get original size var o = this.photo.sizes.o; // If original has incomplete dimensions, check if large size has dimensions if (!o.width || !o.height) { return (this.width && this.height) ? true : null; } // Check original size dimensions return (o.width > 1280 || o.height > 1280) ? true : false; }, // Check if this size has a known image src isKnownSrc: function(){ return (this.exists() && this.extension) ? true : false; }, // Check if this size has known dimensions isKnownSize: function(){ return (this.width && this.height) ? true : false; }, // Check if this size has a known src and dimensions isComplete: function(){ // Check if original size rotation is known var knownRotation = true; if (typeof this.rotated != 'undefined') { knownRotation = (this.rotated !== null); } return (this.isKnownSrc() && this.isKnownSize() && knownRotation) ? true : false; }, // Get an array of the image dimensions getDimensions: function(){ return [this.width, this.height]; }, // Set the dimensions for this size // Optional args: width & height. If not supplied, size is calculated from larger sizes setDimensions: function(){ // If width and height are supplied... if (arguments.length > 0) { this.width = arguments[0]; if (arguments.length > 1) { this.height = arguments[1]; } return this.getDimensions(); } // If width & height not supplied... // Get the largest complete size, as a comparison var largest = this.photo.sizes.getLargestKnownSize(); if (!largest) { return false; } // If the largest size is this size, then return else if (largest.max == this.max) { return false; } // Check if the original was rotated by the owner if (largest.id == 'o' && this.photo.sizes.o.rotated) { // Copy object so that we can temporarily change its properties largest = Object.extend({}, largest); var temp = largest.width; largest.width = largest.height; largest.height = temp; } // If the largest size is smaller than this size if (largest.max < this.max) { // If the largest size is smaller than its max dimensions, then this has the same size if (largest.width < largest.max && largest.height < largest.max) { return this.setDimensions(largest.width, largest.height); } else { return false; } } // If this is the large size, then special rules apply else if (largest.id == 'o' && this.id == 'l' && largest.width <= 1280 && largest.height <= 1280) { return false; } // -- Calculate dimensions for this size // If the comparison size dimensions are no bigger than this size's maximum if (largest.width <= this.max && largest.height <= this.max) { return this.setDimensions(largest.width, largest.height); } // Else var longerSide = Math.max(largest.width, largest.height); var newWidth = Math.round(this.max * (largest.width/longerSide)); var newHeight = Math.round(this.max * (largest.height/longerSide)); return this.setDimensions(newWidth, newHeight); }, // Get the image src for this size // Optional arg: forDownload [boolean], default: false (returns src for the image download version) getSrc: function(){ // If essential info is not known, then return null if (!this.extension) { return null; } // Check forDownload flag if (arguments.length >0) { var suffix = (arguments[0]) ? this.suffix + '_d' : this.suffix; } else { var suffix = this.suffix; } return Flickr.imgRoot + this.photo.server + '/' + this.photo.id + '_' + this.photo.secret + suffix + this.extension; }, // Get an image element for this size getImg: function(){ // Get the image src var src = this.getSrc(); if (!src) { return null; } // Create a new image element and set attributes var img = Object.extend(cE('img'), { src: src, alt: this.photo.getTitle(), title: this.photo.getTitle() }); if (this.width) { img.width = this.width; } if (this.height) { img.height = this.height; } return img; }, // Get HTML tag for the image element getImgHTML: function(){ // Get the image src var src = this.getSrc(); if (!src) { return null; } // Get attributes for tag var width = (this.width) ? ' width="' + this.width + '"' : ''; var height = (this.height) ? ' height="' + this.height + '"' : ''; var title = ' title="' + this.photo.getTitle() + '"'; var alt = ' alt="' + this.photo.getTitle() + '"'; return ''; }, // Get an image element, within an anchor element for this size getImgAnchor: function(){ var href = this.photo.getPhotopageUrl(); var img = this.getImg(); if (!href || !img) { return null; } var a = cE('a'); a.href = href; return a.appendChild(img); }, // Get HTML tags for image within an anchor getImgAnchorHTML: function(){ var href = this.photo.getPhotopageUrl(); var imgHTML = this.getImgHTML(); if (!href || !imgHTML) { return null; } return '' + imgHTML + ''; }, // Get BB Code for image within an anchor getImgAnchorBBCode: function(){ // Get the image src var src = this.getSrc(); var href = this.photo.getPhotopageUrl(); if (!href || !src) { return null; } return '[url=' + href + '][img]' + src + '[/img][/url]'; }, // Get the url for the AllSizes page for this size getAllSizesUrl: function(){ return Flickr.siteRoot + 'photo_zoom.gne?id=' + this.photo.id + '&size=' + this.id; } }; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Add Sizes object to Photo prototype Object.extend(Photo.prototype, { sizes: { // Specific Size Objects as properties of the Sizes Object sq:new Size({id:'sq',label:'Square', suffix:'_s', max:75, width:75, height:75, setDimensions:function(){return true;}}), t: new Size({id:'t', label:'Thumbnail', suffix:'_t', max:100}), s: new Size({id:'s', label:'Small', suffix:'_m', max:240}), m: new Size({id:'m', label:'Medium', suffix:'', max:500}), l: new Size({id:'l', label:'Large', suffix:'_b', max:1024}), o: new Size({id:'o', label:'Original', suffix:'_o', max:Infinity, extension:null, rotated:null}), // Original sizes have unlimited length. Their file extension may be png, gif or jpg, & they may be rotated // Methods of the Sizes object // Return array of srcs known to exist getKnownSrcs: function(){ return $A(this).getBy('isKnownSrc', true, true); }, // Return array of known sizes getKnownSizes: function(){ return $A(this).getBy('isKnownSize', true, true); }, // Return array of sizes that are complete getComplete: function(){ return $A(this).getBy('isComplete', true, true); }, getLargestKnownSrc: function(){ var knownSrcs = this.getKnownSrcs(); return (knownSrcs) ? knownSrcs[knownSrcs.length-1] : null; }, getLargestKnownSize: function(){ var knownSizes = this.getKnownSizes(); return (knownSizes) ? knownSizes[knownSizes.length-1] : null; }, getLargestComplete: function(){ var complete = this.getComplete(); return (complete) ? complete[complete.length-1] : null; }, // SetDimensions for each size smaller than the largest known sizes setDimensions: function(){ var sizes = $A(this); for (var i=0; i= 1); var isLandscapeO = ((size.width / size.height) >= 1); // If they are not the same aspect, then o has been rotated that.o.rotated = (isLandscapeM != isLandscapeO) ? true : false; } // Extend Photo Size object with new-found properties Object.extend(that[size.id], size); that.setDimensions(); while (thisFunc.callbacks.length > 0) { var callback = thisFunc.callbacks.shift(); callback(that.o); } } // innerCallback for processing responses function innerCallback(size){ switch (size){ case false: that.getOriginalViaDownload(callbackAll); break; case null: break; default: if (size.id != 'o') { that.getOriginalViaDownload(callbackAll); } else { callbackAll(size); } break; } } // For pre-existing buttons, try the AllSizes page over HTTP if (AllSizes.buttonExistedBeforeGM) { this.getLargestViaHttp(innerCallback); } // For created buttons, try the API else { this.getLargestViaApi(innerCallback); } }, getLargestViaApi: function(callback){ var onload = function(rsp) { if (!rsp) { callback(false); return; } var largest = rsp.sizes.size[rsp.sizes.size.length-1]; var size = { id: largest.source.slice(-5,-4), extension: largest.source.slice(-4), width: largest.width, height: largest.height }; callback(size); }; Api.callMethod('photos.getSizes', {photo_id: this.o.photo.id}, onload); }, getLargestViaHttp: function(callback){ var url = 'http://www.flickr.com/photo_zoom.gne?id=' + this.o.photo.id + '&size=o'; var onload = function(response) { if (!response) { callback(false); return; } var size = {}; var html = cE('Code'); html.innerHTML = response.responseText; var spans = html.getElementsByTagName('span'); for (var i=spans.length-1; i>=0; i--) { if (spans[i].className == 'Dimensions') { var dims = spans[i].firstChild.nodeValue.slice(1,-1).split(' x '); size.width = dims[0]; size.height = dims[1]; break; } } var images = html.getElementsByTagName('img'); for (var i=0; i < images.length; i++) { var suffix = images[i].src.replace(Flickr.regex.photoSrc, '$6'); if (suffix.length <= 6) { size.extension = suffix.slice(-4); size.id = (suffix.slice(0,2) == '_o') ? 'o' : 'l'; callback(size); return; } } return false; }; ajaxRequest(url, onload); }, // Returns width, height & extension for original size getOriginalViaDownload: function(callback){ var o = this.o; var thisFunc = arguments.callee; // File extension possibilities var extensions = ['.jpg', '.png', '.gif']; // Current extension to test var extId = (arguments.length > 1) ? arguments[1] : 0; if (extId >= extensions.length) { callback(false); return; } // Unavailable image properties (will be downloaded if wrong extension used) var unavail = { width: 500, height: 375, extension: '.gif' }; // Set image src for the test extension var src = Flickr.imgRoot + o.photo.server + '/' + o.photo.id + '_' + o.photo.secret + o.suffix + extensions[extId]; // Whether to download image node (default) or headers var viaNode = (arguments.length > 2) ? arguments[2] : true; if (viaNode) { var img = createImg(src); window.setTimeout(function(){ checkDimensions(img, evaluateImg); }, 10); } else { evaluateHeaders(src, callback); } // +-+-+-+-+ function createImg(src){ var img = cE('img'); img.style.display = 'none'; document.body.appendChild(img); img.src = src; return img; } function destroyImg(img){ img.parentNode.removeChild(img); img.src = ''; img = null; } function checkDimensions(img){ if (img.width == 0) { window.setTimeout(function(){ checkDimensions(img); }, 200); return; } var size = { width:img.width, height:img.height }; destroyImg(img); evaluateImg(size); } function evaluateImg(size){ // If size is same as unavailable image if (size.width == unavail.width && size.height == unavail.height) { // If we haven't tried all extensions, then try again if (extId < extensions.length-1) { thisFunc.call(o.photo.sizes, callback, extId+1); } // Otherwise, size is same as unavail image and extension is unknown else { thisFunc.call(o.photo.sizes, callback, 0, false); } } // Success! Return size object else { size.id = o.id; size.extension = extensions[extId]; callback(size); } } // Get headers for image - only called if original has same size as unavailable image function evaluateHeaders(src, callback){ var onload = function(response) { // In case of error in getting AJAX response if (response === false) { callback(false); return; } // Get the mime-type var mime = response.responseHeaders.match(/Content-Type:\simage\/(jpe?g|png|gif)/gi); if (!mime) { callback(false); return; } // Get the file extension var extension = '.' + mime[0].slice(mime[0].lastIndexOf('/')+1); if (extension == '.jpeg') { extension = '.jpg'; } // If unsuccessful at first attempt if (extension == unavail.extension && extId == 0) { thisFunc.call(o.photo.sizes, callback, extId+1, false); } // Success! else { var size = { extension: extension, width: unavail.width, height: unavail.height }; callback(size); } }; ajaxRequest(src, onload); } }, popUp: function(){ return this[this.popUp.selectedSize].popUp(); } }); // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Photo.prototype.sizes.popUp.selectedSize = Settings.getValue('defaultSize'); // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // SIZE PROTOTYPE Object.extend(Size.prototype, { getCode: function(){ var codeType = GM_getValue('codeType'); return (codeType == 'bbCode') ? this.getImgAnchorBBCode() : this.getImgAnchorHTML(); }, download: function(){ var that = this; function callback(o){ if (that.id == 'l' && !that.exists()) { that = that.photo.sizes.o; } window.location.href = that.getSrc(true); EventLogger.keysDown = ''; } this.photo.sizes.getAllSizes(callback); }, copyToClipboard: function(){ var that = this; function callback(o){ if (that.id == 'l' && !that.exists()) { that = that.photo.sizes.o; } copyToClipboard(that.getCode()); } this.photo.sizes.getAllSizes(callback); }, view: function(){ var that = this; function callback(o){ if (that.id == 'l' && !that.exists()) { that = that.photo.sizes.o; } window.location.href = that.getSrc(); } this.photo.sizes.getAllSizes(callback); }, popUp: function(){ // Check if popUp was already created var popUp = PopUp.getById('Code'); // Update the selected size property var sizesObj = this.photo.sizes; sizesObj.popUp.selectedSize = this.id; // If popUp not yet created if (!popUp) { popUp = new PopUp('Code'); // Shortcut icons var icons = popUp.main.appendChild(cE('span')); icons.className = 'popUpIcons'; // Download button var download = icons.appendChild(cE('img')); download.src = EncodedImages.save; download.alt = 'Download Image'; download.title = 'Download Image'; download.addEventListener('click', function(){ sizesObj[sizesObj.popUp.selectedSize].download(); }, false); // Copy button var copy = icons.appendChild(cE('img')); copy.src = EncodedImages.copy; copy.alt = 'Copy Code to Clipboard'; copy.title = 'Copy Code to Clipboard'; copy.addEventListener('click', function(){ sizesObj[sizesObj.popUp.selectedSize].copyToClipboard(); }, false); // View button var view = icons.appendChild(cE('a')); var img = view.appendChild(cE('img')); img.src = EncodedImages.view; img.alt = 'View Image'; img.title = 'View Image'; view.addEventListener('mouseover', function(e){ e.currentTarget.href = sizesObj[sizesObj.popUp.selectedSize].getSrc(); }, false); // BBCode/HTML button var code = icons.appendChild(cE('img')); var codeType = GM_getValue('codeType'); if (codeType == 'bbCode') { code.src = EncodedImages.html; code.alt = 'to HTML'; } else { code.src = EncodedImages.bbCode; code.alt = 'to BB Code'; } code.title = code.alt; code.addEventListener('click', function(e){ // Change alt and title of button if (e.currentTarget.alt == 'to BB Code') { e.currentTarget.alt = 'to HTML'; e.currentTarget.src = EncodedImages.html; GM_setValue('codeType', 'bbCode'); } else { e.currentTarget.alt = 'to BB Code'; e.currentTarget.src = EncodedImages.bbCode; GM_setValue('codeType', 'html'); } e.currentTarget.title = e.currentTarget.alt; // Update textarea popUp.updateTextarea(); }, false); // AllSizes button var img = cE('img'); img.alt = 'AllSizes Page'; img.src = EncodedImages.allsizesIcon; if (AllSizes.buttonExistedBeforeGM) { var allSizes = icons.appendChild(cE('a')); allSizes.appendChild(img); allSizes.addEventListener('mouseover', function(e){ e.currentTarget.href = sizesObj[sizesObj.popUp.selectedSize].getAllSizesUrl(); }, false); } else { img.alt += ' Not Available for this Photo'; img.className = 'choiceDisabled'; var allSizes = img; } img.title = img.alt; icons.appendChild(allSizes); // Size Menu var sizeMenu = popUp.main.appendChild(cE('p')); sizeMenu.className = 'popUpMenu'; // Function to open popUp for clicked link var onclickSizeMenuLink = function(e){ var id = e.currentTarget.getAttribute('sizeId'); sizesObj[id].popUp(); }; // Go through each size, adding to the menu var sizes = $A(this.photo.sizes); for (var i=0; i 0) { var commentLink = ul.appendChild(cE('li')).appendChild(cE('a', 'Add a Comment')); commentLink.href = '#DiscussPhoto'; commentLink.addEventListener('click', function(e){ popUp.close(); window.setTimeout(function(){ var commentDiv = document.getElementById('DiscussPhoto'); var ta = commentDiv.getElementsByTagName('textarea')[0]; // Focus the textarea element ta.focus(); // Move cursor to start (other GM scripts may have added text) if (ta.selectionStart) { ta.selectionStart = 0; ta.selectionEnd = 0; } }, 5); }, false); } // Get original sizes if (!sizesObj.o.isComplete()) { // If successful, the original size object is returned function callback(o){ if (o) { // If large size doesn't exist if (!o.photo.sizes.l.exists()) { // Remove anchor & divider from size menu for (var i=0; i 0) { popUp.id = 'PopUp_' + arguments[0]; } // Initialise popUp popUp.init(); return popUp; } // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ PopUp.prototype = { init: function(insertNode){ var that = this; this.className = 'PopUp'; // Get node to insert into document var defaultInsertNode = getElementsByCssClass('div', 'photoImgDiv')[0]; var insertNode = (arguments.length > 1) ? arguments[1] : defaultInsertNode; // Close Button var closeBtn = this.appendChild(cE('a')); closeBtn.title = 'Close'; closeBtn.className = 'closeBtn'; var closeBtnImg = closeBtn.appendChild(cE('img')); closeBtnImg.src = EncodedImages.closeWindow; closeBtnImg.alt = 'Close'; closeBtn.addEventListener('click', function(){ that.close(); }, true); // H1 Header var headerText = UserScript.header.replace(/^(.[^A-Z]*)([A-Z].*)$/, '$1~$2').split("~"); if (headerText.length > 0) { this.header = this.appendChild(cE('h1')); var strong = this.header.appendChild(cE('strong')); var pinkText = strong.appendChild(cE('span', headerText[0])); pinkText.className = 'flickrPink'; } if (headerText.length > 1) { var blueText = strong.appendChild(cE('span', headerText[1])); blueText.className = 'flickrBlue'; } // H2 Title if (typeof this.id != 'undefined') { this.appendChild(cE('h2', this.id.slice('PopUp_'.length))); } // Main div this.main = this.appendChild(cE('div')); this.main.className = 'popUpMain'; // Menu this.menu = this.appendChild(PopUp.getMenu(this.id.slice('PopUp_'.length))); // Add this popUp to PopUp inventory PopUp.inventory.push(this); // Insert PopUp styles into stylesheet PopUp.insertStyles(); // Insert popUp div into document insertNode.parentNode.insertBefore(this, insertNode); }, // end init // Create Textarea addTextarea: function(){ ta = this.main.appendChild(cE('textarea')); ta.value = (arguments.length > 0) ? arguments[0] : ''; ta.wrap = 'virtual'; ta.addEventListener('focus', function(){ this.select(); }, true); return ta; }, open: function(){ // Close other popUps for (var i=0; i=0; j--) { if (anchors[j].title.indexOf('Link to ') !== -1) { owner.username = anchors[j].title.replace(/Link to (.*)'s? photos/, '$1'); break; } } return owner; } return null; }, getLicense: function(){ // Find span with class 'license' (only one on original Flickr page, but may be changed by other scripts) var spans = getElementsByCssClass('span', 'license'); for (var i=0; i=0; j--) { var a = anchors[j]; if (a.title.indexOf('Link to ') === -1) { continue; } var userUrl = a.href.replace(/^.*\/([\w-_]*)\/$/, '$1'); var owner = new Person(userUrl); // Get NSID var buddyicon = getFlickrImg('buddyicon', div); if (buddyicon) { owner.nsid = Person.getNsidFromBuddyiconSrc(buddyicon.src); } owner.username = a.title.replace(/Link to (.*)'s? photos/, '$1'); return owner; } } }, getPhotoImg: function(){ var photo = getFlickrImg('photo'); photo.src = photo.src.replace(Flickr.regex.photoSrc, '$1'); var dims = this.getDimensions(); photo.width = dims[0]; photo.height = dims[1]; return photo; }, getDimensions: function(){ var elements = getElementsByCssClass('span', 'Dimensions'); // Cycle through each span found for (var i=0; i element. This is the current image size. var el = elements[i]; if (el.parentNode.getElementsByTagName('strong').length > 0) { // If we have the right text node, then return array [width, height] for (var j=0; j=0; j--) { var a = anchors[j]; for (var k=0; k'; html += '

'; html += '

Remember! Flickr Terms of Service specify that if you post a Flickr photo on an external website, the photo must link back to its photo page. (So, use Option 1.)

'; // Create new node var newNode = cE('div'); newNode.innerHTML = html; // Add code to textarea var ta = newNode.getElementsByTagName('textarea')[0]; ta.value = this.getPhoto().sizes[this.getCurrentSize()].getCode(); // Insert into document elements[0].parentNode.insertBefore(newNode, elements[0]); return true; }, init: function(){ return this.updateTextarea(); } }, // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Find the first page object that matches getCurrentPage: function(){ var pages = $A(this); for (var i=0; i 1) { propsToInclude = arguments[1]; } else { var propsToInclude = function(prop){ return typeof prop == 'object'; }; } var results = []; for (property in iterable) { if (propsToInclude(iterable[property])) { results.push(iterable[property]); } } return results; } // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // OBJECT EXTENSIONS Object.extend(Array.prototype, { // Search objects within an array for a specified property-value or method-result // optional arg: asArray [boolean] - to return an array of objects. Default: false getBy: function(property, value){ if (arguments.length > 2) { if (arguments[2]) { var asArray = true; var returnArray = []; } } if (typeof asArray == 'undefined') { var asArray = false; } for (var i=0; i 0) ? returnArray : null; } else { return null; } }, getById: function(id){ return this.getBy('id', id); } }); // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // DOM API SHORTCUT FUNCTIONS // createElement - optional arg: text contents cE = function(nodeName){ var node = document.createElement(nodeName); if (arguments.length > 1) { var text = arguments[1]; if (typeof text == 'string') { node.appendChild(cTN(text)); } } return node; }; // createTextNode cTN = function(text){ return document.createTextNode(text); }; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // Get elements from the document with a specific cssClass getElementsByCssClass = function(tagName, cssClass){ // Construct empty array to return var returnArray = []; // Get divs in the document var elements = document.getElementsByTagName(tagName); // Cycle through elements for (var i=0; i 1) ? arguments[1] : document; // Find images in the document var images = container.getElementsByTagName('img'); // Find the image src of the photo for (var i=0; i/g, ">").replace(/"/g, """); }; // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ copyToClipboard = function(text){ try { unsafeWindow.netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); } catch(error) { /* User cancellation */ return false; } var component = Components.classes["@mozilla.org/widget/clipboard;1"]; var clipInst = component.createInstance(Components.interfaces.nsIClipboard); var component = Components.classes["@mozilla.org/widget/transferable;1"]; var trans = component.createInstance(Components.interfaces.nsITransferable); trans.addDataFlavor("text/unicode"); var component = Components.classes["@mozilla.org/supports-string;1"]; var supportStr = component.createInstance(Components.interfaces.nsISupportsString); supportStr.data = text; trans.setTransferData("text/unicode", supportStr, text.length*2); var clipboard = Components.interfaces.nsIClipboard; clipInst.setData(trans, null, clipboard.kGlobalClipboard); return text; }; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // INITIALISE OBJECTS Init = function(){}; Init.prototype = Object.extend([], { // Pass object as argument to function init: function(obj){ // Call each function in the init array for(var i=0; i 0) { EventLogger.clicks --; } // Temporary hack to prevent lags activating single-click before double-click executed if (EventLogger.clicks == 1) { EventLogger.clicks --; } return EventLogger.clicks; }, addKey: function(key){ var k = EventLogger.keysDown; // Prevent duplicate entries from holding down key if (k.length > 0) { if (k[k.length-1] == key) { return k; } } EventLogger.keysDown += key; return EventLogger.keysDown; }, removeKey: function(key){ for (var i=0; i < EventLogger.keysDown.length; i++) { if (EventLogger.keysDown[i] == key) { var prefix = (i>0) ? EventLogger.keysDown.slice(0, i) : ''; var suffix = (i'; innerHTML += '

Version: ' + UserScript.version + '

'; innerHTML += '

License: GPL

'; innerHTML += '

Discuss: Feedback, bug reports, feature requests

'; innerHTML += '

by ' + UserScript.author + ''; innerHTML += '

'; div.innerHTML = innerHTML; } return popUp.open(); }, documentation: function(){ // Check if popUp was already created var popUp = PopUp.getById('Documentation'); if (!popUp) { popUp = new PopUp('Documentation'); var innerHTML = ''; innerHTML += '

This script makes it easier to access all the sizes available for a photo - including the original size on non-Pro accounts.

'; innerHTML += '

AllSizes+ lets you copy the code required to post photos on Flickr discussion threads and other websites, as well as download images and more.

'; innerHTML += "

Where's the All Sizes Page Gone?

"; innerHTML += "

To see the standard All Sizes page for a photo, just double-click the AllSizes button in the toolbar above, or click the AllSizes icon in the Code panel of this pop-up.

"; innerHTML += '

Shortcuts

'; innerHTML += "

The Shortcuts panel gives an overview of the different shortcut keys you can hold down when clicking the AllSizes button. You do not need to use any of them, as all the functionality is available by clicking on the different buttons in the pop-up window. But they might save you some time :o)

"; innerHTML += '

Delay for Large & Original Sizes

'; innerHTML += "

When you first click the AllSizes button on a photopage, information about the image sizes is gathered. Details about the medium and smaller sizes are available immediately, but there is a short delay before the large and original sizes are known. This should only be a couple of seconds when using a broadband internet connection.

"; innerHTML += '

Large Size is Not Always Available

'; innerHTML += "

The large size is only available for photos that have an original size larger than 1280 pixels on either side. If you select the large size on a photo that has no large size available, then you will be given the original size instead.

"; innerHTML += '

Original Size on the All Sizes Page

'; innerHTML += "

This script does not (currently) change the size options available on the All Sizes page. On that page, you will not see an option for the original size if the photographer is not a Pro user. However, where the original is no more than 1280 pixels long, the original size image will be shown, but labelled as 'Large' or something else.

"; innerHTML += "

The Code pop-up created by this script always labels the images as they actually are - the original size is labelled 'Original' and the large size is labelled 'Large'.

"; innerHTML += '

BB Code & HTML

'; innerHTML += "

BB Code is a simplified markup language that is similar to HTML and is used on many forum websites.

"; innerHTML += "

You can click the 'to BB Code' button to convert the HTML code to BB Code. If you click it a second time, it will change back to HTML.

"; innerHTML += "

The last code type you viewed is remembered by the script. It will be used by the 'Copy to Clipboard' function to determine if you want to copy HTML or BB Code.

"; innerHTML += '

Copy to Clipboard

'; innerHTML += "

When you choose to copy the code to clipboard, you may see a security pop-up from the browser asking to allow the action. You can choose to allow or deny it, and to remember your decision in future.

"; innerHTML += "

If you choose to always allow the action, then you will not be prompted again for any clipboard actions on Flickr.com, including any Greasemonkey scripts for Flickr that you have installed.

"; innerHTML += "

This is a slight security risk, because a malicious script could read the contents of your clipboard and send it to an external site. However, this probably won't happen, because any malicious behaviour would likely be reported by other users before you choose to install the malicious script.

"; innerHTML += "

You may want to install the AllowClipboard Helper browser extension, to help you monitor which websites are allowed to access your clipboard.

"; innerHTML += "

If you find that the Copy to Clipboard function doesn't seem to work, then try enabling this in your Firefox browser:

"; innerHTML += "
    "; innerHTML += "
  1. Open a new tab and go to the address about:config
  2. "; innerHTML += "
  3. In the 'Filter' bar, type signed.applets.codebase_principal_support
  4. "; innerHTML += "
  5. Right-click on the line of text that appears and choose Toggle, so that the value goes from false to true.
  6. "; innerHTML += "
"; innerHTML += '

Auto-Updates

'; innerHTML += "

By default, the script will automatically check for a newer version once a day (or less often if you don't visit Flickr). If a newer version is available, it will ask you if you want to install it. You can turn off auto-updates in the Settings panel.

"; innerHTML += '

Flickr Terms for Posting Photos

'; innerHTML += "

The Flickr Terms of Service require that any photo you post on another site must include a link back to the photo's Flickr photopage. You can do this by simply using the code in the Code panel.

"; innerHTML += "

Note that, for private photos (including those marked as 'Friends' or 'Family'), the photopage will not be viewable by the general public, even if a posted image of it can be seen.

"; innerHTML += '

All Rights Reserved

'; innerHTML += "

If a photo is marked "© All Rights Reserved", then you must have permission from the owner to use it. This is required by international law and is common courtesy.

"; innerHTML += "

You may be legally allowed to re-post a copyrighted photo without specific permission, if it constitutes 'Fair Use', such as review and critique. Fair Use laws are sometimes vague and vary from country to country. It's best to just ask the owner.

"; innerHTML += '

Creative Commons

'; innerHTML += "

If a photo has a Creative Commons Creative Commons license, then you need to read the specific license to determine if you are allowed to use it without any further permission from the owner.

"; innerHTML += "

In general, if you are using a Creative Commons photo in a non-commercial context and you do not alter the image and you are attributing the owner by linking to the photopage, then you should be fine. More relaxed conditions may apply, depending on the specific license.

"; innerHTML += "

It is good to let the owner know that you've used their photo, even if not required, because people like to hear how others have used their work.

"; innerHTML += '

Further Information

'; innerHTML += "

If you have any questions, bug reports or suggestions for improvement, please leave a post in the discussion thread for this script.

"; // Add HTML to popUp popUp.main.innerHTML = innerHTML; } return popUp.open(); } }; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // FLICKR API Api = { key : 'f11432d99cdf97246d9fe401e524831e', urlRoot: 'http://api.flickr.com/services/rest/', // e.g. callMethod('photos.getSizes', function(){}, {photo_id:34343232}) callMethod: function(method, parameters, callback){ var format = (arguments > 3) ? arguments[3] : 'json'; var url = this.urlRoot + '?method=flickr.' +method+ '&api_key=' +this.key+ '&format=' +format; // Add parameters to url if (parameters) { for (param in parameters) { url += '&' + param + '=' + parameters[param]; } } var handler = function(response){ if (!response) { callback(false); return; } function jsonFlickrApi(rsp) { return rsp; } eval('var rsp = ' + response.responseText + ';'); if (rsp.stat != 'ok') { callback(false); } else { callback(rsp); } }; ajaxRequest(url, handler); } }; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ // SCRIPT SETTINGS // Don't change these settings. Instead, click the 'AllSizes' button and click the 'Settings' link. function SettingsGroup(initObj){ Object.extend(this, initObj); } SettingsGroup.prototype = { type: 'settingsGroup', settings: [], save: function(){ for(var i=0; i{title} (by {user})', type:'textarea' }), new Setting ({ id:'customCodeWhenOthers', label: "On Other's Photos", defaultValue:'{title} (by {user})', type:'textarea' }), ], suffix: function(){ var wrapper = cE('div'); var p = wrapper.appendChild(cE('p')); var showHide = p.appendChild(cE('a', '\u00bb Show Custom Tags')); var node = wrapper.appendChild(cE('div')); node.id = 'settings_custom_tags_info'; node.style.display = 'none'; showHide.addEventListener('click', function(e){ var node = document.getElementById('settings_custom_tags_info'); switch (node.style.display){ case 'none': node.style.display = 'block'; e.currentTarget.firstChild.nodeValue = '\u00bb Hide Custom Tags'; break; default: node.style.display = 'none'; e.currentTarget.firstChild.nodeValue = '\u00bb Show Custom Tags'; break; } }, false); var propValCollections = { Photo: [ ['id', 'Id'], ['title', 'Title'], ['url', 'Photopage url'] ], Image: [ ['src', 'Source url'], ['width', 'Width'], ['height', 'Height'], ['size', 'Abbreviation for the size (sq, s, t, m, l, or o)'] ], Owner: [ ['user', 'Username'], ['nsid', 'User id'], ['photosUrl', 'Photostream url'], ['profileUrl', 'Profile url'] ] }; for (coll in propValCollections) { var h = node.appendChild(cE('h5', coll)); var propVals = propValCollections[coll]; if (propVals.length > 0) { var ul = node.appendChild(cE('ul')); for (var i=0; i