// ==UserScript== // @name Duolingo LevelJumper // @description Provides jump buttons to the next lesson for leveling up (based on minirock / oltodosel autoScroller). // @version 2.10 // @namespace esh // @match https://*.duolingo.com/* // @downloadURL none // ==/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 // 2.9: changed from selecting the last element from the previous level to the first element from the first level and got rid of having maxed out lessons in view // 2.10: added broken levels // TODO: clean up the code, move selectors to constants // TODO: parse over url // Tampermonkey access variables // TODO: fix bug clickable before jumpMark ready // not worth the time // great, it happens, when you reload a page with a target in the url, so it's mostly a developer problem nothing more // can fix it with location.url or something // 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) { // if the element is the first in the tree if (elem[0] === document.querySelector('[data-test="skill-tree"]').querySelector('[alt="crown"]')) { return elem[0]; } else if (elem[0] === undefined) { return null; } else { // first lesson element let lesson = elem[0].parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode; let row = lesson.parentNode.previousSibling; // 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 level0 = document.querySelectorAll('img[src="//d35aaqx5ub95lt.cloudfront.net/images/fafe27c9c1efa486f49f87a3d691a66e.svg"]'); let firstLesson = document.querySelector('[data-test="skill-tree"]').querySelector('[alt="crown"]'); let level = document.querySelectorAll('[data-test="level-crown"]'); let level1 = []; let level2 = []; let level3 = []; let level4 = []; let level5 = []; let levelMissing = []; for (let i=0;i */ `
crown
`; } // jumpMarks only if there are corresponding levels if (anchor1 !== null) jumpMark.innerHTML += prepareJumpMark(anchor1, 1, firstLesson); if (anchor2 !== null) jumpMark.innerHTML += prepareJumpMark(anchor2, 2, firstLesson); if (anchor3 !== null) jumpMark.innerHTML += prepareJumpMark(anchor3, 3, firstLesson); if (anchor4 !== null) jumpMark.innerHTML += prepareJumpMark(anchor4, 4, firstLesson); if (anchor5 !== null) jumpMark.innerHTML += prepareJumpMark(anchor5, 5, firstLesson); console.groupEnd(); // beforebegin instead of afterend plus button insertElement.insertAdjacentElement('beforebegin',jumpMark); if (AUTO_SCROLL) document.getElementById('level' + AUTO_SCROLL).scrollIntoView(); } // returns true if element has the highest level function isMaxedOut(element) { // TODO move to constant if (element.parentNode.parentNode.parentNode.querySelector('[href="//d35aaqx5ub95lt.cloudfront.net/images/9dc5f133240809ea530649bca27c7eca.svg"]')) { //console.debug(element); return true; } else { return false; } } // returns true if element is broken function isBroken(element) { // TODO move to constant if (element.parentNode.parentNode.previousSibling.querySelector('._1m7gz')) { return true; } else { return false; } } 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; if (number !== 5) { innerHTML = `
crown
${number}
`; } else { innerHTML = `
crown
`; } } return innerHTML; }