EnstylerJS

MyDealz Enstyler enhanced features incl. Amazon Mobile Redirect

目前为 2016-11-27 提交的版本。查看 最新版本

// ==UserScript==
// @name        EnstylerJS
// @namespace   Enstyler
// @description MyDealz Enstyler enhanced features incl. Amazon Mobile Redirect
// @include     http://www.mydealz.de/*
// @include     https://www.mydealz.de/*
// @include     https://userstyles.org/styles/128262/*
// @include     https://www.amazon.*/gp/aw/*
// @version     2.11.276
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_log
// @grant       GM_xmlhttpRequest
// @require     http://code.jquery.com/jquery-3.1.1.min.js
// @require     http://cdnjs.cloudflare.com/ajax/libs/jquery-throttle-debounce/1.1/jquery.ba-throttle-debounce.min.js
// @require     http://openuserjs.org/src/libs/sizzle/GM_config.js
// ==/UserScript==

// ========== INIT EnstylerJS =====================================
// init Enstyler environment
var enUserLogin = false;
var enUserName = '';
var enSection = '';
var EnstylerStartTime=Date.now();

// Parse location for later use
var enLocParser= location;

var DEBUG=false;

function EnstylerInit () {
    // hide Enstyler2 CSS (c) text because we have a button now
    addStyleString('.threadWidget-footer::after {display: none !important};');
    
    // get LoginStatus and Username
    if (enUserLogin = $('.avatar--type-nav').length) {
        enUserName = $('.navDropDown a').attr('href');
        enUserName = enUserName.replace(/.*\/profile\/([^\/]+).*/,'$1');
        if(DEBUG) console.error('User: ' +enUserName);
    }
    // get Section (first element in path)
    enSection= enLocParser.pathname.replace(/\/([^\/]+\/*).*/,'/$1');
}


// add actions to tread overview @ some places ==================================

// code used for MyDealz Dealz actions, thanks to mydealz :-)
  var myOuterHtml  = [ '<span class="js-options bg--em bRad--a space--h-3 space--v-3 space--mt-3 text--b">', '</span>'];
    
  var enDealAction = [ '<a title="Sag was dazu" class="link ico ico--pos-l ico--type-comment-blue space--mr-3"'+ // comment 0+1+3
                       'href="<ENSTYLER-HREF-HERE>#comment-form" data-handler="track" data-track="{&quot;action&quot;:&quot;scroll_to_comment_add_form&quot;,&quot;label&quot;:&quot;engagement&quot;}">',
                       'Sag was dazu', '', '</a>',
                       '<a title="Von Liste entfernen" class="link text--color-blue ico ico--type-bookmark-blue ico--pos-l space--mr-3"' + //un-bookmark 4+5+7
                       'data-handler="track replace" data-replace="{&quot;endpoint&quot;:&quot;https:\/\/www.mydealz.de\/threads\/<ENSTYLER-THREADID-HERE>/remove&quot;,&quot;method&quot;:&quot;post&quot;}" data-track="{&quot;action&quot;:&quot;save_thread&quot;,&quot;label&quot;:&quot;engagement&quot;}">',
                       'Von Liste entfernen', '', '</a>',
                       '<a title="Bearbeiten" class="link text--color-blue ico ico--type-pencil-blue ico--pos-l space--mr-3"'+ // edit 8+9+11
                       'href="<ENSTYLER-HREF-HERE>/edit" data-handler="track" data-track="{&quot;action&quot;:&quot;goto_thread_edit_form&quot;,&quot;beacon&quot;:true}">',
                       'Bearbeiten', '', '</a>',
                       '<a title="Abgelaufen?" class="thread-expire link ico ico--type-clock-blue ico--pos-l space--mr-3"'+  // expiried not working :-(
                       'href="<ENSTYLER-HREF-HERE>/expire/report" rel="nofollow" data-handler="track replace" data-track="{&quot;action&quot;:&quot;report_expired_thread&quot;,&quot;label&quot;:&quot;contribution&quot;}" data-replace="{&quot;endpoint&quot;:&quot;https:\/\/www.mydealz.de\/<ENSTYLER-HREF-HERE>/expire\/report&quot;}">',
                       'Abgelaufen?', '', '</a>',
                     ];
  var PATTERN = [ /<ENSTYLER-HREF-HERE>/g,     // pattern to replace by deal link ...
                  /<ENSTYLER-THREADID-HERE>/g // pattern to replace ID
                ];
  var enDealAdd;

function EnstylerDealActions(){
    // use parsed location
    var pathname = enLocParser.pathname;
    var myText=0;
    // no username ??
    if (enUserName != "") {
       pathname = pathname.replace(enUserName + '/',''); // remove username if path is longer
    }  
    // display short/no text?
    if ($('.ico--type-grid-subNavActive').length) { myText=1; }
        
    // default for all Dealz: comment
    enDealAdd = enDealAction[0]+ enDealAction[1+myText] + enDealAction[3];

    // Action for special locations only ===========
    // saved Dealz panel 
    if (pathname.endsWith('profile/saved-deals') ){
        // add for user saved-dealz: un-bookmark
        enDealAdd += enDealAction[4]+ enDealAction[5+myText]; + enDealAction[7]
    }
    if (pathname.endsWith('profile/diskussion') || pathname.endsWith(enUserName)){
        // add user dealz and discussions: comment edit
        enDealAdd +=  enDealAction[8]+ enDealAction[9+myText] + enDealAction[11];
    }
    EnstylerDealActionsDo()
}


function EnstylerDealActionsDo() {
 // if logged in ...
 if (enUserLogin) {

  if (GM_config.get('enConfMoreDeal')) {

    // we have an Action to add to an Deal!
    if (enDealAdd != "") {
        //GM_log('Action:' + myAction);
        // every thread on thread page ...
        $('article').each(function () {
            // get ThreadID, Link to Deal and DealID
            var myThread = $(this).attr('id');
            if (!myThread.startsWith('thread_')) {return;}
            
            var myDealHref = $('#' + myThread + ' .thread-title a').attr('href');
            var myDealID = myThread.replace('thread_','');
            
            if (myThread.startsWith('To be')) {return;}
            
            // compose final HTML
            var newHtml= myOuterHtml[0] + enDealAdd + myOuterHtml[1];
            newHtml = newHtml.replace(PATTERN[0], myDealHref);
            newHtml = newHtml.replace(PATTERN[1], myDealID);
            
            // append HTML to Deal
            $('#' + myThread +' .thread-infoRow').each(function () {
                $(this).append(newHtml);
                $(this).removeClass('thread-infoRow');
            });
        }); 
    }
      
    // actions for somewhere  ===========
    // remove unwanted HTML from deal description
    $('.thread--type-detail .userHtml').each(function () {
        // userhtml code from mydalz, need to find jafascript to save automatically :-(
        var myUserhtml = ['<div class="userHtml overflow--wrap-break space--t-3" data-handler="lightbox-xhr" data-lightbox-xhr="{&quot;name&quot;:&quot;threads&quot;}">',
                          '</div>', // sourround deal description
                         ];
        // get inner html
        var myHtml = $(this).html();

        // remove unwanted Stuff: combined <div><br><br> stuff, created by cut'npaste html
        // not elegant, but works ...
        var newHtml =  myHtml.replace(/<div>|<div><br>|<\/br>|<\/div>/gi,'');
        newHtml =  myHtml.replace(/<p>|<p><br>/gi,'<br><br>');
        newHtml =  newHtml.replace(/<br><br><br>|<br><br><br><br>|<br><br><br><br><br>/gi,'<br><br>');      
        // replace original with modifyed html
        $(this).replaceWith(myUserhtml[0] + newHtml + myUserhtml[1]);        
    });

  } // END enabled    
 }
}


// show popup user info while click on avatar ... ======================
function EnstylerAvatarPopup(){
    EnstylerAvatarPopupDo();
}

function EnstylerAvatarPopupDo() {
  // login needed ... (Error in Popup without login ...)
  if (enUserLogin) {
    // code used for MyDealz avatar popup, thanks to mydealz :-)
    var enPopupUser = ['<button data-handler="track popover" data-track="{"action":"show_short_user_profile","label":"engagement"}" data-popover="{"endpoint":"',
                       '/short","target":"#template-popoverLoader","layout":[{"preset":"e","y":"50%","left":{"offset":0},"width":300,"maxWidth":"100%"}]}">',
                       '</button>',
                      ];
    // remove second image from cardview
    if (GM_config.get('enConfUser')) { addStyleString('.thread-footer-cell a img.avatar.vAlign--all-m.space--mr-1.thread-avatar {display: none;}'); }

    // replace every avatar link without popup
    if (GM_config.get('enConfUser')) {
        $('.thread-footer-cell a.user.linkPlain, .user.linkPlain.thread-user').each(function () {
            var Iam = $(this);
            // get inner html and link to user profile
            var myHtml = Iam.html();
            var mysrc = Iam.attr('href');
            
            // seperate user name from image and add class user
            var myAvatar1 = myHtml.replace(/<span.*/,'');
            var myAvatar2 = myHtml.replace(/.*<span class=".* space--mr-1">/,'<span class=" space--mr-1 user link-plain">');

            // show small / medium sized Avatar
            myAvatar1 =  myAvatar1.replace('avatar--type-s','avatar--type-m'); //in Dealz
            if (GM_config.get('enConfAvatar')) { myAvatar1 =  myAvatar1.replace('thread-avatar','avatar--type-m'); } 
            
            // compose popup
            var myPopup = enPopupUser[0] + Iam.attr('href') + enPopupUser[1] + myAvatar1 +  enPopupUser[2] + '<a href="' + Iam.attr('href') + '">'+ myAvatar2 + '</a>';
            Iam.replaceWith(myPopup);
        });
    }
  }  
}


// create select page or scrollwheel for page navigation =============
var EnstylerPageEnum='enPageEnum';
var selectList = document.createElement("select");
selectList.id = EnstylerPageEnum;
selectList.setAttribute('class', EnstylerPageEnum);
selectList.onchange = EnstylerPageAction; 

var EnstylerPageEnumDown='enPageEnumDown';
var selectListDown = document.createElement("select");
selectListDown.id = EnstylerPageEnumDown;
selectListDown.onchange = EnstylerPageAction; 

function EnstylerPagePickerCreate() {
 // revome existing picker
 EnstylerPagePickerRemove();
    
 // if enabled
 if (GM_config.get('enConfPagePicker')) {
   // init values and clear select list
   var page=1;
   var min=1;
   var max=1;
   $(selectList).empty();
     
   // get page and max from pagenav
   if ( $('.text--color-charcoalTint').length ) {
       // remove linebreaks
       var pageHtml = $('.text--color-charcoalTint').html().replace(/\r?\n|\r/g);
       //locate actual page and last page
       page = pageHtml.replace( /.*>Seite /i ,''); page = page.replace( /<.*/i , '');
       max =  pageHtml.replace( /.*page=/ ,'');  max = max.replace( /[^0-9].*/i , '');
       if (page == '') {page=1;}
       if (max  == '') {max=page;}
   }

   // create page select element
   var x; var last; var option;

   for (x = min; x <= max; ) {
       option = document.createElement("option");
       option.text = x;
       selectList.add(option); //selectListDown.add(option);
       last = x;
   
       // non linear increment
       var diff = Math.abs(x-page);

       if ( x < 10 || diff < 5) { x++; }
       else if ( x < 1000 && diff > 600) { x += Math.floor(diff/100); }
       else { x += Math.floor(diff/2); }
   }
   // add last page
   if (page > max) { max=page;}
   if (last < max ) {
       option = document.createElement("option");
       option.text = max;
       selectList.add(option); //selectListDown.add(option);
   }
   // set default value
   selectList.value = page; //selectListDown.value = page;

   // Deal Page
   if ($('.voteBar').length) {
       selectList.setAttribute('class', EnstylerPageEnum +' subNavMenu-link subNavMenu-btn voteBar--sticky-off--hide');
       $('.voteBar--sticky-off--hide:last').before(selectList);
     
   } else {
  
       // overwiew page 
        if (GM_config.get('enConfBtn')) {
            // Place Picker in subnav
            selectList.setAttribute('class', EnstylerPageEnum+' box--all-i subNavMenu-link subNavMenu-btn');
            $('.subNav-label:last').before(selectList);
        } else {
            //place picker in MainNav
            selectList.setAttribute('class', EnstylerPageEnum+' js-navDropDown-messages vAlign--all-m');
            if ($('.test-loginButton').length) {
                $('.test-loginButton').before(selectList); 
            } else {
                $('.js-navDropDown-messages').before(selectList);
            }
        }
    }
 }        
}

function EnstylerPagePickerCreateDo() {
   // get page and max from pagenav
   if ( $('.js-sticky .text--color-charcoalTint').length ) {
       // remove linebreaks
       var pageHtml = $('.js-sticky .text--color-charcoalTint').html().replace(/\r?\n|\r/g);
       //locate actual page and last page
       page = pageHtml.replace( /.*>Seite /i ,''); page = page.replace( /<.*/i , '');
       if (page == '') {page=1;}
       // set default value
       selectList.value = page; //selectListDown.value = page;
   }
}

// goto selected Page
function EnstylerPageAction() {
    var enPage = 'page=' + $(this).val();
    var enUrl  = window.location.href;
    var path   = window.location.pathname;

    // remove page= and everthing behind
    enUrl = enUrl.replace( /page=.*|#.*/ ,'');
    
    // add new page parameter
    if ( enUrl.endsWith('?') ||  enUrl.endsWith('&')) {
        enUrl += enPage;
    } else {
        enUrl += '?'+enPage;
    }
    
    // add #thread-comments for deal
    if (path.startsWith('/deals/')) { enUrl += '#thread-comments';}
    
    window.location = enUrl;
}

function EnstylerPagePickerRemove() {
        // Removes pagepicker from the document
        $('.'+ EnstylerPageEnum).remove();
}



// blacklist do not show dealz containing blacklistet words ==========================
// search in kategorie, dealtitle, and username
var enClassHidden = 'enClassHidden';
var enClassBlackDone = 'enClassBlackDone';
var enUnblackText = 'unBlacklist <NUM-BLACK> Dealz';
var enBlacklisted=0;

var unwantedRegex = [ /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\ ]/g, // in White/Backlist
                      /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\,]/g  // in Dealtext
                    ]; 
