// ==UserScript== // @name AuthorTodayImprover // @name:ru AuthorTodayImprover // @namespace 90h.yy.zz // @version 0.1.1 // @author Ox90 // @include https://author.today/reader/* // @description The script allows you to select text and use the context menu when reading a book. // @description:ru Скрипт возвращает возможность выделять текст и использовать контекстное меню при чтении на сайте. // @run-at document-start // @license MIT // @downloadURL none // ==/UserScript== (function start() { "use strict"; /** * Старт скрипта. Вызывается после загрузки DOM-дерева. * * @return void */ function start() { [ "#reader.page-content", "div.text-wrapper" ].forEach(function(selector) { const el = document.querySelector(selector); el && processElement(el); }); } /** * Обрабатывает переданный DOM-элемент. * Если у элемента нет класса loading, то он обрабатывается немедленно, * в ином случае на него вешается наблюдатель, а обратотка откладывается. * * @param Element el Обрабатываемый DOM-элемент * * @return void */ function processElement(el) { if (!el.classList.contains("loading")) { fixStyles(removeEvents(el)); return; } (new MutationObserver(function(mutations, observer) { observer.disconnect(); processElement(el); })).observe(el, { attributes: true, attributeFilter: [ "class" ] }); } /** * Подменяет переданный элемент новым, который создается через перенос его детей переднанного элемента в новый * и копирование атрибутов из старого элемента в новый за исключением некоторых атрибутов, которые * блокируют привычную функциональность как выделение текста и контекстное меню. * el.cloneNode(true) здесь не используется, чтобы избежать потери обработчиков у вложенных элементов. * * @param Element el DOM-элемент для замены * * @return Element Вновь созданный элемент */ function removeEvents(el) { const evil_attributes = [ "oncontextmenu", "onmousedown", "onselectstart", "unselectable" ]; const new_el = document.createElement(el.tagName); for (let attr of el.attributes) { if (!evil_attributes.includes(attr.name)) { new_el.setAttribute(attr.name, attr.value); } } while (el.childNodes[0]) { new_el.appendChild(el.childNodes[0]); } el.replaceWith(new_el); return new_el; } /** * Исправляет стили элемента таким образом, чтобы они не блокировали привычную для пользователя функциональность. * * @param Element el DOM-элемент для исравления * * @return void */ function fixStyles(el) { el.classList.remove("noselect"); el.style.userSelect = "text"; } //---------- if (document.readyState === "loading") window.addEventListener("DOMContentLoaded", start); else start(); }());