EnstylerJS

MyDealz Enstyler Frontend and enhanced features

当前为 2016-11-20 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

/// ==UserScript==
// @name        EnstylerJS
// @namespace   Enstyler
// @description MyDealz Enstyler Frontend and enhanced features
// @include     http://www.mydealz.de/*
// @include     https://www.mydealz.de/*
// @include     https://userstyles.org/styles/128262/*
// @version     2.11.202
// @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 USI=true;

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\/(.+)\/overview$/,'$1');
    }
    
    //check for USI - only firefox mobile usercript
    if (versionCompare(GM_info.version, "1.0.0") <0) {
        USI=true;
    }
}





// add actions to tread overview @ some places ==================================
function EnstylerDealActions() {
 // if logged in ...
 if (enUserLogin) {
  // 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
                ]
    
  if (GM_config.get('enConfMoreDeal')) {
    var parser = location; // parse location 
    var pathname = parser.pathname;
    var username = pathname.replace(/.*profile\//,'');
    username = username.replace(/\/.*/,'');
    var myText=0;
    // no username ??
    if (username == "") {
        username = "unknown"; 
    } else {
       pathname = pathname.replace(username + '/',''); // remove username if path is longer
    }  
    // display short/no text?
    if ($('.ico--type-grid-subNavActive').length) { myText=1; }
        
    // default for all Dealz: comment
    var myAction = 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
        myAction += enDealAction[4]+ enDealAction[5+myText]; + enDealAction[7]
    }
    if (pathname.endsWith('profile/diskussion') || pathname.endsWith(username)){
        // add user dealz and discussions: comment edit
        myAction +=  enDealAction[8]+ enDealAction[9+myText] + enDealAction[11];
    }

    // we have an Action to add to an Deal!
    if (myAction != "") {
        //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] + myAction + 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 everywhere  ===========
    // remove unwanted HTML from deal description
    $('.thread--type-detail .userHtml').each(function () {
        // userhtml code from mydalz, need to find jafascript to save automatically :-(
        var enUserhtml = ['<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 =  newHtml.replace(/<br><br><br>|<br><br><br><br>|<br><br><br><br><br>/gi,'<br><br>');      
        
        // replace original with modifyed html
        $(this).replaceWith(enUserhtml[0] + newHtml + enUserhtml[1]);        
    });

  } // END enabled    
 }
}


// show popup user info while click on avatar ... ======================
function EnstylerAvatarPopup() {
  // if logged in ...
  if (enUserLogin) {
    // code used for MyDealz avatar popup, thanks to mydealz :-)
    var enPopupUser = ['<button data-handler="track popover" data-track="{&quot;action&quot;:&quot;show_short_user_profile&quot;,&quot;label&quot;:&quot;engagement&quot;}"data-popover="{&quot;endpoint&quot;:&quot;',
                   '/short&quot;,&quot;target&quot;:&quot;#template-popoverLoader&quot;,&quot;layout&quot;:[{&quot;preset&quot;:&quot;e&quot;,&quot;y&quot;:&quot;50%&quot;,&quot;left&quot;:{&quot;offset&quot;:0},&quot;width&quot;:300,&quot;maxWidth&quot;:&quot;100%&quot;}]}">',
                   '</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() {
 // 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
       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);
            }
        }
    }
 }
}

// 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;

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'));
    }
    // Black/Whitelist active 
    if (GM_config.get('enConfBlackEnable')) {
      var unwantedRegex = [ /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\ ]/g, // in White/Backlist
                            /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\,]/g  // in Dealtext
                           ];
          // convert Black/Whitelist to Array
      var myTemp=GM_config.get('enConfBlacklist');
          myTemp = myTemp.replace(unwantedRegex[0], '');  
      var enBlack = myTemp.split(',');
        
          myTemp=GM_config.get('enConfWhitelist');
          myTemp = myTemp.replace(unwantedRegex[0], '');      
      var enWhite = myTemp.split(',');

        // process every article
        $('article').each(function () {
            if ($(this).hasClass(enClassBlackDone)) { return;}
            // get thrad 
            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;
                }
            }
            // blacklist first, blacklist if found and exit
            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);
       // do nor reprocess after unblack
       //$('.'+enClassBlackDone).removeClass(enClassBlackDone);
}  


// 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')) {
            var parser = location; // parse location 
            var pathname = parser.pathname;
            
            // everywhere but in Deal detail, I like it like it is ... 
            if (!pathname.startsWith('/deals/') ){
                // delete header element with active stuff, but keep inside HTML
                var mySavedHtml = $('header').html();
                $('header').replaceWith(mySavedHtml);
                
                // CSS for everywhere
                addStyleString(myFixedCssMain);
                if (! pathname.match(/^\/$|^\/hot|^\/new|^\/settings|\/discussed/i)) {
                    // additional CSS for categories
                    addStyleString(myFixedCssSub);
                }
            }
        }
}

