EnstylerJS

MyDealz Enstyler Frontend and enhanced features

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

  1. // ==UserScript==
  2. // @name EnstylerJS
  3. // @namespace Enstyler
  4. // @description MyDealz Enstyler Frontend and enhanced features
  5. // @include http://www.mydealz.de/*
  6. // @include https://www.mydealz.de/*
  7. // @include https://userstyles.org/styles/128262/*
  8. // @version 2.11.203
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_log
  12. // @grant GM_xmlhttpRequest
  13. // @require http://code.jquery.com/jquery-3.1.1.min.js
  14. // @require http://cdnjs.cloudflare.com/ajax/libs/jquery-throttle-debounce/1.1/jquery.ba-throttle-debounce.min.js
  15. // @require http://openuserjs.org/src/libs/sizzle/GM_config.js
  16. // ==/UserScript==
  17.  
  18. // ========== INIT EnstylerJS =====================================
  19. // init Enstyler environment
  20. var enUserLogin = false;
  21. var enUserName = '';
  22. var USI=false;
  23. var EnstylerDelay=Date.now()
  24.  
  25. function EnstylerInit () {
  26. // hide Enstyler2 CSS (c) text because we have a button now
  27. addStyleString('.threadWidget-footer::after {display: none !important};');
  28. // get LoginStatus and Username
  29. if (enUserLogin = $('.avatar--type-nav').length) {
  30. enUserName = $('.navDropDown a').attr('href');
  31. enUserName = enUserName.replace(/.*\profile\/(.+)\/overview$/,'$1');
  32. }
  33. //check for USI - only firefox mobile usercript
  34. if (GM_info.version.startsWith('0.')) {
  35. USI=true;
  36. }
  37. }
  38.  
  39.  
  40.  
  41.  
  42.  
  43. // add actions to tread overview @ some places ==================================
  44. function EnstylerDealActions() {
  45. // if logged in ...
  46. if (enUserLogin) {
  47. // code used for MyDealz Dealz actions, thanks to mydealz :-)
  48. var myOuterHtml = [ '<span class="js-options bg--em bRad--a space--h-3 space--v-3 space--mt-3 text--b">', '</span>'];
  49. var enDealAction = [ '<a title="Sag was dazu" class="link ico ico--pos-l ico--type-comment-blue space--mr-3"'+ // comment 0+1+3
  50. '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;}">',
  51. 'Sag was dazu', '', '</a>',
  52. '<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
  53. '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;}">',
  54. 'Von Liste entfernen', '', '</a>',
  55. '<a title="Bearbeiten" class="link text--color-blue ico ico--type-pencil-blue ico--pos-l space--mr-3"'+ // edit 8+9+11
  56. 'href="<ENSTYLER-HREF-HERE>/edit" data-handler="track" data-track="{&quot;action&quot;:&quot;goto_thread_edit_form&quot;,&quot;beacon&quot;:true}">',
  57. 'Bearbeiten', '', '</a>',
  58. '<a title="Abgelaufen?" class="thread-expire link ico ico--type-clock-blue ico--pos-l space--mr-3"'+ // expiried not working :-(
  59. '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;}">',
  60. 'Abgelaufen?', '', '</a>',
  61. ];
  62. var PATTERN = [ /<ENSTYLER-HREF-HERE>/g, // pattern to replace by deal link ...
  63. /<ENSTYLER-THREADID-HERE>/g, // pattern to replace ID
  64. ]
  65. if (GM_config.get('enConfMoreDeal')) {
  66. var parser = location; // parse location
  67. var pathname = parser.pathname;
  68. var username = pathname.replace(/.*profile\//,'');
  69. username = username.replace(/\/.*/,'');
  70. var myText=0;
  71. // no username ??
  72. if (username == "") {
  73. username = "unknown";
  74. } else {
  75. pathname = pathname.replace(username + '/',''); // remove username if path is longer
  76. }
  77. // display short/no text?
  78. if ($('.ico--type-grid-subNavActive').length) { myText=1; }
  79. // default for all Dealz: comment
  80. var myAction = enDealAction[0]+ enDealAction[1+myText] + enDealAction[3];
  81.  
  82. // Action for special locations only ===========
  83. // saved Dealz panel
  84. if (pathname.endsWith('profile/saved-deals') ){
  85. // add for user saved-dealz: un-bookmark
  86. myAction += enDealAction[4]+ enDealAction[5+myText]; + enDealAction[7]
  87. }
  88. if (pathname.endsWith('profile/diskussion') || pathname.endsWith(username)){
  89. // add user dealz and discussions: comment edit
  90. myAction += enDealAction[8]+ enDealAction[9+myText] + enDealAction[11];
  91. }
  92.  
  93. // we have an Action to add to an Deal!
  94. if (myAction != "") {
  95. //GM_log('Action:' + myAction);
  96. // every thread on thread page ...
  97. $('article').each(function () {
  98. // get ThreadID, Link to Deal and DealID
  99. var myThread = $(this).attr('id');
  100. if (!myThread.startsWith('thread_')) {return;}
  101. var myDealHref = $('#' + myThread + ' .thread-title a').attr('href');
  102. var myDealID = myThread.replace('thread_','')
  103. if (myThread.startsWith('To be')) {return;}
  104. // compose final HTML
  105. var newHtml= myOuterHtml[0] + myAction + myOuterHtml[1];
  106. newHtml = newHtml.replace(PATTERN[0], myDealHref);
  107. newHtml = newHtml.replace(PATTERN[1], myDealID);
  108. // append HTML to Deal
  109. $('#' + myThread +' .thread-infoRow').each(function () {
  110. $(this).append(newHtml);
  111. $(this).removeClass('thread-infoRow');
  112. });
  113. });
  114. }
  115. // actions for everywhere ===========
  116. // remove unwanted HTML from deal description
  117. $('.thread--type-detail .userHtml').each(function () {
  118. // userhtml code from mydalz, need to find jafascript to save automatically :-(
  119. var enUserhtml = ['<div class="userHtml overflow--wrap-break space--t-3" data-handler="lightbox-xhr" data-lightbox-xhr="{&quot;name&quot;:&quot;threads&quot;}">',
  120. '</div>', // sourround deal description
  121. ];
  122. // get inner html
  123. var myHtml = $(this).html();
  124.  
  125. // remove unwanted Stuff: combined <div><br><br> stuff, created by cut'npaste html
  126. // not elegant, but works ...
  127. var newHtml = myHtml.replace(/<div>|<div><br>|<\/br>|<\/div>/gi,'');
  128. newHtml = newHtml.replace(/<br><br><br>|<br><br><br><br>|<br><br><br><br><br>/gi,'<br><br>');
  129. // replace original with modifyed html
  130. $(this).replaceWith(enUserhtml[0] + newHtml + enUserhtml[1]);
  131. });
  132.  
  133. } // END enabled
  134. }
  135. }
  136.  
  137.  
  138. // show popup user info while click on avatar ... ======================
  139. function EnstylerAvatarPopup() {
  140. // if logged in ...
  141. if (enUserLogin) {
  142. // code used for MyDealz avatar popup, thanks to mydealz :-)
  143. 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;',
  144. '/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;}]}">',
  145. '</button>',
  146. ];
  147. // remove second image from cardview
  148. if (GM_config.get('enConfUser')) { addStyleString('.thread-footer-cell a img.avatar.vAlign--all-m.space--mr-1.thread-avatar {display: none;}'); }
  149.  
  150. // replace every avatar link without popup
  151. if (GM_config.get('enConfUser')) {
  152. $('.thread-footer-cell a.user.linkPlain, .user.linkPlain.thread-user').each(function () {
  153. var Iam = $(this);
  154. // get inner html and link to user profile
  155. var myHtml = Iam.html();
  156. var mysrc = Iam.attr('href');
  157. // seperate user name from image and add class user
  158. var myAvatar1 = myHtml.replace(/<span.*/,'');
  159. var myAvatar2 = myHtml.replace(/.*<span class=".* space--mr-1">/,'<span class=" space--mr-1 user link-plain">');
  160.  
  161. // show small / medium sized Avatar
  162. myAvatar1 = myAvatar1.replace('avatar--type-s','avatar--type-m'); //in Dealz
  163. if (GM_config.get('enConfAvatar')) { myAvatar1 = myAvatar1.replace('thread-avatar','avatar--type-m'); }
  164. // compose popup
  165. var myPopup = enPopupUser[0] + Iam.attr('href') + enPopupUser[1] + myAvatar1 + enPopupUser[2] + '<a href="' + Iam.attr('href') + '">'+ myAvatar2 + '</a>';
  166. Iam.replaceWith(myPopup);
  167. });
  168. }
  169. }
  170. }
  171.  
  172.  
  173. // create select page or scrollwheel for page navigation =============
  174. var EnstylerPageEnum='enPageEnum';
  175. var selectList = document.createElement("select");
  176. selectList.id = EnstylerPageEnum;
  177. selectList.setAttribute('class', EnstylerPageEnum);
  178. selectList.onchange = EnstylerPageAction;
  179.  
  180. var EnstylerPageEnumDown='enPageEnumDown';
  181. var selectListDown = document.createElement("select");
  182. selectListDown.id = EnstylerPageEnumDown;
  183. selectListDown.onchange = EnstylerPageAction;
  184.  
  185. function EnstylerPagePickerCreate() {
  186. // if enabled
  187. if (GM_config.get('enConfPagePicker')) {
  188. // init values and clear select list
  189. var page=1;
  190. var min=1;
  191. var max=1;
  192. $(selectList).empty();
  193. // get page and max from pagenav
  194. if ( $('.text--color-charcoalTint').length ) {
  195. // remove linebreaks
  196. pageHtml = $('.text--color-charcoalTint').html().replace(/\r?\n|\r/g);
  197. //locate actual page and last page
  198. page = pageHtml.replace( /.*>Seite /i ,''); page = page.replace( /<.*/i , '');
  199. max = pageHtml.replace( /.*page=/ ,''); max = max.replace( /[^0-9].*/i , '');
  200. if (page == '') {page=1;}
  201. if (max == '') {max=page;}
  202. }
  203.  
  204. // create page select element
  205. var x; var last; var option;
  206.  
  207. for (x = min; x <= max; ) {
  208. option = document.createElement("option");
  209. option.text = x;
  210. selectList.add(option); //selectListDown.add(option);
  211. last = x;
  212. // non linear increment
  213. var diff = Math.abs(x-page);
  214.  
  215. if ( x < 10 || diff < 5) { x++; }
  216. else if ( x < 1000 && diff > 600) { x += Math.floor(diff/100); }
  217. else { x += Math.floor(diff/2); }
  218. }
  219. // add last page
  220. if (page > max) { max=page;}
  221. if (last < max ) {
  222. option = document.createElement("option");
  223. option.text = max;
  224. selectList.add(option); //selectListDown.add(option);
  225. }
  226. // set default value
  227. selectList.value = page; //selectListDown.value = page;
  228.  
  229. // Deal Page
  230. if ($('.voteBar').length) {
  231. selectList.setAttribute('class', EnstylerPageEnum +' subNavMenu-link subNavMenu-btn voteBar--sticky-off--hide');
  232. $('.voteBar--sticky-off--hide:last').before(selectList);
  233. } else {
  234. // overwiew page
  235. if (GM_config.get('enConfBtn')) {
  236. // Place Picker in subnav
  237. selectList.setAttribute('class', EnstylerPageEnum+' box--all-i subNavMenu-link subNavMenu-btn');
  238. $('.subNav-label:last').before(selectList);
  239. } else {
  240. //place picker in MainNav
  241. selectList.setAttribute('class', EnstylerPageEnum+' js-navDropDown-messages vAlign--all-m');
  242. if ($('.test-loginButton').length) {
  243. $('.test-loginButton').before(selectList);
  244. } else {
  245. $('.js-navDropDown-messages').before(selectList);
  246. }
  247. }
  248. }
  249. }
  250. }
  251.  
  252. // goto selected Page
  253. function EnstylerPageAction() {
  254. var enPage = 'page=' + $(this).val();
  255. var enUrl = window.location.href;
  256. var path = window.location.pathname;
  257.  
  258. // remove page= and everthing behind
  259. enUrl = enUrl.replace( /page=.*|#.*/ ,'');
  260. // add new page parameter
  261. if ( enUrl.endsWith('?') || enUrl.endsWith('&')) {
  262. enUrl += enPage;
  263. } else {
  264. enUrl += '?'+enPage;
  265. }
  266. // add #thread-comments for deal
  267. if (path.startsWith('/deals/')) { enUrl += '#thread-comments';}
  268. window.location = enUrl;
  269. }
  270.  
  271. function EnstylerPagePickerRemove() {
  272. // Removes pagepicker from the document
  273. $('.'+ EnstylerPageEnum).remove();
  274. }
  275.  
  276.  
  277.  
  278. // blacklist do not show dealz containing blacklistet words ==========================
  279. // search in kategorie, dealtitle, and username
  280. var enClassHidden = 'enClassHidden';
  281. var enClassBlackDone = 'enClassBlackDone';
  282. var enUnblackText = 'unBlacklist <NUM-BLACK> Dealz'
  283. var enBlacklisted=0;
  284.  
  285. function EnstylerBlacklist() {
  286. // if logged in and user is not in whitelist
  287. if (enUserLogin && ! GM_config.get('enConfWhitelist').includes(enUserName)) {
  288. // add actual user to whitelist
  289. GM_config.set('enConfWhitelist', '@'+enUserName +',' + GM_config.get('enConfWhitelist'));
  290. }
  291. // Black/Whitelist active
  292. if (GM_config.get('enConfBlackEnable')) {
  293. var unwantedRegex = [ /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\ ]/g, // in White/Backlist
  294. /[\[\]\(\)\{\}\?\.\:\;\!\"\*\+\,]/g // in Dealtext
  295. ];
  296. // convert Black/Whitelist to Array
  297. var myTemp=GM_config.get('enConfBlacklist');
  298. myTemp = myTemp.replace(unwantedRegex[0], '');
  299. var enBlack = myTemp.split(',');
  300. myTemp=GM_config.get('enConfWhitelist');
  301. myTemp = myTemp.replace(unwantedRegex[0], '');
  302. var enWhite = myTemp.split(',');
  303.  
  304. // process every article
  305. $('article').each(function () {
  306. if ($(this).hasClass(enClassBlackDone)) { return;}
  307. // get thrad
  308. var myThread = $(this).attr('id');
  309. // return if already done or return no deal
  310. if (!myThread.startsWith('thread_')) { ;return;}
  311. // mark as already seen
  312. $(this).addClass(enClassBlackDone);
  313. // get title, categorie, user
  314. var myDealText = $('#' + myThread + ' .thread-category').text();
  315. myDealText += ' ' +$('#' + myThread + ' .thread-title a').text();
  316. myDealText += ' @' +$('#' + myThread + ' .user').text();
  317. myDealText = myDealText.replace(unwantedRegex[1] ,' ');
  318. // Whitelist first
  319. for (var i=-1, length=enWhite.length; ++i < length;) {
  320. // whitelist Regex, exit if found
  321. if (enWhite[i] !='' && myDealText.match(new RegExp(enWhite[i],'i'))) {
  322. return;
  323. }
  324. }
  325. // blacklist first, blacklist if found and exit
  326. for (var i=-1, length=enBlack.length; ++i < length;) {
  327. // whitelist Regex, unwanted characteres are already removed
  328. if (enBlack[i] !='' && myDealText.match(new RegExp(enBlack[i],'i'))) {
  329. $(this).addClass(enClassHidden);
  330. enBlacklisted++;
  331. return;
  332. }
  333. }
  334. }); // END Article
  335. // set label for unBlacklist button
  336. enJSfieldDefs.enConfUnblacklist.label=enUnblackText.replace('<NUM-BLACK>',enBlacklisted)
  337. }
  338. }
  339.  
  340. function EnstylerBlacklistRemove() {
  341. enBlacklisted=0;
  342. $('.'+enClassHidden).removeClass(enClassHidden);
  343. // do nor reprocess after unblack
  344. //$('.'+enClassBlackDone).removeClass(enClassBlackDone);
  345. }
  346.  
  347.  
  348. // Main Nav will stay on TOP of the screen =========================
  349. function EnstylerFixedNav() {
  350. var myFixedCssMain='.nav { display: block; position: fixed; width: 100%; z-index: 120;} .subNav, .userProfile, .tabbedInterface {margin-top: 4.4em;}'
  351. var myFixedCssSub ='.subNav {margin-top: 0 !important;} .nav-subheadline {margin-top: 4.4em;}'
  352. if (GM_config.get('enConfNavFixed')) {
  353. var parser = location; // parse location
  354. var pathname = parser.pathname;
  355. // everywhere but in Deal detail, I like it like it is ...
  356. if (!pathname.startsWith('/deals/') ){
  357. // delete header element with active stuff, but keep inside HTML
  358. var mySavedHtml = $('header').html();
  359. $('header').replaceWith(mySavedHtml);
  360. // CSS for everywhere
  361. addStyleString(myFixedCssMain);
  362. if (! pathname.match(/^\/$|^\/hot|^\/new|^\/settings|\/discussed/i)) {
  363. // additional CSS for categories
  364. addStyleString(myFixedCssSub);
  365. }
  366. }
  367. }
  368. }
  369.  
  370. // bind external links to GM_xmlhttpRequest ================
  371. function EnstylerProcessLinks () {
  372.  
  373. // REGEX to detect external URL
  374. var REGEX_THREAD = /^https?:\/\/www\.mydealz\.de\/visit\/thread(image)?\/\d+$/;
  375. var REGEX_DESC = /^https?:\/\/www\.mydealz\.de\/visit\/threaddesc\/\d+\/\d+$/;
  376. var REGEX_COMMENT = /^https?:\/\/www\.mydealz\.de\/visit\/comment\/\d+\/\d+$/;
  377. var REGEX_AMAZONMOB = /^https?:\/\/www\.amazon\..*\/gp\/aw\/.*$/;
  378. //var REGEX_AMAZON = /^https?:\/\/www\.amazon\..*$/;
  379.  
  380. //* GM_xmlhhtpREquest not supported by firefox mobile :-(((
  381. // abfangen aller links mit jQuery
  382. $('a').bind('click', function(){
  383. var url = this.href;
  384.  
  385. // externer Link mit Redirect ...
  386. // Match REDIRECT URLs and open new Window with finalUrl
  387. if (GM_config.get('enConfFilterLink') && url.match(REGEX_THREAD) || url.match(REGEX_DESC) || url.match(REGEX_COMMENT)) {
  388. // Workaround: pre open window because of popup blockers
  389. var asyncWindow = window.open(url, '_blank');
  390. // now lookup redirecd external URL ...
  391. // alert("external URL detected");
  392. GM_xmlhttpRequest({
  393. method: 'GET',
  394. url: url,
  395. // here we get the final URL from redirect
  396. onload: function (response) {
  397. // process final URL
  398. var newUrl = response.finalUrl;
  399. //alert(newUrl);
  400. // lets see ...
  401. if (newUrl.match(REGEX_AMAZONMOB)) {
  402. newUrl = newUrl.replace("/gp/aw/d/", "/dp/");
  403. newUrl = newUrl.replace("/gp/aw/ol/", "/gp/offer-listing/");
  404. //alert("Amazon rewritten URL: " + newUrl);
  405. }
  406. // load processed URL in preopened window
  407. asyncWindow.location = newUrl;
  408. }
  409. });
  410. // return without link processing by Browser
  411. return false;
  412. }
  413. // return with link processing in Browser
  414. return true;
  415. }); // - END GM_xmlhttpRequest */
  416. } // END EnstylerProcessLinks
  417.  
  418.  
  419. // check for Updates of EnstylerJS and Enstyler2 ================
  420. var enUpdateJSurl = "https://greasyfork.org/de/scripts/24243"; // production version
  421. var enUpdateInterval=1 * (24*60) // 1 day between update checks
  422.  
  423. if (!GM_info.script.namespace.match(/develop/i)) {
  424. var enUpdateJSurl = "https://greasyfork.org/de/scripts/24244"; // developer version
  425. var enUpdateInterval=1 // 1 minute between update checks
  426.  
  427. }
  428.  
  429. function enCheckUpdates() {
  430. // get time and convert to minutes
  431. var myTime=Date.now()/60000|0;
  432. if (GM_config.get('enConfCheckUpdate') & myTime - GM_config.getValue('enLastUpdateCheck') > enUpdateInterval) {
  433. GM_config.setValue('enLastUpdateCheck', myTime);
  434. //GM_config.set('enLastUpdateCheck', myTime);
  435. //GM_config.fields['enLastUpdateCheck'].reload();
  436. enGetOnlineVersions();
  437. }
  438. }
  439.  
  440. // Get actual online version
  441. function enGetOnlineVersions() {
  442. // EnstylerJS
  443. GM_xmlhttpRequest({
  444. method: "GET",
  445. url: enUpdateJSurl,
  446.  
  447. onload: function(response) {
  448. var enJSVersion = GM_info.script.version;
  449. var myResponse=response.responseText;
  450. myResponse=myResponse.replace(/\r?\n|\r| |\t/g,'');
  451. myResponse=myResponse.replace(/^.*\<ddclass=\"script-show-version\"\>\<span\>|\<\/span\>.*/gi,'');
  452. //alert(myResponse)
  453. if (versionCompare(enJSVersion, myResponse) <0) {
  454. //showPopup('New EnstylerJS availible: ' + enJSVersion + '\n\nPlease update!');
  455. } else { myResponse='no'}
  456. GM_config.setValue('enJSVersion', myResponse);
  457. }
  458. });
  459.  
  460. // Enstyler2
  461. // get last known Verion of CSS
  462. GM_xmlhttpRequest({
  463. method: "GET",
  464. url: "https://userstyles.org/styles/128262",
  465. onload: function(response) {
  466. var enEnVersion;
  467. $('.threadWidget-footer').each(function(){ enEnVersion=window.getComputedStyle(this,':after').content;});
  468. enEnVersion = enEnVersion.replace(/ /g,'')
  469. enEnVersion = enEnVersion.replace(/.*Enstyler|-.*/gi,'')
  470. //alert(enEnVersion)
  471. var myResponse=response.responseText;
  472. myResponse=myResponse.replace(/\r?\n|\r| |\t/g,'');
  473. myResponse=myResponse.replace(/^.*additional-info-text.*Version:|\<br\>.*/gi,'');
  474. //alert(myResponse);
  475. if (versionCompare(enEnVersion, myResponse) <0) {
  476. //showPopup('New CSS availible: ' + enEnVersion + '\n\nPlease update!');
  477. } else { myResponse='no'; }
  478. GM_config.setValue('enEnVersion', myResponse);
  479.  
  480. }
  481. });
  482. }
  483.  
  484.  
  485. // create button for display Config ==============
  486. var EnstylerButton = 'EnstylerButton';
  487.  
  488. //var input = document.createElement('a');
  489. // input.setAttribute('href', 'showEnstylerConfig()');
  490. // input.setAttribute('id', EnstylerButton);
  491. var input = document.createElement('input');
  492. input.type = 'button';
  493. input.setAttribute('id', EnstylerButton)
  494. input.onclick = showEnstylerConfig;
  495.  
  496. function EnstylerButtonCreate() {
  497. // add Enstyler Button to ...
  498. var myElement;
  499. var myMain=false;
  500. input.value = 'Enstyler';
  501. // MainNav selected or Deal
  502. if (GM_config.get('enConfBtn'))
  503. { myMain=true; }
  504. if ($('.voteBar').length)
  505. { myMain=true; }
  506. // only if space left
  507. if ($(window).width() < GM_config.get('enConfBtnMinWidth'))
  508. { myMain=false; }
  509. if (myMain) {
  510. // add button to MainNav
  511. var Elements = document.getElementsByClassName("nav-link");
  512. myElement = Elements[3];
  513. input.setAttribute('class', 'vAlign--all-m nav-link-text');
  514. input.setAttribute('style', '');
  515. } else {
  516. // add button to SubNav
  517. myElement = document.getElementById('tour-filter');
  518. input.setAttribute('class', 'box--all-i subNavMenu-link');
  519. input.setAttribute('style', 'font-size: 1.28571em; font-weight: 700; top: 3px; left: -0.7em');
  520. }
  521. // only if myElement exist
  522. if (myElement !== null) {
  523. myElement.appendChild(input);
  524. //insertAfter(input, myElement);
  525. }
  526. }
  527.  
  528. // needed for Enstyler Homepage
  529. function EnstylerHomeButton() {
  530. // add Enstyler Button to ...
  531. input.value = 'Options';
  532. var myElement = document.getElementById('style-settings');
  533. input.setAttribute('style', 'font-size: 1.28571em; padding: 0.8em;');
  534. // only if myElement exist
  535. if (myElement !== null) {
  536. //myElement.appendChild(input);
  537. insertAfter(input, myElement);
  538. }
  539. }
  540.  
  541. function EnstylerButtonRemove() {
  542. // Removes button from the document
  543. $('#'+ EnstylerButton).remove
  544. }
  545.  
  546.  
  547.  
  548. // ============= GM_config functions =======================================
  549.  
  550. // define EnstylerJS GM_config elements
  551. var enJSfieldDefs = {
  552. // Part one: load external content --------
  553. 'enstyler': {
  554. 'section': ['additonal features for Enstyler', ''],
  555. 'label': 'Update CSS...', // Appears on the button
  556. 'type': 'button', // Makes this setting a button input
  557. 'click': function() { // Function to call when button is clicked
  558. GM_config.setValue('enEnVersion', 'no');
  559. GM_config.save();
  560. GM_config.open();
  561. showUrl('https://userstyles.org/styles/128262#style-info');
  562. }
  563. },
  564. 'enstylerJS': {
  565. 'label': 'Update UserScript...', // Appears on the button
  566. 'type': 'button', // Makes this setting a button input
  567. 'click': function() { // Function to call when button is clicked
  568. GM_config.setValue('enJSVersion', 'no');
  569. GM_config.save();
  570. GM_config.open();
  571. showUrl('https://greasyfork.org/de/scripts/24243');
  572.  
  573. }
  574. },
  575.  
  576. 'dontCookies': {
  577. 'label': 'Mozilla no cookies...', // Appears on the button
  578. 'type': 'button', // Makes this setting a button input
  579. 'click': function() { // Function to call when button is clicked
  580. showUrl('https://addons.mozilla.org/de/addon/i-dont-care-about-cookies/'); }
  581. },
  582.  
  583. //* thhis has to be the last one,display only if internal version is disabled
  584. 'externalMobileRedirect': {
  585. 'label': 'Amazon mobile redirect...', // Appears on the button
  586. 'type': 'button', // Makes this setting a button input
  587. 'click': function() { // Function to call when button is clicked
  588. showUrl('https://greasyfork.org/de/scripts/19700'); }
  589. }, // */
  590.  
  591. // part two: EnstylerJS internal configuration options ------
  592. 'hidden1': { // display next section, dont kow why ...
  593. 'section': ['Configuration', ''],
  594. 'type': 'hidden', // Makes this setting a hidden input
  595. 'value': 'Some hidden value' // Value stored
  596. },
  597.  
  598. // postion of enstyler "button"
  599. 'enConfBtn': {
  600. 'label': 'Show Enstyler in MainNav', // Appears next to field
  601. 'type': 'checkbox', // Makes this setting a checkbox input
  602. 'default': false // Default value if user doesn't change it
  603. },
  604. 'enConfBtnMinWidth': {
  605. 'label': 'only if width is bigger than ', // Appears next to field
  606. 'type': 'int', // Makes this setting a text input
  607. 'min': 600, // Optional lower range limit
  608. 'max': 1200, // Optional upper range limit
  609. 'size': 4, // Limit length of input (default is 25)
  610. 'default': 850 // Default value if user doesn't change it
  611. },
  612. 'enConfNavFixed': {
  613. 'label': 'Display FIXED MainNav', // Appears next to field
  614. 'type': 'checkbox', // Makes this setting a checkbox input
  615. 'default': false // Default value if user doesn't change it
  616. },
  617. // ehanced USerInfo
  618. 'enConfUser': {
  619. 'label': 'Show Popuop Userinfo', // Appears next to field
  620. 'type': 'checkbox', // Makes this setting a checkbox input
  621. 'default': true // Default value if user doesn't change it
  622. },
  623. 'enConfAvatar': {
  624. 'label': 'bigger Avatar for Popuop', // Appears next to field
  625. 'type': 'checkbox', // Makes this setting a checkbox input
  626. 'default': true // Default value if user doesn't change it
  627. },
  628. // enable filtering of external links
  629. 'enConfFilterLink': {
  630. 'label': 'Amazon mobile redirect', // Appears next to field
  631. 'type': 'checkbox', // Makes this setting a checkbox input
  632. 'default': true // Default value if user doesn't change it
  633. }, // */
  634.  
  635. // more Deal actions
  636. 'enConfMoreDeal': {
  637. 'label': 'additional Deal actions', // Appears next to field
  638. 'type': 'checkbox', // Makes this setting a checkbox input
  639. 'default': true // Default value if user doesn't change it
  640. },
  641.  
  642. // Page picker
  643. 'enConfPagePicker': {
  644. 'label': 'Enable Page Picker', // Appears next to field
  645. 'type': 'checkbox', // Makes this setting a checkbox input
  646. 'default': false // Default value if user doesn't change it
  647. },
  648. // check for Updates
  649. 'enConfCheckUpdate': {
  650. 'label': 'Check for Updates', // Appears next to field
  651. 'type': 'checkbox', // Makes this setting a checkbox input
  652. 'default': true // Default value if user doesn't change it
  653. },
  654. 'enLastUpdateCheck': {
  655. 'type': 'hidden', // Makes this setting a text input
  656. 'default': '1000' // Default value if user doesn't change it
  657. },
  658. 'enJSVersion': {
  659. 'type': 'hidden', // Makes this setting a text input
  660. 'value': 'no' // Value stored
  661. },
  662. 'enEnVersion': {
  663. 'type': 'hidden', // Makes this setting a text input
  664. 'value': '' // Value stored
  665. },
  666. // Black/Whitelist input
  667. 'enConfBlackEnable': {
  668. 'label': 'Enable Black- / Whitelist', // Appears next to field
  669. 'type': 'checkbox', // Makes this setting a checkbox input
  670. 'default': true // Default value if user doesn't change it
  671. },
  672. 'enConfBlacklist': {
  673. 'label': 'Blacklist - deals, categories, @users', // Appears next to field
  674. 'type': 'text', // Makes this setting a text input
  675. 'size': 70, // Limit length of input (default is 25)
  676. 'default': 'Nutella, Bangood, @Admin' // Default value if user doesn't change it
  677. },
  678. 'enConfWhitelist': {
  679. 'label': 'Whitelist', // Appears next to field
  680. 'type': 'text', // Makes this setting a text input
  681. 'size': 70, // Limit length of input (default is 25)
  682. 'default': '' // Default value if user doesn't change it
  683. },
  684. 'enConfUnblacklist': {
  685. 'label': 'UnBlacklist', // Appears on the button
  686. 'type': 'button', // Makes this setting a button input
  687. 'click': function() { // Function to call when button is clicked
  688. EnstylerBlacklistRemove(); }
  689. },
  690. // display copy message at end of section ...
  691. 'copy': {
  692. '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>'],
  693. 'type': 'hidden', // Makes this setting a hidden input
  694. 'value': 'Some hidden value' // Value stored
  695. },
  696. };
  697.  
  698. // define EnstylerJS GM_config elements
  699. var enHomefieldDefs = {
  700. // Part one: load external content --------
  701. 'saveOpt': {
  702. 'section': ['save your CSS options for next visit', ''],
  703. 'label': 'Select your CSS on main page then come back and klick "Save" ', // Appears near textarea
  704. 'type': 'textarea', // Makes this setting a button input
  705. 'size': 70,
  706. //'click': function() { // Function to call when button is clicked
  707. // showUrl('https://userstyles.org/styles/128262#style-info'); }
  708. },
  709. // display copy message at end of section ...
  710. 'copy': {
  711. '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>'],
  712. 'type': 'hidden', // Makes this setting a hidden input
  713. 'value': 'Some hidden value' // Value stored
  714. },
  715. };
  716.  
  717. // display GM_copnfig as div, so we cam apply CSS!!
  718. var enGMOptChange = false;
  719. var enGMFrame = document.createElement('div');
  720. enGMFrame.setAttribute('class','GM_config');
  721. document.body.appendChild(enGMFrame);
  722.  
  723. // basic config panel formatting, everything else is formatted by enstyler
  724. var enCSS = ['.GM_config {left: 5% !iportant; top: 9% !important; height: auto !important; max-width: 35em !important;}',
  725. '.GM_config input, .GM_config button, .GM_config textarea { border: 1px solid; margin: 0.5em 0em 0.2em 1em; padding: 0.1em;}',
  726. '.GM_config .reset { font-size: 9pt; padding-right: 1em; }',
  727. '#GM_config_enstyler_var:after {content: ". <- please install CSS!"; font-weight: bold;}',
  728. '.enClassHidden {display: none;}',
  729. ].join(" ");
  730. addStyleString(enCSS);
  731.  
  732. var En_Popup = new GM_configStruct(
  733. {
  734. 'id': 'EnPopup', // You need to use a different id for each instance
  735. 'title': 'EnstylerJS - Info',
  736. 'fields': // Fields object
  737. {
  738. 'Text': // This is the id of the field
  739. {
  740. 'label': '', // Appears next to field
  741. 'type': 'textarea', // Makes this setting a text field
  742. 'default': '' // Default value if user doesn't change it
  743. }
  744. },
  745. 'events':
  746. {
  747. 'open': function (doc) {
  748. // translate the buttons
  749. var config = this;
  750. doc.getElementById(config.id + '_saveBtn').textContent = ' OK ';
  751. //doc.getElementById(config.id + '_closeBtn').textContent = 'Cancel';
  752. doc.getElementById(config.id + '_resetLink').textContent = '';
  753. },
  754. },
  755. 'frame': enGMFrame // Element used for the panel
  756. }
  757. );
  758.  
  759. function showPopup(text) {
  760. En_Popup.fields['Text'].value = text;
  761. En_Popup.fields['Text'].reload();
  762. En_Popup.open();
  763. }
  764.  
  765.  
  766. var enGMConfigOpen=false;
  767. // EnstylerJS Config Panel anzeigen
  768. function showEnstylerConfig() {
  769. if(!enGMConfigOpen) {
  770. enGetHome();
  771. GM_config.open();
  772. enGMConfigOpen=true;
  773. } else {
  774. GM_config.close();
  775. }
  776. }
  777.  
  778.  
  779. // EnstylerJS START ========================
  780. if (!window.location.hostname.endsWith('userstyles.org')) {
  781. var enFixedNav=false;
  782. GM_config.init(
  783. {
  784. id: 'GM_config',
  785. title: 'EnstylerJS - Settings',
  786. fields: enJSfieldDefs,
  787. 'events': // Callback functions object
  788. {
  789. //'init': function() { alert('onInit()'); },
  790. // remove elements ich switch is checked or not
  791. 'open': function() {
  792. var enRemoveConfig = [
  793. { check: true, switch: 'enConfFilterLink', remove: 'externalMobileRedirect'},
  794. { check: false, switch: 'enConfBtn', remove: 'enConfBtnMinWidth'},
  795. { check: false, switch: 'enConfUser', remove: 'enConfAvatar'},
  796. { check: false, switch: 'enConfBlackEnable', remove: 'enConfWhitelist'},
  797. { check: false, switch: 'enConfBlackEnable', remove: 'enConfBlacklist'},
  798. { check: false, switch: 'enConfBlackEnable', remove: 'enConfUnblacklist'}
  799. ];
  800. enFixedNav=GM_config.get('enConfNavFixed');
  801. $(enRemoveConfig).each(function() {
  802. if (GM_config.get(this.switch) == this.check) {
  803. GM_config.fields[this.remove].remove();
  804. }
  805. });
  806. if (GM_config.get('enConfCheckUpdate')) {
  807. var myUpdate
  808. if ((myUpdate=GM_config.getValue('enJSVersion')) =='no')
  809. {GM_config.fields['enstylerJS'].remove();}
  810. else {showPopup('New EnstylerJS availible: ' + myUpdate + '\n\nPlease update!');}
  811. if ((myUpdate=GM_config.getValue('enEnVersion')) =='no')
  812. {GM_config.fields['enstyler'].remove();}
  813. else {showPopup('New CSS availible: ' + myUpdate + '\n\nPlease update!');}
  814. }
  815. },
  816. //'reset': function() { alert('reset') },
  817. // relaod page on close after save
  818. 'save': function() {
  819. if (!GM_config.get('enConfNavFixed') && GM_config.get('enConfNavFixed')!=enFixedNav) {window.location.reload(false);}
  820. EnstylerButtonRemove();
  821. EnstylerButtonCreate();
  822. EnstylerPagePickerRemove();
  823. EnstylerPagePickerCreate();
  824. EnstylerBlacklistRemove()
  825. EnstylerBlacklist();
  826. EnstylerFixedNav();
  827. enCheckUpdates();
  828. GM_config.close()
  829. },
  830. 'close': function() { enGMConfigOpen=false;},
  831. },
  832. 'frame': enGMFrame // Element used for the panel
  833. }
  834. );
  835. // dummy, do not delete
  836. function enGetHome() {;}
  837. // ============== START EnstyerJS =============
  838. EnstylerInit();
  839. EnstylerProcessLinks();
  840.  
  841. EnstylerBlacklist();
  842. EnstylerAvatarPopup();
  843. EnstylerDealActions();
  844. EnstylerFixedNav();
  845. // delay Pagepicker and repaeting actions after finishing everything else
  846. function EnstylerDelayedInit() {
  847. EnstylerButtonCreate();
  848. EnstylerPagePickerCreate();
  849. enCheckUpdates();
  850. // track DOM change Events, debounce: wait 1000ms after mutiple events
  851. // then re-apply (somse) changes to dynamic loaded content,
  852. $('.fGrid-last2, .thread-list--type-card').bind("DOMSubtreeModified",$.debounce( 1000, function(){
  853. //GM_log('DOMSubtreeModified detected!!!');
  854. EnstylerBlacklist();
  855. EnstylerDealActions();
  856. EnstylerAvatarPopup();
  857. //EnstylerPagePickerCreate();
  858.  
  859. }));
  860. }
  861. // wait until page is loaded completely
  862. if (!USI) {
  863. // Greasemonkey and Tampeermonky run script on DOM ready
  864. $(window).bind("load", function() {
  865. EnstylerDelayedInit();
  866. });
  867. } else {
  868. // USI run script on page load, give some time to finish rendering
  869. sleepAsync(Date.now()-EnstylerDelay).then(() => {
  870. EnstylerDelayedInit();
  871. });
  872. }
  873.  
  874.  
  875. // ============= EnStyler UserScript Homepage functions =======
  876. // experimental support for EnStyler2 export / import
  877.  
  878. } else {
  879. // we are on ujserstyle
  880. function enGetHome() {
  881. var myOptions='';
  882. $('#style-settings select').each(function() {
  883. var myID = $(this).attr('id');
  884. var myValue = $('#'+myID).val();
  885. var myText = $('option[value='+ myValue +']').text();
  886. myOptions +='#' + myID + ':' + myValue +':' + myText +';\n';
  887. });
  888. $('#style-settings input:checked').each(function() {
  889. var myID = $(this).attr('id');
  890. var myValue = $('#'+myID).val();
  891. var myText = $('label[for='+ myID +']').text();
  892. myOptions +='#' + myID + ':' + myValue +':' + myText +';\n';
  893. });
  894. GM_config.set('saveOpt', myOptions);
  895. }
  896. function enSetHome() {
  897. // get saved options,remove newlines and split to settings array
  898. var myOptions=GM_config.get('saveOpt');
  899. myOptions=myOptions.replace(/\n/g,'');
  900. var mySettings = myOptions.split(';');
  901.  
  902. // abort if no options found
  903. if (myOptions=='' || !myOptions.startsWith('#')) {return;}
  904.  
  905. for (var i=0; i< mySettings.length; i++) {
  906. // each Setting has 3 fields seperated by :, but only 2 used
  907. var myField=mySettings[i].split(':');
  908.  
  909. if (myField[0].match(/^#setting/i)) {
  910. // select
  911. $(myField[0]).val('');
  912. $(myField[0]).val(myField[1]);
  913. } else if (myField[0].startsWith('#option')) {
  914. // option
  915. $(myField[0]).prop('checked', true);
  916. } else {
  917. if (myField[0] != '') {alert('unkown option: "' + myField +'"');}
  918. }
  919. }
  920. }
  921. // activate config for Enstyler Homepage
  922. GM_config.init(
  923. {
  924. id: 'GM_config',
  925. title: 'Enstyler2 - Settings',
  926. fields: enHomefieldDefs,
  927. 'events': // Callback functions object
  928. {
  929. //'init': function() { alert('onInit()'); },
  930. // remove elements ich switch is checked or not
  931. //'open': function() { enGetHome(); },
  932. //'reset': function() { enGMOptChange = true; },
  933. // relaod page on close after save
  934. 'save': function() { enSetHome(); GM_config.close()},
  935. 'close': function() { enGMConfigOpen=false; },
  936. },
  937. 'frame': enGMFrame // Element used for the panel
  938. }
  939. );
  940. // START Enstyler 2 Homepage
  941. EnstylerHomeButton();
  942. // set saved options
  943. enSetHome();
  944.  
  945. }
  946.  
  947.  
  948. //=========== Support functions for actual use ======
  949.  
  950. // display website in external window
  951. function showUrl(str) {
  952. var myDeco = "innerheight=780,innerwidth=550";
  953. var myName = "enstyler";
  954. // workaround for not working window.focus(): close an existing window first
  955. // not working on mobile :-(
  956. //var myWindowShow = window.open('', myName, "width=100,height=100").close();
  957. var myWindowShow = window.open(str, myName, myDeco);
  958. }
  959.  
  960. // add CSS in to document
  961. function addStyleString(str) {
  962. var node = document.createElement('style');
  963. node.innerHTML = str;
  964. document.body.appendChild(node);
  965. }
  966.  
  967. // insertAfter like .insertBefore but as support function
  968. function insertAfter(newNode, referenceNode) {
  969. referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
  970. }
  971.  
  972. // from https://gist.github.com/TheDistantSea/8021359
  973. // returns 0 on equal, 1 on v1 newer, -1 on v2 newer
  974. function versionCompare(v1, v2) {
  975. var lexicographical = false,
  976. zeroExtend = true,
  977. v1parts = v1.split('.'),
  978. v2parts = v2.split('.');
  979.  
  980. function isValidPart(x) { return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); }
  981. if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {return NaN; }
  982.  
  983. if (zeroExtend) {
  984. while (v1parts.length < v2parts.length) v1parts.push("0");
  985. while (v2parts.length < v1parts.length) v2parts.push("0");
  986. }
  987.  
  988. if (!lexicographical) {
  989. v1parts = v1parts.map(Number);
  990. v2parts = v2parts.map(Number);
  991. }
  992.  
  993. for (var i = 0; i < v1parts.length; ++i) {
  994. if (v2parts.length == i) { return 1; }
  995. if (v1parts[i] == v2parts[i]) { continue; }
  996. else if (v1parts[i] > v2parts[i]) { return 1; }
  997. else { return -1; }
  998. }
  999.  
  1000. if (v1parts.length != v2parts.length) { return -1; }
  1001. return 0;
  1002. }
  1003.  
  1004. // sleep time expects milliseconds, then execute code
  1005. // NOTE: code runs in parallel (asnyc)!
  1006. // Usage!
  1007. // sleepAsync(500).then(() => {
  1008. // Do something after the sleep!
  1009. // });
  1010.  
  1011. function sleepAsync (time) {
  1012. return new Promise((resolve) => setTimeout(resolve, time));
  1013. }