The following code is aimed to change the innerHTML of an element (myNote
) to the value of another element (myInput
), each time the Return key is pressed:
// ==UserScript==
// @name Duolingo
// @include *://duolingo.com/*
// @include *://www.duolingo.com/*
// ==/UserScript==
setTimeout(()=>{
let myInput = document.querySelector('._7q434._1qCW5._2fPEB._3_NyK._1Juqt._3WbPm');
let myNote = document.createElement('div');
document.body.appendChild(myNote);
myNote.setAttribute("style", "position: fixed; bottom: 0; right: 0; display: block; width: 400px; height: 400px; background: orange; color: #000");
myInput.addEventListener('keydown', (k)=>{
if ( k.keyCode === 13 ) {
myNote.innerHTML += myInput.value + '<br>';
}
});
}, 2000);
I run the code in Greasemonkey on duolingo.com (a site for learning mind2mind languages like French), in textual question sessions like "translate this sentence".
The code's purpose is to create a small orange-background box containing the inputs I already tried in Duolingo questions, given that Duolingo doesn't save these.
With the script, I could save them and later use them if I retake a language question as it saves me some time retyping most of a sentence.
The code fails because the innerHTML
is changed only once. If the value changes again and I repress Return, nothing will happen.
Reproduce by using the code in Greasemonkey (or a similar program) on duolingo.com.
Why would the innerHTML
be changed only once? Given the addEventListener
listens all the time, why it'll work only once?
Using return
for the change or adding return false
didn't help.
Maybe another approach of adding a new element containing the value each pressing is needed.
Mobius, this is the code I used, which didn't work:
setTimeout(()=>{
window.myCss =`.note {position: fixed; bottom: 0; right: 0; width: 400px; height: 400px; background: orange}`;
style = document.createElement("style");
style.type = "text/css";
style.styleSheet ? style.styleSheet.cssText = myCss : style.appendChild(document.createTextNode(myCss) );
head = document.head || document.getElementsByTagName("head")[0];
head.appendChild(style);
let note = document.createElement('div');
note.classList.add('note');
document.querySelector('body').appendChild(note);
let savedValue;
document.addEventListener('keydown', (e)=>{
let target = e.target;
if (target.nodeName === 'textarea') {
savedValue = e.target.value;
}
});
if (k.keyCode === 13) {
setTimeout(()=>{
document.querySelector('textarea').value = savedValue;
}, 100);
}
}, 2500);
I didn't have any error in console.
I tried to read in the article and I understand that in the original example I babbled instead of captured (and I should capture any handler by going from the html
element to my textarea
element each time anew and capture it's handler, but I couldn't see how to improve the code from that. Maybe I didn't read good as I'm in a bad mood right now...
The code fails (after removing the typo) because it adds the event listener to a textarea that the webpage overwrites with every new question. When the textarea is overwritten, it destroys the previous event listener that you added. It also orphans the value of myInput
in that code.
Use a smarter event listener on a persistent container or the whole body. And, refresh myInput
as needed.
There are other issues:
._7q434._1qCW5._2fPEB._3_NyK._1Juqt._3WbPm
is an exceptionally brittle selector and will break the next time the website code changes. It also already doesn't work on some pages I saw.
textarea[data-test]
will probably work. Or even textarea
might suffice given the rest of the code.
setTimeout
is a brittle way to wait for elements and is already causing you grief. Use waitForKeyElements
, or MutationObserver
or similar instead. But in your case, since you are waiting for user interaction, you probably do not need any other delay mechanism.
Putting that all together, your userscript would be something like:
// ==UserScript==
// @name Duolingo, answer text recycler
// @match *://*.duolingo.com/*
// @run-at document-idle
// ==/UserScript==
let myNote = document.createElement ('div');
document.body.appendChild (myNote);
myNote.setAttribute (
"style",
"position: fixed; bottom: 0; right: 0; width: 400px; height: 400px; background: orange; overflow: auto;"
);
document.addEventListener ('keydown', (zEvent) => {
if (zEvent.keyCode === 13) {
if (zEvent.target.nodeName === 'TEXTAREA') {
let myInput = document.querySelector ('textarea[data-test]');
if (myInput) {
myNote.innerHTML += myInput.value + '<br>';
}
}
}
} );