Skip to content

Instantly share code, notes, and snippets.

@CharlesNepote
Last active April 3, 2020 18:19
Show Gist options
  • Save CharlesNepote/6b9c2c7ae074e7910378c41d3076cf79 to your computer and use it in GitHub Desktop.
Save CharlesNepote/6b9c2c7ae074e7910378c41d3076cf79 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name OFF mass updater user script
// @namespace openfoodfacts.org
// @version 0.1
// @description Mass Editor
// @match https://*.openfoodfacts.org/*
// @exclude https://*.wiki.openfoodfacts.org/*
// @icon http://world.openfoodfacts.org/favicon.ico
// @require http://code.jquery.com/jquery-latest.min.js
// @require http://code.jquery.com/ui/1.12.1/jquery-ui.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.js
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
// Code from https://github.com/roiKosmic/OFFMassUpdate/blob/master/js/content_script.js
// * Allow mass edit of products
// * [UI] the pen icon [🖉] allows to open each product directly in edit mode (without opening "view" mode)
/*$("head").append ( // .append is a jQuery function
+ '<script type="text/javascript" src="http://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>'
);/**/
// Library pre-load. See https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file
function loadScript(url, callback) {
// Adding the script tag to the head as suggested before
var head = document.head;
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
// Then bind the event to the callback function.
// There are several events for cross browser compatibility.
script.onreadystatechange = callback;
script.onload = callback;
// Fire the loading
head.appendChild(script);
}
(function() {
'use strict';
var massEditCss = `
.products > li > a {
padding-bottom: 0;
margin-bottom: 0;
height: 172px;/**/
}
.pus_edit_link {
height: 1rem !important;
display: inline !important;
margin-left: 20px !important;
padding: 0 5px 0 5px !important;
}
.massButton {
background-color: red;
border: none;
text-align: center;
display: inline-block;
font-size: 30px;
color: white;
border-radius: 50%;
position:fixed;
top:3.5rem;
right:20px;
width:50px;
height:50px;
z-index:99;
cursor:pointer;
background-size: cover;
}
.massForms{
background-color: blue;
opacity: .85;
padding: 20px;
color: rgba(255,255,255,.9);
position: fixed;
top:80px;
right:20px;
}
.massFormButton{
background-color: red;
border: none;
color: white;
padding: 2px;
text-align: center;
text-decoration: none;
display: block;
font-size: 16px;
color: white;
margin: 4px 2px;
width:25%;
cursor:pointer;
border-radius: 20px;
}
.upBar{
margin-top:5px;
margin-bottom:10px;
}
#selectAll{
margin-top:5px;
display:inline;
float:left;
}
.counter{
margin-top:15px;
}
#sNumber{
background-color: green;
border: none;
color: white;
text-align: center;
text-decoration: none;
display: float;
float:right;
font-size: 16px;
color: white;
margin: 4px 2px;
border-radius: 50%;
width:20px
}
#eNumber{
background-color: red;
border: none;
color: white;
text-align: center;
text-decoration: none;
display: float;
float:right;
font-size: 16px;
color: white;
margin: 4px 2px;
border-radius: 50%;
width:20px
}
#backButton{
background-color: red;
border: none;
color: white;
padding: 2px;
text-align: center;
text-decoration: none;
display: block;
font-size: 16px;
color: white;
margin-top: 10px;
width:50%;
cursor:pointer;
border-radius: 20px;
}
/* Imorted from https://github.com/roiKosmic/OFFMassUpdate/blob/master/css/external/jquery.tagsinput.css */
div.tagsinput { border:1px solid #CCC; background: #FFF; padding:5px; width:300px; height:100px; overflow-y: auto;}
div.tagsinput span.tag { border: 1px solid #a5d24a; -moz-border-radius:2px; -webkit-border-radius:2px; display: block; float: left; padding: 5px; text-decoration:none; background: #cde69c; color: #638421; margin-right: 5px; margin-bottom:5px;font-family: helvetica; font-size:13px;}
div.tagsinput span.tag a { font-weight: bold; color: #82ad2b; text-decoration:none; font-size: 11px; }
div.tagsinput input { width:80px; margin:0px; font-family: helvetica; font-size: 13px; border:1px solid transparent; padding:5px; background: transparent; color: #000; outline:0px; margin-right:5px; margin-bottom:5px; }
div.tagsinput div { display:block; float: left; }
.tags_clear { clear: both; width: 100%; height: 0px; }
.not_valid {background: #FBD8DB !important; color: #90111A !important;} /**/
}`;
/*--- For this to work well, we must also add-in the jQuery-UI CSS.
We add the CSS this way so that the embedded, relatively linked images load correctly.
(Use //ajax... so that https or http is selected as appropriate to avoid "mixed content".)
*/
$("head").append ( // .append is a jQuery function
+ '<link '
+ 'href="//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.min.css" '
+ 'rel="stylesheet" type="text/css">'
);
// apply custom CSS
var s = document.createElement('style');
s.type = 'text/css';
s.innerHTML = massEditCss;
document.documentElement.appendChild(s);
var form_template = ""
+"<div id='form'>"
+" <div class='upBar'>"
+" <input type='checkbox' id='selectAll'>&nbsp;Select All</input>"
+" </div>"
+" <div>Field to update</div>"
+" <select id='champ'>"
+" <option value='add_packaging' field='packaging'>Packaging</option>"
+" <option value='add_brands' field='brands'>Brands</option>"
+" <option value='add_categories' field='categories'>Categories</option>"
+" <option value='add_labels' field='labels'>Label, certifications, rewards</option>"
+" <option value='add_origins' field='origins'>Ingredients origins</option>"
+" <option value='add_manufacturing_places' field='manufacturing_places'>Manufacturing places</option>"
+" <option value='add_purchase_places' field='purchase_places'>Purchasing places</option>"
+" <option value='add_stores' field='stores'>Stores</option>"
+" <option value='add_countries' field='countries'>Purchasing countries</option>"
+" <option value='quantity' field='quantity'>Quantity</option>"
+" </select>"
+" <div id='tagsHidder'><input name='tags' id='tags' value='' /></div>"
+" <input name='quantity' id='quantity' type='text' value='' />"
+" <div class='massFormButton update'>Update</div>"
+"</div>"
+"<div id='spinner'>Editing of <span id='pNumber'>0</span> products in progress"
+" <div class='counter'>Success&nbsp;<div id='sNumber'>0</div></div>"
+" <div class='counter'>Failures&nbsp;<div id='eNumber'>0</div></div>"
+" <div id='backButton'> < Back</div>"
+"</div>";
var api_url = "/cgi/product_jqm2.pl?";
var api_autocomplete_url = "/cgi/suggest.pl?";
var sField='packaging';
var lang='';
var productToUpdate=0;
$(document).ready(function(){
loadScript("https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.js", massEditorMain); // load library before launching script
//massEditorMain();
});
/**
* massEditorMain: Main function to launch Mass Editor
* @param : none
* @return : none
*/
function massEditorMain(){
if(isAdmin()){
if($(".products").length){
lang = $("html").attr("lang")
addingCheckBox();
addingMassButton();
$('#tags').tagsInput(
{
onChange: function(){
console.log("Tags updated");
//browser.storage.local.set({"tags":$('#tags').val()}); // ----------------------------------------------------
localStorage.setItem('tags', $('#tags').val());
//GM_SuperValue.set(storageVar, {"tags":$('#tags').val()});
},
autocomplete_url: function(request, response) {
var url = api_autocomplete_url+"lc="+lang+"&tagtype="+sField+"&string="+request.term;
$.get(url, function(data){
//data = JSON.parse(data);
response(data);
});
}
}
);
}
}
}
/**
* Description : add a checkbox to each listed product, with product code as "value"
* @param : none
* @return : none
*/
function addingCheckBox(){
console.log("Adding check box");
$(".products > li").append(
"<input class='massUpdateCheckbox' type='checkbox' value=''/>");
$('.massUpdateCheckbox').each(function(){
var myAnchor= $(this).parent().find("a");
var myHref = myAnchor.attr("href"); // /product/3263856632710/franprix
//var myRe = /\/(\w+)\/(\d+)\/(\w+)/;
var myRe = /\/(\w+)\/(\d+)([\/|\w]*)/;
var result = myRe.exec(myHref);
$(this).attr('value',result[2]); // value="3263856632710"
console.log("Value: "+result[2]);
$(this).after('<a class="pus_edit_link" href="'+document.location.protocol + "//" + document.location.host +
"/cgi/product.pl?type=edit&code=" + result[2] + '" target="_blank">🖉</a>');
});
}
/**
* Description : add Mass Editor Button
* @param : none
* @return : none
*/
function addingMassButton(){
$("body").append(
"<div class='massUpdater'>" +
" <div class='massButton'>🖊</div>" +
" <div class='massForms'>"+form_template+"</div>" +
"</div>");
$('.massForms').hide();
$('#spinner').hide();
// ChN: ??????
//initValue();
$(".massButton").click(function(){
if($(".massForms").is(":hidden")){
$('.massForms').show();
$(".massButton").css("background-color","blue");
//browser.storage.local.set({"visible":true}); // ----------------------------------------------------
localStorage.setItem('visible', true);
//GM_SuperValue.set(storageVar, {"visible":true});
}else{
$('.massForms').hide();
$(".massButton").css("background-color","red");
clearAllField();
$("#tagsHidder").show();
$("#quantity").hide();
}
});
$("#backButton").click(function(){
$("#backButton").hide();
$("#spinner").hide();
$('#selectAll').prop("checked",false);
$("#form").show();
resetCounter();
});
$("#quantity").change(function(){
var q = $(this).val();
//browser.storage.local.set({"quantity":q}); // ----------------------------------------------------
localStorage.setItem('quantity', q);
//GM_SuperValue.set(storageVar, {"quantity":q});
});
$(".update").click(function(){
$("#spinner").show();
$("#form").hide();
$("#backButton").hide();
sendMassUpdate();
});
$('#selectAll').change(function(){
if($(this).is(':checked')){
$('.massUpdateCheckbox').prop("checked",true);
}else{
$('.massUpdateCheckbox').prop("checked",false);
}
});
$('#champ').change(function(){
sField = $('#champ').find(':selected').attr("field");
//browser.storage.local.set({"selectedField":sField}); // ----------------------------------------------------
localStorage.setItem('selectedField', sField);
//GM_SuperValue.set(storageVar, {"selectedField":sField});
console.log("Setting: "+sField);
if(sField==='quantity'){
$("#tagsHidder").hide();
$("#quantity").show();
}else{
$("#tagsHidder").show();
$("#quantity").hide();
}
});
}
/**
* Description :
* @param : none
* @return : none
*/
function initValue(){
// browser.storage.local.get(['selectedField'],function(result){ // ----------------------------------------------------
// GM_SuperValue.get(storageValue)
// if(result.selectedField != null){
// $("#champ > option[field='"+result.selectedField+"']").prop("selected",true);
// console.log("getting:" + result.selectedField);
// sField= result.selectedField;
// if(sField==='quantity'){
// $("#tagsHidder").hide();
// $("#quantity").show();
// }else{
// $("#tagsHidder").show();
// $("#quantity").hide();
// }
// }
// });
browser.storage.local.get(['tags'],function(result){ // ----------------------------------------------------
if(result.tags != null){
$('#tags').importTags(result.tags);
}
}
);
browser.storage.local.get(['quantity'],function(result){ // ----------------------------------------------------
if(result.quantity != null){
$('#quantity').val(result.quantity);
}
}
);
browser.storage.local.get(['visible'],function(result){ // ----------------------------------------------------
if(result.visible == true){
$('.massForms').show();
$(".massButton").css("background-color","blue");
} else {
$('.massForms').hide();
$(".massButton").css("background-color","white");
}
});
}
/**
* Description : update each product via the API
* @param : none
* @return : none
*/
function sendMassUpdate(){
var mySelect = $('#champ');
var selectedField = mySelect.find(':selected').val();
productToUpdate= $('.massUpdateCheckbox:checked').length;
$('.massUpdateCheckbox').each(function(){
if($(this).is(':checked')){
var remote_url = api_url+"code="+$(this).attr("value")+"&lc="+lang+"&comment="+encodeURIComponent("Updated via Power User Script")+"&"+selectedField+"=";
if(sField==='quantity'){
remote_url += encodeURIComponent($("#quantity").val());
}else{
remote_url += encodeURIComponent($('#tags').val());
}
console.log("Sending Get request to "+remote_url+"\n");
$.ajax({
type: "GET",
url: remote_url,
success: function (result) {
incrSuccessCounter();
productToUpdate--;
updateProductCounter();
if(productToUpdate <=0) $('#backButton').show();
},
error: function(){
incrFailureCounter();
productToUpdate--;
updateProductCounter();
if(productToUpdate <=0) $('#backButton').show();
}
});
$(this).prop('checked',false);
}
});
}
/**
* Description :
* @param : none
* @return : none
*/
function clearAllField(){
//browser.storage.local.clear(); // ----------------------------------------------------
// =>
localStorage.clear();
$('#tags').importTags("");
$("#quantity").val("");
$("#champ > option[field='packaging']").prop("selected",true);
sField='packaging';
$('.massUpdateCheckbox').prop("checked",false);
$('#selectAll').prop("checked",false);
}
function incrFailureCounter(){
var x = parseInt($("#eNumber").html()) +1;
$("#eNumber").html(x);
}
function incrSuccessCounter(){
var x = parseInt($("#sNumber").html()) +1;
$("#sNumber").html(x);
}
function updateProductCounter(){
$("#pNumber").html(productToUpdate);
}
function resetCounter(){
$("#eNumber").html("0");
$("#sNumber").html("0");
$("#pNumber").html("0");
}
/**
* Description : Detect if user is connected or not
* @param : none
* @return : boolean, state of connection: true|false
*/
function isConnected(){
if($("input[name='user_id']").length) return false;
return true;
}
/**
* Description : Detect if user is admin or not
* @param : none
* @return : boolean, state of connection: true|false
*/
function isAdmin(){
// Detect producers platform // TODO: duplicated code with Power User Script
var regex_pro = RegExp('\.pro\.open');
if(regex_pro.test(document.URL) === true) {
return true;
}
// href="/cgi/user.pl?userid=charlesnepote&type=edit"
var editor_id = $(".side-nav > li > a").attr("href"); // /editor/charlesnepote
console.log("editor_id: " + editor_id);
if(editor_id === undefined) return false;
var user_id = (/\/(.*?)\/(.*)/).exec(editor_id)[2];
console.log("user_id: "+user_id);
var admins = ["aleene", "charlesnepote",
"moon-rabbit", "nutrinet-sante", "sebleouf",
"tacinte", "tacite", "tacite-mass-editor", "teolemon",
"segundo", "stephane", ];
if(admins.includes(user_id)) return true;
return false;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment