EnstylerJS

MyDealz Enstyler enhanced features incl. Amazon Mobile Redirect

当前为 2016-12-22 提交的版本,查看 最新版本

// ==UserScript==
// @name        EnstylerJS
// @namespace   Enstyler
// @description MyDealz Enstyler enhanced features incl. Amazon Mobile Redirect
// @include     https://www.preisjaeger.at/*
// @include     https://www.mydealz.de/*
// @include     https://userstyles.org/styles/128262/*
// @include     https://www.amazon.*/gp/aw/*
// @version     2.12.222
// @grant       GM_getValue
// @grant       GM_setValue
// @require     http://code.jquery.com/jquery-3.1.1.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 = '';
const EnstylerStartTime=Date.now();

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

var DEBUG=false;

// Basic Initialisation ==========================
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');
        } else {
            //restore old last seen if user logs in
            // use this variant for dynamic loaded content click ...
            $(document).on("click",'.test-loginButton', EnstylerLastSeenLast);
        }
        // get Section (first element in path)
        enSection= enLocParser.pathname.replace(/\/([^\/]+\/*).*/,'/$1');
}


// add actions @ some places ==================================

// additional Deal Actions =======================
// code used for MyDealz Dealz actions, thanks to mydealz :-)
  const 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;}">',
                       '<span class="hide--toW3">Sag was dazu</span><span class="hide--fromW3 hide--toW2">Sag</span>', '', '</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;}">',
                       '<span class="hide--toW3">Von Liste entfernen</span><span class="hide--fromW3 hide--toW2">Enft</span>', '', '</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}">',
                       '<span class="hide--toW3">Bearbeiten</span><span class="hide--fromW3 hide--toW2">Bearb</span>', '', '<span></a>',
                       '<a title="Als Mail versenden" class="link text--color-blue ico ico--type-code-blue ico--pos-l space--mr-3"'+ // mail 12+13+15
                       'href="mailto:?subject=<ENSTYLER-TEXT-HERE>" <span class="hide--toW3">',
                       '<span class="hide--toW3">Mail versenden</span><span class="hide--fromW3 hide--toW2">Mail</span>', '', '<span></a>',
                       ];

  const enDealMarker='#thread_';
  const enDealNoAction='.ico--type-clock-grey, .vote-temp--colder';
  var enDealAdd='', enDealUnbook=false;
  var enDealActionMailFooter='';
 
function EnstylerDealActions(){
  // if logged in and enabled ...
  if (enUserLogin && GM_config.get('enConfMoreDeal')) {
    // compose Footer
    enDealActionMailFooter = ('%0D%0A%0D%0A-- %0D%0A'+ enInternationalName + ':%0D%0A'+ $('footer ul li:first p').html().replace(/.*<br>/g,''));
      
    // 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: first comment
    enDealAdd = enDealAction[0]+ enDealAction[1+myText] + enDealAction[3];

    // Action for special locations only ===========
    switch(true) {
      case (pathname.endsWith('profile/saved-deals')):
         // add for user saved-dealz: un-bookmark
         enDealAdd += enDealAction[4]+ enDealAction[5+myText]; + enDealAction[7];
         enDealUnbook=true;
         break;
         
      case (pathname.endsWith('profile/diskussion')):
      case (pathname.endsWith(enUserName)):
         // add user dealz and discussions: comment edit
         enDealAdd +=  enDealAction[8]+ enDealAction[9+myText] + enDealAction[11];
         break;
    }
    // default last: add mail to
    enDealAdd +=  enDealAction[12]+ enDealAction[13+myText] + enDealAction[15];
    
    // Compose final Deal Actions 
    enDealAdd= enDealActionOuterHtml[0] + enDealAdd + enDealActionOuterHtml[1];
      
    // do it ...
    EnstylerDealActionsDo();
  }
}

// surrounding myDealz HTML
const enDealActionOuterHtml  = [ '<span class="js-options bg--em bRad--a space--h-3 space--v-3 space--mt-3 text--b">', '</span>'];