var enBlack;
var enWhite;
var enBlackTemp;
function EnstylerBlacklist() {
 // if logged in and user is not in whitelist
    if (enUserLogin && ! GM_config.get('enConfWhitelist').includes(enUserName)) {
        // add actual user to whitelist
        GM_config.set('enConfWhitelist', '@'+enUserName +',' + GM_config.get('enConfWhitelist'));
    }
   
    // convert Black/Whitelist to Array
      var myTemp=GM_config.get('enConfBlacklist');
          myTemp = myTemp.replace(unwantedRegex[0], '');  
          enBlack = myTemp.split(',');
        
          myTemp=GM_config.get('enConfWhitelist');
          myTemp = myTemp.replace(unwantedRegex[0], '');      
          enWhite = myTemp.split(',');
        
    enBlackTemp= GM_config.get('enConfHideColder');
    EnstylerBlacklistDo();
}

function EnstylerBlacklistDo() {
    if (!GM_config.get('enConfBlackEnable')) { return;}

    // process every article
    $('article').each(function () {
            if ($(this).hasClass(enClassBlackDone)) { return;}
            var myThread = $(this).attr('id');
            // return if already done or return no deal
            if (!myThread.startsWith('thread_')) { return;}
            
            // mark as already seen
            $(this).addClass(enClassBlackDone);
            
        // get title, categorie, user
        var myDealText =        $('#' + myThread + ' .thread-category').text();
            myDealText +=  ' ' +$('#' + myThread + ' .thread-title a').text();
            myDealText += ' @' +$('#' + myThread + ' .user').text();
            myDealText = myDealText.replace(unwantedRegex[1] ,' ');
            
            // Whitelist first
            for (var i=-1,  length=enWhite.length; ++i < length;) {
                // whitelist Regex, exit if found
                if (enWhite[i] !='' && myDealText.match(new RegExp(enWhite[i],'i'))) {
                    return;
                }
            }

        //get temp
        var myVoteTemp = $('#' + myThread + ' .vote-temp').text();
            myVoteTemp = myVoteTemp.replace(/°$/,'');

            // blacklist vote temp
            if (myVoteTemp <= enBlackTemp) {
                    $(this).addClass(enClassHidden);
                    enBlacklisted++;
                    return;
            }
            // blacklist
            for (var i=-1,  length=enBlack.length; ++i < length;) {
                // whitelist Regex, unwanted characteres are already removed
                if (enBlack[i] !='' && myDealText.match(new RegExp(enBlack[i],'i'))) {
                    $(this).addClass(enClassHidden);
                    enBlacklisted++;
                    return;
                }    
            }
    }); // END Article 
    // set label for unBlacklist button
    enJSfieldDefs.enConfUnblacklist.label=enUnblackText.replace('<NUM-BLACK>',enBlacklisted)   
}

