AuthorTodayImprover

The script allows you to select text and use the context menu when reading a book.

安装此脚本
作者推荐脚本

您可能也喜欢AuthorTodayExtractor

安装此脚本
  1. // ==UserScript==
  2. // @name AuthorTodayImprover
  3. // @name:ru AuthorTodayImprover
  4. // @namespace 90h.yy.zz
  5. // @version 0.2.0
  6. // @author Ox90
  7. // @match https://author.today/reader/*
  8. // @description The script allows you to select text and use the context menu when reading a book.
  9. // @description:ru Скрипт возвращает возможность выделять текст и использовать контекстное меню при чтении на сайте.
  10. // @run-at document-start
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function start() {
  15. "use strict";
  16.  
  17. const reader_selectors = [ "#reader.page-content", "div.text-wrapper" ];
  18.  
  19. let active = true;
  20.  
  21. /**
  22. * Старт скрипта. Вызывается после загрузки DOM-дерева.
  23. *
  24. * @return void
  25. */
  26. function run() {
  27. if (testNoselect()) {
  28. active = getValue("disabled", "no") !== "yes";
  29. if (active) processReaderElements();
  30. updateToolbar();
  31. }
  32. }
  33.  
  34. /**
  35. * Обрабатывает все возможные элементы читалки по списку селекторов
  36. *
  37. * @return void
  38. */
  39. function processReaderElements() {
  40. reader_selectors.forEach(sel => {
  41. const el = document.querySelector(sel);
  42. el && processElement(el);
  43. });
  44. }
  45.  
  46. /**
  47. * Обрабатывает переданный DOM-элемент.
  48. * Если у элемента нет класса loading, то он обрабатывается немедленно,
  49. * в ином случае на него вешается наблюдатель, а обработка откладывается.
  50. *
  51. * @param Element el Обрабатываемый DOM-элемент
  52. *
  53. * @return void
  54. */
  55. function processElement(el) {
  56. if (!el.classList.contains("loading")) {
  57. fixStyles(removeEvents(el));
  58. return;
  59. }
  60. (new MutationObserver(function(mutations, observer) {
  61. observer.disconnect();
  62. processElement(el);
  63. })).observe(el, { attributes: true, attributeFilter: [ "class" ] });
  64. }
  65.  
  66. /**
  67. * Подменяет переданный элемент новым. В новый элемент переносятся дети из старого элемента
  68. * и производится копирование атрибутов из старого элемента в новый за исключением атрибутов,
  69. * которые блокируют привычную функциональность как: выделение текста, вызов контекстное меню.
  70. * el.cloneNode(true) здесь не используется, чтобы избежать потери обработчиков у вложенных элементов.
  71. *
  72. * @param Element el DOM-элемент для замены
  73. *
  74. * @return Element Вновь созданный элемент
  75. */
  76. function removeEvents(el) {
  77. const evil_attributes = [
  78. "oncontextmenu", "onmousedown", "onselectstart", "unselectable"
  79. ];
  80.  
  81. const new_el = document.createElement(el.tagName);
  82. for (let attr of el.attributes) {
  83. if (!evil_attributes.includes(attr.name)) {
  84. new_el.setAttribute(attr.name, attr.value);
  85. }
  86. }
  87. while (el.childNodes[0]) {
  88. new_el.appendChild(el.childNodes[0]);
  89. }
  90. el.replaceWith(new_el);
  91. return new_el;
  92. }
  93.  
  94. /**
  95. * Тестирует стили на наличие запрета выделять текст
  96. *
  97. * @return bool
  98. */
  99. function testNoselect() {
  100. return reader_selectors.some(sel => !!document.querySelector(sel + ".noselect"));
  101. }
  102.  
  103. /**
  104. * Исправляет стили элемента таким образом, чтобы они не блокировали привычную для пользователя функциональность.
  105. *
  106. * @param Element el DOM-элемент для исравления
  107. *
  108. * @return void
  109. */
  110. function fixStyles(el) {
  111. el.classList.remove("noselect");
  112. el.style.userSelect = "text";
  113. }
  114.  
  115. /**
  116. * Добавляет кнопку скрипта на тулбар или обновляет, если уже добавлена
  117. *
  118. * @return void
  119. */
  120. function updateToolbar() {
  121. let mobile = false;
  122. let tb = document.querySelector("section>header>nav>.navbar-right");
  123. if (!tb) {
  124. mobile = true;
  125. tb = document.querySelector("div.main-navbar div.right");
  126. }
  127. if (!tb) return;
  128. const bt = (() => {
  129. let el = tb.querySelector(".ati-button");
  130. if (!el) {
  131. if (!mobile) {
  132. el = document.createElement("button");
  133. el.classList.add("btn", "btn-brd", "btn-only-icon", "pull-left", "mr", "hint-bottom");
  134. el.setAttribute("style", "padding:0 !important");
  135. } else {
  136. el = document.createElement("a");
  137. el.classList.add("link", "icon-only", "open-popup");
  138. el.href = "";
  139. }
  140. el.classList.add("ati-button");
  141. el.textContent = "ATI";
  142. el.addEventListener("click", event => {
  143. event.preventDefault();
  144. active = !active;
  145. setValue("disabled", active && "no" || "yes");
  146. updateToolbar();
  147. if (active) {
  148. processReaderElements();
  149. } else {
  150. document.location.reload();
  151. }
  152. });
  153. }
  154. return el;
  155. })();
  156. if (!mobile) {
  157. bt.style.color = active && "green" || "gray";
  158. } else {
  159. bt.style.color = active && "white" || "darkgray";
  160. }
  161. bt.title = "ATI: Копирование текста " + (active && "разблокировано" || "заблокировано");
  162. tb.prepend(bt);
  163. }
  164.  
  165. /**
  166. * Извлекает и возвращает сохраненный элемент данных из локального хранилища
  167. *
  168. * @param string name Имя
  169. * @param string defval Значение по умолчанию
  170. *
  171. * @return string
  172. */
  173. function getValue(name, defval) {
  174. if (localStorage !== undefined) {
  175. const val = localStorage.getItem("ati-" + name);
  176. if (val !== null) return val;
  177. }
  178. return defval;
  179. }
  180.  
  181. /**
  182. * Сохраняет переданный элемент данных в локальном хранилище
  183. *
  184. * @param string name Имя
  185. * @param mixed value Значение
  186. *
  187. * @return void
  188. */
  189. function setValue(name, value) {
  190. if (localStorage !== undefined) {
  191. localStorage.setItem("ati-" + name, value);
  192. }
  193. }
  194.  
  195. //----------
  196.  
  197. if (document.readyState === "loading") window.addEventListener("DOMContentLoaded", run);
  198. else run();
  199.  
  200. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址