function EnstylerDealActionsDo() {
  // if logged in and enabled ...
  if (enUserLogin && GM_config.get('enConfMoreDeal')) {  
    // every thread on thread page ...
    $('article:not(.'+enClassHidden+')').each(function () {
        // get infoRow
        var myInfoRow=$('.thread-infoRow', $(this));
        if (!myInfoRow.length || $(enDealNoAction, $(this)).length) {return;}
        
        // get Titel, Link, DealID num
        var myDeal =$('.thread-title a', $(this));
        var myDealHref = myDeal.attr('href');

        // compose mail subject
        var mySub=encodeURIComponent(enInternationalName+': '+myDeal.text());
        //if (mySub.length < 100 && $('.thread-price', $(this)).length) {
        //    mySub += encodeURIComponent(' ->_' +$('.thread-price', $(this)).text().replace(/\t/g,''));
        //}
                         
        // compose final HTML
        var newHtml = enDealAdd.replace(enPATTERN[0], myDealHref)
                               .replace(enPATTERN[2], truncStringWord(mySub, 160, '%20') +'&body=' +mySub +'%0D%0A%0D%0A' +myDealHref +enDealActionMailFooter);
        if (enDealUnbook) {newHtml = newHtml.replace(enPATTERN[1], ('#'+$(this).attr('id')).replace(enDealMarker,''));}

        // append HTML to Deal
        myInfoRow.append(newHtml);
        myInfoRow.removeClass('thread-infoRow');
    }); 

      
    // actions for somewhere  ===========
    // remove unwanted HTML from deal description
    $('.thread--type-detail .userHtml').each(function () {
        // 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(/<\/p>|<\/div>/gi,'').replace(/<div>|<p>/gi,'<br><br>').replace(/<br>(<br>)*<br>/gi,'<br><br>');
        $(this).html(newHtml);        
    });

  } // END enabled    
}


// show popup user info while click on avatar ... ======================
function EnstylerAvatarPopup(){
    if (enUserLogin && GM_config.get('enConfUser')) {
        // remove second image from cardview
        addStyleString('.thread-footer-cell a img.avatar.vAlign--all-m.space--mr-1.thread-avatar {display: none;}');
        EnstylerAvatarPopupDo();
    }
}

// code used for MyDealz avatar popup, thanks to mydealz :-)
const 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>',
                  ];

const enClassAvatarDone = 'enClassAvatarDone';

function EnstylerAvatarPopupDo() {
    // login needed ... (Error in Popup without login ...)
    // replace every avatar link without popup
    if (enUserLogin && GM_config.get('enConfUser')) {
        var myBigAvatar=GM_config.get('enConfAvatar');
        // each avatar
        $('.thread-footer-cell a.user.linkPlain, .user.linkPlain.thread-user').each(function () {
            if( $(this).hasClass(enClassAvatarDone)|| $(this).hasClass(enClassBlackDone)) return;
            $(this).addClass(enClassAvatarDone);
            
            // get inner html and link to user profile
            var myHtml = $(this).html();
            var mysrc = $(this).attr('href');
            
            // seperate user name from image and add class user
            var myAvatar = myHtml.replace(/<span.*/,'').replace(/avatar--type-s/,'avatar--type-m');
            //var myAvatar2 = myHtml.replace(/.*<span class=".* space--mr-1">/,'<span class=" space--mr-1 user link-plain">');

            if (myBigAvatar) { myAvatar =  myAvatar.replace(/thread-avatar/,'avatar--type-m'); } 
            
            // compose popup
            $(this).html(enPopupUser[0] + mysrc + enPopupUser[1] + myAvatar +  enPopupUser[2]
                          + '<a href="' + mysrc + '">'+ myHtml.replace(/.*<span class=".* space--mr-1">/,'<span class=" space--mr-1 user link-plain">')
                          + '</a>');
        });
    }  
}


// create select page or scrollwheel for page navigation =============
const EnstylerPageEnum='enPageEnum';

const selectList = document.createElement("select");
selectList.id = EnstylerPageEnum;
selectList.setAttribute('class', EnstylerPageEnum);
selectList.onchange = EnstylerPageAction; 

function EnstylerPagePickerCreate() {
 // revome existing picker
 EnstylerPagePickerRemove();
    
 // if enabled
 if (GM_config.get('enConfPagePicker')) {
   // init values and clear select list
   var page=1, 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
       if( isNaN(page = parseInt(pageHtml.replace( /.*>Seite /i ,'')) )) { page=1;}
       if( isNaN(max  = parseInt(pageHtml.replace( /.*page=/ ,'')) ))    { max=page;}
   }

   // create page select element
   for (var x = 1; x <= max; ) {

       var option = document.createElement("option");
       option.text = x;
       selectList.add(option);
       var 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 ) {
       var option = document.createElement("option");
       option.text = max;
       selectList.add(option);
   }
   // set default value
   selectList.value = page;

   // placement of MAIN Picker
   var MainPicker= ['.js-navDropDown-messages', //Element
                  EnstylerPageEnum+' js-navDropDown-messages vAlign--all-m' //class
               ];
   // login button present in Mainnav
   if ($('.test-loginButton').length) {
          MainPicker[0]='.test-loginButton'; //Element
   }
   // in deal always in sticky votebar (was in subnav)
   if ($('.voteBar').length) {
          MainPicker= [ '.voteBar--sticky-off--hide:last', // Element
                        EnstylerPageEnum +' subNavMenu-link subNavMenu-btn voteBar--sticky-off--hide' //class 
                      ];
   }

   // Main  Picker add class and palce before element
   selectList.setAttribute('class',MainPicker[1]);
   $(MainPicker[0]).before(selectList);
 }
}