function EnstylerBlacklistRemove() {
       enBlacklisted=0;
       $('.'+enClassHidden).removeClass(enClassHidden);
       $('.'+enClassBlackDone).removeClass(enClassBlackDone);
}  

function EnstylerBlacklistUnhide() {
       enBlacklisted=0;
       $('.'+enClassHidden).removeClass(enClassHidden);
}  


// Main Nav will stay on TOP of the screen =========================
function EnstylerFixedNav() {
        var myFixedCssMain='.nav { display: block; position: fixed; width: 100%; z-index: 120;} .subNav, .userProfile, .tabbedInterface {margin-top: 4.4em;}';
        var myFixedCssSub ='.subNav {margin-top: 0 !important;} .nav-subheadline {margin-top: 4.4em;}';
        if (GM_config.get('enConfNavFixed')) {
            // everywhere but in Deal detail, I like it like it is ... 
            if (enSection != '/deals/' && enSection != '/gutscheine/' ){
                // delete header element with active stuff, but keep inside HTML
                var mySavedHtml = $('header').html();
                $('header').replaceWith(mySavedHtml);
                
                // CSS for everywhere
                addStyleString(myFixedCssMain);
                if (! enSection.match(/^\/$|^\/hot|^\/new|^\/settings|^\/discussed/)) {
                    // additional CSS for categories
                    addStyleString(myFixedCssSub);
                }
            }
        }
}

// the return of "gestern xx:xx Uhr" ==============
var enNow;
var DealDate;
var TodayStart;
var ShowTime;
         
var EnstylerTimeSeen='enTimeSeen';

function EnstylerDealTime() {
         enNow = new Date();
         enNow.setTime(EnstylerStartTime)
         DealDate=new Date();
         TodayStart = new Date(enNow.getFullYear(), enNow.getMonth(), enNow.getDate());
         ShowTime= GM_config.get('enConfDealMinTime')*3600*1000;
    
         EnstylerDealTimeDo();
}

function EnstylerDealTimeDo() {
  if (GM_config.get('enConfDealTime')) {
         enNow.setTime(Date.now())
         // process every article
         $('time').each(function () {
            // get Deal time Diff, return if no diff or already seen
            var myTime= $(this).text();
            if (!myTime.startsWith('vor ')|| $(this).hasClass(EnstylerTimeSeen)) { return;}
   
            // get Deal Time difference 
            var h = myTime.replace(/.* ([0-9].*) h.*/, '$1'); if (h==myTime) h=0; else h=parseInt(h);
            var m = myTime.replace(/.* ([0-9].*) m.*/, '$1'); if (m==myTime) m=0; else m=parseInt(m);
            var s = myTime.replace(/.* ([0-9].*) s.*/, '$1'); if (s==myTime) s=0; else s=parseInt(s);

            // compose real deal time
            var myDealDiff = ((h*60+m)*60+s)*1000; // Offset deal
            DealDate.setTime( enNow.getTime() - myDealDiff );

            // last midnigth
            if (DealDate < TodayStart) {
                $(this).text('gestern '+ DealDate.toString().slice(16, 21) +' Uhr');
            // more than 6 hours ago
            } else if (myDealDiff > ShowTime){
                $(this).text(myTime + ' (heute '+ DealDate.toString().slice(16, 21) +' Uhr)');
            } else { return; }
            
            $(this).addClass(EnstylerTimeSeen);
         });
  }
}


// mark last seen Deal in Highligth, Hot and New ============================
var mySec='off';

function EnstylerLastSeen(){
  // only in main categories
  if(enSection.match(/^\/$|new$|hot$/)) {
      mySec=enSection.replace(/\//, '');
      // mark last seen article
      EnstylerLastSeenDo();
      
      // save actual last seen
      $('article').each(function () {
          // get ThreadID an return if not thread_
          var myThread = $(this).attr('id');
          if (!myThread.startsWith('thread_')) {return;}
          // store newest article
          GM_config.setValue('enNewestDeal'+enSection.replace(/\//, ''), myThread);
          return false;
      }); 
  }
}


function EnstylerLastSeenDo(){
  // only in main categories
  if(mySec != 'off') {
      var myArticle=GM_config.getValue('enNewestDeal'+ mySec);

      // mark last seen article
      if ( typeof myArticle != 'undefined') {
          $('#'+myArticle).addClass('enClassMarkArticle');
          $('#'+myArticle).prev().addClass('comments-marker');
      }
  }
}

// AMAZON mobile redirect, workaround to not intercept myDealz redirects with GM_xmlhttp... ==============
// stolen from amazon redirect mobile: https://greasyfork.org/de/scripts/19700
function enAmazonMobileRedirect() {
  var enMyLocation=enLocParser.toString();
  // do we run on amazon?
  if (enMyLocation.startsWith("https://www.amazon")) {
    // redirect enabled?  
    if (GM_config.get('enConfAmazonRedirect')) {
      // do it
      if (enMyLocation.includes("/gp/aw/d/")) { window.location.assign(enMyLocation.replace("/gp/aw/d/", "/dp/")) }
      else { window.location.assign(enMyLocation.replace("/gp/aw/ol/", "/gp/offer-listing/")); }
    }
    // Amazon but no redirect enabled
    return false;
  } 
return true;
}

// create button for display Config ==============
var EnstylerButton = 'EnstylerButton';

//var input = document.createElement('a');
//    input.setAttribute('href', 'showEnstylerConfig()');
//    input.setAttribute('id', EnstylerButton);
var input = document.createElement('input');
    input.type = 'button';
    input.setAttribute('id', EnstylerButton);
    input.onclick = showEnstylerConfig;

function EnstylerButtonCreate() {
    // add Enstyler Button to ...
    var myElement;
    var myMain=false;
   
    input.value = 'Enstyler';
    EnstylerButtonRemove();
    // MainNav or Deal
    if (GM_config.get('enConfBtn') || enSection.match(/deals\/|gutscheine\//))
        { myMain=true; }
    
    // only if space left
    if ($(window).width() < GM_config.get('enConfBtnMinWidth'))
        { myMain=false; }
    
    if (myMain) {
       // add button to MainNav
       var Elements = document.getElementsByClassName("nav-link");
       myElement = Elements[3]; 
       input.setAttribute('class', 'vAlign--all-m nav-link-text');
       input.setAttribute('style', '');
    } else {
       // add button to SubNav
       myElement = document.getElementById('tour-filter');
       input.setAttribute('class', 'box--all-i subNavMenu-link');
       input.setAttribute('style', 'font-size: 1.28571em; font-weight: 700; top: 3px; left: -0.7em'); 
    }
  
    // only if myElement exist
    if (myElement !== null) {
        myElement.appendChild(input);
        //insertAfter(input, myElement);
    }
}

// needed for Enstyler Homepage
function EnstylerHomeButton() {
    // add Enstyler Button to ...
    input.value = 'Options';
    var  myElement = document.getElementById('style-settings');
    input.setAttribute('style', 'font-size: 1.28571em; padding: 0.8em;'); 
  
    // only if myElement exist
    if (myElement !== null) {
        //myElement.appendChild(input);
        insertAfter(input, myElement);
    }
}

function EnstylerButtonRemove() {
        // Removes button from the document
        $('#'+ EnstylerButton).remove
}


// ============= GM_config functions =======================================
var enJSAutoUpdate=GM_info.scriptWillUpdate;
var enUpdateJSurl = "https://greasyfork.org/de/scripts/24243"; // production version
var enUpdateInterval=1 * (24*60); // 1 day between update checks

// define EnstylerJS GM_config elements
var enJSfieldDefs = {
    // Part one: load external content --------
 'enstyler': { 
      'section': ['additonal features for Enstyler', ''],
      'label': 'Update CSS...', // Appears on the button
      'type': 'button', // Makes this setting a button input
      'click': function() { // Function to call when button is clicked
               //GM_config.setValue('enEnVersion', 'no');
               //GM_config.save();
               //GM_config.open();
               window.open('https://userstyles.org/styles/128262#style-info');
               }
    },
    'enstylerJS': { 
      'label': 'Update UserScript...', // Appears on the button
      'type': 'button', // Makes this setting a button input
      'click': function() { // Function to call when button is clicked
               //GM_config.setValue('enJSVersion', 'no');
               //GM_config.save();
               //GM_config.open();
               window.open(enUpdateJSurl);

               }
    },

   'dontCookies': {
      'label': 'Mozilla no cookies...', // Appears on the button
      'type': 'button', // Makes this setting a button input
      'click': function() { // Function to call when button is clicked
               window.open('https://addons.mozilla.org/de/addon/i-dont-care-about-cookies/'); }
    },

    /* thhis has to be the last one,display only if internal version is disabled 
    'externalMobileRedirect': {
      'label': 'Amazon mobile redirect...', // Appears on the button
      'type': 'button', // Makes this setting a button input
      'click': function() { // Function to call when button is clicked
               showUrl('https://greasyfork.org/de/scripts/19700'); }
    }, // */

   // part two: EnstylerJS internal configuration options ------ 
   'hidden1': { // display next section, dont kow why ...
      'section': ['Configuration', ''],
        'type': 'hidden', // Makes this setting a hidden input
        'value': 'Some hidden value' // Value stored
   },

   // postion of enstyler "button"
   'enConfBtn': {
      'label': 'Enstyler in MainNav', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': false // Default value if user doesn't change it
    },
    
    'enConfBtnMinWidth': {
      'label': 'if width is bigger than ', // Appears next to field
      'type': 'int', // Makes this setting a text input
      'min': 600, // Optional lower range limit
      'max': 1200, // Optional upper range limit
      'size': 4, // Limit length of input (default is 25)
      'default': 850 // Default value if user doesn't change it
    },
    
   'enConfNavFixed': {
      'label': 'Display FIXED MainNav', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': false // Default value if user doesn't change it
    },
    
   // ehanced USerInfo
   'enConfUser': {
      'label': 'Show Popuop Userinfo', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': true // Default value if user doesn't change it
    },
   'enConfAvatar': {
      'label': 'bigger Avatar for Popuop', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': true // Default value if user doesn't change it
    },   
    
  // enable filtering of external links
  'enConfAmazonRedirect': { 
      'label': 'Amazon mobile redirect', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': true // Default value if user doesn't change it
    }, // */
  

   // more Deal actions
   'enConfMoreDeal': {
      'label': 'additional Deal actions', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': true // Default value if user doesn't change it
    },   // more Deal actions

    // show real Dealtime 
   'enConfDealTime': {
      'label': 'show real Deal Time', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': true // Default value if user doesn't change it
    },
    'enConfDealMinTime': {
      'label': 'if older than Hours', // Appears next to field
      'type': 'int', // Makes this setting a text input
      'min': 1, // Optional lower range limit
      'max': 24, // Optional upper range limit
      'size': 4, // Limit length of input (default is 25)
      'default': 6 // Default value if user doesn't change it
   },    

    // Page picker
   'enConfPagePicker': {
      'label': 'Enable Page Picker', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': false // Default value if user doesn't change it
    },

    /* DISBALED check for Updates
    'enConfCheckEnUpdate': {
      'label': 'Check for Updates', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': true // Default value if user doesn't change it
    },
   'enLastUpdateCheck': {
      'type': 'hidden', // Makes this setting a text input
      'default': '0' // Default value if user doesn't change it
   },
   'enJSVersion': {
      'type': 'hidden', // Makes this setting a text input
      'value': 'no' // Value stored
   },
   'enEnVersion': {
   'type': 'hidden', // Makes this setting a text input
      'value': 'no' // Value stored
   },/* end DISABLED */

    // Black/Whitelist input
   'enConfBlackEnable': {
      'label': 'Enable Black- / Whitelist', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': true // Default value if user doesn't change it
    },
   'enConfHideColder': {
      'label': 'Blacklist if colder then', // Appears next to field
      'type': 'int', // Makes this setting a text input
      'min': -9999, // Optional lower range limit
      'max': -9, // Optional upper range limit
      'size': 4, // Limit length of input (default is 25)
      'default': -999 // Default value if user doesn't change it
   },
    'enConfBlacklist': {
      'label': 'Blacklist - deals, categories, @users', // Appears next to field
      'type': 'text', // Makes this setting a text input
      'size': 70, // Limit length of input (default is 25)
      'default': 'Nutella, Bangood, @Admin' // Default value if user doesn't change it
    },
    'enConfWhitelist': {
      'label': 'Whitelist', // Appears next to field
      'type': 'text', // Makes this setting a text input
      'size': 70, // Limit length of input (default is 25)
      'default': '' // Default value if user doesn't change it
    },
    'enConfUnblacklist': {
      'label': 'UnBlacklist', // Appears on the button
      'type': 'button', // Makes this setting a button input
      'click': function() { // Function to call when button is clicked
               EnstylerBlacklistUnhide(); }
    },
    
    // store newest loaded deal
    'enNewestDealnew': {
      'type': 'hidden', // Makes this setting a text input
      'default': '0' // Default value if user doesn't change it
   },
   'enNewestDealhot': {
      'type': 'hidden', // Makes this setting a text input
      'default': '0' // Default value if user doesn't change it
   },
   'enNewestDeal': {
     'type': 'hidden', // Makes this setting a text input
     'default': '0' // Default value if user doesn't change it
   }, 
   
   // display copy message at end of section ...
   'copy': {
        'section': ['', '(c) Gnadelwartz - <a target="blank" href="https://www.mydealz.de/diskussion/enstyler2-style-your-mydealz-incl-pepper-sites-736219">Enstyler2 - Style your MyDealz</a>'],
        'type': 'hidden', // Makes this setting a hidden input
        'value': 'Some hidden value' // Value stored
   },
};

// define EnstylerJS GM_config elements
var enHomefieldDefs = {
    // Part one: load external content --------
    'saveOpt': { 
      'section': ['save your CSS options for next visit', ''],
      'label': 'Select your CSS on main page then come back and klick "Save"  ', // Appears near textarea
      'type': 'textarea', // Makes this setting a button input
      'size': 70,
      //'click': function() { // Function to call when button is clicked
      //         showUrl('https://userstyles.org/styles/128262#style-info'); }
    },
   
   // display copy message at end of section ...
   'copy': {
        'section': ['', '(c) Gnadelwartz - <a target="blank" href="https://www.mydealz.de/diskussion/enstyler2-style-your-mydealz-incl-pepper-sites-736219">Enstyler2 - Style your MyDealz</a>'],
        'type': 'hidden', // Makes this setting a hidden input
        'value': 'Some hidden value' // Value stored
   },
   
};

// display GM_copnfig as div, so we can apply CSS easy!!
var enGMOptChange = false;
var enGMFrame = document.createElement('div');
enGMFrame.setAttribute('class','GM_config');
document.body.appendChild(enGMFrame);

// basic config panel formatting, everything else is formatted by enstyler
var enCSS = ['.GM_config {left: 5% !iportant; top: 9% !important; height: auto !important; max-width: 35em !important;}',
             '.GM_config input, .GM_config button, .GM_config textarea { border: 1px solid; margin: 0.5em 0em 0.2em 1em; padding: 0.1em;}',
             '.GM_config .reset { font-size: 9pt; padding-right: 1em; }',
             '#GM_config_enstyler_var:after {content: ". <- please install CSS!"; font-weight: bold;}',
             '.enClassHidden, #EnPopup_closeBtn {display: none;}',
            ].join(" ");
addStyleString(enCSS);

var En_Popup = new GM_configStruct(
   {
  'id': 'EnPopup', // You need to use a different id for each instance
  'title': 'EnstylerJS - Info', 
  'fields': // Fields object
    {
       'Text': // This is the id of the field
       {
         'label': '', // Appears next to field
         'type': 'textarea', // Makes this setting a text field
         'default': '' // Default value if user doesn't change it
       }
    },
  'events':
    {
      'open': function (doc) {
              // rename the buttons
              var config = this;
              doc.getElementById(config.id + '_saveBtn').textContent = ' OK ';
              //doc.getElementById(config.id + '_closeBtn').textContent = 'Cancel';
             doc.getElementById(config.id + '_resetLink').textContent = '';
          },
      'save':  function() {
              enUpdateChecked=false;
              enCheckUpdates();
              En_Popup.close();
            },
      'close': function() { enGMConfigOpen=false;},
    },
  'frame': enGMFrame // Element used for the panel
  }
);

function showPopup(text) {
    En_Popup.fields['Text'].value = text;
    En_Popup.fields['Text'].reload();
    En_Popup.open();
}


var enGMConfigOpen=false;
// EnstylerJS Config Panel anzeigen
function showEnstylerConfig() {
  if(!enGMConfigOpen) {
      enGetHome();
      GM_config.open();
      enGMConfigOpen=true;
  } else {
      GM_config.close();
  }    
}


// EnstylerJS START ========================
if (!window.location.hostname.endsWith('userstyles.org')) {
    var enFixedNav=false;
    GM_config.init(
      {
        id: 'GM_config',
        title: !DEBUG ? 'EnstylerJS - Settings' : ' EnstylerJS - >> Debug <<', 
        fields: enJSfieldDefs,
       'events': // Callback functions object
         {
          //'init': function() { alert('onInit()'); },
          // remove elements ich switch is checked or not
          'open': function() { 
          var enRemoveConfig = [
               // { check: true,  switch: 'enConfFilterLink',  remove: 'externalMobileRedirect'},
               { check: false, switch: 'enConfBtn',         remove: 'enConfBtnMinWidth'},
               { check: false, switch: 'enConfUser',        remove: 'enConfAvatar'},
               { check: false, switch: 'enConfDealTime',    remove: 'enConfDealMinTime'},
               { check: false, switch: 'enConfBlackEnable', remove: 'enConfWhitelist'},
               { check: false, switch: 'enConfBlackEnable', remove: 'enConfBlacklist'},
               { check: false, switch: 'enConfBlackEnable', remove: 'enConfHideColder'},
               { check: false, switch: 'enConfBlackEnable', remove: 'enConfUnblacklist'}
              ];
             enFixedNav=GM_config.get('enConfNavFixed');
              
             
              // remove unneeded controls
             $(enRemoveConfig).each(function() {
                 if (GM_config.get(this.switch) == this.check) {
                  GM_config.fields[this.remove].remove();
                 }
             });
              
             // remove / display update dialog
             if (enJSAutoUpdate) {GM_config.fields['enstylerJS'].remove();}
              
          },
          //'reset': function() { alert('reset') },
          // relaod page on close after save
          'save':  function() {
              if (!GM_config.get('enConfNavFixed') && GM_config.get('enConfNavFixed')!=enFixedNav) {window.location.reload(false);}
              EnstylerButtonCreate();
              EnstylerPagePickerCreate();
              EnstylerBlacklistRemove();
              EnstylerBlacklist();
              EnstylerFixedNav();
              EnstylerDealTime()
              GM_config.close();
              GM_config.open();
            },
         'close': function() { enGMConfigOpen=false;},
         },
       'frame': enGMFrame // Element used for the panel
      }
    );
    
   // dummy, do not delete
   function enGetHome() {;}
   // HACK: we are NOT on Amazon
   if (enAmazonMobileRedirect()) {

    // =============== START EnstyerJS ===================
    EnstylerInit();
    EnstylerFixedNav();
    EnstylerLastSeen();
    EnstylerDealTime();
    EnstylerBlacklist();
    EnstylerAvatarPopup();
    EnstylerDealActions();



    
    // delay Pagepicker and repaeting actions after finishing everything else
    function EnstylerDelayedInit() {
           // don't know why, but works only if called with delay ...
           EnstylerButtonCreate();
           EnstylerPagePickerCreate();
        
          // track DOM change Events, debounce: wait 1000ms after mutiple events
          // then re-apply (somse) changes to dynamic loaded content, 
          $('.fGrid-last2, .thread-list--type-card').bind("DOMSubtreeModified",$.debounce( 300, function(){
              EnstylerLastSeenDo();
              EnstylerDealTimeDo();
              EnstylerBlacklistDo();
              EnstylerAvatarPopupDo();
              EnstylerDealActionsDo();
              EnstylerPagePickerCreateDo();
          }));
    }
 
    // wait until page is loaded completely
    if (document.readyState == 'loading' || document.readyState == 'interactive'){  // Greasemonkey and Tampermonky -> runs script on DOM ready -> wait for load
        if(DEBUG) console.error('Run on DOM ready');
        $(window).bind("load", function() { EnstylerDelayedInit(); });
    } 
    else { // if script run on page loaded -> give some time to finish rendering
         if(DEBUG) console.error('Run on Document loaded');
         sleepAsync(Date.now()-EnstylerStartTime).then(() => { EnstylerDelayedInit(); });
    }
   } // NOT on Amazon


// ============= EnStyler UserScript Homepage functions =======
// experimental support for EnStyler2 export / import

} else {
    // we are on ujserstyle
    function enGetHome() {
        var myOptions='';
        $('#style-settings select').each(function() {
            var myID = $(this).attr('id');
            var myValue = $('#'+myID).val();
            var myText  = $('option[value='+ myValue +']').text();     
            myOptions +='#' + myID + ':' + myValue +':' + myText +';\n';
        });
        $('#style-settings input:checked').each(function() {
            var myID = $(this).attr('id');
            var myValue = $('#'+myID).val();
            var myText  = $('label[for='+ myID +']').text();     
            myOptions +='#' + myID + ':' + myValue +':' + myText +';\n';
        });
       GM_config.set('saveOpt', myOptions);
    }
    
    function enSetHome() {
        // get saved options,remove newlines and split to settings array
        var myOptions=GM_config.get('saveOpt');
        myOptions=myOptions.replace(/\n/g,'');
        var mySettings = myOptions.split(';');

        // abort if no options found
        if (myOptions=='' || !myOptions.startsWith('#')) {return;}

        for (var i=0; i< mySettings.length; i++) {
           // each Setting has 3 fields seperated by :, but only 2 used
           var myField=mySettings[i].split(':');

           if (myField[0].match(/^#setting/i)) {
                // select 
                $(myField[0]).val('');
                $(myField[0]).val(myField[1]);
            } else if (myField[0].startsWith('#option')) {
                // option
                $(myField[0]).prop('checked', true);
            } else {
                if (myField[0] != '') {alert('unkown option: "' + myField +'"');}
            }
        }
    } 
    
    // activate config for Enstyler Homepage
    GM_config.init(
      {
        id: 'GM_config',
        title: 'Enstyler2 - Settings', 
        fields: enHomefieldDefs,
       'events': // Callback functions object
         {
          //'init': function() { alert('onInit()'); },
          // remove elements ich switch is checked or not
          //'open': function() { enGetHome(); },
          //'reset': function() { enGMOptChange = true; },
          // relaod page on close after save
          'save':  function() { enSetHome(); GM_config.close();},
          'close': function() { enGMConfigOpen=false; },
         },
       'frame': enGMFrame // Element used for the panel
      }
    );
    
    // START Enstyler 2 Homepage
    EnstylerHomeButton();
    // set saved options
    enSetHome();
    
    

}


//=========== Support functions for actual use ======

// add CSS in to document
function addStyleString(str) {
    var node = document.createElement('style');
    node.innerHTML = str;
    document.body.appendChild(node);
}

// insertAfter like .insertBefore but as support function
function insertAfter(newNode, referenceNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

// from https://gist.github.com/TheDistantSea/8021359
// returns 0 on equal, 1 on v1 newer, -1 on v2 newer 
function versionCompare(v1, v2) {
    var lexicographical = false,
        zeroExtend = true,
        v1parts = v1.split('.'),
        v2parts = v2.split('.');

    function isValidPart(x) { return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); }
    if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {return NaN; }

    if (zeroExtend) {
        while (v1parts.length < v2parts.length) v1parts.push("0");
        while (v2parts.length < v1parts.length) v2parts.push("0");
    }

    if (!lexicographical) {
        v1parts = v1parts.map(Number);
        v2parts = v2parts.map(Number);
    }

    for (var i = 0; i < v1parts.length; ++i) {
        if (v2parts.length == i) { return 1; }
        if (v1parts[i] == v2parts[i]) { continue; }
        else if (v1parts[i] > v2parts[i]) { return 1; }
        else { return -1; }
    }

    if (v1parts.length != v2parts.length) { return -1; }
    return 0;
}

// sleep time expects milliseconds, then execute code
// NOTE: code runs in parallel (asnyc)!
// Usage!
//     sleepAsync(500).then(() => {
//               Do something after the sleep!
//      });

function sleepAsync (time) {
  return new Promise((resolve) => setTimeout(resolve, time));
}