[UPDATED] Customizable LingQ Reader + Add-ons

New hotkeys

  • Move the cursor to the reference input (`): it directly moves the keyboard cursor to the text field in the widget.
  • Open Dictionary (d or f): it works when multiple words are selected
  • Open Translator (t): it works when multiple words are selected
  • Copy selected text (c): copy selected text in a clipboard (I use it when using LLM)

Of course, you can change the hotkeys in your favor.


The Open Dictionary key clicks the first dictionary and the Open Translator selects the last. So you need to set them properly

image


You can change the order of the dicts here.


// ==UserScript==
// @name         LingQ Addon
// @description  Custom embedded player, and sort course automatically.
// @match        https://www.lingq.com/*/learn/*/web/reader/*
// @match        https://www.lingq.com/*/learn/*/web/library
// @match        https://www.lingq.com/*/learn/*/web/library/course/*
// @version      1.5
// ==/UserScript==

(function () {
  "use strict";
  
  // Style settings
  var style = document.createElement("style");
  style.textContent = `
  :root {
    --width_small: 440px;
    --height_small: 260px;
    --right_pos: 0.5%;
    --bottom_pos: 5.5%;
    
    --width_big: calc(100vw - 424px - 40px);
    --height_big: 470px;
    
    --font_size: 1.1rem;
    --font_color: #e0e0e0;
    --line_height: 1.7;
    
    --lingq_background: hsl(41 43% 30% / 0.7);
    --lingq_border: hsl(43 99% 64% / 0.3);
    --lingq_border_learned: hsl(43 99% 64% / 0.5);
    --blue_border: hsl(213 99% 64% / 0.5);
    
    --is_playing_underline: #ffffff;
    
    --grid-layout: 1fr var(--height_big) 80px;
    --video_margin: 0 0 90px 10px !important;
    --article_height: calc(var(--app-height) - 165px - var(--height_big));
  }
  
  .main-wrapper {
    padding-top: calc(var(--spacing)* 12) !important;
  }
  
  #main-nav .navbar, #main-nav .navbar-brand {
    min-height: 2.75rem !important;
  }
  
  .main-header svg {
    width: 20px !important;
    height: 20px !important;
  }
  
  #lesson-reader {
    grid-template-rows: var(--grid-layout);
    overflow-y: hidden;
  }
  
  .sentence-text {
  height: var(--article_height) !important;
  }
  
  .reader-container-wrapper {
    height: 100% !important;
  }
  
  /*video viewer*/
  
  .main-footer.has-video {
    grid-area: 3 / 1 / 3 / 1 !important;
    align-self: end;
  }
  
  .widget-area {
    grid-area: 2 / 2 / -1 / 2 !important;
  }
  
  .main-content {
    grid-template-rows: 25px 1fr !important;
    overflow: hidden; 
    grid-area: 1 / 1 / 1 / -1 !important;
    align-items: anchor-center;
  }
  
  .modal-container .modls {
    pointer-events: none;
    justify-content: end !important;
    align-items: flex-start;
  }
  
  .modal-background {
    background-color: rgb(26 28 30 / 0%) !important;
  }
  
  .modal-section.modal-section--head {
    display: none !important;
  }
  
  .video-player .video-wrapper,
  .sent-video-player .video-wrapper {
    height: var(--height_big);
    overflow: hidden;
    pointer-events: auto;
  }

  .modal.video-player .modal-content {
    max-width: var(--width_big);
    margin: var(--video_margin);
  }

  .video-player.is-minimized .video-wrapper,
  .sent-video-player.is-minimized .video-wrapper {
    height: var(--height_small);
    width: var(--width_small);
  }

  .video-player.is-minimized .modal-content,
  .sent-video-player.is-minimized .modal-content {
    max-width: var(--width_small);
    margin-bottom: 0;
  }

  .video-player.is-minimized,
  .sent-video-player.is-minimized {
    left: auto;
    top: auto;
    right: var(--right_pos);
    bottom: var(--bottom_pos);
    z-index: 99999999;
    overflow: visible
  }

  /*make prev/next page buttons compact*/

  .reader-component {
    grid-template-columns: 0.5rem 1fr 0rem !important;
  }

  .reader-component>div>a.button>span {
    width: 0.5rem !important;
  }

  .reader-component>div>a.button>span>svg {
    width: 15px !important;
    height: 15px !important;
  }

  /*font settings*/
  
  .reader-container {
    margin: 0 !important;
    float: left !important;
    line-height: var(--line_height) !important;
    padding: 0 !important;
    font-size: var(--font_size) !important;
    columns: unset !important;
    overflow-y: scroll !important;
    max-width: unset !important;
  }

  .reader-container p {
    margin-top: 0 !important;
  }

  .reader-container p span.sentence-item {
    color: var(--font_color) !important;
  }

  .sentence.is-playing,
  .sentence.is-playing span {
    text-underline-offset: .2em !important;
    text-decoration-color: var(--is_playing_underline) !important;
  }
  
  /*LingQ highlightings*/

  .phrase-item {
    padding: 0 !important;
  }

  .phrase-item:not(.phrase-item-status--4, .phrase-item-status--4x2) {
    background-color: var(--lingq_background) !important;
  }

  .phrase-item.phrase-item-status--4, 
  .phrase-item.phrase-item-status--4x2 {
    background-color: rgba(0, 0, 0, 0) !important;
  }

  .phrase-cluster:not(:has(.phrase-item-status--4, .phrase-item-status--4x2)) {
    border: 1px solid var(--lingq_border) !important;
    border-radius: .25rem;
  }

  .phrase-cluster:has(.phrase-item-status--4, .phrase-item-status--4x2) {
    border: 1px solid var(--lingq_border_learned) !important;
    border-radius: .25rem;
  }

  .reader-container .sentence .lingq-word:not(.is-learned) {
    border: 1px solid var(--lingq_border) !important;
    background-color: var(--lingq_background) !important;
  }

  .reader-container .sentence .lingq-word.is-learned {
    border: 1px solid var(--lingq_border_learned) !important;
  }
  
  .reader-container .sentence .blue-word {
    border: 1px solid var(--blue_border) !important;
  }
  
  .phrase-cluster:hover,
  .phrase-created:hover {
    padding: 0 !important;
  }
  
  .phrase-cluster:hover .phrase-item,
  .phrase-created .phrase-item {
    padding: 0 !important;
  }
  
  .reader-container .sentence .selected-text {
    padding: 0 !important;
  }
  `;
  document.querySelector("head").appendChild(style);
  
  
  // Add new shortcuts
  document.addEventListener('keydown', function(event) {
    const targetElement = event.target;
    const isTextInput = (targetElement.type === 'text' || targetElement.type === 'textarea');
    if (isTextInput) {
      return;
    }
    
    // video full screen toggle
    if (event.key === 'q' || event.key === 'Q') {
      const full_screen_btn = document.querySelector('.modal-section > div > button:nth-child(2)');
      if (full_screen_btn) {
        full_screen_btn.click();
      }
    }
    
    // 5 sec Backward (Same as the 'Ctrl + ,')
    if (event.key === 'w') {
      const backward_btn = document.querySelector('.audio-player--controllers > div:nth-child(1) > a');
      if (backward_btn) {
        backward_btn.click();
      }
      event.stopPropagation();
    }
    
    // 5 sec Forward (Same as the 'Ctrl + .')
    if (event.key === 'e') {
      const forkward_btn = document.querySelector('.audio-player--controllers > div:nth-child(2) > a');
      if (forkward_btn) {
        forkward_btn.click();
      }
      event.stopPropagation();
    }
    
    // Make the selected word Known (Same as the 'k')
    if (event.key === 'r') {
      document.dispatchEvent(new KeyboardEvent('keydown', {key: 'k'}));
      event.stopPropagation();
    }
    
    // Move cursor to the reference input
    if (event.key === '`') {
      const reference_input = document.querySelector('.reference-input-text');

      if (reference_input) {
          reference_input.focus();
          reference_input.setSelectionRange(reference_input.value.length, reference_input.value.length);
      }
      event.preventDefault();
      event.stopPropagation();  
    }
    
    // Open Dictionary
    if (event.key === 'd' || event.key === 'f') {
      const dict_btn = document.querySelector('.dictionary-resources > a:nth-child(1)');
      if (dict_btn) {
        dict_btn.click();
      }
      event.stopPropagation();
    }
    
    // Open Translator
    if (event.key === 't') {
      const translator_btn = document.querySelector('.dictionary-resources > a:nth-last-child(1)');
      if (translator_btn) {
        translator_btn.click();
      }
      event.stopPropagation();
    }
    
    // Copy selected text
    if (event.key === 'c') {
      const selected_text = document.querySelector('.reference-word');
      if (selected_text) {
        navigator.clipboard.writeText(selected_text.textContent);
      }
      event.stopPropagation();
    }
    
  });
  

  // Custom embedded player
  function replaceNoCookie() {
    document.querySelectorAll('iframe').forEach(function(iframe) {
        let src = iframe.getAttribute('src');

        if (src && src.includes('autoplay=0')) {
            src = src.replace('autoplay=0', 'autoplay=1'); // video will automatically start to play.
            src = src.replace('disablekb=1', 'disablekb=0'); // keyboard controls are enabled.
            src = src + '&cc_load_policy=1' // caption is shown by default.
            src = src + '&controls=0' // player controls do not display in the player.
            iframe.setAttribute('src', src);
            console.log(src);
        }
    });
  }

  const iframeObserver = new MutationObserver(function(mutationsList, observer) {
      for (const mutation of mutationsList) {
          if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
              mutation.addedNodes.forEach(node => {
                  if (node.nodeName === 'IFRAME') {
                      replaceNoCookie();
                  } else if (node.querySelectorAll) {
                      node.querySelectorAll('iframe').forEach(replaceNoCookie);
                  }
              });
          } else if (mutation.type === 'attributes' && mutation.attributeName === 'src' && mutation.target.nodeName === 'IFRAME') {
              replaceNoCookie();
          }
      }
  });

  if (document.URL.includes("/reader/")) {
    iframeObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'] });
  }


  // Change the amount of a scroll
  setTimeout(() => {
    console.log('scroll event!');
    const myDiv = document.querySelector('.reader-container');
    myDiv.addEventListener('wheel', (event) => {
      event.preventDefault();
      const delta = event.deltaY;
      const scrollAmount = 0.3;
      myDiv.scrollTop += delta * scrollAmount;
    });
  }, 3000);


  // Focus on playing sentence
  function focusPlayingSentence() {
    const playingSentence = document.querySelector('.sentence.is-playing');

    if (playingSentence) {
    playingSentence.scrollIntoView({behavior: 'smooth', block: 'center'});
    }
  }

  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
    if (mutation.type === 'attributes' && mutation.attributeName === 'class' && mutation.target.classList.contains('sentence')) {
      focusPlayingSentence();
    }
    });
  });

  const container = document.querySelector('.sentence-text'); 
  if (container) {
  	console.log('observer setted!')
    observer.observe(container, { attributes: true, subtree: true }); 
  }

  
  // Sort courses
  function addClickListener() {
    document.querySelectorAll("div.library-item-wrap").forEach((item) => {
      if (!item.dataset.listenerAdded) {
        item.addEventListener("click", function () {
          setTimeout(() => {
            /* Change the number in .dropdown-item:nth-child(3) by your preference
               1: All lessons
               2: Oldest to Newest
               3: Newest to Oldest
               4: Last Opened
               5: New Words %
               6: A-Z */
            document.querySelector(
              ".library-item--menu-box .collection-section--controllers .dropdown-item:nth-child(5)"
            ).click();
          }, 1000);
        });
        item.dataset.listenerAdded = true;
      }
    });
  }

  const libraryObserver = new MutationObserver(addClickListener);
  if (document.URL.includes("/library")) {
    libraryObserver.observe(document.body, { childList: true, subtree: true });
    addClickListener();
  }
  
  if (document.URL.includes("/library/course/")) {
    setTimeout(() => {
      document.querySelector(
        ".library-sections--item .dropdown-content .dropdown-item:nth-child(5)"
      ).click();
    }, 2000);
  }
})();