// ==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 2.4
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function () {
"use strict";
// Function to load a value from GM storage
function getStoredValue(key, defaultValue) {
const value = GM_getValue(key);
return value === undefined ? defaultValue : value;
}
// Function to save a value to GM storage
function setStoredValue(key, value) {
GM_setValue(key, value);
}
// Create the style type selector (Combobox)
const styleTypeSelector = document.createElement("select"); // Moved here
styleTypeSelector.id = "styleTypeSelector";
// Add options to the selector
const options = ["video", "video(wide)", "audio", "off"];
options.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
styleTypeSelector.appendChild(opt);
});
// Load the previous selected setting from GM storage
const storedStyleType = getStoredValue("styleType", "video");
styleTypeSelector.value = storedStyleType;
// Find the #main-nav element
// More robust selector, checks nav first
let mainNav = document.querySelector(
"#main-nav > nav > div:nth-child(2) > div:nth-child(1)"
);
if (!mainNav) {
// Fallback to simpler selector if the first one fails
mainNav = document.querySelector("#main-nav");
}
if (mainNav) {
// Insert the combobox inside #main-nav
mainNav.appendChild(styleTypeSelector);
// Style the combobox's text color using CSS
styleTypeSelector.style.color = "#ffffff"; // Example: Set text color to white
styleTypeSelector.style.padding = "5px";
styleTypeSelector.style.position = "relative"; //Remove fixed positioning
} else {
console.error("#main-nav element not found. ComboBox not inserted.");
}
// No longer appending to body
let styleElement = null;
// Base CSS (always applied)
const baseCSS = `
:root {
--width_small: 440px;
--height_small: 260px;
--right_pos: 0.5%;
--bottom_pos: 5.5%;
--width_big: calc(100vw - 424px - 5px);
--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) - 205px - 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 {
grid-area: 3 / 1 / 3 / 1 !important;
align-self: end;
}
.main-content {
grid-template-rows: 45px 1fr !important;
overflow: hidden;
align-items: anchor-center;
}
.main-content > .main-header {
margin-top: 30px;
}
.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 0 100px 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-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;
}
`;
function applyStyles(styleType) {
let css = baseCSS; // Start with the base CSS
let specificCSS = "";
switch (styleType) {
case "audio":
specificCSS = `
:root {
--height_big: 10px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 2 / 1 !important;
}
`;
break;
case "video":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--height_big: 400px;
}
.widget-area {
grid-area: 2 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 1 / -1 !important;
}
`;
break;
case "video(wide)":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
.page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
grid-template-columns: 1fr 424px 1fr;
}
.main-content {
grid-area: 1 / 1 / -1 / 1 !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-footer {
grid-area: 2 / 1 / 2 / 1 !important;
}
:root {
--width_big: calc(50vw - 217px);
--height_big: calc(100vh - 80px);
--grid-layout: 1fr 80px;
--video_margin: 0 10px 20px 10px !important;
--article_height: calc(var(--app-height) - 265px);
}
.modal-container .modls {
align-items: end;
}
`;
break;
case "off":
css = ""; // No styles
break;
}
// Append the style-specific CSS
css += specificCSS;
// Remove the old style tag.
if (styleElement) {
styleElement.remove();
styleElement = null;
}
// Create & append the new style tag
if (css) {
styleElement = document.createElement("style");
styleElement.textContent = css;
document.querySelector("head").appendChild(styleElement);
}
}
// Apply styles on initial load
applyStyles(storedStyleType);
// Apply styles when the combobox changes
styleTypeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
applyStyles(selectedStyleType);
setStoredValue("styleType", selectedStyleType); // Save to GM storage
});
// 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();
}
event.preventDefault();
event.stopPropagation();
}
// 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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
// Make the selected word Known (Same as the 'k')
if (event.key === "5") {
document.dispatchEvent(
new KeyboardEvent("keydown", {
key: "k",
})
);
event.preventDefault();
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.preventDefault();
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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
});
// Custom embedded player
function replaceNoCookie() {
document.querySelectorAll("iframe").forEach(function (iframe) {
let src = iframe.getAttribute("src");
if (src && src.includes("disablekb=1")) {
// 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);
}
})();
Amazing work, congratulations!
Would you adapt your last script to screens 4k and white themes? I had tried with chatGPT but the result is so bad
Thank you!!
Change the style-related variables here.
--font_color: #e0e0e0;
is the font-color.
:root {
--width_small: 440px;
--height_small: 260px;
--right_pos: 0.5%;
--bottom_pos: 5.5%;
--width_big: calc(100vw - 424px - 5px);
--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) - 205px - var(--height_big));
}
The video size is dependent on this code.
:root {
--height_big: 400px;
}
Perfect!
I would like to move it to the right sidebar so that it takes up the entire side. Is it possible? now I can’t set the score of know word because it is not viewable.
TIP: You can also change the status of the word with hot-keys ‘1, 2, 3, 4, k’
Patch note:
- Fixed some hotkeys-related glitches.
// ==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 2.6
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function () {
"use strict";
// Function to load a value from GM storage
function getStoredValue(key, defaultValue) {
const value = GM_getValue(key);
return value === undefined ? defaultValue : value;
}
// Function to save a value to GM storage
function setStoredValue(key, value) {
GM_setValue(key, value);
}
// Create the style type selector (Combobox)
const styleTypeSelector = document.createElement("select"); // Moved here
styleTypeSelector.id = "styleTypeSelector";
// Add options to the selector
const options = ["video", "video(wide)", "audio", "off"];
options.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
styleTypeSelector.appendChild(opt);
});
// Load the previous selected setting from GM storage
const storedStyleType = getStoredValue("styleType", "video");
styleTypeSelector.value = storedStyleType;
// Find the #main-nav element
// More robust selector, checks nav first
let mainNav = document.querySelector(
"#main-nav > nav > div:nth-child(2) > div:nth-child(1)"
);
if (!mainNav) {
// Fallback to simpler selector if the first one fails
mainNav = document.querySelector("#main-nav");
}
if (mainNav) {
// Insert the combobox inside #main-nav
mainNav.appendChild(styleTypeSelector);
// Style the combobox's text color using CSS
styleTypeSelector.style.color = "#ffffff"; // Example: Set text color to white
styleTypeSelector.style.padding = "5px";
styleTypeSelector.style.position = "relative"; //Remove fixed positioning
} else {
console.error("#main-nav element not found. ComboBox not inserted.");
}
// No longer appending to body
let styleElement = null;
// Base CSS (always applied)
const baseCSS = `
:root {
--width_small: 440px;
--height_small: 260px;
--right_pos: 0.5%;
--bottom_pos: 5.5%;
--width_big: calc(100vw - 424px - 5px);
--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) - 205px - 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 {
grid-area: 3 / 1 / 3 / 1 !important;
align-self: end;
}
.main-content {
grid-template-rows: 45px 1fr !important;
overflow: hidden;
align-items: anchor-center;
}
.main-content > .main-header {
margin-top: 30px;
}
.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 0 100px 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;
}
`;
function applyStyles(styleType) {
let css = baseCSS; // Start with the base CSS
let specificCSS = "";
switch (styleType) {
case "audio":
specificCSS = `
:root {
--height_big: 10px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 2 / 1 !important;
}
`;
break;
case "video":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--height_big: 400px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 1 / 1 !important;
}
`;
break;
case "video(wide)":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
.page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
grid-template-columns: 1fr 424px 1fr;
}
.main-content {
grid-area: 1 / 1 / -1 / 1 !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-footer {
grid-area: 2 / 1 / 2 / 1 !important;
}
:root {
--width_big: calc(50vw - 217px);
--height_big: calc(100vh - 80px);
--grid-layout: 1fr 80px;
--video_margin: 0 10px 20px 10px !important;
--article_height: calc(var(--app-height) - 265px);
}
.modal-container .modls {
align-items: end;
}
`;
break;
case "off":
css = ""; // No styles
break;
}
// Append the style-specific CSS
css += specificCSS;
// Remove the old style tag.
if (styleElement) {
styleElement.remove();
styleElement = null;
}
// Create & append the new style tag
if (css) {
styleElement = document.createElement("style");
styleElement.textContent = css;
document.querySelector("head").appendChild(styleElement);
}
}
// Apply styles on initial load
applyStyles(storedStyleType);
// Apply styles when the combobox changes
styleTypeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
applyStyles(selectedStyleType);
setStoredValue("styleType", selectedStyleType); // Save to GM storage
});
// 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();
}
event.preventDefault();
event.stopPropagation();
}
// 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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
// Make the selected word Known (Same as the 'k')
if (event.key === "r") {
document.dispatchEvent(
new KeyboardEvent("keydown", {
key: "k",
})
);
event.preventDefault();
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") && !event.ctrlKey && !event.shiftKey && !event.altKey) {
const dict_btn = document.querySelector(
".dictionary-resources > a:nth-child(1)"
);
if (dict_btn) {
dict_btn.click();
}
event.preventDefault();
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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
}, true);
// Custom embedded player
function replaceNoCookie() {
document.querySelectorAll("iframe").forEach(function (iframe) {
let src = iframe.getAttribute("src");
if (src && src.includes("disablekb=1")) {
// 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);
}
})();
it is almost perfect. I just need to know how change colors new brown LingQ to yellow original range. And the other side, now when I play audio or video I have lost the sync audio display style underline
is it possible repair those?
PLZ read this post
I named the variables to make them easy to understand. Change them.
Sorry, I saw the post but just wanted to know the original RGB colors, HSV in this case.
The main trouble is that It lost sync between audio and text and now the underlined no appear
For white background
// ==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 2.6
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function () {
"use strict";
// Function to load a value from GM storage
function getStoredValue(key, defaultValue) {
const value = GM_getValue(key);
return value === undefined ? defaultValue : value;
}
// Function to save a value to GM storage
function setStoredValue(key, value) {
GM_setValue(key, value);
}
// Create the style type selector (Combobox)
const styleTypeSelector = document.createElement("select"); // Moved here
styleTypeSelector.id = "styleTypeSelector";
// Add options to the selector
const options = ["video", "video(wide)", "audio", "off"];
options.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
styleTypeSelector.appendChild(opt);
});
// Load the previous selected setting from GM storage
const storedStyleType = getStoredValue("styleType", "video");
styleTypeSelector.value = storedStyleType;
// Find the #main-nav element
// More robust selector, checks nav first
let mainNav = document.querySelector(
"#main-nav > nav > div:nth-child(2) > div:nth-child(1)"
);
if (!mainNav) {
// Fallback to simpler selector if the first one fails
mainNav = document.querySelector("#main-nav");
}
if (mainNav) {
// Insert the combobox inside #main-nav
mainNav.appendChild(styleTypeSelector);
// Style the combobox's text color using CSS
styleTypeSelector.style.color = "#000000"; // Example: Set text color to white
styleTypeSelector.style.padding = "5px";
styleTypeSelector.style.position = "relative"; //Remove fixed positioning
} else {
console.error("#main-nav element not found. ComboBox not inserted.");
}
// No longer appending to body
let styleElement = null;
// Base CSS (always applied)
const baseCSS = `
:root {
--width_small: 440px;
--height_small: 260px;
--right_pos: 0.5%;
--bottom_pos: 5.5%;
--width_big: calc(100vw - 424px - 5px);
--font_size: 1.1rem;
--line_height: 1.7;
--grid-layout: 1fr var(--height_big) 80px;
--video_margin: 0 0 90px 10px !important;
--article_height: calc(var(--app-height) - 205px - 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 {
grid-area: 3 / 1 / 3 / 1 !important;
align-self: end;
}
.main-content {
grid-template-rows: 45px 1fr !important;
overflow: hidden;
align-items: anchor-center;
}
.main-content > .main-header {
margin-top: 30px;
}
.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 0 100px 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 {
}
.sentence.is-playing,
.sentence.is-playing span {
text-underline-offset: .2em !important;
}
/*LingQ highlightings*/
.phrase-item {
padding: 0 !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;
}
`;
function applyStyles(styleType) {
let css = baseCSS; // Start with the base CSS
let specificCSS = "";
switch (styleType) {
case "audio":
specificCSS = `
:root {
--height_big: 10px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 2 / 1 !important;
}
`;
break;
case "video":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--height_big: 400px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 1 / 1 !important;
}
`;
break;
case "video(wide)":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
.page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
grid-template-columns: 1fr 424px 1fr;
}
.main-content {
grid-area: 1 / 1 / -1 / 1 !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-footer {
grid-area: 2 / 1 / 2 / 1 !important;
}
:root {
--width_big: calc(50vw - 217px);
--height_big: calc(100vh - 80px);
--grid-layout: 1fr 80px;
--video_margin: 0 10px 20px 10px !important;
--article_height: calc(var(--app-height) - 265px);
}
.modal-container .modls {
align-items: end;
}
`;
break;
case "off":
css = ""; // No styles
break;
}
// Append the style-specific CSS
css += specificCSS;
// Remove the old style tag.
if (styleElement) {
styleElement.remove();
styleElement = null;
}
// Create & append the new style tag
if (css) {
styleElement = document.createElement("style");
styleElement.textContent = css;
document.querySelector("head").appendChild(styleElement);
}
}
// Apply styles on initial load
applyStyles(storedStyleType);
// Apply styles when the combobox changes
styleTypeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
applyStyles(selectedStyleType);
setStoredValue("styleType", selectedStyleType); // Save to GM storage
});
// 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();
}
event.preventDefault();
event.stopPropagation();
}
// 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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
// Make the selected word Known (Same as the 'k')
if (event.key === "r") {
document.dispatchEvent(
new KeyboardEvent("keydown", {
key: "k",
})
);
event.preventDefault();
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") && !event.ctrlKey && !event.shiftKey && !event.altKey) {
const dict_btn = document.querySelector(
".dictionary-resources > a:nth-child(1)"
);
if (dict_btn) {
dict_btn.click();
}
event.preventDefault();
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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
}, true);
// Custom embedded player
function replaceNoCookie() {
document.querySelectorAll("iframe").forEach(function (iframe) {
let src = iframe.getAttribute("src");
if (src && src.includes("disablekb=1")) {
// 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);
}
})();
``
Patchnote
- A toggle for dark/white backgound theme was added.
(But you still need to change the LingQ theme setting manually.
// ==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 2.7
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function () {
"use strict";
// Function to load a value from GM storage
function getStoredValue(key, defaultValue) {
const value = GM_getValue(key);
return value === undefined ? defaultValue : value;
}
// Function to save a value to GM storage
function setStoredValue(key, value) {
GM_setValue(key, value);
}
// Create the style type selector (Combobox)
const styleTypeSelector = document.createElement("select"); // Moved here
styleTypeSelector.id = "styleTypeSelector";
// Add options to the selector
const options = ["video", "video2", "audio", "off"];
options.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
styleTypeSelector.appendChild(opt);
});
// Load the previous selected setting from GM storage
const storedStyleType = getStoredValue("styleType", "video");
styleTypeSelector.value = storedStyleType;
// Create the color mode selector (Toggle)
const colorModeSelector = document.createElement("select");
colorModeSelector.id = "colorModeSelector";
// Add options to the color mode selector
const colorOptions = ["dark", "white"];
colorOptions.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
colorModeSelector.appendChild(opt);
});
// Load the previous selected color mode from GM storage
const storedColorMode = getStoredValue("colorMode", "dark");
colorModeSelector.value = storedColorMode;
// Find the #main-nav element
// More robust selector, checks nav first
let mainNav = document.querySelector(
"#main-nav > nav > div:nth-child(2) > div:nth-child(1)"
);
if (!mainNav) {
// Fallback to simpler selector if the first one fails
mainNav = document.querySelector("#main-nav");
}
if (mainNav) {
// Insert the combobox inside #main-nav
mainNav.appendChild(styleTypeSelector);
// Insert the color mode selector inside #main-nav
mainNav.appendChild(colorModeSelector);
} else {
console.error("#main-nav element not found. ComboBox not inserted.");
}
// No longer appending to body
let styleElement = null;
// Base CSS (always applied)
const baseCSS = `
:root {
--width_small: 440px;
--height_small: 260px;
--right_pos: 0.5%;
--bottom_pos: 5.5%;
--width_big: calc(100vw - 424px - 5px);
--font_size: 1.1rem;
--line_height: 1.7;
--grid-layout: 1fr var(--height_big) 80px;
--video_margin: 0 0 90px 10px !important;
--article_height: calc(var(--app-height) - 205px - var(--height_big));
}
#styleTypeSelector, #colorModeSelector {
color: var(--font_color);
padding: 5px;
position: relative;
font-size: 0.9rem;
}
.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 {
grid-area: 3 / 1 / 3 / 1 !important;
align-self: end;
}
.main-content {
grid-template-rows: 45px 1fr !important;
overflow: hidden;
align-items: anchor-center;
}
.main-content > .main-header {
margin-top: 30px;
}
.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 0 100px 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;
}
`;
// Dark mode CSS
const darkModeCSS = `
:root {
--font_color: #e0e0e0;
--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;
}
`;
// White mode CSS
const whiteModeCSS = `
:root {
--font_color: #000000;
--lingq_background: hsl(47, 100%, 50%, 0.4);
--lingq_border: hsl(47, 100%, 50%, 0.3);
--lingq_border_learned: hsl(47, 100%, 50%, 1);
--blue_border: hsl(214, 100%, 50%, 0.3);
--is_playing_underline: #000000;
}
`;
function applyStyles(styleType, colorMode) {
let css = baseCSS; // Start with the base CSS
let specificCSS = "";
let colorCSS = "";
// Apply color mode CSS
switch (colorMode) {
case "dark":
colorCSS = darkModeCSS;
break;
case "white":
colorCSS = whiteModeCSS;
break;
}
// Apply style type CSS
switch (styleType) {
case "audio":
specificCSS = `
:root {
--height_big: 10px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 2 / 1 !important;
}
`;
break;
case "video":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--height_big: 400px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 1 / 1 !important;
}
`;
break;
case "video(wide)":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
.page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
grid-template-columns: 1fr 424px 1fr;
}
.main-content {
grid-area: 1 / 1 / -1 / 1 !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-footer {
grid-area: 2 / 1 / 2 / 1 !important;
}
:root {
--width_big: calc(50vw - 217px);
--height_big: calc(100vh - 80px);
--grid-layout: 1fr 80px;
--video_margin: 0 10px 20px 10px !important;
--article_height: calc(var(--app-height) - 265px);
}
.modal-container .modls {
align-items: end;
}
`;
break;
case "off":
css = ""; // No styles
break;
}
// Append the style-specific CSS and color mode CSS
css += colorCSS + specificCSS;
// Remove the old style tag.
if (styleElement) {
styleElement.remove();
styleElement = null;
}
// Create & append the new style tag
if (css) {
styleElement = document.createElement("style");
styleElement.textContent = css;
document.querySelector("head").appendChild(styleElement);
}
}
// Apply styles on initial load
applyStyles(storedStyleType, storedColorMode);
// Apply styles when the style type combobox changes
styleTypeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
const selectedColorMode = colorModeSelector.value;
applyStyles(selectedStyleType, selectedColorMode);
setStoredValue("styleType", selectedStyleType); // Save to GM storage
});
// Apply styles when the color mode combobox changes
colorModeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
const selectedColorMode = colorModeSelector.value;
applyStyles(selectedStyleType, selectedColorMode);
setStoredValue("colorMode", selectedColorMode); // Save to GM storage
});
// 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();
}
event.preventDefault();
event.stopPropagation();
}
// 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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
// Make the selected word Known (Same as the 'k')
if (event.key === "r") {
document.dispatchEvent(
new KeyboardEvent("keydown", {
key: "k",
})
);
event.preventDefault();
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") && !event.ctrlKey && !event.shiftKey && !event.altKey) {
const dict_btn = document.querySelector(
".dictionary-resources > a:nth-child(1)"
);
if (dict_btn) {
dict_btn.click();
}
event.preventDefault();
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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
}, true);
// Custom embedded player
function replaceNoCookie() {
document.querySelectorAll("iframe").forEach(function (iframe) {
let src = iframe.getAttribute("src");
if (src && src.includes("disablekb=1")) {
// 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);
}
})();
- Refactored some of the code
- You can resize and move the video player in the “off” setting.
- Removed the “course sorting”
// ==UserScript==
// @name LingQ Addon
// @description Custom embedded player, and sort course automatically.
// @match https://www.lingq.com/*/learn/*/web/reader/*
// @version 2.8
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function () {
"use strict";
// Function to load a value from GM storage
function getStoredValue(key, defaultValue) {
const value = GM_getValue(key);
return value === undefined ? defaultValue : value;
}
// Function to save a value to GM storage
function setStoredValue(key, value) {
GM_setValue(key, value);
}
// Create the style type selector (Combobox)
const styleTypeSelector = document.createElement("select"); // Moved here
styleTypeSelector.id = "styleTypeSelector";
// Add options to the selector
const options = ["video", "video2", "audio", "off"];
options.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
styleTypeSelector.appendChild(opt);
});
// Load the previous selected setting from GM storage
const storedStyleType = getStoredValue("styleType", "video");
styleTypeSelector.value = storedStyleType;
// Create the color mode selector (Toggle)
const colorModeSelector = document.createElement("select");
colorModeSelector.id = "colorModeSelector";
// Add options to the color mode selector
const colorOptions = ["dark", "white"];
colorOptions.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
colorModeSelector.appendChild(opt);
});
// Load the previous selected color mode from GM storage
const storedColorMode = getStoredValue("colorMode", "dark");
colorModeSelector.value = storedColorMode;
// Find the #main-nav element
// More robust selector, checks nav first
let mainNav = document.querySelector(
"#main-nav > nav > div:nth-child(2) > div:nth-child(1)",
);
if (!mainNav) {
// Fallback to simpler selector if the first one fails
mainNav = document.querySelector("#main-nav");
}
if (mainNav) {
// Insert the combobox inside #main-nav
mainNav.appendChild(styleTypeSelector);
// Insert the color mode selector inside #main-nav
mainNav.appendChild(colorModeSelector);
} else {
console.error("#main-nav element not found. ComboBox not inserted.");
}
// No longer appending to body
let styleElement = null;
// Base CSS (always applied)
const baseCSS = `
:root {
--font_size: 1.1rem;
--line_height: 1.7;
--grid-layout: 1fr var(--height_big) 80px;
--article_height: calc(var(--app-height) - 205px - var(--height_big));
}
#styleTypeSelector, #colorModeSelector {
color: var(--font_color);
padding: 5px;
position: relative;
font-size: 0.9rem;
}
.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 {
grid-area: 3 / 1 / 3 / 1 !important;
align-self: end;
}
.main-content {
grid-template-rows: 45px 1fr !important;
overflow: hidden;
align-items: anchor-center;
}
.main-content > .main-header {
margin-top: 30px;
}
.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) !important;
margin: var(--video_margin);
}
/*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 0 100px 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;
}
`;
// Dark mode CSS
const darkModeCSS = `
:root {
--font_color: #e0e0e0;
--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;
}
`;
// White mode CSS
const whiteModeCSS = `
:root {
--font_color: #000000;
--lingq_background: hsl(47, 100%, 50%, 0.4);
--lingq_border: hsl(47, 100%, 50%, 0.3);
--lingq_border_learned: hsl(47, 100%, 50%, 1);
--blue_border: hsl(214, 100%, 50%, 0.3);
--is_playing_underline: #000000;
}
`;
function applyStyles(styleType, colorMode) {
let css = baseCSS; // Start with the base CSS
let specificCSS = "";
let colorCSS = "";
// Apply color mode CSS
switch (colorMode) {
case "dark":
colorCSS = darkModeCSS;
break;
case "white":
colorCSS = whiteModeCSS;
break;
}
// Apply style type CSS
switch (styleType) {
case "video":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--width_big: calc(100vw - 424px - 5px);
--height_big: 400px;
--video_margin: 0 0 90px 10px !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 1 / 1 !important;
}
`;
break;
case "video2":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--width_big: calc(50vw - 217px);
--height_big: calc(100vh - 80px);
--grid-layout: 1fr 80px;
--video_margin: 0 10px 20px 10px !important;
--article_height: calc(var(--app-height) - 265px);
}
.page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
grid-template-columns: 1fr 424px 1fr;
}
.main-content {
grid-area: 1 / 1 / -1 / 1 !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-footer {
grid-area: 2 / 1 / 2 / 1 !important;
}
.modal-container .modls {
align-items: end;
}
`;
break;
case "audio":
specificCSS = `
:root {
--height_big: 10px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 2 / 1 !important;
}
`;
break;
case "off":
css = `
:root {
--width_small: 440px;
--height_small: 260px;
--right_pos: 0.5%;
--bottom_pos: 5.5%;
}
.video-player.is-minimized .video-wrapper,
.sent-video-player.is-minimized .video-wrapper {
height: var(--height_small);
width: var(--width_small);
overflow: auto;
resize: both;
}
.video-player.is-minimized .modal-content,
.sent-video-player.is-minimized .modal-content {
max-width: calc(var(--width_small)* 3);
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
}
`;
break;
}
// Append the style-specific CSS and color mode CSS
css += colorCSS + specificCSS;
// Remove the old style tag.
if (styleElement) {
styleElement.remove();
styleElement = null;
}
// Create & append the new style tag
if (css) {
styleElement = document.createElement("style");
styleElement.textContent = css;
document.querySelector("head").appendChild(styleElement);
}
}
// Apply styles on initial load
applyStyles(storedStyleType, storedColorMode);
// Apply styles when the style type combobox changes
styleTypeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
const selectedColorMode = colorModeSelector.value;
applyStyles(selectedStyleType, selectedColorMode);
setStoredValue("styleType", selectedStyleType); // Save to GM storage
});
// Apply styles when the color mode combobox changes
colorModeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
const selectedColorMode = colorModeSelector.value;
applyStyles(selectedStyleType, selectedColorMode);
setStoredValue("colorMode", selectedColorMode); // Save to GM storage
});
// 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();
}
event.preventDefault();
event.stopPropagation();
}
// 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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
// Make the selected word Known (Same as the 'k')
if (event.key === "r") {
document.dispatchEvent(
new KeyboardEvent("keydown", {
key: "k",
}),
);
event.preventDefault();
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") &&
!event.ctrlKey &&
!event.shiftKey &&
!event.altKey
) {
const dict_btn = document.querySelector(
".dictionary-resources > a:nth-child(1)",
);
if (dict_btn) {
dict_btn.click();
}
event.preventDefault();
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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
},
true,
);
// Custom embedded player
function replaceNoCookie() {
document.querySelectorAll("iframe").forEach(function (iframe) {
let src = iframe.getAttribute("src");
if (src && src.includes("disablekb=1")) {
// 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();
}
}
});
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,
});
}
})();
- Now, the video player is automatically expanded.
(You can still expand/shrink with theq
key.)
// ==UserScript==
// @name LingQ Addon
// @description Provides custom LingQ layouts
// @match https://www.lingq.com/*/learn/*/web/reader/*
// @version 2.9
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function () {
"use strict";
// Function to load a value from GM storage
function getStoredValue(key, defaultValue) {
const value = GM_getValue(key);
return value === undefined ? defaultValue : value;
}
// Function to save a value to GM storage
function setStoredValue(key, value) {
GM_setValue(key, value);
}
// Create the style type selector (Combobox)
const styleTypeSelector = document.createElement("select"); // Moved here
styleTypeSelector.id = "styleTypeSelector";
// Add options to the selector
const options = ["video", "video2", "audio", "off"];
options.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
styleTypeSelector.appendChild(opt);
});
// Load the previous selected setting from GM storage
const storedStyleType = getStoredValue("styleType", "video");
styleTypeSelector.value = storedStyleType;
// Create the color mode selector (Toggle)
const colorModeSelector = document.createElement("select");
colorModeSelector.id = "colorModeSelector";
// Add options to the color mode selector
const colorOptions = ["dark", "white"];
colorOptions.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
colorModeSelector.appendChild(opt);
});
// Load the previous selected color mode from GM storage
const storedColorMode = getStoredValue("colorMode", "dark");
colorModeSelector.value = storedColorMode;
// Find the #main-nav element
// More robust selector, checks nav first
let mainNav = document.querySelector(
"#main-nav > nav > div:nth-child(2) > div:nth-child(1)",
);
if (!mainNav) {
// Fallback to simpler selector if the first one fails
mainNav = document.querySelector("#main-nav");
}
if (mainNav) {
// Insert the combobox inside #main-nav
mainNav.appendChild(styleTypeSelector);
// Insert the color mode selector inside #main-nav
mainNav.appendChild(colorModeSelector);
} else {
console.error("#main-nav element not found. ComboBox not inserted.");
}
// No longer appending to body
let styleElement = null;
// Base CSS (always applied)
const baseCSS = `
:root {
--font_size: 1.1rem;
--line_height: 1.7;
--grid-layout: 1fr var(--height_big) 80px;
--article_height: calc(var(--app-height) - 205px - var(--height_big));
}
#styleTypeSelector, #colorModeSelector {
color: var(--font_color);
padding: 5px;
position: relative;
font-size: 0.9rem;
}
.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 {
grid-area: 3 / 1 / 3 / 1 !important;
align-self: end;
}
.main-content {
grid-template-rows: 45px 1fr !important;
overflow: hidden;
align-items: anchor-center;
}
.main-content > .main-header {
margin-top: 30px;
}
.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) !important;
margin: var(--video_margin);
}
/*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 0 100px 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;
}
`;
// Dark mode CSS
const darkModeCSS = `
:root {
--font_color: #e0e0e0;
--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;
}
`;
// White mode CSS
const whiteModeCSS = `
:root {
--font_color: #000000;
--lingq_background: hsl(47, 100%, 50%, 0.4);
--lingq_border: hsl(47, 100%, 50%, 0.3);
--lingq_border_learned: hsl(47, 100%, 50%, 1);
--blue_border: hsl(214, 100%, 50%, 0.3);
--is_playing_underline: #000000;
}
`;
function applyStyles(styleType, colorMode) {
let css = baseCSS; // Start with the base CSS
let specificCSS = "";
let colorCSS = "";
// Apply color mode CSS
switch (colorMode) {
case "dark":
colorCSS = darkModeCSS;
break;
case "white":
colorCSS = whiteModeCSS;
break;
}
// Apply style type CSS
switch (styleType) {
case "video":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--width_big: calc(100vw - 424px - 5px);
--height_big: 400px;
--video_margin: 0 0 90px 10px !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 1 / 1 !important;
}
`;
break;
case "video2":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--width_big: calc(50vw - 217px);
--height_big: calc(100vh - 80px);
--grid-layout: 1fr 80px;
--video_margin: 0 10px 20px 10px !important;
--article_height: calc(var(--app-height) - 265px);
}
.page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
grid-template-columns: 1fr 424px 1fr;
}
.main-content {
grid-area: 1 / 1 / -1 / 1 !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-footer {
grid-area: 2 / 1 / 2 / 1 !important;
}
.modal-container .modls {
align-items: end;
}
`;
break;
case "audio":
specificCSS = `
:root {
--height_big: 10px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 2 / 1 !important;
}
`;
break;
case "off":
css = `
:root {
--width_small: 440px;
--height_small: 260px;
--right_pos: 0.5%;
--bottom_pos: 5.5%;
}
.video-player.is-minimized .video-wrapper,
.sent-video-player.is-minimized .video-wrapper {
height: var(--height_small);
width: var(--width_small);
overflow: auto;
resize: both;
}
.video-player.is-minimized .modal-content,
.sent-video-player.is-minimized .modal-content {
max-width: calc(var(--width_small)* 3);
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
}
`;
break;
}
// Append the style-specific CSS and color mode CSS
css += colorCSS + specificCSS;
// Remove the old style tag.
if (styleElement) {
styleElement.remove();
styleElement = null;
}
// Create & append the new style tag
if (css) {
styleElement = document.createElement("style");
styleElement.textContent = css;
document.querySelector("head").appendChild(styleElement);
}
}
// Apply styles on initial load
applyStyles(storedStyleType, storedColorMode);
// Apply styles when the style type combobox changes
styleTypeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
const selectedColorMode = colorModeSelector.value;
applyStyles(selectedStyleType, selectedColorMode);
setStoredValue("styleType", selectedStyleType); // Save to GM storage
});
// Apply styles when the color mode combobox changes
colorModeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
const selectedColorMode = colorModeSelector.value;
applyStyles(selectedStyleType, selectedColorMode);
setStoredValue("colorMode", selectedColorMode); // Save to GM storage
});
// 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();
}
event.preventDefault();
event.stopPropagation();
}
// 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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
// Make the selected word Known (Same as the 'k')
if (event.key === "r") {
document.dispatchEvent(
new KeyboardEvent("keydown", {
key: "k",
}),
);
event.preventDefault();
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") &&
!event.ctrlKey &&
!event.shiftKey &&
!event.altKey
) {
const dict_btn = document.querySelector(
".dictionary-resources > a:nth-child(1)",
);
if (dict_btn) {
dict_btn.click();
}
event.preventDefault();
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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
},
true,
);
// Custom embedded player
function replaceNoCookie() {
document.querySelectorAll("iframe").forEach(function (iframe) {
let src = iframe.getAttribute("src");
if (src && src.includes("disablekb=1")) {
// 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();
document.querySelector('.modal-section.modal-section--head button[title="Expand"]').click();
}
});
}
}
});
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,
});
}
})();
Thanks for this.
I use 2 monitors and I wonder if there is a way to let the translation “window” appear on the second monitor? (since I still think the main text and video is all I really want on my main screen)
There’s no way.
- I can not extract the translations. (I don’t use translation feature provided by LingQ).
- Controlling another window is out of the scope of Userstyle.
- You need to manage the translation in the other window manually.
Thanks.
Not sure what I am doing wrong, but the text is all at the left and scrolls.
Also, I do not want the yellow box around the characters. And, dark mode turns the text to a hardly legible beige ?
Video1
Video2
Change the value of the border-related variables
--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);
--lingq_background: hsl(47, 100%, 50%, 0.4);
--lingq_border: hsl(47, 100%, 50%, 0.3);
--lingq_border_learned: hsl(47, 100%, 50%, 1);
--blue_border: hsl(214, 100%, 50%, 0.3);
Last value is the oppacity.
Text is still in the left corner…I will look into this on the weekend. I am sure I will figure it out. Thanks again
Change the layout to the Video
Video2
lists the article, the word widget, and the video horizontally. It’s for wide-screen users.
- Whenever you change the dark/white theme setting, LingQ’s theme also changes automatically.
// ==UserScript==
// @name LingQ Addon
// @description Provides custom LingQ layouts
// @match https://www.lingq.com/*/learn/*/web/reader/*
// @version 2.10
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function () {
"use strict";
// Function to load a value from GM storage
function getStoredValue(key, defaultValue) {
const value = GM_getValue(key);
return value === undefined ? defaultValue : value;
}
// Function to save a value to GM storage
function setStoredValue(key, value) {
GM_setValue(key, value);
}
// Create the style type selector (Combobox)
const styleTypeSelector = document.createElement("select"); // Moved here
styleTypeSelector.id = "styleTypeSelector";
// Add options to the selector
const options = ["video", "video2", "audio", "off"];
options.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
styleTypeSelector.appendChild(opt);
});
// Load the previous selected setting from GM storage
const storedStyleType = getStoredValue("styleType", "video");
styleTypeSelector.value = storedStyleType;
// Create the color mode selector (Toggle)
const colorModeSelector = document.createElement("select");
colorModeSelector.id = "colorModeSelector";
// Add options to the color mode selector
const colorOptions = ["dark", "white"];
colorOptions.forEach((option) => {
const opt = document.createElement("option");
opt.value = option;
opt.textContent = option.charAt(0).toUpperCase() + option.slice(1); // Capitalize
colorModeSelector.appendChild(opt);
});
// Load the previous selected color mode from GM storage
const storedColorMode = getStoredValue("colorMode", "dark");
colorModeSelector.value = storedColorMode;
// Find the #main-nav element
// More robust selector, checks nav first
let mainNav = document.querySelector(
"#main-nav > nav > div:nth-child(2) > div:nth-child(1)",
);
if (!mainNav) {
// Fallback to simpler selector if the first one fails
mainNav = document.querySelector("#main-nav");
}
if (mainNav) {
// Insert the combobox inside #main-nav
mainNav.appendChild(styleTypeSelector);
// Insert the color mode selector inside #main-nav
mainNav.appendChild(colorModeSelector);
} else {
console.error("#main-nav element not found. ComboBox not inserted.");
}
// No longer appending to body
let styleElement = null;
// Base CSS (always applied)
const baseCSS = `
:root {
--font_size: 1.1rem;
--line_height: 1.7;
--grid-layout: 1fr var(--height_big) 80px;
--article_height: calc(var(--app-height) - 205px - var(--height_big));
}
#styleTypeSelector, #colorModeSelector {
color: var(--font_color);
padding: 5px;
position: relative;
font-size: 0.9rem;
}
.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 {
grid-area: 3 / 1 / 3 / 1 !important;
align-self: end;
}
.main-content {
grid-template-rows: 45px 1fr !important;
overflow: hidden;
align-items: anchor-center;
}
.main-content > .main-header {
margin-top: 30px;
}
.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) !important;
margin: var(--video_margin);
}
/*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 0 100px 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;
}
`;
// Dark mode CSS
const darkModeCSS = `
:root {
--font_color: #e0e0e0;
--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;
}
`;
// White mode CSS
const whiteModeCSS = `
:root {
--font_color: #000000;
--lingq_background: hsl(47, 100%, 50%, 0.4);
--lingq_border: hsl(47, 100%, 50%, 0.3);
--lingq_border_learned: hsl(47, 100%, 50%, 1);
--blue_border: hsl(214, 100%, 50%, 0.3);
--is_playing_underline: #000000;
}
`;
function applyStyles(styleType, colorMode) {
let css = baseCSS; // Start with the base CSS
let specificCSS = "";
let colorCSS = "";
let theme_btn = "";
// Apply color mode CSS
switch (colorMode) {
case "dark":
colorCSS = darkModeCSS;
theme_btn = document.querySelector(".reader-themes-component > button:nth-child(5)");
break;
case "white":
colorCSS = whiteModeCSS;
theme_btn = document.querySelector(".reader-themes-component > button:nth-child(1)");
break;
}
// Apply style type CSS
switch (styleType) {
case "video":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--width_big: calc(100vw - 424px - 5px);
--height_big: 400px;
--video_margin: 0 0 90px 10px !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 1 / 1 !important;
}
`;
break;
case "video2":
// Add video-specific styles here if you have any, otherwise leave empty.
specificCSS = `
:root {
--width_big: calc(50vw - 217px);
--height_big: calc(100vh - 80px);
--grid-layout: 1fr 80px;
--video_margin: 0 10px 20px 10px !important;
--article_height: calc(var(--app-height) - 265px);
}
.page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
grid-template-columns: 1fr 424px 1fr;
}
.main-content {
grid-area: 1 / 1 / -1 / 1 !important;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-footer {
grid-area: 2 / 1 / 2 / 1 !important;
}
.modal-container .modls {
align-items: end;
}
`;
break;
case "audio":
specificCSS = `
:root {
--height_big: 10px;
}
.widget-area {
grid-area: 1 / 2 / -1 / 2 !important;
}
.main-content {
grid-area: 1 / 1 / 2 / 1 !important;
}
`;
break;
case "off":
css = `
:root {
--width_small: 440px;
--height_small: 260px;
--right_pos: 0.5%;
--bottom_pos: 5.5%;
}
.video-player.is-minimized .video-wrapper,
.sent-video-player.is-minimized .video-wrapper {
height: var(--height_small);
width: var(--width_small);
overflow: auto;
resize: both;
}
.video-player.is-minimized .modal-content,
.sent-video-player.is-minimized .modal-content {
max-width: calc(var(--width_small)* 3);
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
}
`;
break;
}
// Append the style-specific CSS and color mode CSS
css += colorCSS + specificCSS;
// Remove the old style tag.
if (styleElement) {
styleElement.remove();
styleElement = null;
}
// Create & append the new style tag
if (css) {
styleElement = document.createElement("style");
styleElement.textContent = css;
document.querySelector("head").appendChild(styleElement);
}
if (theme_btn) {
theme_btn.click();
}
}
// Apply styles on initial load
applyStyles(storedStyleType, storedColorMode);
// Apply styles when the style type combobox changes
styleTypeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
const selectedColorMode = colorModeSelector.value;
applyStyles(selectedStyleType, selectedColorMode);
setStoredValue("styleType", selectedStyleType); // Save to GM storage
});
// Apply styles when the color mode combobox changes
colorModeSelector.addEventListener("change", function () {
const selectedStyleType = styleTypeSelector.value;
const selectedColorMode = colorModeSelector.value;
applyStyles(selectedStyleType, selectedColorMode);
setStoredValue("colorMode", selectedColorMode); // Save to GM storage
});
// 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();
}
event.preventDefault();
event.stopPropagation();
}
// 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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
// Make the selected word Known (Same as the 'k')
if (event.key === "r") {
document.dispatchEvent(
new KeyboardEvent("keydown", {
key: "k",
}),
);
event.preventDefault();
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") &&
!event.ctrlKey &&
!event.shiftKey &&
!event.altKey
) {
const dict_btn = document.querySelector(
".dictionary-resources > a:nth-child(1)",
);
if (dict_btn) {
dict_btn.click();
}
event.preventDefault();
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.preventDefault();
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.preventDefault();
event.stopPropagation();
}
},
true,
);
// Custom embedded player
function replaceNoCookie() {
document.querySelectorAll("iframe").forEach(function (iframe) {
let src = iframe.getAttribute("src");
if (src && src.includes("disablekb=1")) {
// 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();
document.querySelector('.modal-section.modal-section--head button[title="Expand"]').click();
}
});
}
}
});
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,
});
}
})();
Patch Note
- 1.3: Article is displayed in a single page.
- 1.4: Now the sentence playing is located at the center of the screen. You don’t need to scrol acording to the playing video.
- 1.5: Provide new hotkeys: Move the cursor to the reference input (`), Open Dictionary (d or f), Open Translator (t), and Copy selected text (c)
- 1.6: Now, LingQ uses “youtube.com” player, not “youtube-nocookie.com 1”. Some code is updated for that change.
- 2.3 Provide three layout optionss: Video, Video(wide), Audio, off.
- 2.4: Provide video(wide) layout options
- 2.6: Fixed some hotkeys-related glitches.
- 2.7: Added a toggle for dark/white backgound theme.
- 2.8: The video player in the “off” setting is resizable and movable, Removed the “course sorting”
- 2.9: The video player is automatically expanded.
- 2.10: Whenever you change the dark/white theme setting, LingQ’s theme also changes automatically.
- 3.0: You can set the options according to your preference in the options pop-up.
- 3.1: Changed the location of the reset button. Fix some unit-related problems.
- 3.2: Changed color pickers and default values.
You can install the extension from here: