Giter Club home page Giter Club logo

duo-strength's Introduction

Duo Strength

Duo Strength is a browser extension to improve the user interface Duolingo language learning platform. At its core it adds a visual indication of hidden and removed details into your language tree and Duolingo interface itself, in particular the strength of each individual skill. Inspired by the user profile pages on the duome unofficial streak hall of fame website, Duo Strength gives a way to view much of this information and more without needed to leave Duolingo or have two pages open.

Please see the duome.eu forum post for a replacement to the shutdown official forums.

Main features:

  • Strength bars added under each skill in the tree
  • Quick access lists added at the top of the tree summarising skills that need attention
  • Breakdowns of XP and Crowns progress with level up date predictions
  • Addition of optional listen and translate questions in lessons through obscuring question text
  • Ability to customise and toggle all features to your needs

Download

Available to add to Chrome from: Duo Strength in the Chrome Web Store

Available to add to Firefox and Firefox Android from: Duo Strength in the Firefox Add-ons Store

Available to add to Opera from: Duo Strength in Opera Addons

License

Duo Strength is licensed under MIT, see LICENSE for more details.

Screenshots of Duo Strength in use:

Top of Tree Summary and Strength Bars under Skills

Top of Tree SummaryStrength Bars

Mark Skills as Mastered

Mastered Skill

Jump to Skill Popout from Top of Tree Summary Buttons

Top of Tree Summary Link Popout Button

Additional Crowns & XP Information and Coloured Flag Borders to Show Tree Level

Crowns InfoXP InfoLanguage Change Flag Borders

Lanuages Info and Total Strength Box

Languages Info BoxTotal Strength Box

Buttons to Practise a Skill, Redo a Passed Checkpoint and Show Words List

Practise ButtonRedo CheckpointsWords List Button

Customise Features in the Options Popup

Options Page

duo-strength's People

Contributors

lauyuen avatar toransharma avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

duo-strength's Issues

suggestion : play audio keyboard shortcut

to save on mouse clicks and movement please add (if possible) a shortcuts to replay audio.
Some sentences are too long to remember and in others some words have poor pronunciation, so i keep re-playing those audios
for example Left Shift for normal speed and Right Shift for slow playback
Thanks
Screen Shot 2020-01-31 at 17 00 20
Screen Shot 2020-01-31 at 16 58 58

Strength bars not showing in EN>ES tree due to change in bonus skills id

Hi. Thank you for the update from yesterday. However, it seems that there still some problems. In my EN>ES tree, I don't see the strength bars. I did some digging, and it seems that it is (again) connected to a change of the ID of bonus skills.
This line fixed the problem for me: const BONUS_SKILL_DIVIDER = "_23P6X";

Duo-Strength version: 1.2.10
Browser: Vivaldi (basically Chrome)

This is the full log from the console:

duoStrength.js:711 Uncaught (in promise) TypeError: Cannot read property '0' of undefined
at addStrengths (duoStrength.js:711)
at getStrengths (duoStrength.js:2020)
at handleDataResponse (duoStrength.js:2205)
at duoStrength.js:2235
at requestResponseMutationHandle (duoStrength.js:2133)
at MutationObserver.requestResponseHelper (duoStrength.js:2092)

Suggestion: Link Show/Hide Button and Hotkey

Screen Shot 2020-04-25 at 12 28 39

Suggestion: Link Show/Hide Button and Hotkey & Make 2nd pressing of hotkey to hide text again
Screen Shot 2020-04-25 at 12 34 30
After i use hotkey to unhide the text it is strange to see that button behavior did not change
Screen Shot 2020-04-25 at 12 34 16

I understand that button sets a global behavior, while hotkey a local one, so call it a feature and explain it in the FAQ :)

Suggestion: Add language TTL

I think displaying the info in this dialog (time to complete & course completion percentage)
image
Will be pretty awesome, as it's a huge motivational!
:)

hide text - partly not working

Uncaught TypeError: Cannot read property 'getAttribute' of null
at hideTranslationText (duoStrength.js:2268)
at MutationObserver.childListMutationHandle (duoStrength.js:2591)

sometimes button to hide text is not shown and text is not hidden

skills that need strengthening: practice or next lesson

Hi
When i do "skill strengthening" from the duome.eu website it goes to practise i.e. https://www.duolingo.com/skill/es/Pronouns/practice
If i do skill strengthening in your extension it goes to the next lesson in the skill (unless it is L5) i.e. https://www.duolingo.com/skill/es/Pronouns

This may or may not be desired function. i.e. if you wish to keep your language tree (skill) at a particular level.

It was suggested on the forum tha practice exercises at L4 are more difficult that at L5. Don't remember if it was about individual skill practice or global (whole tree)

Bug? strength bar not shown for En<>Es trees

For whatever reason strength bar and XP info are not shown for En<>Es trees , as well as Lists ( cracked, strengthening)

Screen Shot 2020-02-18 at 13 10 36

disabling strength bar will show previously missing data

Screen Shot 2020-02-18 at 13 12 24

This behavior does not affect other trees that i have

Visual suggestion for the strengh bars

As I have also my own extension (still on developing), I added one extra style above your extension when my extension is running

Please try to add this css line to head > style and see if you like it.