function EnstylerPagePickerDo() {
   // get page and max from pagenav
   if ( $('.js-sticky .text--color-charcoalTint').length ) {
       //locate actual page incl remove line breaks
       var page = parseInt($('.js-sticky .text--color-charcoalTint').html().replace(/\r?\n|\r/g).split( '--toW2">Seite ')[1]);

       // set default value
       selectList.value = page; //selectListDown.value = page;
   }
}

// goto selected Page
function EnstylerPageAction() {
    var enPage = 'page=' + $(this).val();

    // remove page= and everthing behind
    var enUrl = enLocParser.toString().replace( /page=.*|#.*/ ,'');
    
    // add new page parameter
    if ( enUrl.endsWith('?') ||  enUrl.endsWith('&')) {
        enUrl += enPage;
    } else {
        enUrl += '?'+enPage;
    }
    
    // add #thread-comments for deal
    if (enSection == '/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
const enClassHidden = 'enClassHidden';
const enClassBlackDone = 'enClassBlackDone';
var enBlacklisted=0;

const unwantedRegex = [ /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\ ]/g, // in White/Backlist
                      /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\,]/g  // in Dealtext
                    ]; 
var enBlack, enBlackTrue;
var enWhite, enWhiteTrue;
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'));
        GM_config.setValue('enConfWhitelist', GM_config.get('enConfWhitelist'));
    }
   
    // convert Black/Whitelist to RegEx
    enBlack=RegExp(GM_config.get('enConfBlacklist').replace(unwantedRegex[0], '').replace(/^,|,$/g,'').replace(/(.),(.)/g,'$1|$2'),'i');
    ''.match(enBlack) ? enBlackTrue=false : enBlackTrue=true ;
    
    enWhite=RegExp(GM_config.get('enConfWhitelist').replace(/^,|,$/g,'').replace(/(.),(.)/g,'$1|$2'),'i');
    ''.match(enWhite) ? enWhiteTrue=false : enWhiteTrue=true ;
    
    enBlackTemp= GM_config.get('enConfHideColder');
    EnstylerBlacklistRemove()
    EnstylerBlacklistDo();
}

function EnstylerBlacklistDo() {
    if (!GM_config.get('enConfBlackEnable') || ( !enBlackTrue && enBlackTemp < -900)) { return;}
    
    // process every article
    $('article:not(.'+enClassBlackDone+', .threadWidget-item)').each( function () {
        // mark as already seen
        $(this).addClass(enClassBlackDone);

        // get title, categorie, user, remove unwanted chars
        var myDealText = ($('.thread-category',$(this)).text()
              +' ' +$('.thread-title a',$(this)).text()
              +' @' +$('.user',$(this)).text()).replace(unwantedRegex[1] ,' ');

        // whitelist Regex, exit if found
        if ( enWhiteTrue && myDealText.match(enWhite)) { return; }

        // vote temp & blacklist
        if (parseInt($('.vote-temp', $(this)).text()) <= enBlackTemp
            || enBlackTrue  &&  myDealText.match(enBlack)) {
                $(this).addClass(enClassHidden);
                enBlacklisted++;
                EnstylerLastSeenSkip('#'+$(this).attr('id'));
        }    
    }); // END Article 

    // set label for unBlacklist button
    EnstylerBlacklistShow()   
}

// blacklist support functions ....
const enUnblackText = 'unBlacklist <ENSTYLER-TEXT-HERE> Dealz';
function EnstylerBlacklistShow() {
       enJSfieldDefs.enConfUnblacklist.label=enUnblackText.replace(enPATTERN[2],enBlacklisted)
}

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

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


// Main Nav will stay on TOP of the screen =========================
const myFixedCSS = [ /* 0 everywhere */'.enFixedNav { display: block; position: fixed; width: 100%; z-index: 120;} .subNav, .userProfile, .tabbedInterface, .splitPage-wrapper {margin-top: <ENSTYLER-TEXT-HERE>px;}',
                     /* 1 subnav */    '.subNav {margin-top: 0 !important;} .nav-subheadline {margin-top: <ENSTYLER-TEXT-HERE>px;}',
                     /* 2 diskussion */'.tGrid.page2-center.height--all-full {margin-top: calc(<ENSTYLER-TEXT-HERE>px + 10px);}'
                   ];
function EnstylerFixedNav() {
        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('<header class="enFixedNav">'+mySavedHtml+'</header>');
                
                // fixed NAV for everywhere
                var myFixedStyle=myFixedCSS[0];
                
                // additionla CSS for different sections
                if (enSection == '/diskussion/') {  myFixedStyle+=myFixedCSS[2];  }
                if ($('.nav-subheadline').length || enSection=='/profile/') {
                    // additional CSS for categories
                    myFixedStyle+=myFixedCSS[1];                    
                }
                
                myFixedStyle= myFixedStyle.replace(enPATTERN[2], enMainHeigth)
                addStyleString(myFixedStyle)
            }
        }
}

// 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, optimization: not if class TiemSeen
         $('time:not(.'+EnstylerTimeSeen+')').each(function () {
            // get Deal time, 
            var myTime= $(this).text();
            // next article if less than an h or older than 24h
            if ( myTime.length <9 || !myTime.startsWith('v')) {return;}

            // compose  deal offset
            var myDealDiff = (parseInt(myTime.replace(/.* ([0-9].*) h.*/, '$1'))*60+parseInt(myTime.replace(/.* ([0-9].*) m.*/, '$1')))*60000;
            DealDate.setTime( enNow.getTime() - myDealDiff );

            // last midnigth
            if (DealDate < TodayStart) {
                $(this).text('gestern '+ DealDate.toString().slice(16, 21) +' Uhr');
            // more than x hours ago
            } else if (myDealDiff > ShowTime){
                $(this).html(myTime + ' (<span class="hide--toW2">heute </span>'+ DealDate.toString().slice(16, 21) +' Uhr)');
            } else { return; }
            
            $(this).addClass(EnstylerTimeSeen);
         });
  }
}

// mark last seen Deal in Highligth, Hot and New ============================
var enSec='';
var enSeenArticle='';

// GM variables used here 
// store newest loaded deal
// 'enNewestDeal...new'
// 'enNewestDeal...hot'
// 'enNewestDeal...'
// international support added
const enNewestBase='enNewest'+enLocParser.hostname.replace('www','');
var LastSeenOnce=true;

function EnstylerLastSeen(){
  // only once and in main categories
  if(LastSeenOnce) {
    LastSeenOnce=false;

    // store last seen for Main catergories
    if(enSection.match(enMainSectionMatch) && enLocParser.search == '') {
      // get section and save
      enSec= enNewestBase + enSection.replace(/\//, '');
      GM_setValue(enNewestBase+'LastSec', enSec)
      // get last seen article
      enSeenArticle=GM_getValue(enSec);
      if ( typeof enSeenArticle == 'undefined') {enSeenArticle='';}

      EnstylerLastSeenDo();
      
      // save actual last seen
      $('article:not(.threadWidget-item)').each(function () {
          // pinned ?
          if (!('.cept-pinned-flag',$(this)).length) {return;}

          //store actual seen
          GM_setValue(enSec, $(this).attr('id'));
          //store last seen
          GM_setValue(enSec+'Last', enSeenArticle);

          // exit loop
          return false;
      }); 
    } else {
        // if we are not in main categorie => restore last value
        EnstylerLastSeenLast()
    }
  }
}

function EnstylerLastSeenDo(){
  // only in main categories
  if(enSec != '') {
      // mark last seen article
      if (enSeenArticle != '') {
          //store last marked
          GM_setValue(enSec+'Last', enSeenArticle);
          $('#'+enSeenArticle).addClass('enClassMarkArticle');
      } else {
          // first time
          GM_setValue(enSec, 'thread_1');
      }

  }
}

// restore last seen from last last seen
function EnstylerLastSeenLast(){
      // restore last value
      enSec=GM_getValue(enNewestBase+'LastSec');
      GM_setValue(enSec, GM_getValue(enSec+'Last'));
    
}

// article is not availible i.e. blacklisted
function EnstylerLastSeenSkip(DealID) {
    // if article last seen one, skip to next
    if (DealID == '#'+enSeenArticle) {
        // magic, get ID of next article
        enSeenArticle=$(DealID).next().attr('id');
        EnstylerLastSeenDo(); 
    }
}



// compose Nav Menu items  =======================================
// i.e. create button for display Config ======================
// define pattern actions here, incl. international support
  const enMainSectionMatch=/^\/$|^\/hot$|^\/new$|^\/settings$|^\/discussed$|^\/hei%C3%9F$|^\/diskutiert$/;

  const enPATTERN = [ /<ENSTYLER-HREF-HERE>/g,   // 0 pattern to insert link ...
                    /<ENSTYLER-THREADID-HERE>/g, // 1 pattern to insert ID
                    /<ENSTYLER-TEXT-HERE>/g,     // 2 pattern to insert Text
                ];

  const enNavEntry='enNavEntry';
  const enMenuItemCode = [ /*0 MainNav*/  '<a class="enNavEntry navMenu-link" id="<ENSTYLER-THREADID-HERE>" href="<ENSTYLER-HREF-HERE>" data-handler="track" data-track="{&quot;action&quot;:&quot;goto_main_target&quot;,&quot;beacon&quot;:true}"><span class="navMenu-link-ico ico ico--type-discussion-navMenuLayerItem navMenu-ico--selected--type-discussion navMenu-ico--hover--type-discussion"></span><ENSTYLER-TEXT-HERE></a>',
                           /*1 SubNav */  '<li class="enNavEntry subNavMenu-item--separator test-tablink-discussed"><a  href="<ENSTYLER-HREF-HERE>" class="subNavMenu-item subNavMenu-link space--h-4 vAlign--all-m" id="<ENSTYLER-THREADID-HERE>" data-handler="track" data-track="{&quot;action&quot;:&quot;goto_menu_target sort&quot;,&quot;label&quot;:&quot;diskutiert&quot;,&quot;beacon&quot;:true}"><span class="box--all-i size--all-xl vAlign--all-m"><ENSTYLER-TEXT-HERE></span><span class="js-vue-container--threadcount" data-handler="vue" data-vue="{&quot;count&quot;:null}"></span></a></li>',
                           /*2 MainBut */ '<a class="enNavEntry navMenu-link" id="<ENSTYLER-THREADID-HERE>"><span class="navMenu-link-ico ico  ico--type-discussion-navMenuLayerText"></span><ENSTYLER-TEXT-HERE></a>',
                           /*3 SubBut  */ '<li class="enNavEntry subNavMenu-item--separator test-tablink-discussed"><a  class="subNavMenu-item subNavMenu-link space--h-4 vAlign--all-m" id="<ENSTYLER-THREADID-HERE>"><span class="box--all-i size--all-xl vAlign--all-m"><ENSTYLER-TEXT-HERE></span></a></li>'
                         ];
  const enMenuMain=0; const enMenuMainButton=2;
  //const const enMenuSub=1; const enMenuSubButton=3;
  const enMenuItemLength= enMenuItemCode.length;

  // Enstyler Button
  const EnstylerButton = 'EnstylerButton';

// compose default Enstyler Menu
function EnstylerMenuActions(){
    EnstylerNavRemove()
    if (!enInternational) {
            // MyDealz only: alle Diskussionen 
            EnstylerAddNav(enMenuMain,'Alle Diskussionen', 'https://www.mydealz.de/diskussion','enMainDiscussion')
    }
    // add Enstyler Homepage
    EnstylerAddNav(enMenuMain, 'Enstyler Homepage', 'https://www.mydealz.de/diskussion/enstyler-856062" target="_blank','enMainHomepage', 'building');
    // add EnstylerJS config
    EnstylerAddNav(enMenuMainButton, 'Enstyler Einstellungen', showEnstylerConfig, EnstylerButton, 'page');
    
    // add to Sub Nav
    //EnstylerAddNav(enMenuSubButton, 'Enstyler' , showEnstylerConfig, EnstylerButton)
}


// add to Nav ======================
// nav = menu action
// text = menu text
// target = URL to show, in case of Button function to call
// Icon can be home, tag, scissors, free, discussion (default), building, star, snowflake, page (button), star (button)
var enNavIconPat='--type-discussion';

function EnstylerAddNav(nav,text,target,ID, Icon) {
    // exit if no defined Menu action
    if (nav >= enMenuItemLength) {return;}
    if (typeof Icon == 'undefined' || Icon == '') Icon=enNavIconPat;
    
    var isFunc=false;
    
    // compose menu entry
    var myEntry = enMenuItemCode[nav].replace(enPATTERN[1],ID).replace(enPATTERN[2],text);
    if(Icon !=enNavIconPat) { myEntry = myEntry.split(enNavIconPat).join('--type-'+Icon)}
    
    // target can be a function
    if (typeof target === "function") { isFunc=true;
    } else {
        myEntry = myEntry.replace(enPATTERN[0],target);
    }
 
    switch(nav) {
        case enMenuMain: // Main Nav add delayed
        case enMenuMainButton:
            // first Main menu entry, start listen to klick
            if(enAddMain == '')  { $('.nav-link.navMenu-trigger').click(debounce( 300, EnstylerMainDo)); }
            enAddMain += myEntry;
            if (isFunc) { enAddMainFunc[enAddMainCount++]= { ID: ID , target: target}; }
            break;
/*
        case enMenuSub: // Sub Nav, add now
        case enMenuSubButton:
            // ad to Subnav, click if visible
            $('.subNavMenu-list').append(myEntry);
            if(isFunc) { $('#'+ID).click(target); }
            
            // handler if dropdown, start listen to klick
            if(enAddSub == '')  { $('.subNavMenu-trigger').click(debounce( 300, EnstylerSubDo)); enAddSub='done'; }         
            if(isFunc) { enAddSubFunc[enAddSubCount++]= { ID: ID , target: target}; }
            break;
/**/
    } 
}

// Show items in Sub / Main Menu ===== 
// store ID and function to call on click
var enAddMain='';
var enAddMainFunc= [ ];
var enAddMainCount=0;

function EnstylerMainDo() {
      // klick event handler, call with debounce( 300) to wait until menu is created and avoid double klicks
      //add items
      $(enAddMain).insertBefore('.popover-content nav .navMenu-div:first');

      // create space for new entrys
      var myMenu=$('.popover--mainNav');
    
      // +35px per new items
      var myHeigth= 35*(enAddMain.split(enNavEntry).length -1) + parseInt(myMenu.attr('style').split('height: ')[1]);
      myMenu.attr('style',myMenu.attr('style').replace(/height: [0-9.]*px/,'height: '+myHeigth+'px'));
          
      // add button callbacks
      for (var i=0; i<enAddMainCount; i++ ) {
          $('section #' + enAddMainFunc[i].ID).click(enAddMainFunc[i].target);
      }
}

/*
var enAddSub='';
var enAddSubFunc= [ ];
var enAddSubCount=0;

function EnstylerSubDo() {
      // klick event handler, call with debounce( 300) to wait until menu is created and avoid double klicks
      //add items
      if(DEBUG) console.error('Add Menu Items to Sub ...')
      for (var i=0; i<enAddSubCount; i++ ) {
          $('section #' + enAddSubFunc[i].ID).click(enAddSubFunc[i].target);
      }
}
/**/

function EnstylerNavRemove() {
       // Clear Menu Items stored
       enAddMain='';
       enAddMainFunc= [ ];
       enAddMainCount=0;
       $('.navMenu-page').unbind('click');
    
       // remove visible items
       $('.'+enNavEntry).remove();

}  

// ============= GM_config functions =======================================
const enJSAutoUpdate=GM_info.scriptWillUpdate;
var enUpdateWindow;

const enInternationalSite=enLocParser.hostname.replace('www','');
const enInternationalName=capitalizeFirstLetter(enInternationalSite.replace(/^\.|\..*/g,''));
const enInternational=(enInternationalName != 'Mydealz');

// dom we run on FF? GM hast noch scriptHandler, USI may or may not, so NOT Tampermonkey is OK
const isMozilla=(typeof GM_info.scriptHandler == "undefined" || !GM_info.scriptHandler.startsWith('Tamp'))


// define EnstylerJS GM_config elements
const 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
               enUpdateWindow=window.open('https://userstyles.org/styles/128262#style-info',
                           'UserCSS', 'width=600,height=950,left=0,top=0');
               }
    },
    '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
               enUpdateWindow=window.open(!DEBUG ? 'https://greasyfork.org/scripts/24243-enstylerjs/code/EnstylerJS.user.js' :
                                          ' https://greasyfork.org/scripts/24244-enstylerjs-develop/code/EnstylerJS Develop.user.js',
                                          'UserScript', 'width=110,height=110,left=0,top=0');
               // give 5s to start update, then close
               sleepAsync(5000).then(function() { enUpdateWindow.close(); });

               }
    },

   '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/firefox/addon/self-destructing-cookies/'); }
    },

   // part two: EnstylerJS internal configuration options ------ 
   'Section': { // display next section, dont kow why ...
      'section': ['Configuration', ''],
        'type': 'hidden', // Makes this setting a hidden input
   },
    
   'enConfNavFixed': {
      'label': 'Display FIXED MainNav', // Appears next to field
      'type': 'checkbox', // Makes this setting a checkbox input
      'default': true // 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

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

    // 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
   },    
    
    // 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(); }
    },
   
   // used to not destroy saved Enstyler2 Options
  'saveOpt': { 
      'type': 'hidden', // Makes this setting a button input
    },


};

