Old Code
// ==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,
});
}
})();