Instantly share code, notes, and snippets.

Embed
What would you like to do?
Fixes crappy design of new Shutterstock earnings page, enables sort on ID and hopefully will fix other stupid things SS team did
// ==UserScript==
// @name Shutterstock.NewEarnings
// @namespace
// @version 1.2.16
// @updateURL https://gist.github.com/deymosD/126aa0a849de4196547ef5662684b270/raw/a8d13fad3d8decc18d871c7d3bb9458dfb5cbdd9/Shutterstock.NewEarnings.user.js
// @description Fixes crappy design of new Shutterstock earnings page, enables sort on ID and hopefully will fix other stupid things SS team did
// @author GG
// @match http://submit.shutterstock.com/earnings*
// @match https://submit.shutterstock.com/earnings*
// @require https://code.jquery.com/jquery-latest.min.js
// @require https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.28.5/js/jquery.tablesorter.js
// @grant none
// ==/UserScript==
// NEW:
// v1.0 First attempt at fixing crap that SS programmers did with non-existant UX skills
// 1.0.10 forgot to keep track of changes. removes $ sign, fixes date format, sorts table - parameters give an idea what it does
// 1.1 enables alternative display
// 1.2 enables choice of display
// TBD:
// load tabs immediatelly
// show everything on one page, as it should be
'use strict';
var $j = jQuery.noConflict();
// =========== PARAMETERS ===========
var enableDatePicker = true; // if you don't need date picker, set this to false
var enableAlternativeDisplay = true; //if you don't like tables, this gives cool display...
var showImageInfo = false; // show image info on ID click
var enableTableSorter = true; // SS sorts on stupid things, this enables sort by ID (puts newest on top)
var loadMonthyEarningsOnYMChange = true; // will change month when new month/year selected in datepicker
var linkToOldDailyStats = false; // option not supported by ss anymore
var addRows = true; // load all rows to earnings table
var timeout = 2000; // wait so many ms before applying tablesorter - gotta do it, because of the async calls
// in reverse image search, don't return results from stock sites (idea by mandritoiu); add other sites in the form --> +-site:.domain.com (sometimes Google ignores this):
var noStockSites = "-site:dreamstime.com+-site:shutterstock.com+-site:shutterstock.in+-site:istockphoto.com+-site:fotolia.com+-site:123rf.com+-site:veer.com+-site:istockphoto.com";
// not sure why they q= and oq= are used, but here are both :D, can't hurt; i could do it much nicer, maybe in the future... :)
// Google Images sometimes ignores excludes and will serve content from SS or other excluded sites
var googleSearch = "https://www.google.com/searchbyimage?q=" + noStockSites + "&oq=" + noStockSites + "&image_url=";
var what = ["id", "earnings", "downloads"];
var order= ["asc", "asc", "asc"];
var headerText = $j("div#earnings-container div.h3").html();
// ==/UserScript==
var daily = window.location.href.indexOf("daily") + 1;
var alternativeCreated = false;
// i use smoothness theme for the datepicker and here we load external CSS
if ((daily) && (enableDatePicker)) {
var link = window.document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/smoothness/jquery-ui.css'; // get smoothness CSS; check http://jqueryui.com/themeroller/ for others, just change the theme name in url
document.getElementsByTagName("HEAD")[0].appendChild(link); // other themes: http://rtsinani.github.io/jquery-datepicker-skins/, have fun!
}
if ((daily) && (enableTableSorter)) {
var link = window.document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.28.5/css/theme.default.min.css';
document.getElementsByTagName("HEAD")[0].appendChild(link);
}
$j(document).ready(function() {
CreateStyles();
(!daily) && fixDailyStatLinks();
(daily) && (enableDatePicker) && addDatePicker();
(daily) && (addRows) && ($j("ul.pagination")) && addTableRows();
(daily) && (showImageInfo) && imageInfo();
(daily) && (enableTableSorter) &&
setTimeout(function(){
$j("table.table").tablesorter( {sortList: [[1,1]]} ); // [1,1] reverse sort column with index 1 (second) - [1,0] will sort ascending
// if (enableAlternativeDisplay) makeAlternativeDisplay();
// now, let's hope all is loaded - we can calculate earnings
var totalEarnings = 0;
$j("table.table:first tbody tr td:nth-child(3)").each( function() {
var earn = parseFloat( $j(this).text().replace("$",""));
totalEarnings += earn;
});
totalEarnings = parseFloat(totalEarnings).toFixed(2);
$j("p big b").append(" - <u>Earnings: " + totalEarnings + "$</u> (referals not included)") ;
if (enableAlternativeDisplay) {
$j("div#earnings-container div.h3").html(headerText + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#" class="showalternative">Show alternative layout</a>');
$j(document).on('click', '.showalternative', function() { // resort by ID
makeAlternativeDisplay();
});
}
}, timeout) && (removeEarningsDownloadsLinks());
});
function imageInfo(){
var div = document.createElement('div');
div.className = "gginfo";
if (localStorage.getItem("positionImageInfo")) {
var position = $j.parseJSON(localStorage.getItem("positionImageInfo"));
div.style.top = position.top + "px";
div.style.left = position.left + "px";
}
$j("div#earnings-container").append(div);
$j("div.gginfo").draggable({
stop: function(event, ui){
localStorage.setItem("positionImageInfo", JSON.stringify(ui.position));
}
});
$j("div.gginfo").hover( function() {$j(this).css("cursor", "move");}, function(){ $j(this).css("cursor", "default"); });
resetDivInfo();
$j(document).on('click', 'table.table:first tbody tr td:nth-child(2)', function() { // resort by downloads
GetImageData(div, $j(this).text());
});
}
function resetDivInfo(){
$j("div.gginfo").html("<b>Click on image ID to show image info.</b>");
return;
}
function addDatePicker() {
// wrap table element with div.ggHack because we're gonna need to import HTML content of different pages
// get date from URL
var datepicker = document.createElement('div');
datepicker.id = "datepicker";
if (localStorage.getItem("positionDatePicker")) {
var position = $j.parseJSON(localStorage.getItem("positionDatePicker"));
datepicker.style.top = position.top + "px";
datepicker.style.left = position.left + "px";
}
$j("div#earnings-container").append(datepicker);
$j("div#datepicker").draggable({
stop: function(event, ui){
localStorage.setItem("positionDatePicker", JSON.stringify(ui.position));
}
});
$j("div#datepicker").hover( function() {$j(this).css("cursor", "move");}, function(){ $j(this).css("cursor", "default"); });
$j("div#datepicker").datepicker({ // check https://api.jqueryui.com/datepicker/ for API and play with options
maxDate: "+0D", // not much fun to go beyond today, no stats will be available
showOtherMonths: true,
changeMonth: true,
changeYear: true,
defaultDate: window.location.href.match(/date=(\d\d\d\d\-\d\d\-\d\d)/)[1], // get the default date from the URL
dateFormat:"yy-mm-dd",
firstDay: 1, // start with monday, set to 0 for sunday (but why!?)
showButtonPanel: true,
currentText: "This month",
yearRange:"2003:+0", // don't want to go to pre-Shutterstock times...
onSelect: function(selected,event) {
updateTable(selected);
$j("div.gginfo").show(); // when we select date, load stats for the date and show the image info div
}
});
if (loadMonthyEarningsOnYMChange) {
$j("div#datepicker").datepicker("option", "onChangeMonthYear", function (year, month, i) { // when we change month or year, load earnings for that month in that year
var monthLink = window.location.protocol + "//submit.shutterstock.com/earnings?year=" + year + "&month=" + month;
window.location.href = monthLink; // some day i may load just the table, but today is not that day
}
)
}
}
function updateTable(date){
var newLink = window.location.protocol + "//submit.shutterstock.com/earnings/daily?category=25_a_day&language=en&date=" + date;
window.location.href = newLink; // some day i may load just the table, but today is not that day
}
// following code created by https://gist.github.com/satinka/5479a93d389a07d41246
function addTableRows() {
var howMany;
try {
howMany = getNumberofPages();
}
catch(err) {
howMany = 1;
}
var url = window.location.href;
var fetchurl = url;
var numOfDownloads = $j("div#earnings-container div.row ul.nav li a").text().match(/\d+/g);
var numSubs = numOfDownloads[0];
var numODD = numOfDownloads[1];
var numEnhanced = numOfDownloads[2];
var numCart = numOfDownloads[3];
var numPacks = numOfDownloads[4];
var numSod = numOfDownloads[5];
var div = document.createElement('div');
div.className = "col-md-12";
var total = parseInt(numSubs) + parseInt(numODD) + parseInt(numEnhanced) + parseInt(numCart) + parseInt(numPacks) + parseInt(numSod);
div.innerHTML = '<p><big><b>Total: ' + total + ' - Subscription: ' + numSubs + ', ODDs: ' + numODD + ', Enhanced: ' + numEnhanced + ', Cart sales: ' + numCart + ', Clip packs: ' + numPacks + ', Single & other: ' + numSod + '</b></big></p>';
$j("div#earnings-container div.row:first").append(div);
$j("table.table tbody:first tr").each( function(){
$j(this).addClass("25_a_day");
});
if (numSubs >=20) {
// this is cruel - it would be better to read the tabs and number of sales inside () but i'm lazy now
getTheRest(fetchurl, 2, howMany, "25_a_day"); // that gets us subs recursively
// now let's fetch on_demands;
}
if (numODD != 0) {
fetchurl = url.replace("25_a_day", "on_demand");
getTheRest(fetchurl, 1, 1, "ondemand");
}
if (numEnhanced != 0) {
fetchurl = url.replace("25_a_day", "enhanced");
getTheRest(fetchurl, 1, 1, "enhanced");
}
if (numCart != 0) {
fetchurl = url.replace("25_a_day", "cart_sales");
getTheRest(fetchurl, 1, 1, "cart_sales");
}
if (numPacks != 0) {
fetchurl = url.replace("25_a_day", "clip_packs");
getTheRest(fetchurl, 1, 1, "clip_packs");
}
if (numSod != 0) {
fetchurl = url.replace("25_a_day", "single_image_and_other");
getTheRest(fetchurl, 1, 1, "single_image_and_other");
}
removePaginationForm();
// prepare for alternative layout
};
function getNumberofPages() {
return $j("ul.pagination input")[0].max;
}
function removePaginationForm(){
$j("form.pagination-form").remove();
$j("div#earnings-container div.row ul.nav").hide();
}
function getTheRest(url, counter, howMany, type) {
if (counter > howMany) {return;};
var newurl = url + "&page=" + counter;
console.log(newurl);
$j("div#earnings-container div.row:last").append("<table id=\"temp" + type + counter + "\"></table>");
$j("table#temp" + type + counter).hide();
$j("table#temp" + type + counter).load(newurl + " table.table tbody:first", function () {
$j("table#temp" + type + counter + " tbody tr").each(function(a,tr) {
$j(tr).addClass(type);
$j("table.table tbody:first").append(tr);
$j("table#temp" + type + counter).remove();
});
getTheRest(url, counter+1, howMany, type);
});
}
function restoreTable() {
$j("table.table:first").show();
$j("div.ggWrapper").remove();
$j("div#earnings-container div.h3").html(headerText + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a class="showalternative">Show alternative layout</a>');
}
function makeAlternativeDisplay () {
var image = "";
var href = "";
var ID = "" ;
var earnings = "";
var downloads = "";
var temp = "";
var type="";
var div = document.createElement('div');
div.className = "ggWrapper";
$j("div#earnings-container").append(div);
div = document.createElement('div');
div.className = "ggLinks";
$j("div#earnings-container div.ggWrapper").append(div);
div.innerHTML = 'Sort by ID: <a class="sort1">asc</a> - Sort by earnings:<a class="sort2">asc</a> - Sort by downloads: <a class="sort3">asc</a>';
$j("div#earnings-container div.h3").html(headerText + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a class="backtotable">Show table view</a>');
div = document.createElement('div');
div.className = "ggImages";
$j("div#earnings-container div.ggWrapper").append(div);
$j("table.table tbody:first tr").each( function() {
temp = $j(this).children("td")[0].innerHTML;
href = $j(temp).attr('href');
image = $j(temp).find("img.thumbnail-image").attr("src");
ID = $j(this).children("td")[1].innerHTML;
earnings = $j(this).children("td")[2].innerHTML.replace("$","");
downloads = $j(this).children("td")[3].innerHTML;
type=$j(this).attr('class');
div = document.createElement('div');
$j(div).attr("data-earnings", earnings);
$j(div).attr("data-id", ID);
$j(div).attr("data-downloads", downloads);
if ((earnings > 0.38) && (earnings < 2.86)) {
div.className = "ondemand";
}
if ((earnings > 2.86) && (earnings < 15)) {
div.className = "sodek";
}
if ((earnings > 15) && (earnings < 50)) {
div.className = "sod";
}
if (earnings > 50) {
div.className = "megasod";
}
div.className += div.className ? ' ggAlternative' : 'ggAlternative';
$j("div#earnings-container div.ggImages").append(div);
div.innerHTML = "<a target=\"_ggWin\" href=\"" + href + "\"><img src=\"" + image + "\"></a><br /><b>ID:</b> <span class=\"imageId\">" + ID + "</span><br /><b>Earnings:</b> " + earnings + "<br /><b>Downloads:</b> " + downloads + "<br /><b>Type:</b> " + type;
$j("table.table:first").hide();
$j("div#earnings-container div.row ul.nav").hide();
});
if (!(alternativeCreated)) {
$j(document).on('click', 'div.ggImages span', function() { // resort by downloads
var imageID = $j(this).text();
div = $j("div.gginfo").get(0);
GetImageData(div, imageID);
});
$j(document).on('click', '.sort1', function() { // resort by ID
resort(0);
});
$j(document).on('click', '.sort2', function() { // resort by earnings
resort(1);
});
$j(document).on('click', '.sort3', function() { // resort by downloads
resort(2);
});
$j(document).on('click', '.backtotable', function() { // resort by downloads
restoreTable();
}); }
alternativeCreated = true;
}
function resort(id){
var divList = $j("div.ggImages div");
var tag = id+1;
if (order[id] == "desc") {
order[id] = "asc";
}
else {
order[id] = "desc";
}
$j("a.sort" + tag).text(order[id]);
// console.log("a.sort" + tag, what[id], order[id]);
divList.sort(function(a, b){
if (order[id] == "desc") {
return $(a).data(what[id]) > $(b).data(what[id]) ? 1 : -1;
}
else {
return $(a).data(what[id]) < $(b).data(what[id]) ? 1 : -1;
}
});
$j("div.ggImages").html(divList);
}
function fixDailyStatLinks() {
var url = window.location.href;
var date;
var newdate;
var newurl;
$j("tr.earnings-day td a").each( function() {
date=$j(this).text(); //.replace(/\//g,"-");
newdate = date.replace( /(\d{1,2})\/(\d{1,2})\/(\d{2})/, "$2-$1-" + 20 + "$3");
$j(this).text(newdate);
/* if (linkToOldDailyStats) {
(newurl = "https://submit.shutterstock.com/stats_date.mhtml?date=" + newdate);
$j(this).prop("href", newurl);
}
*/
});
// fix $ sign
$j(".text-right").each( function() {
$j(this).text($j(this).text().replace(/\$/,""));
});
};
function removeEarningsDownloadsLinks() {
$j("table.table:first thead th.sortable a").each( function() {
var orig = $j(this).text();
$j(this).parent().text(orig);
});
// be polite and add header above images
$j("table.table:first thead tr th:first").text("Image thumbnail");
}
function GetImageData(div, imageID) {
div.innerHTML = "<h4>Loading...</h4>";
var d = new Date();
var n = d.getTimezoneOffset() / 60;
$j.ajax({
url: window.location.protocol + '//submit.shutterstock.com/show_component.mhtml?component_path=mobile/comps/image_detail.mj&photo_id=' + imageID,
type: "get",
dataType: "html",
cache: true,
error: function (request, status, error) {
console.log(request.responseText);
},
success: function( data ){
var imageInfo = $j.parseJSON(data);
if (imageInfo) {
var downloads = imageInfo.latest_downloads;
var keywords = imageInfo.keywords;
var totals = imageInfo.totals;
var today = totals.today;
var all = totals.all_time;
var table;
div.innerHTML = "<span class=\"closeInfo\">Close</span><br />";
div.innerHTML += "<h4>Image statistics</h4>";
div.innerHTML += "<a target=\"_new\" href=\"https://www.shutterstock.com/pic.mhtml?id=" + imageID + "\"><img align=\"right\" class=\"resize\" src=\"https:" + imageInfo.thumb_url + "\" /></a>";
div.innerHTML += "<h5> " + imageInfo.description + "</h5>";
div.innerHTML += "<b>Image ID:</b> <a target=\"_new\" href=\"https://www.shutterstock.com/pic.mhtml?id=" + imageID + "\">" + imageID + "</a><br />";
var uploaded = new Date( ((imageInfo.uploaded_date/1000) + (6+n) * 3600)*1000).toDateString();
var daysonline = Math.round((new Date().getTime() - imageInfo.uploaded_date) / (1000 * 24 * 60 * 60));
div.innerHTML += "<b>Uploaded on</b> " + uploaded + " (" + daysonline + " days ago)<br /><br />";
div.innerHTML += "Earned <b>" + all.earnings + "$</b>";
if (today.earnings > 0) {
div.innerHTML += " (<b>" + today.earnings + "$</b> today)";
}
div.innerHTML += "<br />";
var s1 = (today.downloads) != 1 ? "s" : "";
var s2 = (all.downloads) != 1 ? "s" : "";
div.innerHTML += "Downloaded <b>" + all.downloads + " time" + s2;
if (today.downloads > 0) {
div.innerHTML += " (<b>" + today.downloads + " time" + s1 + "</b> today)";
}
div.innerHTML += "<br />";
if (all.downloads > 0) {
var rpd = (all.earnings / all.downloads).toFixed(2);
div.innerHTML += "Return per download: <b>" + rpd + "$</b><br />";
}
var editURL = window.location.protocol + "//submit.shutterstock.com/edit_media.mhtml?type=photos&approved=1&id=" + imageID;
div.innerHTML += "<a href=\"" + editURL + "\" target=\"_new\">Edit title and keywords</a> (opens new window)<br/>";
div.innerHTML += "<br /><a target=\"_new\" href =\"" + googleSearch + window.location.protocol + imageInfo.thumb_url + "\">Find image in use via Google Images</a><br />";
if (keywords.length > 0) {
div.innerHTML += "<br /><b>Keywords used to download image:</b><br />";
table = "<table width=\"200px\">";
table += "<thead><th align=\"right\">Keyword</th><th align=\"right\">% used</th></thead><tbody>";
keywords.forEach(function(kw) {
table += "<tr><td align=\"right\">" + kw.keyword + "</td><td align=\"right\">" + parseFloat(kw.percentage).toFixed(2) + "</td></tr>";
});
table += "</tbody></table>";
div.innerHTML += table;
}
else {
div.innerHTML += "<br /><b>No keyword info available.</b><br />";
}
if (downloads.length > 0) {
div.innerHTML += "<br /><b>Latest downloads</b> (max 20 or max last 365 days):</b><br />";
table = "<table width=\"300px\">";
table += "<thead><th align=\"left\">Date</th><th align=\"right\">Earnings</th></thead><tbody>";
downloads.forEach(function(arg) {
var date = new Date ( ((arg.date_time/1000) + (6+n) * 3600)*1000).toUTCString().toLocaleString();
table += "<tr><td align=\"left\">" + date + "</td><td align=\"right\">" + arg.amount + "</td></tr>";
});
table += "</tbody></table><br /";
div.innerHTML += table;
}
else
{
div.innerHTML += "<br /><b>No downloads in the last 365 days.</b><br />";
}
}
$j("span.closeInfo").on("click", function(){
// $j("div.gginfo").hide(); // i'd rather keep the info on the screen than hide the div
resetDivInfo();
opened = 0;
});
}
});
}
function CreateStyles() {
var sheet = (function() {
var style = document.createElement("style");
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
return style.sheet;
})();
var closelink = "cursor: hand; cursor: pointer; text-decoration: underline; ";
var gginfo = "position: fixed; top: 60px; right: 10px; border:4px solid #eeeeee;" +
"width: 330px; max-height: 70%;" +
"font-weight: normal;" +
"resize: both;" +
"padding: 10px 10px 10px 10px;" +
"background-color: white;" +
"opacity: 1;" +
"overflow: auto;" +
"font-size: 12px;";
var resize = "max-width: 40%; max-height:40%;";
var alink = "cursor: hand; cursor: pointer;";
var ggAlternative = "float: left; width: 185px; height: 250px; background-color: #ececec; padding: 5px; margin: 3px;";
var ondemand = "background-color: #efd2ac !important;";
var sodek = "background-color: #f9bbbb !important;"; //f9bbbb
var sod = "background-color: #ff9393 !important;"; //ff9393
var megasod = "background-color: #e8616a !important; color: white;";
var datepick = "position: fixed; top: 60px; left: 15px; font-size: 12px; width: 300px";
var ggImg = " height: auto; width: auto; max-width: 175px; max-height: 175px; display: block; margin:0 auto 0 auto;";
addCSSRule(sheet, "div.gginfo", gginfo, 0);
addCSSRule(sheet, "span.closeInfo", closelink, 0);
addCSSRule(sheet, "a.link:hover", alink, 0);
addCSSRule(sheet, "div.ggAlternative", ggAlternative, 0);
addCSSRule(sheet, "div.ondemand", ondemand, 0);
addCSSRule(sheet, "div.sod", sod, 0);
addCSSRule(sheet, "div.sodek", sodek, 0);
addCSSRule(sheet, "div.megasod", megasod, 0);
addCSSRule(sheet, "div.ggAlternative img", ggImg, 0);
addCSSRule(sheet, "img.resize", resize, 0);
addCSSRule(sheet, "div#datepicker a", alink, 0); // make pointer when hovering over linkable elements
addCSSRule(sheet, "div#datepicker", datepick, 0);
}
function addCSSRule(sheet, selector, rules, index) {
if("insertRule" in sheet) {
sheet.insertRule(selector + "{" + rules + "}", index);
}
else if("addRule" in sheet) {
sheet.addRule(selector, rules, index);
}
}
@dell640

This comment has been minimized.

Copy link

dell640 commented Feb 8, 2017

Hi, thanks for sharing, its great! Can you please mux all Earning types (Subscriptions+On demand+Enhanced+Single & other) to one page as it was in old style? That will make your code perfect :)

Ilja

@claudio0

This comment has been minimized.

Copy link

claudio0 commented Feb 13, 2017

Hi, thanks, great script!

Error report (version 1.2.11): in "Image statistics" the total Earned amount - and as a result the Return per download - are wrong, not taking today's downloads into account, e.g.:
Earned 0.38$ (0.38$ Today)
Downloaded 2 times (1 time today)
Return per download: 0.19$

@marlazinger

This comment has been minimized.

Copy link

marlazinger commented Feb 13, 2017

Hi! Thank you for sharing!
In my opinion, the current color differentiation on the "alternative arrangement" is not very informative. Maybe, you can divide the colors under license. For example: green for the requirements of blue - video, red - enhanced ...
And since all the types of licenses are currently in one page in the "representation of the table", different colors may also be useful
Thanks

@Artushfoto

This comment has been minimized.

Copy link

Artushfoto commented Feb 15, 2017

Hello thank You for script. Please add option "sort by download time"

@Valon2017

This comment has been minimized.

Copy link

Valon2017 commented Apr 26, 2017

Hello!
Probably on the website Shutterstock something changed. MISSING string "Show alternative layout". Please specially Your script. Thank you!

@ziggzaggy

This comment has been minimized.

Copy link

ziggzaggy commented Apr 26, 2017

Same here, alternative layout has gone, I think since they added the 'View payment history' link. :( :(

@ziggzaggy

This comment has been minimized.

Copy link

ziggzaggy commented May 10, 2017

Big thank you for the update! My eyes are much happier :)

@Valon2017

This comment has been minimized.

Copy link

Valon2017 commented May 18, 2017

Thanks!!!!!

@ziggzaggy

This comment has been minimized.

Copy link

ziggzaggy commented Jun 2, 2017

Clicking on the image id has stopped working now, they must have changed something else :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment