LingQ Extension (Custom Layouts, GPT, AI-TTS, Lesson Audio Generation, etc.)

Why can users implement these changes, and why can’t lingQ make these changes?

Maybe… Because they should satisfy all of the users, but I don’t have that restriction and made this script just by my preference. :slight_smile:

Can I switch between the script for video mode and audio mode together? Right now I can only open one of these scripts to use.

I’m not sure that’s technically possible. For now, you need to switch scripts manually. (Import both audio/video script and turn on and off in the extension program)

1 Like

I come up with an idea. I’m gonna try adding a switch button on the navigation bar or somewhere.

1 Like

Cool, man, thank you! I suggest having the text on the left and the video on the right, which is more in line with reading habits. If you can do that, that would be really great!

1 Like

Change the elements’ grid-area options. They define the layout.

1 Like

It is currently very useful, and it would be excellent if the new features could be implemented.

Now you can toggle Video/Audio/Off styles. And it is maintained until you manually change it.



Old Code
// ==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.3
// @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", "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;
    }

    .widget-area {
      grid-area: 2 / 2 / -1 / 2 !important;
    }

    .main-content {
      grid-template-rows: 45px 1fr !important;
      overflow: hidden;
      grid-area: 1 / 1 / 1 / -1 !important;
      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: 510px;
            }

            .widget-area {
                grid-area: 2 / 2 / -1 / 2 !important;
            }

            .main-content {
                grid-area: 1 / 1 / 1 / -1 !important;
            }
        `;
        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") {
      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);
  }
})();
1 Like

Hey there, I really appreciate all your help—I’ve got one more favor to ask, if you don’t mind. Could you help me with a layout setup?

  1. In video mode, from left to right, the layout should go: text section, then dictionary section, and finally the video section.
  2. In audio mode, the text should be centered, with the dictionary on the right.

Since wide screens are pretty common nowadays, this kind of layout makes reading way easier. Otherwise, our poor necks have to swing back and forth like we’re watching a tennis match—which is not ideal. Thanks a ton in advance!

Use the old css until I made a change for you.

1 Like

Thank you, my friend. I tried to modify the code myself using ChatGPT, but I couldn’t make it work, so I have to ask for your help.
Actually, I just hope that the article and the dictionary can be closer together. Since my screen is wide, the video can stay on the right side.

Now there’re four options:

  • video
  • video(wide)
  • audio
  • off

Old Code
// ==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-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: 510px;
            }

            .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 === "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") {
      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);
  }
})();
1 Like


Thank you so much, master, but in wide screen mode, the button to enlarge the video size seems to have disappeared. What should I do to make the video occupy the empty space on my right?

Press the Q key! It expands and shrinks the video player.

1 Like

Super cool, thanks a lot, you’re the best!

1 Like

Wow, that’s awesome! Thank you so much!

Master, I don’t know how to use that ‘buy you a coffee’ thing… Do you have Alipay?

Also, could you help me write a piece of code that uses LingQ’s original highlighting system? That is, a level 4 word has a dotted underline, and known words have no display at all.

Don’t mind.

Refer this topic

Most of the case, you can set the highlighting style as you want by simply changing the variables’ values.
For more setting, change the code below the /*LingQ highlightings*/.

I just tried it, but unfortunately it didn’t work. I hope to revert to the previous state where the words were marked with the “Ling q” tags. In addition, I would like to change the current shortcut key from “K” to “5”. Could you please help me by generating a complete script for this?

Thank you very, very much for your help!

1 Like