// bind external links to GM_xmlhttpRequest ================
function EnstylerProcessLinks () {

    // REGEX to detect external URL
    var REGEX_THREAD     = /^https?:\/\/www\.mydealz\.de\/visit\/thread(image)?\/\d+$/;
    var REGEX_DESC       = /^https?:\/\/www\.mydealz\.de\/visit\/threaddesc\/\d+\/\d+$/;
    var REGEX_COMMENT    = /^https?:\/\/www\.mydealz\.de\/visit\/comment\/\d+\/\d+$/;
    var REGEX_AMAZONMOB  = /^https?:\/\/www\.amazon\..*\/gp\/aw\/.*$/;
    //var REGEX_AMAZON     = /^https?:\/\/www\.amazon\..*$/;

    //* GM_xmlhhtpREquest not supported by firefox mobile :-((( 
    // abfangen aller links mit jQuery
    $('a').bind('click', function(){
         var url = this.href;

         // externer Link mit Redirect ...
         // Match REDIRECT URLs and open new Window with finalUrl
         if (GM_config.get('enConfFilterLink') && url.match(REGEX_THREAD) || url.match(REGEX_DESC) || url.match(REGEX_COMMENT)) {
             // Workaround: pre open window because of popup blockers
             var asyncWindow = window.open(url, '_blank');
         
             // now lookup redirecd external URL ...
             // alert("external URL detected");
             GM_xmlhttpRequest({
                 method: 'GET',
                 url: url,
                 // here we get the final URL from redirect
                 onload: function (response) {
                   // process final URL
                   var newUrl = response.finalUrl;
                   //alert(newUrl);
                   // lets see ...
                   if (newUrl.match(REGEX_AMAZONMOB)) {
                       newUrl = newUrl.replace("/gp/aw/d/", "/dp/");
                       newUrl = newUrl.replace("/gp/aw/ol/", "/gp/offer-listing/");
                       //alert("Amazon rewritten URL: " + newUrl);
                   }
                   // load processed URL in preopened window
                   asyncWindow.location = newUrl;
                 }    
             });
             // return without link processing by Browser
             return false;
         }
  
         // return with link processing in Browser
         return true;  
    }); //  - END GM_xmlhttpRequest */
} // END EnstylerProcessLinks


// check for Updates of EnstylerJS and Enstyler2 ================
var enUpdateJSurl = "https://greasyfork.org/de/scripts/24243"; // production version
var enUpdateInterval=1 * (24*60) // 1 day between update checks

if (!GM_info.script.namespace.match(/develop/i)) {
    var enUpdateJSurl = "https://greasyfork.org/de/scripts/24244"; // developer version
    var enUpdateInterval=1 // 1 minute between update checks

} 

function enCheckUpdates() {
     // get time and convert to minutes
     var myTime=Date.now()/60000|0;
     if (GM_config.get('enConfCheckUpdate') & myTime - GM_config.getValue('enLastUpdateCheck') > enUpdateInterval) {
         GM_config.setValue('enLastUpdateCheck', myTime);
         //GM_config.set('enLastUpdateCheck', myTime);
         //GM_config.fields['enLastUpdateCheck'].reload();
         enGetOnlineVersions();
     }
}