// define EnstylerJS GM_config elements
const 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,
    },
   
   // 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_config as div, so we can apply CSS easy!! ======================
const enGMFrame = document.createElement('div');
enGMFrame.setAttribute('class','GM_config');
document.body.appendChild(enGMFrame);

// basic config panel formatting, everything else is formatted by enstyler
const enCSS = ['.GM_config {left: 5% !iportant; top: 8% !important; height: auto !important; max-width: 35em !important; background-color: white;}',
             '.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; }',
             '.enClassHidden, #EnPopup_closeBtn {display: none;}',
            ].join(" ");


// get colors of page to integrate better in international pages
// and save hight of navigation ...
var enMainHeigth;
if ($('.nav').length){ // pepper site detected ===============
   // calc colors and topx
   var myBgColor=$('.nav').css('background-color');
   var enGmCss = ' .GM_config {background-color: '+ shadeRGBColor(myBgColor, -0.30) + ' !important; color: '+ shadeRGBColor(myBgColor, 0.50);

   enMainHeigth = parseInt($('header').outerHeight());
   enGmCss += '; top: '+ enMainHeigth +'px !important;}'

   // calc navlink hover color
   enGmCss += ' .nav-link-text:hover, .js-navDropDown-messages:hover, .js-navDropDown-activities:hover  { background-color: ' +shadeRGBColor(myBgColor, 0.11)+ ' !important;}';
}

