Samlib Reader

Делает самиздатовские текста более читабельными: смена фона на тёмный, смена шрифта на verdana, смена цвета текста на светлый, текст выровнен по ширине строки, добавлен автоматический перенос слов. Дополнительно добавлено окно настроек, позволяющее изменить ширину текста, тип и размер шрифта, цвет общего фона страницы, а также принудительно сменить цвет текста, в случае, когда автор вручную установил цвет части текста. Работает также на zhurnal.lib.ru, budclub.ru

  1. // ==UserScript==
  2. // @name Samlib Reader
  3. // @description Делает самиздатовские текста более читабельными: смена фона на тёмный, смена шрифта на verdana, смена цвета текста на светлый, текст выровнен по ширине строки, добавлен автоматический перенос слов. Дополнительно добавлено окно настроек, позволяющее изменить ширину текста, тип и размер шрифта, цвет общего фона страницы, а также принудительно сменить цвет текста, в случае, когда автор вручную установил цвет части текста. Работает также на zhurnal.lib.ru, budclub.ru
  4. // @copyright 2019, Angens (https://openuserjs.org/users/angens)
  5. // @license MIT
  6. // @version 3.0.1
  7. // @match http://samlib.ru/*
  8. // @match http://zhurnal.lib.ru/*
  9. // @match http://budclub.ru/*
  10. // @grant none
  11. // @namespace https://greasyfork.org/users/386214
  12. // ==/UserScript==
  13. //
  14. // ==OpenUserJS==
  15. // @author angens
  16. // ==/OpenUserJS==
  17.  
  18.  
  19.  
  20. /* Функция простановки мягких дефисов в каждое слово */
  21. const rules = [
  22. [/[йъь][аеёиоуыэюяАЕЁИОУЫЭЮЯбвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][аеёиоуыэюяАЕЁИОУЫЭЮЯбвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ]/g, 1],
  23. [/[аеёиоуыэюя][аеёиоуыэюяАЕЁИОУЫЭЮЯ]/g, 1],
  24. [/[бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][аеёиоуыэюяАЕЁИОУЫЭЮЯ][аеёиоуыэюяАЕЁИОУЫЭЮЯ][бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ]/g, 2],
  25. [/[бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][аеёиоуыэюяАЕЁИОУЫЭЮЯ][бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][аеёиоуыэюяАЕЁИОУЫЭЮЯ]/g, 2],
  26. [/[аеёиоуыэюяАЕЁИОУЫЭЮЯ][бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][аеёиоуыэюяАЕЁИОУЫЭЮЯ]/g, 2],
  27. [/[аеёиоуыэюяАЕЁИОУЫЭЮЯ][бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][бвгджзклмнпрстфхцчшщБВГДЖЗКЛМНПРСТ][аеёиоуыэюяАЕЁИОУЫЭЮЯ]/g, 3]
  28. ]
  29. function replacer(match) {
  30. let pos = 0
  31. rules.forEach(rule => {
  32. if(rule[0].test(match)){pos = rule[1]}
  33. })
  34. let result = match
  35. if(pos != 0) {result = match.slice(0, pos) + "­" + match.slice(pos)}
  36. return result
  37. }
  38. function process_text(text) {
  39. let text_2 = text
  40. rules.forEach(rule => {
  41. while(text_2.match(rule[0]) != null) {
  42. text_2 = text_2.replaceAll(rule[0], replacer)
  43. }
  44. })
  45. return text_2
  46. }
  47.  
  48.  
  49. class samlibReader {
  50. constructor() {
  51. this.init_styles()
  52. this.init_text_and_description()
  53. this.init_controls()
  54. let main_container = document.createElement('div')
  55. main_container.classList.add('samlibReader', 'mainContainer')
  56. let sub_container = document.createElement('div')
  57. sub_container.classList.add('samlibReader', 'subContainer')
  58. main_container.append(sub_container, this.description)
  59. sub_container.append(this.controls, this.new_element)
  60. document.body.append(this.styles, main_container)
  61. this.new_element.innerHTML = process_text(this.new_element.innerHTML)
  62. }
  63. /* Парсим блок текста и блок описания */
  64. init_text_and_description() {
  65. let lines = document.querySelectorAll('hr')
  66. let tail = document.createElement('div')
  67. while (lines[lines.length - 3].nextSibling){
  68. tail.append(lines[lines.length - 3].nextSibling)
  69. }
  70. let descLines = tail.querySelectorAll('hr')
  71. // Создаём блок описания
  72. this.description = document.createElement('div')
  73. while (descLines[descLines.length - 2].nextSibling){
  74. this.description.append(descLines[descLines.length - 2].nextSibling)
  75. }
  76. this.description.insertBefore(descLines[descLines.length - 2], this.description.firstChild)
  77. // Создаём блок текста
  78. this.new_element = document.createElement('div')
  79. while (tail.firstChild){
  80. this.new_element.append(tail.firstChild)
  81. }
  82. tail = null
  83. this.new_element.classList.add('samlibReader', 'newElement')
  84. this.description.classList.add('samlibReader', 'description')
  85. }
  86. /* Создём блок настроек */
  87. init_controls() {
  88. this.controls = document.createElement('div')
  89. this.controls.classList.add('samlibReader', 'controls')
  90. let controls_container = document.createElement('div')
  91. controls_container.classList.add('samlibReader', 'controlsContainer')
  92. let controls_overlay = document.createElement('a')
  93. controls_overlay.classList.add('samlibReader', 'controlsOverlay')
  94. this.controls_content = document.createElement('div')
  95. this.controls_content.classList.add('samlibReader', 'controlsContent')
  96. controls_overlay.addEventListener('click', () => {
  97. if(controls_overlay.classList.contains('active')){
  98. this.controls_content.style.setProperty('display', "none")
  99. this.controls_height = this.controls_height_d
  100. controls_overlay.classList.remove('active')
  101. this.set_styles()
  102. }else{
  103. this.controls_content.style.setProperty('display', "flex")
  104. this.controls_height = this.controls_content.offsetHeight
  105. controls_overlay.classList.add('active')
  106. this.set_styles()
  107. }
  108. })
  109. controls_overlay.innerText = "⚙"
  110. this.controls.append(controls_container)
  111. controls_container.append(controls_overlay, this.controls_content)
  112. this.set_controls()
  113. }
  114. set_controls() {
  115. let width_control = this.create_width_control()
  116. let font_control = this.create_font_control()
  117. let background_control = this.create_list_block('Сделать весь фон тёмным')
  118. let font_color_control = this.create_list_block('Принудительно применить стили шрифта')
  119. let font_bgcolor_control = this.create_list_block('Принудительно сменить цвет фона в тексте')
  120. background_control.addEventListener('click', () => {
  121. if(background_control.classList.contains("active")){
  122. this.body_bgcolor = "#212127"
  123. this.body_color = "white"
  124. this.body_link_color = this.link_color
  125. this.body_link_color_v = this.link_color_v
  126. this.set_styles()
  127. } else {
  128. this.body_bgcolor = ""
  129. this.body_color = ""
  130. this.body_link_color = ""
  131. this.body_link_color_v = ""
  132. this.set_styles()
  133. }
  134. })
  135. font_color_control.addEventListener('click', () => {
  136. if(font_color_control.classList.contains("active")){
  137. this.font_font = "inherit"
  138. this.font_size = "inherit"
  139. this.font_color = "inherit"
  140. this.font_align = "justify"
  141. this.set_styles()
  142. } else {
  143. this.font_font = ""
  144. this.font_size = ""
  145. this.font_color = ""
  146. this.font_align = ""
  147. this.set_styles()
  148. }
  149. })
  150. font_bgcolor_control.addEventListener('click', () => {
  151. if(font_bgcolor_control.classList.contains("active")){
  152. this.font_bgcolor = "inherit"
  153. this.set_styles()
  154. } else {
  155. this.font_bgcolor = ""
  156. this.set_styles()
  157. }
  158. })
  159. background_control.click()
  160. this.controls_content.append(width_control, font_control, background_control, font_color_control, font_bgcolor_control)
  161. }
  162. create_width_control() {
  163. let ui = document.createElement('div')
  164. let buttonbox = document.createElement('div')
  165. let sliderbox = document.createElement('div')
  166. let s30 = document.createElement('button')
  167. let s40 = document.createElement('button')
  168. let s50 = document.createElement('button')
  169. let slider = document.createElement('input')
  170. let value = document.createElement('a')
  171. let preview = document.createElement('div')
  172. ui.append(buttonbox, sliderbox)
  173. buttonbox.append(s30, s40, s50)
  174. sliderbox.append(slider, value)
  175. this.new_element.prepend(preview)
  176. slider.setAttribute('type', "range")
  177. slider.min = 1
  178. slider.max = 100
  179. slider.value = 40
  180. slider.step = 1
  181. sliderbox.addEventListener('mouseover', () => {
  182. document.querySelector(".samlibReader.previewBox").style.setProperty('width', `calc(${slider.value}vw - 66px)`)
  183. document.querySelector(".samlibReader.previewBox").style.setProperty('display', "block")
  184. })
  185. sliderbox.addEventListener('mouseleave', () => {
  186. document.querySelector(".samlibReader.previewBox").style.setProperty('display', "none")
  187. })
  188. slider.addEventListener('input', () => {
  189. document.querySelector(".samlibReader.previewBox").style.setProperty('width', `calc(${slider.value}vw - 66px)`)
  190. value.innerText = slider.value + "%"
  191. })
  192. slider.addEventListener('change', () => {
  193. this.text_width = slider.value
  194. value.innerText = slider.value + "%"
  195. this.set_styles()
  196. })
  197. s30.addEventListener('click', () => {
  198. this.text_width = 30
  199. slider.value = 30
  200. value.innerText = 30 + "%"
  201. this.set_styles()
  202. })
  203. s40.addEventListener('click', () => {
  204. this.text_width = 40
  205. slider.value = 40
  206. value.innerText = 40 + "%"
  207. this.set_styles()
  208. })
  209. s50.addEventListener('click', () => {
  210. this.text_width = 50
  211. slider.value = 50
  212. value.innerText = 50 + "%"
  213. this.set_styles()
  214. })
  215. value.innerText = this.text_width
  216. s30.innerText = 30 + "%"
  217. s40.innerText = 40 + "%"
  218. s50.innerText = 50 + "%"
  219. s40.click()
  220. ui.classList.add('samlibReader', 'controls', 'widthControl')
  221. buttonbox.classList.add('samlibReader', 'controls', 'buttonbox')
  222. sliderbox.classList.add('samlibReader', 'controls', 'sliderbox')
  223. preview.classList.add('samlibReader', 'previewBox')
  224. return ui
  225. }
  226. create_font_control() {
  227. let ui = document.createElement('div')
  228. let family_box = document.createElement('div')
  229. let size_box = document.createElement('div')
  230. let family_selector = document.createElement('select')
  231. let family_input = document.createElement('input')
  232. let size_minus = document.createElement('button')
  233. let size_plus = document.createElement('button')
  234. let size_value = document.createElement('a')
  235. ui.append(family_box, size_box)
  236. family_box.append(family_selector, family_input)
  237. size_box.append(size_minus, size_value, size_plus)
  238. ui.classList.add('samlibReader', 'controls', 'fontControl')
  239. family_box.classList.add('samlibReader', 'controls', 'familyBox')
  240. size_box.classList.add('samlibReader', 'controls', 'sizeBox')
  241. let options = ["verdana", "arial", "roboto", "roboto condensed", "собственный"]
  242. options.forEach(optionText => {
  243. let option = document.createElement('option')
  244. option.text = optionText
  245. family_selector.add(option)
  246. })
  247. family_selector.addEventListener('input', () => {
  248. if(family_selector.selectedIndex != options.length-1){
  249. this.text_font = options[family_selector.selectedIndex]
  250. family_input.style.setProperty('display', "none")
  251. this.set_styles()
  252. } else {
  253. this.text_font = family_input.value
  254. family_input.style.setProperty('display', "block")
  255. this.set_styles()
  256. }
  257. })
  258. family_input.addEventListener('input', () => {
  259. this.text_font = family_input.value
  260. this.set_styles()
  261. })
  262. size_plus.addEventListener('click', () => {
  263. this.text_size += 1
  264. size_value.innerText = this.text_size
  265. this.set_styles()
  266. })
  267. size_minus.addEventListener('click', () => {
  268. this.text_size -= 1
  269. size_value.innerText = this.text_size
  270. this.set_styles()
  271. })
  272. family_input.style.setProperty('display', "none")
  273. size_plus.innerText = "+"
  274. size_minus.innerText = "−"
  275. size_value.innerText = this.text_size
  276. return ui
  277. }
  278. create_list_block(text) {
  279. let ui = document.createElement('div')
  280. let input = document.createElement('input')
  281. let label = document.createElement('label')
  282. label.innerText = text
  283. input.setAttribute('type', "checkbox")
  284. ui.append(input, label)
  285. ui.classList.add('samlibReader', 'controls', 'listBlock')
  286. ui.addEventListener('click', () => {
  287. if(ui.classList.contains('active')) {
  288. ui.classList.remove('active')
  289. input.checked = false
  290. } else {
  291. ui.classList.add('active')
  292. input.checked = true
  293. }
  294. })
  295. return ui
  296. }
  297. /* Создаём блок стилей */
  298. init_styles() {
  299. this.styles = document.createElement('style')
  300. this.controls_width = 40
  301. this.controls_height = 40
  302. this.controls_height_d = 40
  303. this.controls_content_width = 400
  304. this.background_color = "#212127"
  305. this.text_width = 100
  306. this.text_font = "verdana"
  307. this.text_size = 18
  308. this.font_font = ""
  309. this.font_size = ""
  310. this.font_color = ""
  311. this.font_bgcolor = ""
  312. this.font_align = ""
  313. this.link_color = "#A4A4AA"
  314. this.link_color_v = "#FEE6FB"
  315. this.body_bgcolor = ""
  316. this.body_color = ""
  317. this.body_link_color = ""
  318. this.body_link_color_v = ""
  319. this.set_styles()
  320. }
  321. set_styles() {
  322. this.styles.innerHTML = `
  323. body{
  324. background-color: ${this.body_bgcolor};
  325. color: ${this.body_color};
  326. }
  327. a[href] {
  328. color: ${this.body_link_color};
  329. }
  330. a[href]:visited {
  331. color: ${this.body_link_color_v};
  332. }
  333. font[color="#555555"] {
  334. color: ${this.body_color};
  335. }
  336. .samlibReader.mainContainer{
  337. display: flex;
  338. flex-direction: column;
  339. }
  340. .samlibReader.subContainer{
  341. font-family: ${this.text_font};
  342. font-size: ${this.text_size};
  343. background-color: ${this.background_color};
  344. color: wheat;
  345. display: flex;
  346. flex-direction: row;
  347. position: relative;
  348. }
  349. .samlibReader a[href]{
  350. color: ${this.link_color};
  351. }
  352. .samlibReader a[href]:visited{
  353. color: ${this.link_color_v};
  354. }
  355. .samlibReader font{
  356. color: ${this.font_color};
  357. font-size: ${this.font_size};
  358. font-family: ${this.font_font};
  359. }
  360. .samlibReader.newElement{
  361. margin: 0 auto 0 auto;
  362. text-align: justify;
  363. width: ${this.text_width}vw;
  364. padding-right: ${this.controls_width}px;
  365. }
  366. .samlibReader.newElement *{
  367. font-family: ${this.font_font};
  368. font-size: ${this.font_size};
  369. background-color: ${this.font_bgcolor};
  370. color: ${this.font_color};
  371. text-align: ${this.font_align};
  372. overflow-wrap: break-word;
  373. }
  374. .samlibReader.newElement img{
  375. max-width: 100%;
  376. height: auto;
  377. }
  378. .samlibReader.previewBox{
  379. display: none;
  380. box-sizing: border-box;
  381. left: ${this.controls_width}px;
  382. right: 0px;
  383. margin: auto;
  384. pointer-events: none;
  385. position: absolute;
  386. border: 5px solid cyan;
  387. border-style: solid;
  388. border-color: cyan;
  389. border-width: 0px 5px 0px 5px;
  390. height: 100%;
  391. }
  392. .samlibReader.controls > * {
  393. font-family: Segoe UI;
  394. font-size: 14px;
  395. color: white;
  396. user-select: none;
  397. margin: 1px;
  398. }
  399. .samlibReader.controls{
  400. width: ${this.controls_width}px;
  401. }
  402. .samlibReader.controls > button {
  403. height: 20px;
  404. border-width: 1px 1px 0px 1px;
  405. border-style: solid;
  406. border-color: white;
  407. background-color: transparent;
  408. border-radius: 5px;
  409. }
  410. .samlibReader.controls > button:hover {
  411. border-width: 2px 2px 0px 2px;
  412. }
  413. .samlibReader.controls > button:active {
  414. border-width: 2px 2px 0px 2px;
  415. background-color: rgba(255,255,255,0.2);
  416. }
  417. .samlibReader.controlsContainer{
  418. width: ${this.controls_width}px;
  419. position: sticky;
  420. top: 30%;
  421. display: flex;
  422. flex-direction: row;
  423. }
  424. .samlibReader.controlsOverlay{
  425. width: ${this.controls_width}px;
  426. height: ${this.controls_height}px;
  427. border-color: white;
  428. border-width: 1px 1px 1px 1px;
  429. border-style: solid;
  430. border-radius: 15px 15px 15px 15px;
  431. font-size: ${this.controls_width - 10}px;
  432. text-align: center;
  433. }
  434. .samlibReader.controlsOverlay:hover{
  435. background-color: rgba(255,255,255,0.1);
  436. }
  437. .samlibReader.controlsOverlay.active{
  438. height: ${this.controls_height - 2}px;
  439. border-color: white;
  440. border-width: 1px 0px 1px 1px;
  441. border-style: solid;
  442. border-radius: 15px 0px 0px 15px;
  443. }
  444. .samlibReader.controlsContent{
  445. width: ${this.controls_content_width}px;
  446. background-color: ${this.background_color};
  447. position: absolute;
  448. left: ${this.controls_width}px;
  449. display: none;
  450. flex-direction: column;
  451. border-color: white;
  452. border-width: 1px 1px 1px 0px;
  453. border-style: solid;
  454. border-radius: 0px 15px 15px 0px;
  455. }
  456. .samlibReader.controls.widthControl{
  457. width: ${this.controls_content_width}px;
  458. display: flex;
  459. flex-direction: column;
  460. }
  461. .samlibReader.controls.buttonbox{
  462. display: flex;
  463. flex-direction: row;
  464. }
  465. .samlibReader.controls.sliderbox{
  466. width: ${this.controls_content_width}px;
  467. display: flex;
  468. flex-direction: row;
  469. }
  470. .samlibReader.controls.sliderbox > input{
  471. flex-grow: 1;
  472. }
  473. .samlibReader.controls.sliderbox > a{
  474. margin: 0px 2px 0px 2px;
  475. }
  476. .samlibReader.controls.fontControl{
  477. width: ${this.controls_content_width}px;
  478. display: flex;
  479. flex-direction: column;
  480. }
  481. .samlibReader.controls.familyBox{
  482. display: flex;
  483. flex-direction: row;
  484. }
  485. .samlibReader.controls.familyBox > * {
  486. color: black;
  487. }
  488. .samlibReader.controls.sizeBox{
  489. display: flex;
  490. flex-direction: row;
  491. }
  492. .samlibReader.controls.listBlock{
  493. width: ${this.controls_content_width}px;
  494. display: flex;
  495. flex-direction: row;
  496. white-space: nowrap;
  497. }
  498. .samlibReader.controls.listBlock:hover{
  499. text-decoration: underline;
  500. }
  501. .samlibReader.controls.listBlock.active{
  502. text-decoration: underline;
  503. }
  504. .samlibReader.controls.listBlock > input{
  505. appearance: none;
  506. width: 20px;
  507. height: 20px;
  508. border-radius: 20px;
  509. background-color: transparent;
  510. border-style: solid;
  511. border-width: 1px 1px 0px 1px;
  512. border-color: white;
  513. }
  514. .samlibReader.controls.listBlock > input:hover{
  515. border-width: 2px 2px 0px 2px;
  516. }
  517. .samlibReader.controls.listBlock > input:checked{
  518. border-width: 2px 2px 0px 2px;
  519. background-color: #005CC8;
  520. }
  521. `
  522. }
  523. }
  524.  
  525.  
  526. function main() {
  527. let mRef = /http:\/\/(zhurnal\.lib\.ru|samlib\.ru|budclub\.ru)\/.\/.+\/.+\.shtml/
  528. let exRef = /http:\/\/(zhurnal\.lib\.ru|samlib\.ru|budclub\.ru)\/.\/.+\/(index|stat).+\.shtml/
  529. let currRef = window.location.href
  530. if (mRef.test(currRef) && !exRef.test(currRef)) {
  531. let a = new samlibReader()
  532. }
  533. }
  534.  
  535. document.addEventListener("DOMContentLoaded", main())