// Get actual online version 
function enGetOnlineVersions() {
  // EnstylerJS
  GM_xmlhttpRequest({
    method: "GET",
    url: enUpdateJSurl, 

    onload: function(response) {
      var enJSVersion = GM_info.script.version;
      var myResponse=response.responseText;
      myResponse=myResponse.replace(/\r?\n|\r| |\t/g,'');
      myResponse=myResponse.replace(/^.*\<ddclass=\"script-show-version\"\>\<span\>|\<\/span\>.*/gi,'');
      //alert(myResponse)
      if (versionCompare(enJSVersion, myResponse) <0) {
         //showPopup('New EnstylerJS availible: ' + enJSVersion + '\n\nPlease update!');
      } else { myResponse='no'}
        
      GM_config.setValue('enJSVersion', myResponse);
    }
  });

  // Enstyler2
  // get last known Verion of CSS
   GM_xmlhttpRequest({
        method: "GET",
        url: "https://userstyles.org/styles/128262",
    
        onload: function(response) {
          var enEnVersion;
          $('.threadWidget-footer').each(function(){ enEnVersion=window.getComputedStyle(this,':after').content;});
          enEnVersion = enEnVersion.replace(/ /g,'')
          enEnVersion = enEnVersion.replace(/.*Enstyler|-.*/gi,'')
          //alert(enEnVersion)
          
          var myResponse=response.responseText;
          myResponse=myResponse.replace(/\r?\n|\r| |\t/g,'');
          myResponse=myResponse.replace(/^.*additional-info-text.*Version:|\<br\>.*/gi,'');
          //alert(myResponse);
          if (versionCompare(enEnVersion, myResponse) <0) {
              //showPopup('New CSS availible: ' + enEnVersion + '\n\nPlease update!');
          } else { myResponse='no'; }
             
          GM_config.setValue('enEnVersion', myResponse);

        }
   });
}


// 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';
    
    // MainNav selected or Deal
    if (GM_config.get('enConfBtn'))
        { myMain=true; }
    
    if ($('.voteBar').length)
        { 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 =======================================

// 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();
               showUrl('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();
               showUrl('https://greasyfork.org/de/scripts/24243');

               }
    },

   '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
               showUrl('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': 'Show 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': 'only 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
  'enConfFilterLink': { 
      '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
    },

    // 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
    },
    
    // check for Updates
    'enConfCheckUpdate': {
      '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': '1000' // 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': '' // Value stored
   },
    
    // 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
    },
    '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
               EnstylerBlacklistRemove(); }
    },
   
   // 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 cam apply CSS!!
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 {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) {
        // translate 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 = '';
      },
    },
  '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: 'EnstylerJS - Settings', 
        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: 'enConfBlackEnable', remove: 'enConfWhitelist'},
               { check: false, switch: 'enConfBlackEnable', remove: 'enConfBlacklist'},
               { check: false, switch: 'enConfBlackEnable', remove: 'enConfUnblacklist'}
              ];
             enFixedNav=GM_config.get('enConfNavFixed');
             $(enRemoveConfig).each(function() {
                 if (GM_config.get(this.switch) == this.check) {
                  GM_config.fields[this.remove].remove();
                 }
             });
             if (GM_config.get('enConfCheckUpdate')) {
               var myUpdate
               if ((myUpdate=GM_config.getValue('enJSVersion')) =='no') 
                 {GM_config.fields['enstylerJS'].remove();}
                 else {showPopup('New EnstylerJS availible: ' + myUpdate + '\n\nPlease update!');}
                 
               if ((myUpdate=GM_config.getValue('enEnVersion')) =='no') 
                 {GM_config.fields['enstyler'].remove();}
                 else {showPopup('New CSS availible: ' + myUpdate + '\n\nPlease update!');}
            }
          },
          //'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);}
              EnstylerButtonRemove();
              EnstylerButtonCreate();
              EnstylerPagePickerRemove();
              EnstylerPagePickerCreate();
              EnstylerBlacklistRemove()
              EnstylerBlacklist();
              EnstylerFixedNav();
              enCheckUpdates();
              GM_config.close()
            },
         'close': function() { enGMConfigOpen=false;},
         },
       'frame': enGMFrame // Element used for the panel
      }
    );
    
   // dummy, do not delete
   function enGetHome() {;}
    
    // ============== START EnstyerJS =============
    EnstylerInit();
    EnstylerProcessLinks();

    EnstylerBlacklist();
    EnstylerAvatarPopup();
    EnstylerDealActions();
    EnstylerFixedNav();
    
    // delay Pagepicker and repaeting actions after finishing everything else
    function EnstylerDelayedInit() {
           EnstylerButtonCreate();
           EnstylerPagePickerCreate();
           enCheckUpdates();
        
          // 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( 1000, function(){
            //GM_log('DOMSubtreeModified detected!!!');
            EnstylerBlacklist();
            EnstylerDealActions();
            EnstylerAvatarPopup();
            //EnstylerPagePickerCreate();

          }));
    }
 
    // wait until page is loaded completely
    if (!USI) {
        // Greasemonkey and Tampeermonky run script on DOM ready
        $(window).bind("load", function() {
           EnstylerDelayedInit();
        });
    } else {
        // USI run script on page load, give some time to finish rendering
         sleepAsync(1000).then(() => {
             EnstylerDelayedInit();
         });
    }


// ============= 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 ======

// display website in external window
function showUrl(str) {
    var myDeco = "innerheight=780,innerwidth=550";
    var myName = "enstyler";
    
    // workaround for not working window.focus(): close an existing window first
    // not working on mobile :-(
    //var myWindowShow = window.open('', myName, "width=100,height=100").close();
    var myWindowShow = window.open(str, myName, myDeco);
}

// 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));
}