Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@deymosD
Last active January 8, 2021 18:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save deymosD/126aa0a849de4196547ef5662684b270 to your computer and use it in GitHub Desktop.
Save deymosD/126aa0a849de4196547ef5662684b270 to your computer and use it in GitHub Desktop.
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
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
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
Copy link

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
Copy link

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

@Valon2017
Copy link

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

@ziggzaggy
Copy link

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

@ziggzaggy
Copy link

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

@Valon2017
Copy link

Thanks!!!!!

@ziggzaggy
Copy link

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

@UltimaGaina
Copy link

It would be great if someone could add a next day/previous day option, to easily navigate back and forth, without having to pick a day from the calendar or getting back to the parent page!

@straannick
Copy link

Thank you. I think it would be very useful to write a script that generates an HTML document with a table showing a list of images with links to them that have never been sold. This will allow them to be deleted, re-loaded or sent to other stocks. Could you do this?

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