.strengthBarHolder > div:last-child { position: absolute; left: 37%; width: 25% !important; color: #fff; font-size: 0.8em;}

Is just a visual suggestion, not a issue

Regards,
Carlos

Navigation bar UI change causes DuoStrength not to load

Some, if not all users have been moved to the new white navigation top bar UI. The current methods for selecting the topBar now fail due to restructuring of the tree. New UI looks like:

The current page HTML looks like:

May be other changes that need fixing.

TOP_BAR variable used without definition

I get the error that getElementsByClassName is not defined. So where is this called?
On line 4382 of duoStrength.js:
let numNavButtons = topBarDiv.getElementsByClassName(NAVIGATION_BUTTON).length;
So where is topBarDiv defined? On line 4378:
topBarDiv = rootChild.getElementsByClassName(TOP_BAR)[0];
So where is TOP_BAR defined? It is never defined. Same with NAVIGATION_BUTTON, but that error doesn't occur because the previous error halts execution.
This breaks the extension.

Duostrength for crown branch

Hi again :-D

I can't upload, edit, fork and pull, or whatever in your branch for crown, I updated the js file with the last changes and add a small tweak. I put the value for example 145/330 inside the crown image. Seems nicer
The "Your tree is at Level 0" I think is not working well, the array with the loop just loops ones and leave, giving level tree=0.

You can contact me if you wish via email by my username of github plus @gmail.com and I can upload the files from there.

Regards,
Carlos

const GOLD = "rgb(248, 176, 45)";
const RED = "rgb(219, 62, 65)";
var languageCode = "";
var languageCodeChanged = false;
var language = "";
var languageChanged = false;
var languageLogo;


var username = "";
var userData = Object();
var newUIVersion = false;
var numBonusSkillsInTree = 0;

var rootElem;
var dataReactRoot;
var topBarDiv;
var topBarMobilePractice;

var onMainPage;

function resetLanguageFlags()
{
	// reset to be called after finished successfully displaying everything.
	// need to be ready for a change so we reset them back to false.
	languageChanged = false;
	languageCodeChanged = false;
	numBonusSkillsInTree = 0;
}

function removeStrengthBars()
{
	var bars = document.getElementsByClassName("strengthBarHolder");
	for (var bar of bars)
	{
		bar.parentNode.removeChild(bar);
	}
}

function removeNeedsStrengtheningBox()
{
	var strengthenBox = document.getElementById("strengthenBox");
	if(strengthenBox != null) // could be null if changed from old to new UI and the topOfTree div gets removed.
	{
		strengthenBox.parentNode.removeChild(strengthenBox);
	}
}

function addStrengths(strengths) // Adds strength bars and percentages under each skill in the tree.
{
	/*
		The structure of skill tree is as follows:
		<div class="_2GJb6"> 												<-- container for row of skills, has classes _1H-7I and _1--zr if bonus skill row
			<a class="Af4up" href="javascript:;"> 							<-- container for individuale skill
				<div class="_2albn">
					<div>														<-- possibly new container as of 2019-03-01 holds skill icon and progress ring
						<div class="_3zkuO _39IKr">     						<-- progress ring container
							<div class="_2xGPj">								<-- progress ring container
								<svg>...</svg>          						<-- progress ring svg
							</div>
						</div>
						<span class="_1z_vo _3hKMG ewiWc _2vstG">				<-- skill icon background
							<span class="..."></span>							<-- skill icon
							<div clas ="_26l3y">...</div>						<-- skill crowns logo and number
						</span>
					</div>
					<div>														<-- another possibly new container as of 2019-03-01 holds skill name
						####################################################	<-- Strength Bar to be inserted here
						<span class="_378Tf _3qO9M _33VdW">Skill Name</span>	<-- Skill name
					</div>

					####### when skill clicked on new div below is appended ##########
					<div class="_2EYQL _2HujR _1ZY-H gqrCU ewiWc">				<-- popup box backgorund
						<div>...</div>											<-- popup box info container
						::after													<-- popup box 'speach bubble' style arrow at top
					</div>
				</div>
			</a>
		</div>
	*/

	var skillElements = document.getElementsByClassName('Af4up'); // Af4up is class of skill containing element, may change.
	var skills = Array();
	/*
		Each element of skills array will be an array with the following information:
		0:	skill icon element (unused currently).
		1:	skill name element.
		2:	skill strength between 0 and 1.0.
	*/
	var bonusElementsCount = 0;
	for (var i=0; i<skillElements.length; i++)
	{

		 var elementContents = [
		 	skillElements[i].childNodes[0].childNodes[0],
		 	skillElements[i].childNodes[0].childNodes[1].getElementsByClassName("_33VdW")[0]
		 ];
		
		/* old way of finding name element before new containers

		// name is a span element, normally it is the last element but if the skill is clicked then a new div is created with the start lesson button etc below the name plate. So need to find the correct span element.
		for (var spanElement of Array.from(skillElements[i].childNodes[0].getElementsByTagName('span')))
		{
			if (spanElement.parentNode == elementContents[0].parentNode)
			{
				elementContents.push(spanElement);
			}
		}
		*/


		if (skillElements[i].parentNode.classList.contains("_1H-7I") || skillElements[i].parentNode.classList.contains("_1--zr"))
		{
			// these skill elements are in the bonus skill section.
			elementContents.push(strengths[1][bonusElementsCount]);

			bonusElementsCount ++;

			skills.push(elementContents);
		} else
		{
			// Normal skill
			elementContents.push(strengths[0][i - bonusElementsCount]);
			
			skills.push(elementContents);
		}
	}

	numBonusSkillsInTree += bonusElementsCount; // update number of bonus elements that were in the tree for use in strengthenBox.
	
	var numBarsAdded = 0
	
	for (var i = 0; i< skills.length; i++)
	{
		var iconElement = skills[i][0];
		var nameElement = skills[i][1];
		var name = nameElement.innerHTML;
		var strength = skills[i][2]*1.0;
		
		if(document.getElementsByClassName("strengthBarHolder").length == numBarsAdded) // if we have only the number of bars added this time round, keep adding new ones.
		{
			var strengthBarHolder = document.createElement("div");
			strengthBarHolder.className = "strengthBarHolder";
			strengthBarHolder.style['width'] = "100%";
			strengthBarHolder.style['padding'] = "0 5%";
			
			nameElement.parentNode.insertBefore(strengthBarHolder, nameElement);
			
			var strengthBar = document.createElement("div");
			strengthBar.className = "strengthBar";
			strengthBar.id = name + "StrengthBar";
			strengthBar.style['display'] = "inline-block";
			strengthBar.style['width'] = (strength*75)+"%";
			strengthBar.style['height'] = "1em";
			strengthBar.style['backgroundColor'] = (strength == 1.0 ? GOLD : RED);
			strengthBar.style['borderRadius'] = "0.5em";
			strengthBar.style['verticalAlign'] = "text-top";
			
			var strengthValue = document.createElement("div");
			strengthValue.className = "strengthValue";
			strengthValue.id = name + "StrengthValue";
			strengthValue.style['display'] = "inline-block";
			strengthValue.style['width'] = ((1-strength)*75+25)+"%";
			strengthValue.style['textAlign'] = "right";
			strengthValue.innerHTML = strength*100 + "%";
			
			strengthBarHolder.appendChild(strengthBar);
			strengthBarHolder.appendChild(strengthValue);
			
			numBarsAdded ++; // added a bar so increment counter.
			
		} else // we already have the elements made prerviously, just update their values.
		{
			var strengthBar = document.getElementById(name + "StrengthBar");
			strengthBar.style['width'] = (strength*75)+"%";
			strengthBar.style['backgroundColor'] = (strength == 1.0 ? GOLD : RED);
			
			var strengthValue = document.getElementById(name + "StrengthValue");
			strengthValue.style['width'] = ((1-strength)*75+25)+"%";
			strengthValue.innerHTML = strength*100 + "%";
		}
	}
}

function displayNeedsStrengthening(needsStrengthening) // adds clickable list of skills that need strengthening to top of the tree.
{
	/* Old version where newUI had Part headings. Also mAsUf no longer seems to be used.
	var topOfTree;
	if(newUIVersion)
	{
		topOfTree = document.getElementsByClassName('_2GJb6')[0]; // top of tree is first row which has part 1.
		topOfTree.childNodes[0].style['marginBottom'] = "1em"; // reduced margin between part 1 heading an strengthenBox;
	} else
	{
		// old UI version.
		if(document.getElementsByClassName('mAsUf').length != 0)
		{
			// mAsUf is class of the container element just above tree with language name and shop button, may change.
			topOfTree = document.getElementsByClassName('mAsUf')[0].childNodes[1];
		} else
		{
			// body hasn't loaded yet so element not there.
			setTimeout(displayNeedsStrengthening(needsStrengthening), 500);
			return false;
		}
	}
	*/

	// Found as of 2019-03-15 new UI version no longer has Part 1, Part 2 etc. headings
	// Trees seem to be consistant so longer need for newUIversion detection

	if(
			document.getElementsByClassName("i12-l").length != 0 &&
			document.getElementsByClassName("w8Lxd").length != 0 &&
			document.getElementsByClassName("_2GJb6").length != 0
		) // Has the tree loaded from a page change
	{
		var skillTree = document.getElementsByClassName("i12-l")[0];
		var topOfTree = document.getElementsByClassName("w8Lxd")[0];
		// or var topOfTree = skillTree.childNodes[0]
		var firstSkillRow = document.getElementsByClassName("_2GJb6")[0];
	}
	else
	{
		// body hasn't loaded yet so element not there, lets try again after a small wait, but only if we are still on the main page.
		if(onMainPage)
		{
			setTimeout(displayNeedsStrengthening(needsStrengthening), 500);
		}
		else
		{
			// swtiched away before we got a chance to try again.
		}
		return false;
	}
  
	var strengthenBox; // will be a div to hold list of skills that need strengthenening
	var needToAddBox = false;
	if (document.getElementById("strengthenBox") == null) // if we haven't made the box yet, make it
	{
		needToAddBox = true;
		strengthenBox = document.createElement("div");
		strengthenBox.id = "strengthenBox";
		strengthenBox.style['textAlign'] = "left";
		strengthenBox.style['marginBottom'] = "2em";
	}
	else
	{
		strengthenBox = document.getElementById("strengthenBox");
	}

	var numSkillsToBeStrengthened = needsStrengthening[0].length +needsStrengthening[1].length;

	strengthenBox.innerHTML = "The following " + numSkillsToBeStrengthened +
								((needsStrengthening[0].length + numBonusSkillsInTree != 1) ? " skills need": " skill needs") +
								" strengthening: <br/>";
	
	for (var i = 0; i < numSkillsToBeStrengthened - 1; i++)
	{
		if (i < needsStrengthening[0].length)
		{
			// index is in normal skill range
			strengthenBox.innerHTML += "<a href='/skill/" +
									languageCode + "/" +
									needsStrengthening[0][i]['url_title'] +
									((needsStrengthening[0][i]['progress_v3']['level'] == 5)? "/practice'>":"'>" ) + // 5 crown skill doesn't decay AFAIK so needless but included JIC.
									needsStrengthening[0][i]['title'] + "</a>, ";
		} else
		{
			// index has past normal skills so doing bonus skills now.
			bonusSkillIndex = i - needsStrengthening[0].length;
			strengthenBox.innerHTML += "<a href='/skill/" +
									languageCode + "/" +
									needsStrengthening[1][bonusSkillIndex]['url_title'] +
									((needsStrengthening[1][bonusSkillIndex]['progress_v3']['level'] == 1)? "/practice'>":"'>" ) + // 1 crown bonus skill does decay but is on practice not lessons.
									needsStrengthening[1][bonusSkillIndex]['title'] + "</a>, ";
		}
		
	}
	strengthenBox.innerHTML = strengthenBox.innerHTML.substring(0, strengthenBox.innerHTML.length - 2);
	strengthenBox.innerHTML += (numSkillsToBeStrengthened > 1) ? " & ": "";
	if(needsStrengthening[1].length > 0)
	{
		// last skill to be displayed is a bonus skill
		strengthenBox.innerHTML += "<a href='/skill/" +
										languageCode + "/" +
										needsStrengthening[1][needsStrengthening[1].length - 1]['url_title'] +
										((needsStrengthening[1][needsStrengthening[1].length - 1]['progress_v3']['level'] == 1)? "/practice'>":"'>" ) +
										needsStrengthening[1][needsStrengthening[1].length - 1]['title'] + "</a>";
	} else
	{
		// last skill to be displayed is a normal skill
		strengthenBox.innerHTML += "<a href='/skill/" +
										languageCode + "/" +
										needsStrengthening[0][needsStrengthening[0].length -1]['url_title'] +
										((needsStrengthening[0][needsStrengthening[0].length -1]['progress_v3']['level'] == 5)? "/practice'>":"'>" ) +
										needsStrengthening[0][needsStrengthening[0].length -1]['title'] + "</a>";
	}
	if(needToAddBox)
	{
		skillTree.insertBefore(strengthenBox, firstSkillRow);
	}
}

function displayCrownsBreakdown(crownLevelCount, maxCrownCount)
{
	/*
		Side bar HTML structure:
		<div class="_2_lzu">							<-- Side bar container div
			<div class="aFqnr _1E3L7">					<-- Crown Level Box container div
				<h2>Crown Level</h2>
				<div class="_1kQ6y">					<-- Crown image and count container div
					<img class="_2vQZX" src="..." />	<-- Crown image svg
					<div class="nh1S1">CROWNCOUNT</div>	<-- Crown count text
					###################################	<-- /Maximum crown count will be put here.
				</div>
				####################################### <-- Crown Level breakdown will be put here.
			</div>
			<div class="-X3R5 _2KDjt">...</div>			<-- Advert/ disable ad blocker
			<div class="_21w25 _1E3L7">...</div>		<-- Daily Goal, xp graph & practice button
			<div class="_2SCNP _1E3L7">...</div>		<-- Achievements
			<div class="a5SW0">...</div>				<-- Friends list
			<div class="a5SW0">...</div>				<-- Duolingo Social Media links
		</div>

		Note a5SW0 seems to correspond with clear background, and _1E3L7 with whtie background.
		_1E3L7 is in class name of main tree, which has a white background.
	*/

	var treeLevel = 0;
/*NEEDS IMPROVEMENT!!! treeLevel is 0  *********************************/
	var i = 0;
	while (crownLevelCount[i] == 0 && i < 6)
	{
		treeLevel++;
		i++;
	}
/****************************************************/

	var crownLevelContainer = document.getElementsByClassName('aFqnr _1E3L7')[0];
	var crownTotalContainer = crownLevelContainer.childNodes[1];

	var maximumCrownCountContainer = document.createElement("span");
	maximumCrownCountContainer.id = "maxCrowns";
	maximumCrownCountContainer.innerHTML = "/" + maxCrownCount;
/*
	maximumCrownCountContainer.style =	"position:absolute;"
									+ 	"top: 50%;"
									+	"right: 0;"
									+	"margin-top: 5px;"
									+	"font-size: 36px;"
									+	"font-weight: 700;"
									+	"transform: translateY(-50%);";
*/

	var breakdownContainer = document.createElement("div");
	breakdownContainer.id = "crownLevelBreakdownContainer";
	breakdownContainer.style = "margin-top: 1em; text-align: left;";

	var treeLevelContainer = document.createElement("div");
	treeLevelContainer.id = "treeLevel";
	treeLevelContainer.style = "display: inline-block";
	treeLevelContainer.innerHTML = treeLevel;

	var breakdownList = document.createElement("ul");

	var imgContainer = document.createElement("div");
	imgContainer.style = "position: relative;"
						+"vertical-align: middle;"
						+"display: inline-block;"
						+"height: 1.3em;"
						+"width: 1.3em;"
						+"top: -0.15em;";
	
	var levelContainer = document.createElement("div");
	levelContainer.style =	"position: absolute;"
						+	"top: 50%;"
						+   "left: 50%;"
						+   "transform: translateX(-50%) translateY(-50%);"
						+	"margin-top: 10%;"
						+	"z-index: 2;"
						+	"font-size: 0.75em;"
						+	"color: white;";

	var crownImg = document.createElement("img");
	crownImg['alt'] = "crown";
	// Class name _2PyWM used for other samll crowns on skills. Corresponds to height & width 100% and z-index 1.
	crownImg.style = "height: 100%; width: 100%; z-index: 1;";
	crownImg['src'] = "//d35aaqx5ub95lt.cloudfront.net/images/crown-small.svg";

	imgContainer.appendChild(crownImg);
	imgContainer.appendChild(levelContainer);

	if(document.getElementsByClassName("crownLevelItem").length == 0) // We haven't added the breakdown data yet, so lets add it.
	{
		for(var crownLevel = 0; crownLevel < crownLevelCount.length; crownLevel++)
		{
			var skillCount = crownLevelCount[crownLevel];
			var crownCount = skillCount * crownLevel;
		
			levelContainer.id = "crownLevel" + crownLevel + "Count";
			levelContainer.innerHTML = crownLevel;

			var breakdownListItem = document.createElement("li");
			breakdownListItem.className = "crownLevelItem";
			breakdownListItem.innerHTML = skillCount + " skill"+ ((skillCount == 1 )?"":"s") + " at ";

			breakdownListItem.appendChild(imgContainer);

			breakdownListItem.innerHTML += " = " + crownCount + " crown" + ((crownCount == 1 )?"":"s");

			breakdownList.appendChild(breakdownListItem);
		}
//lest define the font and the MAx level count
var crownTotalContainer = document.getElementsByClassName('nh1S1')[0];
crownTotalContainer.style.fontSize="22px";

		crownTotalContainer.appendChild(maximumCrownCountContainer);

		breakdownContainer.appendChild(document.createElement("p"))
		breakdownContainer.lastChild.style = "text-align: center;";
		breakdownContainer.lastChild.innerHTML = "Your tree is at Level ";
		breakdownContainer.lastChild.appendChild(treeLevelContainer);
		breakdownContainer.appendChild(breakdownList);
		crownLevelContainer.appendChild(breakdownContainer);
	}
	else // We have already added the breakdown data, just update it.
	{
		for(var crownLevel = 0; crownLevel < crownLevelCount.length; crownLevel++)
		{
			levelContainerElement = document.getElementById("crownLevel" + crownLevel + "Count");
			levelContainerElement.innerHTML = crownLevel;
		}

		document.getElementById("maxCrowns").innerHTML = "/" + maxCrownCount;
		document.getElementById("treeLevel").innerHTML = treeLevel;
	}
}

function httpGetAsync(url, responseHandler)
{
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function()
	{ 
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            responseHandler(xmlHttp.responseText);
    }
    xmlHttp.open("GET", url, true); // true for asynchronous 
	xmlHttp.send(null);
}

function getStrengths() // parses the data from duolingo.com/users/USERNAME and extracts strengths and skills that need strengthening
{
	/*
		Data comes formatted as such:
		{
			'language_data': {
				'es': {
					...
					skills			: [...],
					bonus_skills	: [...],
					...
				}
			}
		}
		each skill in either skills or bonus_skills has a number or properties including 'strength', 'title', 'url_title', 'coords_x', 'coords_y'.
	*/
	
	var strengths = [[],[]];	// will hold array of the strength values for each skill in tree in order top to bottom, left to right and array of strengths of bonus skills. values between 0 and 1.0 in 0.25 steps.
	var needsStrengthening = [[],[]]; // will hold the objects for the skills that have strength < 1.0 and the bonus skills that have strength < 1.0.
 	var crownLevelCount = Array(6).fill(0); // will hold number skills at each crown level, index 0 : crown 0 (not finished), index 1 : crown 1, etc.
	
	languageCode = Object.keys(userData['language_data'])[0]; // only one child of 'language_data', a code for active language.

	var skills = userData['language_data'][languageCode]['skills']; // skills appear to be inconsistantly ordered so need sorting for ease of use.
	var bonusSkills = userData['language_data'][languageCode]['bonus_skills'];
//var bonusSkills = userData['language_data'][Object.keys(userData['language_data'])[0]]['bonus_skills'];

	function sortSkills(skill1,skill2)
	{
		if (skill1['coords_y'] < skill2['coords_y']) // x above y give x
		{
			return -1;
		} else if (skill1['coords_y'] > skill2['coords_y'])// x below y give y
		{
			return 1;
		} else // x and y on same level
		{
			if (skill1['coords_x'] < skill2['coords_x']) // x to left of y give x
			{
				return -1;
			} else // x to right of y give y
			{
				return 1;
			}
		}
	}

	skills.sort(sortSkills);
	bonusSkills.sort(sortSkills);

	for (var skill of skills)
	{
		strengths[0].push(skill['strength']);
		
		if(skill['strength'] != 1 && skill['strength'] != 0)
		{
			//Add to needs strengthening if nog at 100% and not at 0% i.e. not started
			needsStrengthening[0].push(skill);
		}

		crownLevelCount[skill['progress_v3']['level']]++;
	}

	for (var bonusSkill of bonusSkills)
	{
		strengths[1].push(bonusSkill['strength']);
		if(bonusSkill['strength'] != 1 && bonusSkill['strength'] != 0)
		{
			//Add to needs strengthening if nog at 100% and not at 0% i.e. not started
			needsStrengthening[1].push(bonusSkill);
		}

		crownLevelCount[bonusSkill['progress_v3']['level']]++;
	}

	addStrengths(strengths); // call function to add these strengths under the skills
	
	if (needsStrengthening[0].length+needsStrengthening[1].length !=0)
	{
		displayNeedsStrengthening(needsStrengthening); // if there are skills needing to be strengthened, call function to display this list
	}

	displayCrownsBreakdown(crownLevelCount, skills.length*5 + bonusSkills.length); // call function to add breakdown of crown levels under crown total.

	// All done displaying what needs doing so let reset and get ready for another change.
	resetLanguageFlags();
}

function handleDataResponse(responseText)
{
	userData = JSON.parse(responseText); // store response text as JSON object.
	newDataLanguageCode = Object.keys(userData['language_data'])[0];
	if((!languageCodeChanged) && languageChanged && newDataLanguageCode == languageCode)
	{
		// languageCode hasn't been changed yet but we have changed langauge but the data isn't up to date yet.
		// so request the data again after a little wait, but only if still on the main page.
		if(onMainPage)
		{
			setTimeout(function() {httpGetAsync(encodeURI(window.location+"users/"+username), handleDataResponse);}, 100);
			//setTimeout(function() {httpGetAsync("/users/"+ username, handleDataResponse);}, 100);
		}
	}
	else {
		languageCodeChanged = true;
		getStrengths();	// actual processing of the data.
	}
}

function requestData() // requests data for actively logged in user.
{
	if (!(Object.keys(userData).length === 0 && userData.constructor === Object) && (!languageChanged))
	{
		// If there is already userData and not changing language, display current data while requesting new data.
		getStrengths(userData);
	}
	if(document.getElementsByClassName("_2R9gT").length != 0) // Check if there is a username element
	{
		username = document.getElementsByClassName("_2R9gT")[0].innerHTML;
		httpGetAsync(encodeURI(window.location+"users/"+username), handleDataResponse); // asks for data and async calls handle function when ready.
	} else
	{
		// user not logged in.
	}
}

function checkUIVersion(){
	if (document.getElementsByClassName('_1bcgw').length != 0)
	{
		// Seem to be using new version of tree with the pentagonal checkpoint nodes
		newUIVersion = true;
	} else
	{
		newUIVersion = false;
	}
}

// detect changes to class using mutation of attributes, may trigger more than necessary but it catches what we need.
var childListMutationHandle = function(mutationsList, observer)
{
	for (var mutation of mutationsList)
	{
		if(mutation.target == rootElem)
		{
			// root child list has changed so dataReactRoot has probably been replaced, lets redefine it.
			dataReactRoot = rootElem.childNodes[0];
		}
		if(dataReactRoot.childNodes[1].className ==  "_6t5Uh")
		{
			// Top bar div has class for main tree page or words page.
			topBarDiv = dataReactRoot.childNodes[1]
			languageChanged = false; // language hasn't changed this update
			init();
		}
	}
};

var classNameMutationHandle = function(mutationsList, observer)
{
	for (var mutation of mutationsList)
	{
		if(topBarDiv.className == "_6t5Uh" && document.getElementsByClassName("_2XW92").length == 0) // _6t5Uh means we are on body on main page or words page. no _2XW92 means not on words page. So we are on main page.
		{
			onMainPage = true;
			if (language != "")
			{
				// language has previously been set so not first time on home page.
				 if (language != document.getElementsByClassName("_3I51r _2OF7V")[0].childNodes[1].innerHTML)
				{
					// language has just changed so set flag to true
					languageChanged = true;
					language = document.getElementsByClassName("_3I51r _2OF7V")[0].childNodes[1].innerHTML;
					// as the language has just changed, need to wipe the slate clean so no old data is shown after change.
					removeStrengthBars();
					removeNeedsStrengtheningBox();
				} else
				{
					// language hasn't just changed set flag to false
					languageChanged = false;
				}
				checkUIVersion(); // here for case of switching language with different UI versions
				requestData(); // call on attribute change
			} else
			{
				//language had not been previously set so first time on homepage
				init();
			}
		}
		else
		{
			onMainPage = false;
		}
	}
};

var classNameObserver = new MutationObserver(classNameMutationHandle);
var childListObserver = new MutationObserver(childListMutationHandle);

function init()
{
	rootElem = document.getElementById("root"); // When logging in child list is changed.
	dataReactRoot = rootElem.childNodes[0]; // When entering or leaving a lesson children change there is a new body so need to detect that to know when to reload the bars.
	
	childListObserver.observe(rootElem,{childList: true});
	childListObserver.observe(dataReactRoot,{childList: true});
	
	if(dataReactRoot.childNodes.length == 1) // If there is only one child of dataReactRoot then we are on the log in page.
	{
		// On login page so cannot continue to turn rest of init process.
		onMainPage = false;
		return false;
	}

	var mainBodyElemIn3rd = !dataReactRoot.childNodes[1].classList.contains("_3MLiB") && dataReactRoot.childNodes[2].classList.contains("_3MLiB");
	// Main body container element has class _3MLiB. If in second place, there is no topbar Div, if it is in thrid place, then second should be topBarDiv.
	
	if(mainBodyElemIn3rd) // If main body element is in 3rd place then we are not in a lesson.
	{
		topBarDiv = dataReactRoot.childNodes[1];

		// topBarMobilePractice either has class _2XW92 or _3IyDY _1NQPL.
		if(topBarDiv.getElementsByClassName("_3IyDY _1NQPL").length != 0)
		{
			topBarMobilePractice = topBarDiv.getElementsByClassName("_3IyDY _1NQPL")[0]; // Class for when on homepage, store and labs
		}
		else if(topBarDiv.getElementsByClassName("_2XW92").length != 0)
		{
			topBarMobilePractice = topBarDiv.getElementsByClassName("_2XW92")[0]; // Class for when on words page
		}
		else
		{
			// Element not found or has a class we are not expecting. It should be the 2nd child of topBarDiv so lets just set it as that and hope for the best.
			topBarMobilePractice = topBarDiv.childNodes[1];
		}

		classNameObserver.observe(topBarDiv,{attributes: true}); // Observing to see if class of topBarDiv changes to tell if we have switched between main or words page and store or labs page.
		classNameObserver.observe(topBarMobilePractice,{attributes: true}); // Observing to see if class of topBarMobilePractice changes to tell if we have swtiched between main and words page.

		if(topBarDiv.className == "_6t5Uh" && document.getElementsByClassName("_2XW92").length == 0) // if we are on the homepage. _2XW92 is class of mobile view practice button when on words page. On home page it is _3IyDY _1NQPL.
		{
			onMainPage = true;
			if(languageLogo != document.getElementsByClassName("_3I51r _2OF7V")[0].childNodes[0])
			{
				languageLogo = document.getElementsByClassName("_3I51r _2OF7V")[0].childNodes[0];
				classNameObserver.observe(languageLogo,{attributes: true});
			}
			language = document.getElementsByClassName("_3I51r _2OF7V")[0].childNodes[1].innerHTML;
			checkUIVersion();
			requestData();
		}
		else if(topBarDiv.className == "_6t5Uh" && document.getElementsByClassName("_2XW92").length != 0) // if we are on the words page
		{
			// We are on the words page. Don't need to do anything further, we have set up the observer to see if we go back to the main page from here.
			onMainPage = false;
		}
	} else
	{
		// page we are on is most likely a lesson, and we got here from a link in the strengthenBox.
		onMainPage = false;
	}
}

document.body.onload = init(); // call function to start display sequence on first load

//observer.disconnet(); can't disconnect as always needed while page is loaded.

Not opening the lessons

When I use the extension, it will not let me start a new lesson a lot of the time. When I hover the mouse over the start lessons button it stays as the mouse rather than whiching to the hand and when I click on start lesson, nothing happens. When I disable the chrome extension it works fine again

Language change broken in v1.0.20

Detection of language changing is broken in v1.0.20. the title string has been changed and the setting of the language variable now doesn't change when switching languages.

The way this variable is set was always a bit dodgy since the new white top bar UI update. This will be changed as soon as possible in v1.0.21.

Fully Strengthened Tree Skill Suggestion

May I suggest you change the text
"All the skills that you have learnt so far are fully strengthened. The next skill to learn is: Descriptions 2"
to start 2nd sentence from the next line : i.e.
"All the skills that you have learnt so far are fully strengthened.
The next skill to learn is: Descriptions 2"

If anyone uses https://github.com/camiloaa/duolingotreeenhancer the message is not displayed properly
Screen Shot 2020-04-12 at 08 13 44

The other messages at least are displayed OK... Inside the DuolingoTreeEnhancer , but ok :)
Screen Shot 2020-04-12 at 08 15 10

