您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The script allows you to select text and use the context menu when reading a book.
- // ==UserScript==
- // @name AuthorTodayImprover
- // @name:ru AuthorTodayImprover
- // @namespace 90h.yy.zz
- // @version 0.2.0
- // @author Ox90
- // @match 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
- // ==/UserScript==
- (function start() {
- "use strict";
- const reader_selectors = [ "#reader.page-content", "div.text-wrapper" ];
- let active = true;
- /**
- * Старт скрипта. Вызывается после загрузки DOM-дерева.
- *
- * @return void
- */
- function run() {
- if (testNoselect()) {
- active = getValue("disabled", "no") !== "yes";
- if (active) processReaderElements();
- updateToolbar();
- }
- }
- /**
- * Обрабатывает все возможные элементы читалки по списку селекторов
- *
- * @return void
- */
- function processReaderElements() {
- reader_selectors.forEach(sel => {
- const el = document.querySelector(sel);
- 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;
- }
- /**
- * Тестирует стили на наличие запрета выделять текст
- *
- * @return bool
- */
- function testNoselect() {
- return reader_selectors.some(sel => !!document.querySelector(sel + ".noselect"));
- }
- /**
- * Исправляет стили элемента таким образом, чтобы они не блокировали привычную для пользователя функциональность.
- *
- * @param Element el DOM-элемент для исравления
- *
- * @return void
- */
- function fixStyles(el) {
- el.classList.remove("noselect");
- el.style.userSelect = "text";
- }
- /**
- * Добавляет кнопку скрипта на тулбар или обновляет, если уже добавлена
- *
- * @return void
- */
- function updateToolbar() {
- let mobile = false;
- let tb = document.querySelector("section>header>nav>.navbar-right");
- if (!tb) {
- mobile = true;
- tb = document.querySelector("div.main-navbar div.right");
- }
- if (!tb) return;
- const bt = (() => {
- let el = tb.querySelector(".ati-button");
- if (!el) {
- if (!mobile) {
- el = document.createElement("button");
- el.classList.add("btn", "btn-brd", "btn-only-icon", "pull-left", "mr", "hint-bottom");
- el.setAttribute("style", "padding:0 !important");
- } else {
- el = document.createElement("a");
- el.classList.add("link", "icon-only", "open-popup");
- el.href = "";
- }
- el.classList.add("ati-button");
- el.textContent = "ATI";
- el.addEventListener("click", event => {
- event.preventDefault();
- active = !active;
- setValue("disabled", active && "no" || "yes");
- updateToolbar();
- if (active) {
- processReaderElements();
- } else {
- document.location.reload();
- }
- });
- }
- return el;
- })();
- if (!mobile) {
- bt.style.color = active && "green" || "gray";
- } else {
- bt.style.color = active && "white" || "darkgray";
- }
- bt.title = "ATI: Копирование текста " + (active && "разблокировано" || "заблокировано");
- tb.prepend(bt);
- }
- /**
- * Извлекает и возвращает сохраненный элемент данных из локального хранилища
- *
- * @param string name Имя
- * @param string defval Значение по умолчанию
- *
- * @return string
- */
- function getValue(name, defval) {
- if (localStorage !== undefined) {
- const val = localStorage.getItem("ati-" + name);
- if (val !== null) return val;
- }
- return defval;
- }
- /**
- * Сохраняет переданный элемент данных в локальном хранилище
- *
- * @param string name Имя
- * @param mixed value Значение
- *
- * @return void
- */
- function setValue(name, value) {
- if (localStorage !== undefined) {
- localStorage.setItem("ati-" + name, value);
- }
- }
- //----------
- if (document.readyState === "loading") window.addEventListener("DOMContentLoaded", run);
- else run();
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址