Local TXT Reader

Enhanced reading experience for local txt files with Element UI style and improved themes

  1. // ==UserScript==
  2. // @name Local TXT Reader
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.5
  5. // @description Enhanced reading experience for local txt files with Element UI style and improved themes
  6. // @author JiuYou2020
  7. // @license MIT
  8. // @match file:///*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // Ensure the script runs only on .txt files
  17. if (window.location.pathname.endsWith('.txt')) {
  18. // Set up styles
  19. const style = document.createElement('style');
  20. style.innerHTML = `
  21. body {
  22. margin: 0;
  23. padding: 0;
  24. display: flex;
  25. height: 100vh;
  26. overflow: hidden;
  27. transition: background-color 0.3s, color 0.3s;
  28. background-size: cover;
  29. background-position: center;
  30. }
  31. .outer-container {
  32. flex: 1;
  33. overflow-y: auto;
  34. scroll-behavior: smooth;
  35. padding: 20px;
  36. }
  37. .content-container {
  38. width: 55%;
  39. margin: 0 auto;
  40. transition: font-size 0.3s, line-height 0.3s, background-color 0.3s, color 0.3s;
  41. padding: 20px;
  42. border-radius: 10px;
  43. }
  44. .content-container p {
  45. text-indent: 2em;
  46. margin-top: 1em;
  47. }
  48. #settingsBtn {
  49. position: fixed;
  50. right: 20px;
  51. bottom: 20px;
  52. z-index: 1000;
  53. padding: 12px;
  54. background-color: #409EFF;
  55. color: white;
  56. border: none;
  57. border-radius: 4px;
  58. cursor: pointer;
  59. font-size: 16px;
  60. box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
  61. transition: background-color 0.3s;
  62. display: flex;
  63. align-items: center;
  64. justify-content: center;
  65. }
  66. #settingsBtn:hover {
  67. background-color: #66b1ff;
  68. }
  69. #settingsPanel {
  70. display: none;
  71. position: fixed;
  72. right: 20px;
  73. bottom: 80px;
  74. background-color: white;
  75. border: 1px solid #EBEEF5;
  76. border-radius: 4px;
  77. padding: 20px;
  78. z-index: 1000;
  79. box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
  80. }
  81. #settingsPanel div {
  82. margin-bottom: 15px;
  83. }
  84. #settingsPanel button {
  85. margin-right: 5px;
  86. padding: 8px 15px;
  87. border: 1px solid #DCDFE6;
  88. border-radius: 4px;
  89. cursor: pointer;
  90. transition: all 0.3s;
  91. background-color: white;
  92. color: #606266;
  93. }
  94. #settingsPanel button:hover {
  95. color: #409EFF;
  96. border-color: #c6e2ff;
  97. background-color: #ecf5ff;
  98. }
  99. .theme-btn {
  100. width: 30px;
  101. height: 30px;
  102. border-radius: 4px;
  103. }
  104. #classicTheme { background-color: #e7e3d8; }
  105. #whiteTheme { background-color: #ffffff; border: 1px solid #DCDFE6 !important; }
  106. #darkTheme { background-color: #1e1e1e; }
  107. .font-btn, .line-height-btn {
  108. background-color: #409EFF !important;
  109. color: white !important;
  110. border: none !important;
  111. }
  112. .font-btn:hover, .line-height-btn:hover {
  113. background-color: #66b1ff !important;
  114. color: white !important;
  115. }
  116. #resetDefault {
  117. background-color: #F56C6C !important;
  118. color: white !important;
  119. border: none !important;
  120. }
  121. #resetDefault:hover {
  122. background-color: #f78989 !important;
  123. }
  124. #customBgInput {
  125. display: none;
  126. }
  127. #customBgLabel {
  128. display: inline-block;
  129. padding: 8px 15px;
  130. background-color: #67C23A;
  131. color: white;
  132. border-radius: 4px;
  133. cursor: pointer;
  134. transition: background-color 0.3s;
  135. }
  136. #customBgLabel:hover {
  137. background-color: #85ce61;
  138. }
  139. `;
  140. document.head.appendChild(style);
  141.  
  142. // Get the text content
  143. const text = document.body.textContent.trim();
  144.  
  145. // Split the text into paragraphs by newlines
  146. const paragraphs = text.split(/\n+/).map(paragraph => paragraph.trim());
  147.  
  148. // Clear the body content
  149. document.body.innerHTML = '';
  150.  
  151. // Create the outer container
  152. const outerContainer = document.createElement('div');
  153. outerContainer.className = 'outer-container';
  154. document.body.appendChild(outerContainer);
  155.  
  156. // Create the content container
  157. const contentContainer = document.createElement('div');
  158. contentContainer.className = 'content-container';
  159. outerContainer.appendChild(contentContainer);
  160.  
  161. // Add paragraphs to the content container
  162. paragraphs.forEach(paragraph => {
  163. if (paragraph) { // Only add non-empty paragraphs
  164. const p = document.createElement('p');
  165. p.textContent = paragraph;
  166. contentContainer.appendChild(p);
  167. }
  168. });
  169.  
  170. // Create settings button
  171. const settingsBtn = document.createElement('button');
  172. settingsBtn.id = 'settingsBtn';
  173. settingsBtn.textContent = '设置';
  174. document.body.appendChild(settingsBtn);
  175.  
  176. // Create settings panel
  177. const settingsPanel = document.createElement('div');
  178. settingsPanel.id = 'settingsPanel';
  179. settingsPanel.innerHTML = `
  180. <div>
  181. 大小:
  182. <button id="decreaseFont" class="font-btn">-</button>
  183. <span id="currentFontSize"></span>
  184. <button id="increaseFont" class="font-btn">+</button>
  185. </div>
  186. <div>
  187. 行高:
  188. <button id="decreaseLineHeight" class="line-height-btn">-</button>
  189. <span id="currentLineHeight"></span>
  190. <button id="increaseLineHeight" class="line-height-btn">+</button>
  191. </div>
  192. <div>
  193. 主题:
  194. <button id="classicTheme" class="theme-btn" title="经典"></button>
  195. <button id="whiteTheme" class="theme-btn" title="纯白"></button>
  196. <button id="darkTheme" class="theme-btn" title="暗黑"></button>
  197. </div>
  198. <div>
  199. <label for="customBgInput" id="customBgLabel">自定义背景</label>
  200. <input type="file" id="customBgInput" accept="image/*">
  201. </div>
  202. <div>
  203. <button id="resetDefault">恢复默认设置</button>
  204. </div>
  205. `;
  206. document.body.appendChild(settingsPanel);
  207.  
  208. // Define themes
  209. const themes = {
  210. classic: { bg: '#e7e3d8', text: '#333333', contentBg: '#f1ede4' },
  211. white: { bg: '#ffffff', text: '#333333', contentBg: '#ffffff' },
  212. dark: { bg: '#1e1e1e', text: '#ffffff', contentBg: 'rgba(30, 30, 30, 0.8)' }
  213. };
  214.  
  215. // Function to save settings
  216. function saveSettings() {
  217. const settings = {
  218. fontSize: contentContainer.style.fontSize,
  219. lineHeight: contentContainer.style.lineHeight,
  220. theme: currentTheme,
  221. customBg: document.body.style.backgroundImage,
  222. btnPosition: {
  223. right: settingsBtn.style.right,
  224. bottom: settingsBtn.style.bottom
  225. }
  226. };
  227. GM_setValue('readerSettings', JSON.stringify(settings));
  228. }
  229.  
  230. // Function to load settings
  231. function loadSettings() {
  232. const savedSettings = GM_getValue('readerSettings', null);
  233. if (savedSettings) {
  234. const settings = JSON.parse(savedSettings);
  235. contentContainer.style.fontSize = settings.fontSize || '19px';
  236. contentContainer.style.lineHeight = settings.lineHeight || '1.5';
  237. applyTheme(settings.theme || 'classic');
  238. if (settings.customBg) {
  239. document.body.style.backgroundImage = settings.customBg;
  240. }
  241. if (settings.btnPosition) {
  242. settingsBtn.style.right = settings.btnPosition.right;
  243. settingsBtn.style.bottom = settings.btnPosition.bottom;
  244. }
  245. } else {
  246. resetToDefault();
  247. }
  248. }
  249.  
  250. // Function to apply theme
  251. function applyTheme(theme) {
  252. if (theme !== 'custom') {
  253. document.body.style.backgroundColor = themes[theme].bg;
  254. document.body.style.color = themes[theme].text;
  255. contentContainer.style.backgroundColor = themes[theme].contentBg;
  256. document.body.style.backgroundImage = '';
  257. }
  258. currentTheme = theme;
  259. }
  260.  
  261. // Function to reset to default settings
  262. function resetToDefault() {
  263. contentContainer.style.fontSize = '19px';
  264. contentContainer.style.lineHeight = '1.5';
  265. applyTheme('classic');
  266. document.body.style.backgroundImage = '';
  267. settingsBtn.style.right = '20px';
  268. settingsBtn.style.bottom = '20px';
  269. updateSettingsDisplay();
  270. saveSettings();
  271. }
  272.  
  273. // Load settings
  274. let currentTheme = 'classic';
  275. loadSettings();
  276.  
  277. // Function to save the current scroll position
  278. function saveScrollPosition() {
  279. const scrollPosition = outerContainer.scrollTop;
  280. const fileName = window.location.pathname.split('/').pop();
  281. GM_setValue(fileName, scrollPosition);
  282. }
  283.  
  284. // Function to load the saved scroll position
  285. function loadScrollPosition() {
  286. const fileName = window.location.pathname.split('/').pop();
  287. const savedPosition = GM_getValue(fileName, 0);
  288. outerContainer.scrollTop = savedPosition;
  289. }
  290.  
  291. // Load the saved position when the page loads
  292. loadScrollPosition();
  293.  
  294. // Save the position when the user scrolls
  295. outerContainer.addEventListener('scroll', saveScrollPosition);
  296.  
  297. // Handle arrow key navigation
  298. document.addEventListener('keydown', function(e) {
  299. const scrollAmount = 100; // Adjust this value to change scroll speed
  300. if (e.key === 'ArrowUp') {
  301. outerContainer.scrollBy({top: -scrollAmount, behavior: 'smooth'});
  302. e.preventDefault();
  303. } else if (e.key === 'ArrowDown') {
  304. outerContainer.scrollBy({top: scrollAmount, behavior: 'smooth'});
  305. e.preventDefault();
  306. }
  307. });
  308.  
  309. // Toggle settings panel
  310. settingsBtn.addEventListener('click', () => {
  311. settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'block' : 'none';
  312. updatePanelPosition();
  313. });
  314.  
  315. // Make settings button draggable
  316. let isDragging = false;
  317. let dragOffsetX, dragOffsetY;
  318.  
  319. settingsBtn.addEventListener('mousedown', (e) => {
  320. isDragging = true;
  321. dragOffsetX = e.clientX - settingsBtn.offsetLeft;
  322. dragOffsetY = e.clientY - settingsBtn.offsetTop;
  323. });
  324.  
  325. document.addEventListener('mousemove', (e) => {
  326. if (isDragging) {
  327. const newX = e.clientX - dragOffsetX;
  328. const newY = e.clientY - dragOffsetY;
  329. settingsBtn.style.right = `${document.body.clientWidth - newX - settingsBtn.offsetWidth}px`;
  330. settingsBtn.style.bottom = `${document.body.clientHeight - newY - settingsBtn.offsetHeight}px`;
  331. updatePanelPosition();
  332. }
  333. });
  334.  
  335. document.addEventListener('mouseup', () => {
  336. if (isDragging) {
  337. isDragging = false;
  338. saveSettings();
  339. }
  340. });
  341.  
  342. // Update settings panel position
  343. function updatePanelPosition() {
  344. const btnRect = settingsBtn.getBoundingClientRect();
  345. settingsPanel.style.right = settingsBtn.style.right;
  346. settingsPanel.style.bottom = `${parseFloat(settingsBtn.style.bottom) + btnRect.height + 10}px`;
  347. }
  348.  
  349. // Font size controls
  350. document.getElementById('decreaseFont').addEventListener('click', () => {
  351. const currentSize = parseInt(contentContainer.style.fontSize);
  352. if (currentSize > 10) {
  353. contentContainer.style.fontSize = (currentSize - 1) + 'px';
  354. updateSettingsDisplay();
  355. saveSettings();
  356. }
  357. });
  358.  
  359. document.getElementById('increaseFont').addEventListener('click', () => {
  360. const currentSize = parseInt(contentContainer.style.fontSize);
  361. if (currentSize < 30) {
  362. contentContainer.style.fontSize = (currentSize + 1) + 'px';
  363. updateSettingsDisplay();
  364. saveSettings();
  365. }
  366. });
  367.  
  368. // Line height controls
  369. document.getElementById('decreaseLineHeight').addEventListener('click', () => {
  370. const currentHeight = parseFloat(contentContainer.style.lineHeight);
  371. if (currentHeight > 1) {
  372. contentContainer.style.lineHeight = (currentHeight - 0.1).toFixed(1);
  373. updateSettingsDisplay();
  374. saveSettings();
  375. }
  376. });
  377.  
  378. document.getElementById('increaseLineHeight').addEventListener('click', () => {
  379. const currentHeight = parseFloat(contentContainer.style.lineHeight);
  380. if (currentHeight < 3) {
  381. contentContainer.style.lineHeight = (currentHeight + 0.1).toFixed(1);
  382. updateSettingsDisplay();
  383. saveSettings();
  384. }
  385. });
  386.  
  387. // Theme controls
  388. document.getElementById('classicTheme').addEventListener('click', () => {
  389. applyTheme('classic');
  390. saveSettings();
  391. });
  392.  
  393. document.getElementById('whiteTheme').addEventListener('click', () => {
  394. applyTheme('white');
  395. saveSettings();
  396. });
  397.  
  398. document.getElementById('darkTheme').addEventListener('click', () => {
  399. applyTheme('dark');
  400. saveSettings();
  401. });
  402.  
  403. // Custom background
  404. document.getElementById('customBgInput').addEventListener('change', (e) => {
  405. const file = e.target.files[0];
  406. if (file) {
  407. const reader = new FileReader();
  408. reader.onload = function(e) {
  409. document.body.style.backgroundImage = `url(${e.target.result})`;
  410. currentTheme = 'custom';
  411. saveSettings();
  412. }
  413. reader.readAsDataURL(file);
  414. }
  415. });
  416.  
  417. // Reset to default
  418. document.getElementById('resetDefault').addEventListener('click', resetToDefault);
  419.  
  420. // Update display of current settings
  421. function updateSettingsDisplay() {
  422. document.getElementById('currentFontSize').textContent = contentContainer.style.fontSize;
  423. document.getElementById('currentLineHeight').textContent = contentContainer.style.lineHeight;
  424. }
  425.  
  426. updateSettingsDisplay();
  427. }
  428. })();