// ==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="{"action":"scroll_to_comment_add_form","label":"engagement"}">',
'<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="{"endpoint":"https:\/\/www.mydealz.de\/threads\/<ENSTYLER-THREADID-HERE>/remove","method":"post"}" data-track="{"action":"save_thread","label":"engagement"}">',
'<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="{"action":"goto_thread_edit_form","beacon":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="{"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>',
];
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="{"action":"goto_main_target","beacon":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="{"action":"goto_menu_target sort","label":"diskutiert","beacon":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="{"count":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);
};
}