您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Provides jump buttons to the next lesson for leveling up (based on minirock / oltodosel autoScroller).
当前为
// ==UserScript== // @name Duolingo LevelJumper // @description Provides jump buttons to the next lesson for leveling up (based on minirock / oltodosel autoScroller). // @version 2.8.1 // @namespace esh // @match https://*.duolingo.com/* // ==/UserScript== // 2.3: find a better solution to move to the first lesson of this level // 2.4: take into concern if there are different levels in one row // 2.4: better jumpMark more satisfying // 2.5: set jumpMark to the crown-div, so it moves the old lessons out of view // 2.5: jumpMark for Crown 3 does not work???? // 2.6: fix if jumpMark is the very first lesson // 2.7: should work now without plus button // 2.8: auto-scroll if you want, set it below at const AUTO_SCROLL // 2.8.1: what happens, if some crowns do not exist, E.g. lessons level 4 and lessons level 2, but no lessons level 3 available // I suppose it's just set to the lower level, in this example level 2. // yeah, of course it broke the script ... but not any longer // TODO: clean up the code, move selectors to constants // ok, I had a flash of genius ... I just could match the very first lesson of a level and go up one row for the jump target. // don't know for sure, well maybe because level 0 is not matchable with the given class. // Well if nothing works any more I do a rewrite, but for now, it works good enough not to spend any more time with it ;) // ok genius! if you start with the first element you have to sort out all the lessons which do not level until level 5 // not worth the time // TODO: fix bug clickable before jumpMark ready // I don't know why it happens and I don't know what to do against // great, it happens, when you reload a page with a target in the url, so it's mostly a developer problem nothing more // TODO: highlight lesson by mouseover / mousein / pointerin / focus event // elem.dispatchEvent(event) // this is the 20 % which take me 80 % of the time // could be silly thinking as I got a proper positioning in 2.5 // false or level to go to // E.g. if you want to bring every lesson to level 2 // AUTO_SCROLL = 2 // options: false, 0, 1, 2, 3, 4 const AUTO_SCROLL = false; new MutationObserver(checkJumpMark).observe(document.body, { childList: true, subtree: true }); window.onload = function () { //let lessons = document.querySelectorAll('img[src="//d35aaqx5ub95lt.cloudfront.net/images/b3ede3d53c932ee30d981064671c8032.svg"]'); //lessons[lessons.length-1].scrollIntoView(); //document.body.addEventListener('pageshow', addJumpMarks()); //addJumpMarks(); }; function checkJumpMark() { if (document.querySelectorAll('.GkDDe').length!=0) { if (document.querySelector('#jumpMark')===null) { if (document.querySelector('._3yqw1')!=null) addJumpMarks(); } } } function getAnchorElement(elem) { // last lesson element let lesson = elem[elem.length-1].parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode; let row; // if it is not the last lesson in the row, anchor has to be on the previous row // means the lesson you want to go to is in this row // it it is the last lesson, anchor has to be in the same row to focus on the following row // means the lesson you want to go to is in the next row if(lesson.nextSibling) { row = lesson.parentNode.previousSibling; } else { row = lesson.parentNode; } // sometimes the previous row is a divider if (!row.querySelector('[alt="crown"]')) { row = row.previousSibling; } // select the crown image as anchor element return row.querySelector('[alt="crown"]'); } function addJumpMarks() { // let notDone = document.querySelectorAll('img[src="//d35aaqx5ub95lt.cloudfront.net/images/b3ede3d53c932ee30d981064671c8032.svg"]'); // notDone[notDone.length-1].id = 'notDone'; let firstLesson = document.querySelector('[data-test="skill-tree"]').querySelector('[alt="crown"]'); let level = document.querySelectorAll('.GkDDe'); let level1 = []; let level2 = []; let level3 = []; let level4 = []; let level5 = []; let levelMissing = []; for (let i=0;i<level.length;i++) { switch(level[i].innerHTML) { case '1': level1.push(level[i]); // if there is no level 2 the first level 1 should be selected for becoming 2 //if(level2[0]===null) level2[0] = level[i]; break; case '2': level2.push(level[i]); //if(level3[0]===null) level3[0] = level[i]; break; case '3': level3.push(level[i]); //if(level4[0]===null) level4[0] = level[i]; break; case '4': level4.push(level[i]); //if(level5[0]===null) level5[0] = level[i]; break; case '5': //level5.push(level[i]); //if(level5[0]===null) level5[0] = level[i]; break; } } levelMissing = [null, level1.length, level2.length, level3.length, level4.length, level5.length]; // if some level inbetween is missing // E.g. level 3 is missing and we need it to anchor the first level 2 // so we replace level 3 with level 4 if (level5.length === 0) level5[0] = null; if (level4.length === 0) level4 = level5; if (level3.length === 0) level3 = level4; if (level2.length === 0) level2 = level3; if (level1.length === 0) level1 = level2; // the last element of the higher level is used to anchor the level below // E.g. First level 4 = last level 5 // if it doesn't exist, we have to the first element of the level below if(level5[0] === null && level4[0] !== null) level5[0] = level4[0]; if(level4[0] === null && level3[0] !== null) level4[0] = level3[0]; if(level3[0] === null && level2[0] !== null) level3[0] = level2[0]; if(level2[0] === null && level1[0] !== null) level2[0] = level1[0]; // freaky selecting the previous row // level1 is the anchor element for new lessons let anchor0 = null; if(level1[0] !== null) anchor0 = getAnchorElement(level1); // level2 is the anchor element for level 1 lessons let anchor1 = null; if(level2[0] !== null) anchor1 = getAnchorElement(level2); // level3 is the anchor element for level 2 lessons let anchor2 = null; if(level3[0] !== null) anchor2 = getAnchorElement(level3); // level4 is the anchor element for level 3 lessons let anchor3 = null; if(level4[0] !== null) anchor3 = getAnchorElement(level4); // level5 is the anchor element for level 4 lessons let anchor4 = null; if(level5[0] !== null) anchor4 = getAnchorElement(level5); // _3NYLT instead of _3yqw1 (plus button) let insertElement = document.querySelector('._3NYLT'); let jumpMark = document.createElement('div'); jumpMark.setAttribute('class','_3yqw1 np6Tv'); jumpMark.setAttribute('style','padding-top: 0.6rem; padding-right: 0.6rem; top: 148px;'); jumpMark.innerHTML = '<div id="jumpMark"/>'; insertElement.insertAdjacentElement('beforebegin',jumpMark); console.group('Not overriding double id tags'); if(anchor0 !== null) { // last level 1 element get the id = level0 for jumping to new lessons let id = 'level0'; anchor0.id = id; anchor0 === firstLesson? id = 'javascript:scroll(0,0);' : id = '#'+id; jumpMark.innerHTML += /* `<a href="#notDone"><img src="//d35aaqx5ub95lt.cloudfront.net/images/fafe27c9c1efa486f49f87a3d691a66e.svg"/></a> */ `<div class="_2-dXY _1swBH" style="font-size: 14.84px;"> <a href="${id}"> <img alt="crown" class="_18sNN" src="//d35aaqx5ub95lt.cloudfront.net/images/fafe27c9c1efa486f49f87a3d691a66e.svg"> </a> </div>`; } // jumpMarks only if there are corresponding levels if (levelMissing[1] > 0) jumpMark.innerHTML += prepareJumpMark(anchor1, 1, firstLesson); if (levelMissing[2] > 0) jumpMark.innerHTML += prepareJumpMark(anchor2, 2, firstLesson); if (levelMissing[3] > 0) jumpMark.innerHTML += prepareJumpMark(anchor3, 3, firstLesson); if (levelMissing[4] > 0) jumpMark.innerHTML += prepareJumpMark(anchor4, 4, firstLesson); /* if(anchor1 !== null) { // show the first level 1 lesson let id = 'level1'; // if there have two level the same jump mark, it uses the given one instead of overriding it if(anchor1.id !== '') { console.info('Id already exists'); console.info(anchor1.id); id = anchor1.id; } else { anchor1.id = id; } anchor1 === firstLesson? id = 'javascript:scroll(0,0);' : id = '#'+id; jumpMark.innerHTML += `<div class="_2-dXY _1swBH" style="font-size: 14.84px;"> <a href="${id}"> <img alt="crown" class="_18sNN" src="//d35aaqx5ub95lt.cloudfront.net/images/b3ede3d53c932ee30d981064671c8032.svg"> <div class="GkDDe" data-test="level-crown">1</div> </a> </div>`; } if(anchor2 !== null) { // show the first level 2 lesson let id = 'level2'; if(anchor2 !== '') { console.info('Id already exists'); console.info(anchor2.id); id = anchor2.id; } else { anchor2.id = id; } //level3[level3.length-1].id = 'level2'; anchor2 === firstLesson? id = 'javascript:scroll(0,0);' : id = '#'+id; jumpMark.innerHTML += `<div class="_2-dXY _1swBH" style="font-size: 14.84px;"> <a href="${id}"> <img alt="crown" class="_18sNN" src="//d35aaqx5ub95lt.cloudfront.net/images/b3ede3d53c932ee30d981064671c8032.svg"> <div class="GkDDe" data-test="level-crown">2</div> </a> </div>`; } if(anchor3 !== null) { // show the first level 3 lesson let id = 'level3'; if(anchor3.id !== '') { console.info('Id already exists'); console.info(anchor3.id); id = anchor3.id; } else { anchor3.id = id; } //level4[level4.length-1].id = 'level3'; anchor3 === firstLesson? id = 'scroll(0,0)' : id = '#'+id; jumpMark.innerHTML += `<div class="_2-dXY _1swBH" style="font-size: 14.84px;"> <a href="${id}"> <img alt="crown" class="_18sNN" src="//d35aaqx5ub95lt.cloudfront.net/images/b3ede3d53c932ee30d981064671c8032.svg"> <div class="GkDDe" data-test="level-crown">3</div> </a> </div>`; } if(anchor4 !== null) { // show the first level 4 lesson let id = 'level4'; if(anchor4.id !== '') { console.info('Id already exists'); console.info(anchor4.id); id = anchor4.id; } else { anchor4.id = id; } //level5[level5.length-1].id = 'level4'; anchor4 === firstLesson? id = 'javascript:scroll(0,0);' : id = '#'+id; jumpMark.innerHTML += `<div class="_2-dXY _1swBH" style="font-size: 14.84px;"> <a href="${id}"> <img alt="crown" class="_18sNN" src="//d35aaqx5ub95lt.cloudfront.net/images/b3ede3d53c932ee30d981064671c8032.svg"> <div class="GkDDe" data-test="level-crown">4</div> </a> </div>`; } */ console.groupEnd(); // beforebegin instead of afterend plus button //insertElement.insertAdjacentElement('beforebegin',jumpMark); if (AUTO_SCROLL) document.getElementById('level' + AUTO_SCROLL).scrollIntoView(); } function prepareJumpMark(anchor, number, firstLesson) { let innerHTML = ''; if(anchor !== null) { // show the first level 'number' lesson let id = 'level' + number; // if there have two level the same jump mark, it uses the given one instead of overriding it if(anchor.id !== '') { console.info('Id already exists'); console.info(anchor.id); id = anchor.id; } else { anchor.id = id; } anchor === firstLesson? id = 'javascript:scroll(0,0);' : id = '#'+id; innerHTML = `<div class="_2-dXY _1swBH" style="font-size: 14.84px;"> <a href="${id}"> <img alt="crown" class="_18sNN" src="//d35aaqx5ub95lt.cloudfront.net/images/b3ede3d53c932ee30d981064671c8032.svg"> <div class="GkDDe" data-test="level-crown">${number}</div> </a> </div>`; } return innerHTML; }