// add predefined styles and page colors
addStyleString(enCSS+enGmCss);


// EnstylerJS Config Panel anzeigen =====================
var enGMConfigOpen=false;

function showEnstylerConfig () {
  if(!enGMConfigOpen) {
      enGetHome();
      GM_config.open();
      // hide menu
      $('.popover--mainNav').remove();
      enGMConfigOpen=true;
  } else {
      GM_config.close();
  }    
}

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



// EnstylerJS START ============================================
var EnstylerStartupDelay;

if (!window.location.hostname.endsWith('userstyles.org')) {
    var enFixedNavLast=false;
    GM_config.init(
      {
                       //  international sites support
        id: enInternational ? 'GM_config' + enInternationalSite : '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: 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'}
              ];
             enFixedNavLast=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();}
             if (!isMozilla)     {GM_config.fields['dontCookies'].remove();}
              
          },
          //'reset': function() { alert('reset') },
          // relaod page on close after save
          'save':  function() {
              // disabeling FixedNav can only done with reload
              if (!GM_config.get('enConfNavFixed') && GM_config.get('enConfNavFixed')!=enFixedNavLast) {window.location.reload(false);}

              // restart Enstyler magic
              GM_config.close();
              EnstylerStart();
              EnstylerMenuActions();
              EnstylerPagePickerCreate();
              // show changes in config after processing magic
              GM_config.open();
            },
         'close': function() { enGMConfigOpen=false;},
         },
       'frame': enGMFrame // Element used for the panel
      }
    );
   
   // Enstyler internal Startup functions ======================
   // HACK: we are NOT on Amazon
   if (enAmazonMobileRedirect()) {
    

    // dummy, do not delete
    function enGetHome() {;}

    // Start Enstyler Magic
    function EnstylerStart() {
        EnstylerFixedNav();
        EnstylerDealTime();
        EnstylerLastSeen();
        EnstylerBlacklist();
        EnstylerAvatarPopup();
        EnstylerDealActions();
    }

    // delayed actions after finishing everything else
    function EnstylerDelayedInit() {
           // don't know why, but works only if called with delay ...
           EnstylerMenuActions();
           EnstylerPagePickerCreate();
        
          // track DOM change Events, debounce: wait 1000ms after mutiple events
          // then re-apply (somse) changes to dynamic loaded content, 
         // $('.cept-event-deals, .thread-list--type-card').bind("DOMSubtreeModified",$.debounce( 400, function(){
         $('.js-pagi-bottom').bind("DOMSubtreeModified", debounce( 100, function(){
              EnstylerLastSeenDo();
              EnstylerPagePickerDo();
              EnstylerDealTimeDo();
              EnstylerBlacklistDo();
              EnstylerAvatarPopupDo();
              EnstylerDealActionsDo();
 

          }));
    }
   


    // =============== MAIN: START EnstyerJS ===================
    EnstylerInit();
    EnstylerStart();
    EnstylerStartupDelay=Date.now()-EnstylerStartTime;
     
    // wait until page is loaded completely
    if (document.readyState == 'loading' || document.readyState == 'interactive'){  // Greasemonkey and Tampermonky -> runs script on DOM ready -> wait for load
        $(window).bind("load", function() { EnstylerDelayedInit(); });
    } 
    else { // if script run on page loaded -> give some time to finish rendering
         sleepAsync(EnstylerStartupDelay).then(function() { EnstylerDelayedInit(); });
    }
   } // END Enstyler MAIN


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

} else {
    // we are on ujserstyle
    if (DEBUG) console.error('On Userstyle ...')
    var input = document.createElement('input');
    input.type = 'button';
    input.setAttribute('id', EnstylerButton);
    input.onclick = showEnstylerConfig;
    input.value = 'Save Options';
        
    function EnstylerHomeButton() {
        $('#'+EnstylerButton).remove()

        input.setAttribute('style', 'font-size: 1.1em; padding: 0.8em;');  
        $('#style-settings').after(input);
    }

    function enGetHome() {
        var myOptions='';
        $('#style-settings select').each(function() {
            var myID = $(this).attr('id');
            var myValue = $(this).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 = $(this).val();
            var myText  = $('label[for='+ myID +']').text();     
            myOptions +='#' + myID + ':' + myValue +':' + myText +';\n';
        });
       GM_config.set('saveOpt', myOptions);
    }
    
    function enSetHome() {
        input.value = 'Save Options';
        // get saved options,remove newlines and split to settings array
        var myOptions=GM_config.get('saveOpt');
        // if(DEBUG) console.error('Saved Options: ' + myOptions);
        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++) {
           //if(DEBUG) console.error('process:' + mySettings[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 +'"');}
            }
        }
        // update shown otions
        HideShowLogoSelect();
        HideShowEnstyler();
    } 
    
    // close Window after Click on Update
    function closeOnClick () {
       sleepAsync(10000).then(function() { window.close(); });
    }
    $(".install").click(closeOnClick);

    // show if options not saved
    function showNotSaved() {
        input.value = 'Options not saved!';
        EnstylerHomeButton();
    }
    $("#style-settings").click(showNotSaved);
    
    function ShowHideItem(selectID, hideVal, jqSelektor) {
        if ($(selectID).val() == hideVal) {
            $('#style-settings '+jqSelektor).parent().addClass(enClassHidden);
        } else {
            $('#style-settings '+jqSelektor).parent().removeClass(enClassHidden);
        }
    }
        
    // remove / display logo selection
    function HideShowLogoSelect() { ShowHideItem('#setting-455195', 'ik-logo1', 'label:contains(MyDealz Logo)' ); }
    $('#setting-455195').change(HideShowLogoSelect);
    
    // remove / display enstyler options 
    function HideShowEnstyler() { ShowHideItem('#setting-451668', 'ik-compact1', 'label:contains(|---)' ); }
    $('#setting-451668').change(HideShowEnstyler);
    
    // activate config for Enstyler Homepage
    GM_config.init(
      {
        id: 'GM_config',
        title: 'Enstyler2 - Settings', 
        fields: enHomefieldDefs,
       'events': // Callback functions object
         {
          'save':  function() { enSetHome(); EnstylerHomeButton(); 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);
}

function capitalizeFirstLetter(string) {
    return string[0].toUpperCase() + string.slice(1);
}

// truncate String add word boundary
function truncStringWord(string, n, worddelim ){
    if (typeof worddelim === 'undefined') worddelim=' ';
    if (string.length > n) {
       string = string.substr(0,n-1);
       return string.substr(0,string.lastIndexOf(worddelim)) + '...';
    }
    return string;

};

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

function sleepAsync(time) {
    var p = new Promise();
    setTimeout(function() { p.resolve(); }, time);
    return p.promise();  
}

// make colors ligther or darker
// http://stackoverflow.com/questions/5560248
//color = "rbg(63,131,163)";
//lighterColor = shadeRGBColor(color, 0.5);  //  rgb(159,193,209)
//darkerColor = shadeRGBColor(color, -0.25); //  rgb(47,98,122)

function shadeRGBColor(color, percent) {
    var f=color.split(","),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=parseInt(f[0].slice(4)),G=parseInt(f[1]),B=parseInt(f[2]);
    return "rgb("+(Math.round((t-R)*p)+R)+","+(Math.round((t-G)*p)+G)+","+(Math.round((t-B)*p)+B)+")";
}


// https://remysharp.com/2010/07/21/throttling-function-calls
// $('input.username').keypress(debounce(250, function));
// Ensytler debounce Funtionen, modified: parameter swapped, no args passed
// todo: dynamic delay?
function debounce(delay, fn) {
  var timer = null;
  return function () {
    clearTimeout(timer);
    timer = setTimeout(function () {
      fn.call(this);
    }, delay);
  };
}