Skill strengths not updating and not listed at top

Hey!
First of all I just wanted to say i love this extension and although simple, very helpful.

To my issue, on my German tree, 2 skills (Intro and Food 1) are both at 0% which leads me to the first issue. These 2 skills are not listed at the top of the page as skills which require strengthening. The second issue is that when I go to practice the skill and return back to the tree, the practiced skill is still on 0% and is not updated. Bellow I have provided a screenshot to show you what I mean. I hope you can fix this so that myself and others can use this extension to its full ability.

Thanks.

capture

V1.3.1 review

  1. add ver number to options title

Screen Shot 2020-04-13 at 10 24 46

  1. Strength bar & words & practice buttons some times loads ONLY after page RELOAD :( not a big thing but certainly it causes some confusion.

  2. Languages with 0 XP should not be displayed in language table. IMHO

  3. Flags border appeared only after a particular language was activated. I understand it's feature. May be it's time to make a FAQ page, so many features :))

  4. I did not know that bonus skills can crack

  5. I am disabling league by making my profile private - may be "grey-out" hide leagues in the options in such case?

  6. You did not added redo checkpoint & test-out to the shining owl :(

  7. Words are great ! have a look at this script. may be you can add translations too ? https://github.com/x-inkfish-x/DuolingoUserscripts/wiki/Userscript:-Skill-Vocabulary-Viewer

  8. Duolingo site is down or slow or acting strangely ... that it for now... I was not able to make extension work for Uk => En and don't see checkpoints prediction ... more on that later.

Cheers !

Sometimes the extension is not running

I am using Firefox (76.0.1). The extension is not working sometimes (after I refresh it).

In the console, I can not see any errors from Duo-Strength. Any ideas?

image

append is not a function

Hi,

Got one small issue in your last version 1.0.14
"append is not a function"

Find this:

if(needToAddBox)
{
topOfTree.append(strengthenBox);
}
with this:
if(needToAddBox)
{
topOfTree.appendChild(strengthenBox);
}

Regards

Site access

Can you modify site access to https://*.duolingo.com/* and \ or add https://preview.duolingo.com/*

FYI: preview is used for b-testing. apparently new version is released every Thursday and pushed to everyone on Monday. So far they don't tell us what are the new improvements :(

Spanish tree issues with new grammar skills

Using 1.3.34. My Spanish tree was recently upgraded with new grammar skills that have been put into place all over the tree. This seems to be throwing off Duo Strength - there are several issues.

  1. Incorrect list of skills that need strengthening. Please see the first screenshot. The tree highlights the skill "Shopping" as the one that is on 75% but it says that the "Pres Tense 1" needs strengthening. The other 2 skills on the list are pointing to correct skills this time. I think I've seen the same issue with "cracked skills" but I'm not sure.

Screenshot 2020-10-17 12 27 43

  1. The "words" bubble doesn't work correctly with these new grammar skills.

Screenshot 2020-10-17 12 28 13

Please let me know if you need more input from me or help with testing. I am not sure if these new grammar skills are being rolled out gradually or a part of an A/B test. They also seem to carry a number of new question types and overall I find them a bit annoying (too many click type questions, etc). I have already reported two issues to DL directly, perhaps they are not fully ironed out yet.

UI: additional icons (info) for languages

Hi Toran
On the forum over the years there were multiple suggestion to have somewhat different icons (flags) for the completed language trees and / or to show progress
https://forum.duolingo.com/comment/8335323
https://forum.duolingo.com/comment/9896339
https://forum.duolingo.com/comment/12853199
https://forum.duolingo.com/comment/4891839
Screen Shot 2020-01-30 at 10 39 00
See if you can implement something like that or add info similar to what is at duome now
Screen Shot 2020-01-30 at 10 39 15
all this info can be an overkill but a small icon (owl?) next to the the flag will be really cool
Screen Shot 2020-01-30 at 10 39 25
Screen Shot 2020-01-30 at 10 55 27

TopOfTree element removed - needs strengthening list / skill suggestion not displayed

The topOfTree (class name w8Lxd) element has been removed from at least some trees. The needs strengthening list and skill suggestions no longer have an element to be inserted into so are not displayed. This manifests for the skill suggestion as a maximum call stack size exceeded error due to repeated checked for the topOfTree element if it is not present and we are on the main page.

To be fixed in v1.1.6

Focus change in V1.3.3 :(

After i finish a lesson or practise of a skill the focus is changed to the top of the tree :( Please keep it on the current skill.
Thanks

PS option "Focus First Skill in Lists" was disabled

The issue is NOT happening when strength bars are enabled :)

I got error on console time to time on last version

Hi again,

I have message errors to time to time with duoStrength.js

And I think I know why but didnt search quite one solution

at line 130 you have this

for (var i = 0; i< skills.length; i++)
{
	var iconElement = skills[i][0];
	var nameElement = skills[i][1];
	var name = nameElement.innerHTML;
	var strength = skills[i][2]*1.0;

more bellow in the ELSE condition at line 170

	} else // we already have the elements made prerviously, just update their values.
	{
		var strengthBar = document.getElementById(name + "StrengthBar");
		strengthBar.style['width'] = (strength*75)+"%";

Explanation:
If the elements are already made

the "name" at line 169 document.getElementById(name + "StrengthBar"); is using this variable "var name = nameElement.innerHTML;" from line 134

nameElement.innerHTML is not a Valid id to use at line 169 with getElementById, is a div html element.

Perhaps adding new variable with "var nameId = nameElement.id" around line 135 and then in the ELSE condition to use this document.getElementById(nameId + "StrengthBar"); will solve the error.

I hope I explained well this issue.
In resume, happens when the strenghts are made more than one(in the update values).

To replicate this, run getStrengths() in your console in the "Duo Strengh" namespace after the page is loaded

Regards

Strength bars not showing in Chrome desktop or Firefox Android.

Desktop: when I first load the web page, I do not see any strength bars. After I test out of a skill, they do appear (and will continue to do so even if I load Duolingo.com in a new tab, until I exit Chrome entirely).

Android: have not yet fully tested behaviour.

"Show Translation Text" not working

Text does not hide anymore, not event the button to toggle hide/unhide appears on the screen.
I have to check/uncheck any option of the Show Translation Text tree for it to work, but then for the next text it stops working again

I have version v1.3.15 and happens on mozilla (77.0.1) and edge (83.0.478.54) in Windows 10

Crowns in table view & XP in pop-up view & DND mode suggestions

Please consider adding option for alternative displays of information in table & pop-up respectively.

  1. For crowns in a table - when skills are at all 5 crowns + bonus and graph is displayed it is inconvenient to scroll the pop-up. Table view will be more suited. PS: Now I come to think this scrolling can be due to over zoom in my browser

  2. For xp in a pop-up under streak (flame) as you did initially. When you are at the middle or bottom of the tree and want to check how much XP you obtained - it's a bother to scroll up the page to check - a pop-up view will be more suited.

  3. Do Not Disturb mode : Options to hide XP Progress table, Next Achievements table, Friends Table and others

Fully Strengthened Tree Skill Suggestion Option - Unclear Behavior

Please consider adding help or information to user manual as of how
Fully Strengthened Tree Skill Suggestion - Skill at the Current Crown Level
Option is supposed to work
My tree has skills at L5, L4 & L3. what is Current Crown Level ?

see image below. clearly tree is NOT fully strengthened
Screen Shot 2020-01-29 at 17 08 44
and with Needs Strengthening Option enabled
Screen Shot 2020-01-29 at 17 10 40

Please add filter (5 check box options) to select Level of the skills that need strengthening : for example if i want to focus on skills at L3 only and ignore skills at other Levels

The owl lost its functions ??

Was that intended?
I don't see "retry checkpoint challenge"
I don't see "retry crown level 1 test out"
And the owl is so sad :-(

Spanish tree - strengthen link not working for grammar skills

I think this is a follow-up to the fixes in issue #111 - the link for the new grammar skills that need strengthening does not seem to work and gets redirected back to the main page at (duolingo.com/learn).

I believe this may be because it lacks the /practice suffix? Right now my "Pres Tense 1" skill is marked and the link points to https://www.duolingo.com/skill/es/Pres-Tense-1 whereas regular skills seem to include the /practice at the end (after the skill name).

duolingo_practice_link

It stopped working since yesterday

Uncaught (in promise) TypeError: Cannot read property 'href' of null
at init (duoStrength.js:4398)
at document.body.onload (duoStrength.js:4520)

UPD: Mysteriously extension works after i visit setting page .

Page focus & Strength bar

with disabled strength bar page stays focused on the last lesson completed
Screen Shot 2020-02-25 at 11 36 07

with enabled strength bar page focus shifts
Screen Shot 2020-02-25 at 11 31 56

it is uncomfortable to scroll down to the lesson in question every time

PS. I've seen skills crack at lower levels too. You may want to add sorting by crown level in the cracked list option.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.