New hotkeys
- Move the cursor to the reference input (`): it directly moves the keyboard cursor to the text field in the widget.
- Open Dictionary (d or f): it works when multiple words are selected
- Open Translator (t): it works when multiple words are selected
- Copy selected text (c): copy selected text in a clipboard (I use it when using LLM)
Of course, you can change the hotkeys in your favor.
The Open Dictionary
key clicks the first dictionary and the Open Translator
selects the last. So you need to set them properly
You can change the order of the dicts here.
// ==UserScript==
// @name LingQ Addon
// @description Custom embedded player, and sort course automatically.
// @match https://www.lingq.com/*/learn/*/web/reader/*
// @match https://www.lingq.com/*/learn/*/web/library
// @match https://www.lingq.com/*/learn/*/web/library/course/*
// @version 1.5
// ==/UserScript==
(function () {
"use strict";
// Style settings
var style = document.createElement("style");
style.textContent = `
:root {
--width_small: 440px;
--height_small: 260px;
--right_pos: 0.5%;
--bottom_pos: 5.5%;
--width_big: calc(100vw - 424px - 40px);
--height_big: 470px;
--font_size: 1.1rem;
--font_color: #e0e0e0;
--line_height: 1.7;
--lingq_background: hsl(41 43% 30% / 0.7);
--lingq_border: hsl(43 99% 64% / 0.3);
--lingq_border_learned: hsl(43 99% 64% / 0.5);
--blue_border: hsl(213 99% 64% / 0.5);
--is_playing_underline: #ffffff;
--grid-layout: 1fr var(--height_big) 80px;
--video_margin: 0 0 90px 10px !important;
--article_height: calc(var(--app-height) - 165px - var(--height_big));
}
.main-wrapper {
padding-top: calc(var(--spacing)* 12) !important;
}
#main-nav .navbar, #main-nav .navbar-brand {
min-height: 2.75rem !important;
}
.main-header svg {
width: 20px !important;
height: 20px !important;
}
#lesson-reader {
grid-template-rows: var(--grid-layout);
overflow-y: hidden;
}
.sentence-text {
height: var(--article_height) !important;
}
.reader-container-wrapper {
height: 100% !important;
}
/*video viewer*/
.main-footer.has-video {
grid-area: 3 / 1 / 3 / 1 !important;
align-self: end;
}
.widget-area {
grid-area: 2 / 2 / -1 / 2 !important;
}
.main-content {
grid-template-rows: 25px 1fr !important;
overflow: hidden;
grid-area: 1 / 1 / 1 / -1 !important;
align-items: anchor-center;
}
.modal-container .modls {
pointer-events: none;
justify-content: end !important;
align-items: flex-start;
}
.modal-background {
background-color: rgb(26 28 30 / 0%) !important;
}
.modal-section.modal-section--head {
display: none !important;
}
.video-player .video-wrapper,
.sent-video-player .video-wrapper {
height: var(--height_big);
overflow: hidden;
pointer-events: auto;
}
.modal.video-player .modal-content {
max-width: var(--width_big);
margin: var(--video_margin);
}
.video-player.is-minimized .video-wrapper,
.sent-video-player.is-minimized .video-wrapper {
height: var(--height_small);
width: var(--width_small);
}
.video-player.is-minimized .modal-content,
.sent-video-player.is-minimized .modal-content {
max-width: var(--width_small);
margin-bottom: 0;
}
.video-player.is-minimized,
.sent-video-player.is-minimized {
left: auto;
top: auto;
right: var(--right_pos);
bottom: var(--bottom_pos);
z-index: 99999999;
overflow: visible
}
/*make prev/next page buttons compact*/
.reader-component {
grid-template-columns: 0.5rem 1fr 0rem !important;
}
.reader-component>div>a.button>span {
width: 0.5rem !important;
}
.reader-component>div>a.button>span>svg {
width: 15px !important;
height: 15px !important;
}
/*font settings*/
.reader-container {
margin: 0 !important;
float: left !important;
line-height: var(--line_height) !important;
padding: 0 !important;
font-size: var(--font_size) !important;
columns: unset !important;
overflow-y: scroll !important;
max-width: unset !important;
}
.reader-container p {
margin-top: 0 !important;
}
.reader-container p span.sentence-item {
color: var(--font_color) !important;
}
.sentence.is-playing,
.sentence.is-playing span {
text-underline-offset: .2em !important;
text-decoration-color: var(--is_playing_underline) !important;
}
/*LingQ highlightings*/
.phrase-item {
padding: 0 !important;
}
.phrase-item:not(.phrase-item-status--4, .phrase-item-status--4x2) {
background-color: var(--lingq_background) !important;
}
.phrase-item.phrase-item-status--4,
.phrase-item.phrase-item-status--4x2 {
background-color: rgba(0, 0, 0, 0) !important;
}
.phrase-cluster:not(:has(.phrase-item-status--4, .phrase-item-status--4x2)) {
border: 1px solid var(--lingq_border) !important;
border-radius: .25rem;
}
.phrase-cluster:has(.phrase-item-status--4, .phrase-item-status--4x2) {
border: 1px solid var(--lingq_border_learned) !important;
border-radius: .25rem;
}
.reader-container .sentence .lingq-word:not(.is-learned) {
border: 1px solid var(--lingq_border) !important;
background-color: var(--lingq_background) !important;
}
.reader-container .sentence .lingq-word.is-learned {
border: 1px solid var(--lingq_border_learned) !important;
}
.reader-container .sentence .blue-word {
border: 1px solid var(--blue_border) !important;
}
.phrase-cluster:hover,
.phrase-created:hover {
padding: 0 !important;
}
.phrase-cluster:hover .phrase-item,
.phrase-created .phrase-item {
padding: 0 !important;
}
.reader-container .sentence .selected-text {
padding: 0 !important;
}
`;
document.querySelector("head").appendChild(style);
// Add new shortcuts
document.addEventListener('keydown', function(event) {
const targetElement = event.target;
const isTextInput = (targetElement.type === 'text' || targetElement.type === 'textarea');
if (isTextInput) {
return;
}
// video full screen toggle
if (event.key === 'q' || event.key === 'Q') {
const full_screen_btn = document.querySelector('.modal-section > div > button:nth-child(2)');
if (full_screen_btn) {
full_screen_btn.click();
}
}
// 5 sec Backward (Same as the 'Ctrl + ,')
if (event.key === 'w') {
const backward_btn = document.querySelector('.audio-player--controllers > div:nth-child(1) > a');
if (backward_btn) {
backward_btn.click();
}
event.stopPropagation();
}
// 5 sec Forward (Same as the 'Ctrl + .')
if (event.key === 'e') {
const forkward_btn = document.querySelector('.audio-player--controllers > div:nth-child(2) > a');
if (forkward_btn) {
forkward_btn.click();
}
event.stopPropagation();
}
// Make the selected word Known (Same as the 'k')
if (event.key === 'r') {
document.dispatchEvent(new KeyboardEvent('keydown', {key: 'k'}));
event.stopPropagation();
}
// Move cursor to the reference input
if (event.key === '`') {
const reference_input = document.querySelector('.reference-input-text');
if (reference_input) {
reference_input.focus();
reference_input.setSelectionRange(reference_input.value.length, reference_input.value.length);
}
event.preventDefault();
event.stopPropagation();
}
// Open Dictionary
if (event.key === 'd' || event.key === 'f') {
const dict_btn = document.querySelector('.dictionary-resources > a:nth-child(1)');
if (dict_btn) {
dict_btn.click();
}
event.stopPropagation();
}
// Open Translator
if (event.key === 't') {
const translator_btn = document.querySelector('.dictionary-resources > a:nth-last-child(1)');
if (translator_btn) {
translator_btn.click();
}
event.stopPropagation();
}
// Copy selected text
if (event.key === 'c') {
const selected_text = document.querySelector('.reference-word');
if (selected_text) {
navigator.clipboard.writeText(selected_text.textContent);
}
event.stopPropagation();
}
});
// Custom embedded player
function replaceNoCookie() {
document.querySelectorAll('iframe').forEach(function(iframe) {
let src = iframe.getAttribute('src');
if (src && src.includes('autoplay=0')) {
src = src.replace('autoplay=0', 'autoplay=1'); // video will automatically start to play.
src = src.replace('disablekb=1', 'disablekb=0'); // keyboard controls are enabled.
src = src + '&cc_load_policy=1' // caption is shown by default.
src = src + '&controls=0' // player controls do not display in the player.
iframe.setAttribute('src', src);
console.log(src);
}
});
}
const iframeObserver = new MutationObserver(function(mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(node => {
if (node.nodeName === 'IFRAME') {
replaceNoCookie();
} else if (node.querySelectorAll) {
node.querySelectorAll('iframe').forEach(replaceNoCookie);
}
});
} else if (mutation.type === 'attributes' && mutation.attributeName === 'src' && mutation.target.nodeName === 'IFRAME') {
replaceNoCookie();
}
}
});
if (document.URL.includes("/reader/")) {
iframeObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'] });
}
// Change the amount of a scroll
setTimeout(() => {
console.log('scroll event!');
const myDiv = document.querySelector('.reader-container');
myDiv.addEventListener('wheel', (event) => {
event.preventDefault();
const delta = event.deltaY;
const scrollAmount = 0.3;
myDiv.scrollTop += delta * scrollAmount;
});
}, 3000);
// Focus on playing sentence
function focusPlayingSentence() {
const playingSentence = document.querySelector('.sentence.is-playing');
if (playingSentence) {
playingSentence.scrollIntoView({behavior: 'smooth', block: 'center'});
}
}
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class' && mutation.target.classList.contains('sentence')) {
focusPlayingSentence();
}
});
});
const container = document.querySelector('.sentence-text');
if (container) {
console.log('observer setted!')
observer.observe(container, { attributes: true, subtree: true });
}
// Sort courses
function addClickListener() {
document.querySelectorAll("div.library-item-wrap").forEach((item) => {
if (!item.dataset.listenerAdded) {
item.addEventListener("click", function () {
setTimeout(() => {
/* Change the number in .dropdown-item:nth-child(3) by your preference
1: All lessons
2: Oldest to Newest
3: Newest to Oldest
4: Last Opened
5: New Words %
6: A-Z */
document.querySelector(
".library-item--menu-box .collection-section--controllers .dropdown-item:nth-child(5)"
).click();
}, 1000);
});
item.dataset.listenerAdded = true;
}
});
}
const libraryObserver = new MutationObserver(addClickListener);
if (document.URL.includes("/library")) {
libraryObserver.observe(document.body, { childList: true, subtree: true });
addClickListener();
}
if (document.URL.includes("/library/course/")) {
setTimeout(() => {
document.querySelector(
".library-sections--item .dropdown-content .dropdown-item:nth-child(5)"
).click();
}, 2000);
}
})();