您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Provides custom LingQ layouts
当前为
// ==UserScript== // @name LingQ Addon // @description Provides custom LingQ layouts // @match https://www.lingq.com/*/learn/*/web/reader/* // @version 3.5 // @grant GM_setValue // @grant GM_getValue // @namespace https://gf.qytechs.cn/users/1458847 // ==/UserScript== (function () { "use strict"; // Utility functions const storage = { get: (key, defaultValue) => { const value = GM_getValue(key); return value === undefined ? defaultValue : value; }, set: (key, value) => GM_setValue(key, value) }; // Default values const defaults = { styleType: "video", colorMode: "dark", fontSize: 1.1, lineHeight: 1.7, heightBig: 400, sentenceHeight: 400, darkColors: { fontColor: "#e0e0e0", lingqBackground: "rgba(109, 89, 44, 0.7)", lingqBorder: "rgba(254, 203, 72, 0.3)", lingqBorderLearned: "rgba(254, 203, 72, 0.5)", blueBorder: "rgba(72, 154, 254, 0.5)", playingUnderline: "#ffffff" }, whiteColors: { fontColor: "#000000", lingqBackground: "rgba(255, 200, 0, 0.4)", lingqBorder: "rgba(255, 200, 0, 0.3)", lingqBorderLearned: "rgba(255, 200, 0, 1)", blueBorder: "rgba(0, 111, 255, 0.3)", playingUnderline: "#000000" } }; // Load stored settings const settings = { styleType: storage.get("styleType", defaults.styleType), colorMode: storage.get("colorMode", defaults.colorMode), fontSize: storage.get("fontSize", defaults.fontSize), lineHeight: storage.get("lineHeight", defaults.lineHeight), heightBig: storage.get("heightBig", defaults.heightBig), sentenceHeight: storage.get("sentenceHeight", defaults.sentenceHeight) }; // Helper function to get color settings based on mode function getColorSettings(colorMode) { const prefix = colorMode === "dark" ? "dark_" : "white_"; const defaultColors = colorMode === "dark" ? defaults.darkColors : defaults.whiteColors; return { fontColor: storage.get(prefix + "fontColor", defaultColors.fontColor), lingqBackground: storage.get(prefix + "lingqBackground", defaultColors.lingqBackground), lingqBorder: storage.get(prefix + "lingqBorder", defaultColors.lingqBorder), lingqBorderLearned: storage.get(prefix + "lingqBorderLearned", defaultColors.lingqBorderLearned), blueBorder: storage.get(prefix + "blueBorder", defaultColors.blueBorder), playingUnderline: storage.get(prefix + "playingUnderline", defaultColors.playingUnderline) }; } // Get current color settings const colorSettings = getColorSettings(settings.colorMode); // UI Creation function createUI() { // Create settings button const settingsButton = document.createElement("button"); settingsButton.id = "lingqAddonSettings"; settingsButton.textContent = "⚙️"; settingsButton.title = "LingQ Addon Settings"; settingsButton.style.cssText = ` background: none; border: none; cursor: pointer; font-size: 1.2rem; margin-left: 10px; padding: 5px; `; // Create lesson complete button const completeLessonButton = document.createElement("button"); completeLessonButton.id = "lingqLessonComplete"; completeLessonButton.textContent = "✔"; completeLessonButton.title = "Complete Lesson Button"; completeLessonButton.style.cssText = ` background: none; border: none; cursor: pointer; font-size: 1.2rem; margin-left: 10px; padding: 5px; `; // Find the #main-nav element let mainNav = document.querySelector( "#main-nav > nav > div:nth-child(2) > div:nth-child(1)", ) || document.querySelector("#main-nav"); if (mainNav) { mainNav.appendChild(settingsButton); mainNav.appendChild(completeLessonButton); } else { console.error("#main-nav element not found. Settings button not inserted."); } // Create settings popup const settingsPopup = createSettingsPopup(); document.body.appendChild(settingsPopup); // Add event listeners setupEventListeners(settingsButton, completeLessonButton, settingsPopup); } // Create settings popup with all controls function createSettingsPopup() { const popup = document.createElement("div"); popup.id = "lingqAddonSettingsPopup"; popup.style.cssText = ` position: fixed; top: 40%; left: 40%; transform: translate(-40%, -40%); background-color: var(--background-color, #2a2c2e); color: var(--font_color, #e0e0e0); border: 1px solid s; border-radius: 8px; box-shadow: 8px 8px 8px rgba(0, 0, 0, 0.2); z-index: 10000; display: none; width: 400px; max-height: 80vh; overflow-y: auto; `; // popup content const content = document.createElement("div"); content.style.cssText = `padding: 20px; `; content.innerHTML = generatePopupContent(); // drag handle const dragHandle = document.createElement("div"); dragHandle.id = "lingqAddonSettingsDragHandle"; dragHandle.style.cssText = ` cursor: move; background-color: rgba(128, 128, 128, 0.2); padding: 8px; border-radius: 8px 8px 0 0; text-align: center; user-select: none; `; dragHandle.innerHTML = `<h3 style="margin: 0; user-select: none;">LingQ Addon Settings</h3>`; popup.appendChild(dragHandle); popup.appendChild(content); return popup; } // Generate HTML content for the popup function generatePopupContent() { return ` <div style="margin-bottom: 15px;"> <label for="styleTypeSelector">Layout Style:</label> <select id="styleTypeSelector" style="width: 100%; margin-top: 5px; padding: 5px;"> <option value="video" ${settings.styleType === "video" ? "selected" : ""}>Video</option> <option value="video2" ${settings.styleType === "video2" ? "selected" : ""}>Video2</option> <option value="audio" ${settings.styleType === "audio" ? "selected" : ""}>Audio</option> <option value="off" ${settings.styleType === "off" ? "selected" : ""}>Off</option> </select> </div> <div id="videoSettings" style="margin-bottom: 15px; ${settings.styleType === "video" ? "" : "display: none"}"> <label for="heightBigSlider">Video Height: <span id="heightBigValue">${settings.heightBig}</span>px</label> <input type="range" id="heightBigSlider" min="300" max="800" step="10" value="${settings.heightBig}" style="width: 100%;"> </div> <div id="offSettings" style="margin-bottom: 15px; ${settings.styleType === "off" ? "" : "display: none"}"> <label for="sentenceHeightSlider">Sentence Video Height: <span id="sentenceHeightValue">${settings.sentenceHeight}</span>px</label> <input type="range" id="sentenceHeightSlider" min="300" max="600" step="10" value="${settings.sentenceHeight}" style="width: 100%;"> </div> <div style="margin-bottom: 15px;"> <label for="fontSizeSlider">Font Size: <span id="fontSizeValue">${settings.fontSize}</span>rem</label> <input type="range" id="fontSizeSlider" min="0.8" max="1.8" step="0.05" value="${settings.fontSize}" style="width: 100%;"> </div> <div style="margin-bottom: 15px;"> <label for="lineHeightSlider">Line Height: <span id="lineHeightValue">${settings.lineHeight}</span></label> <input type="range" id="lineHeightSlider" min="1.2" max="3.0" step="0.1" value="${settings.lineHeight}" style="width: 100%;"> </div> <div style="margin-bottom: 15px; border: 1px solid var(--font_color, #e0e0e0); padding: 10px; border-radius: 5px;"> <label for="colorModeSelector">Color Mode:</label> <select id="colorModeSelector" style="width: 100%; margin-top: 5px; padding: 5px;"> <option value="dark" ${settings.colorMode === "dark" ? "selected" : ""}>Dark</option> <option value="white" ${settings.colorMode === "white" ? "selected" : ""}>White</option> </select> <div style="margin-top: 10px;"> <label for="fontColorText">Font Color:</label> <div style="display: flex; align-items: center;"> <div id="fontColorPicker" class="color-picker"></div> <input type="text" id="fontColorText" value="${colorSettings.fontColor}" style="flex-grow: 1; padding: 5px; margin-left: 10px;"> </div> </div> <div style="margin-top: 10px;"> <label for="lingqBackgroundText">LingQ Background:</label> <div style="display: flex; align-items: center;"> <div id="lingqBackgroundPicker" class="color-picker"></div> <input type="text" id="lingqBackgroundText" value="${colorSettings.lingqBackground}" style="flex-grow: 1; padding: 5px; margin-left: 10px;"> </div> </div> <div style="margin-top: 10px;"> <label for="lingqBorderText">LingQ Border:</label> <div style="display: flex; align-items: center;"> <div id="lingqBorderPicker" class="color-picker"></div> <input type="text" id="lingqBorderText" value="${colorSettings.lingqBorder}" style="flex-grow: 1; padding: 5px; margin-left: 10px;"> </div> </div> <div style="margin-top: 10px;"> <label for="lingqBorderLearnedText">LingQ Border Learned:</label> <div style="display: flex; align-items: center;"> <div id="lingqBorderLearnedPicker" class="color-picker"></div> <input type="text" id="lingqBorderLearnedText" value="${colorSettings.lingqBorderLearned}" style="flex-grow: 1; padding: 5px; margin-left: 10px;"> </div> </div> <div style="margin-top: 10px;"> <label for="blueBorderText">Blue Border:</label> <div style="display: flex; align-items: center;"> <div id="blueBorderPicker" class="color-picker"></div> <input type="text" id="blueBorderText" value="${colorSettings.blueBorder}" style="flex-grow: 1; padding: 5px; margin-left: 10px;"> </div> </div> <div style="margin-top: 10px;"> <label for="playingUnderlineText">Playing Underline:</label> <div style="display: flex; align-items: center;"> <div id="playingUnderlinePicker" class="color-picker"></div> <input type="text" id="playingUnderlineText" value="${colorSettings.playingUnderline}" style="flex-grow: 1; padding: 5px; margin-left: 10px;"> </div> </div> </div> <div style="display: flex; justify-content: space-between; margin-top: 20px;"> <button id="resetSettingsBtn" style="padding: 5px 10px; margin-right: 10px; cursor: pointer;">Reset</button> <button id="closeSettingsBtn" style="padding: 5px 10px; cursor: pointer;">Close</button> </div> `; } // Add Pickr CSS to the document function addPickrStyles() { const pickrCSS = document.createElement('style'); pickrCSS.textContent = ` .color-picker { width: 30px; height: 30px; border-radius: 4px; cursor: pointer; position: relative; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } .pcr-app { z-index: 10001 !important; } .pcr-app .pcr-interaction .pcr-result { color: var(--font_color) !important; } `; document.head.appendChild(pickrCSS); } // Initialize Pickr color pickers function initializePickrs() { // Load Pickr library dynamically return new Promise((resolve) => { // Add Pickr CSS const pickrCss = document.createElement('link'); pickrCss.rel = 'stylesheet'; pickrCss.href = 'https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css'; document.head.appendChild(pickrCss); // Add Pickr JS const pickrScript = document.createElement('script'); pickrScript.src = 'https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js'; pickrScript.onload = () => resolve(); document.head.appendChild(pickrScript); }).then(() => { // Add custom styles for Pickr addPickrStyles(); // Setup each color picker setupRGBAPickr('lingqBackgroundPicker', 'lingqBackgroundText', 'lingqBackground', '--lingq_background'); setupRGBAPickr('lingqBorderPicker', 'lingqBorderText', 'lingqBorder', '--lingq_border'); setupRGBAPickr('lingqBorderLearnedPicker', 'lingqBorderLearnedText', 'lingqBorderLearned', '--lingq_border_learned'); setupRGBAPickr('blueBorderPicker', 'blueBorderText', 'blueBorder', '--blue_border'); setupRGBAPickr('fontColorPicker', 'fontColorText', 'fontColor', '--font_color'); setupRGBAPickr('playingUnderlinePicker', 'playingUnderlineText', 'playingUnderline', '--is_playing_underline'); }); } // Setup RGBA color picker with Pickr function setupRGBAPickr(pickerId, textId, settingKey, cssVar) { const pickerElement = document.getElementById(pickerId); const textElement = document.getElementById(textId); if (!pickerElement || !textElement) return; // Set initial color for the picker element pickerElement.style.backgroundColor = textElement.value; // Create Pickr instance const pickr = Pickr.create({ el: pickerElement, theme: 'nano', useAsButton: true, default: textElement.value, components: { preview: true, opacity: true, hue: true, } }); // Handle color change pickr.on('change', (color) => { const rgbaColor = color.toRGBA(); // Round the RGBA values const r = Math.round(rgbaColor[0]); const g = Math.round(rgbaColor[1]); const b = Math.round(rgbaColor[2]); const a = rgbaColor[3]; const roundedRGBA = `rgba(${r}, ${g}, ${b}, ${a})`; textElement.value = roundedRGBA; pickerElement.style.backgroundColor = roundedRGBA; // Update picker background document.documentElement.style.setProperty(cssVar, roundedRGBA); saveColorSetting(settingKey, roundedRGBA); }); // Update picker when text input changes textElement.addEventListener('change', function () { const rgbaColor = this.value; pickr.setColor(this.value); saveColorSetting(settingKey, rgbaColor); document.documentElement.style.setProperty(cssVar, rgbaColor); pickerElement.style.backgroundColor = rgbaColor; }); // Update picker background color when color changes pickr.on('hide', () => { const rgbaColor = pickr.getColor().toRGBA().toString(); pickerElement.style.backgroundColor = rgbaColor; }); } function makeDraggable(element, handle) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; if (handle) { handle.onmousedown = dragMouseDown; } else { element.onmousedown = dragMouseDown; } function dragMouseDown(e) { e = e || window.event; e.preventDefault(); if (element.style.transform && element.style.transform.includes('translate')) { const rect = element.getBoundingClientRect(); element.style.transform = 'none'; element.style.top = rect.top + 'px'; element.style.left = rect.left + 'px'; } pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } } // Set up all event listeners for the settings UI function setupEventListeners(settingsButton, completeLessonButton, settingsPopup) { // Initialize Pickr after popup is displayed settingsButton.addEventListener("click", () => { setTimeout(() => { initializePickrs(); }, 100); }); // Drag popup settingsButton.addEventListener("click", () => { settingsPopup.style.display = "block"; const dragHandle = document.getElementById("lingqAddonSettingsDragHandle"); if (dragHandle) { makeDraggable(settingsPopup, dragHandle); } }); // Toggle popup visibility settingsButton.addEventListener("click", () => { settingsPopup.style.display = "block"; }); // Complete a lesson completeLessonButton.addEventListener("click", () => { document.querySelector(".reader-component > .nav--right > a").click(); }); // Close button document.getElementById("closeSettingsBtn").addEventListener("click", () => { settingsPopup.style.display = "none"; }); // Reset button document.getElementById("resetSettingsBtn").addEventListener("click", resetSettings); // Style type selector const styleTypeSelector = document.getElementById("styleTypeSelector"); styleTypeSelector.addEventListener("change", function () { const selectedStyleType = this.value; storage.set("styleType", selectedStyleType); document.getElementById("videoSettings").style.display = selectedStyleType === "video" ? "block" : "none"; applyStyles(selectedStyleType, document.getElementById("colorModeSelector").value); }); // Color mode selector document.getElementById("colorModeSelector").addEventListener("change", updateColorMode); // Setup sliders setupSlider("fontSizeSlider", "fontSizeValue", "fontSize", "rem", "--font_size", (val) => `${val}rem`); setupSlider("lineHeightSlider", "lineHeightValue", "lineHeight", "", "--line_height", (val) => val); setupSlider("heightBigSlider", "heightBigValue", "heightBig", "px", "--height_big", (val) => `${val}px`); setupSlider("sentenceHeightSlider", "sentenceHeightValue", "sentenceHeight", "px", "--sentence_height", (val) => `${val}px`); } // Helper function to set up slider controls function setupSlider(sliderId, valueId, settingKey, unit, cssVar, valueTransform) { const slider = document.getElementById(sliderId); const valueDisplay = document.getElementById(valueId); slider.addEventListener("input", function () { const value = parseFloat(this.value); const transformedValue = valueTransform(value); valueDisplay.textContent = transformedValue.toString().replace(unit, ''); storage.set(settingKey, value); document.documentElement.style.setProperty(cssVar, transformedValue); }); } // Function to save color setting with appropriate prefix function saveColorSetting(key, value) { const currentColorMode = document.getElementById("colorModeSelector").value; const prefix = currentColorMode === "dark" ? "dark_" : "white_"; storage.set(prefix + key, value); } // Update color mode and related settings function updateColorMode(event) { event.stopPropagation(); const selectedColorMode = this.value; const settingsPopup = document.getElementById("lingqAddonSettingsPopup"); settingsPopup.style.backgroundColor = selectedColorMode === "dark" ? "#2a2c2e" : "#ffffff"; storage.set("colorMode", selectedColorMode); // Load color settings for the selected mode const colorSettings = getColorSettings(selectedColorMode); // Update all color inputs updateColorInputs(colorSettings); // Update CSS variables document.documentElement.style.setProperty( "--background-color", selectedColorMode === "dark" ? "#2a2c2e" : "#ffffff" ); updateCssColorVariables(colorSettings); applyStyles(document.getElementById("styleTypeSelector").value, selectedColorMode); // Update color picker backgrounds updateColorPickerBackgrounds(colorSettings); } // Update all color input fields with new settings function updateColorInputs(colorSettings) { document.getElementById("fontColorText").value = colorSettings.fontColor; document.getElementById("lingqBackgroundText").value = colorSettings.lingqBackground; document.getElementById("lingqBorderText").value = colorSettings.lingqBorder; document.getElementById("lingqBorderLearnedText").value = colorSettings.lingqBorderLearned; document.getElementById("blueBorderText").value = colorSettings.blueBorder; document.getElementById("playingUnderlineText").value = colorSettings.playingUnderline; //Update Pickr color pickers with new values const fontColorPicker = document.getElementById("fontColorPicker"); if (fontColorPicker) fontColorPicker.style.backgroundColor = colorSettings.fontColor; const playingUnderlinePicker = document.getElementById("playingUnderlinePicker"); if (playingUnderlinePicker) playingUnderlinePicker.style.backgroundColor = colorSettings.playingUnderline; } // Update color picker backgrounds function updateColorPickerBackgrounds(colorSettings) { const pickerIds = [ { id: "lingqBackgroundPicker", color: colorSettings.lingqBackground }, { id: "lingqBorderPicker", color: colorSettings.lingqBorder }, { id: "lingqBorderLearnedPicker", color: colorSettings.lingqBorderLearned }, { id: "blueBorderPicker", color: colorSettings.blueBorder }, { id: "fontColorPicker", color: colorSettings.fontColor }, { id: "playingUnderlinePicker", color: colorSettings.playingUnderline } ]; pickerIds.forEach(item => { const picker = document.getElementById(item.id); if (picker) { picker.style.backgroundColor = item.color; } }); } // Update CSS color variables function updateCssColorVariables(colorSettings) { document.documentElement.style.setProperty("--font_color", colorSettings.fontColor); document.documentElement.style.setProperty("--lingq_background", colorSettings.lingqBackground); document.documentElement.style.setProperty("--lingq_border", colorSettings.lingqBorder); document.documentElement.style.setProperty("--lingq_border_learned", colorSettings.lingqBorderLearned); document.documentElement.style.setProperty("--blue_border", colorSettings.blueBorder); document.documentElement.style.setProperty("--is_playing_underline", colorSettings.playingUnderline); } // Reset settings to defaults function resetSettings() { if (!confirm("Reset all settings to default?")) return; const currentColorMode = document.getElementById("colorModeSelector").value; // Default values const defaultSettings = { styleType: "video", colorMode: currentColorMode, fontSize: 1.1, lineHeight: 1.7, heightBig: 400, sentenceHeight: 400 }; // Default color settings for current mode const defaultColorSettings = currentColorMode === "dark" ? defaults.darkColors : defaults.whiteColors; // Update all inputs document.getElementById("styleTypeSelector").value = defaultSettings.styleType; document.getElementById("fontSizeSlider").value = defaultSettings.fontSize; document.getElementById("fontSizeValue").textContent = defaultSettings.fontSize; document.getElementById("lineHeightSlider").value = defaultSettings.lineHeight; document.getElementById("lineHeightValue").textContent = defaultSettings.lineHeight; document.getElementById("heightBigSlider").value = defaultSettings.heightBig; document.getElementById("heightBigValue").textContent = defaultSettings.heightBig; document.getElementById("sentenceHeightSlider").value = defaultSettings.sentenceHeight; document.getElementById("sentenceHeightValue").textContent = defaultSettings.sentenceHeight; // Update color inputs updateColorInputs(defaultColorSettings); // Update color picker backgrounds updateColorPickerBackgrounds(defaultColorSettings); // Save general settings for (const [key, value] of Object.entries(defaultSettings)) { storage.set(key, value); } // Save color settings with prefix const prefix = currentColorMode === "dark" ? "dark_" : "white_"; for (const [key, value] of Object.entries(defaultColorSettings)) { storage.set(prefix + key, value); } // Apply styles applyStyles(defaultSettings.styleType, currentColorMode); // Show/hide video settings document.getElementById("videoSettings").style.display = defaultSettings.styleType === "video" ? "block" : "none"; // Update CSS variables directly document.documentElement.style.setProperty("--font_size", `${defaultSettings.fontSize}rem`); document.documentElement.style.setProperty("--line_height", defaultSettings.lineHeight); document.documentElement.style.setProperty("--height_big", `${defaultSettings.heightBig}px`); document.documentElement.style.setProperty("--sentence_height", `${defaultSettings.sentenceHeight}px`); updateCssColorVariables(defaultColorSettings); } // CSS Management let styleElement = null; // Apply styles based on current settings function applyStyles(styleType, colorMode) { // Load color settings for the current mode const colorSettings = getColorSettings(colorMode); let css = generateBaseCSS(colorSettings, colorMode); let specificCSS = ""; let theme_btn = ""; // Apply color mode CSS switch (colorMode) { case "dark": theme_btn = document.querySelector(".reader-themes-component > button:nth-child(5)"); break; case "white": theme_btn = document.querySelector(".reader-themes-component > button:nth-child(1)"); break; } // Apply style type CSS switch (styleType) { case "video": specificCSS = generateVideoCSS(); break; case "video2": specificCSS = generateVideo2CSS(); break; case "audio": specificCSS = generateAudioCSS(); break; case "off": css = generateOffModeCSS(colorSettings); 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); } if (theme_btn) { theme_btn.click(); } } // Generate base CSS function generateBaseCSS(colorSettings, colorMode) { return ` :root { --font_size: ${settings.fontSize}rem; --line_height: ${settings.lineHeight}; --article_height: calc(var(--app-height) - var(--height_big) - 45px); --grid-layout: calc(var(--article_height) - 10px) calc(var(--height_big) - 80px) 90px; --font_color: ${colorSettings.fontColor}; --lingq_background: ${colorSettings.lingqBackground}; --lingq_border: ${colorSettings.lingqBorder}; --lingq_border_learned: ${colorSettings.lingqBorderLearned}; --blue_border: ${colorSettings.blueBorder}; --is_playing_underline: ${colorSettings.playingUnderline}; --background-color: ${colorMode === "dark" ? "#2a2c2e" : "#ffffff"} } .color-picker { height: 15px !important; } #lingqAddonSettings { color: var(--font_color); } #lingqAddonSettingsPopup { background-color: var(--background-color); color: var(--font_color); } .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: calc(var(--article_height) - 70px) !important; } .reader-container-wrapper { height: 100% !important; } /*video viewer*/ .main-footer { grid-area: 3 / 1 / 3 / 1 !important; align-self: end; margin: 10px 0; } .main-content { grid-template-rows: 45px 1fr !important; overflow: hidden; align-items: anchor-center; } .main-content > .main-header { margin: 5px 0; } .main-content > .main-header > section:nth-child(1) { 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) !important; overflow: hidden; pointer-events: auto; } .modal.video-player .modal-content { max-width: var(--width_big) !important; margin: var(--video_margin); } .rc-slider-rail { background-color: dimgrey !important; } .lingq-audio-player { margin-left: 10px; } /*make prev/next page buttons compact*/ .reader-component { grid-template-columns: 0rem 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; } .loadedContent { padding: 0 0 5px 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, .reader-container p .sentence { 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; } `; } // Generate Video mode CSS function generateVideoCSS() { return ` :root { --width_big: calc(100vw - 424px - 10px); --height_big: ${settings.heightBig}px; --video_margin: 0 0 10px 10px !important; } .main-content { grid-area: 1 / 1 / 2 / 2 !important; } .widget-area { grid-area: 1 / 2 / 3 / 2 !important; height: 100% !important; } .main-footer { grid-area: 3 / 2 / 4 / 3 !important; align-self: end; } .section--player.is-expanded { padding: 5px 0px !important; width: 390px !important; margin-left: 10px !important; } .sentence-mode-button { margin: 0 0 10px 0; } .player-wrapper { grid-template-columns: 1fr 40px !important; padding: 0 !important; } .audio-player { padding: 0 0.5rem !important; } `; } // Generate Video2 mode CSS function generateVideo2CSS() { return ` :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; margin: 10px 0; } .modal-container .modls { align-items: end; } `; } // Generate Audio mode CSS function generateAudioCSS() { return ` :root { --height_big: 80px; } .main-content { grid-area: 1 / 1 / 2 / 2 !important; } .widget-area { grid-area: 1 / 2 / 2 / 2 !important; } `; } // Generate Off mode CSS function generateOffModeCSS(colorSettings) { return ` :root { --width_small: 440px; --height_small: 260px; --sentence_height: ${settings.sentenceHeight}px; --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 } /*Sentence mode*/ .loadedContent:has(#sentence-video-player-portal) { grid-template-rows: var(--sentence_height) auto auto 1fr !important; } #sentence-video-player-portal .video-section { width: 100% !important; max-width: none !important; } #sentence-video-player-portal .video-wrapper { height: 100% !important; max-height: none !important; } `; } // Keyboard shortcuts and other functionality function setupKeyboardShortcuts() { document.addEventListener("keydown", function (event) { const targetElement = event.target; const isTextInput = targetElement.type === "text" || targetElement.type === "textarea"; if (isTextInput) return; const shortcuts = { 'q': () => clickElement(".modal-section > div > button:nth-child(2)"), // video full screen toggle 'Q': () => clickElement(".modal-section > div > button:nth-child(2)"), // video full screen toggle 'w': () => clickElement(".audio-player--controllers > div:nth-child(1) > a"), // 5 sec Backward 'e': () => clickElement(".audio-player--controllers > div:nth-child(2) > a"), // 5 sec Forward 'r': () => document.dispatchEvent(new KeyboardEvent("keydown", { key: "k" })), // Make word Known '`': () => focusElement(".reference-input-text"), // Move cursor to reference input 'd': () => clickElement(".dictionary-resources > a:nth-child(1)"), // Open Dictionary 'f': () => clickElement(".dictionary-resources > a:nth-child(1)"), // Open Dictionary 't': () => clickElement(".dictionary-resources > a:nth-last-child(1)"), // Open Translator 'c': () => copySelectedText() // Copy selected text }; if (shortcuts[event.key]) { event.preventDefault(); event.stopPropagation(); shortcuts[event.key](); } }, true); } // Helper function to click an element function clickElement(selector) { const element = document.querySelector(selector); if (element) element.click(); } // Helper function to focus an element function focusElement(selector) { const element = document.querySelector(selector); if (element) { element.focus(); element.setSelectionRange(element.value.length, element.value.length); } } // Helper function to copy selected text function copySelectedText() { const selected_text = document.querySelector(".reference-word"); if (selected_text) { navigator.clipboard.writeText(selected_text.textContent); } } // Custom embedded player function setupYoutubePlayerCustomization() { function replaceNoCookie() { document.querySelectorAll("iframe").forEach(function (iframe) { let src = iframe.getAttribute("src"); if (src && src.includes("disablekb=1")) { 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); } }); } const iframeObserver = new MutationObserver(function (mutationsList) { for (const mutation of mutationsList) { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach((node) => { if (node.nodeName === "IFRAME") { replaceNoCookie(); clickElement('.modal-section.modal-section--head button[title="Expand"]'); } }); } } }); iframeObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["src"] }); } // Scroll customization function setupScrollCustomization() { setTimeout(() => { const readerContainer = document.querySelector(".reader-container"); if (readerContainer) { readerContainer.addEventListener("wheel", (event) => { event.preventDefault(); const delta = event.deltaY; const scrollAmount = 0.3; readerContainer.scrollTop += delta * scrollAmount; }); } }, 3000); } // Focus on playing sentence function setupSentenceFocus() { 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) { observer.observe(container, { attributes: true, subtree: true }); } } // Initialize everything function init() { createUI(); applyStyles(settings.styleType, settings.colorMode); setupKeyboardShortcuts(); setupYoutubePlayerCustomization(); setupScrollCustomization(); setupSentenceFocus(); } // Start the script init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址