// ==UserScript== // @name 摸鱼小说阅读器 Loafing-Reader // @namespace http://tampermonkey.net/ // @version 1.0 // @description 内嵌浏览器里用来上班摸鱼看小说 // @author HanaYabuki // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @downloadURL none // ==/UserScript== (function () { const cssText = ` .loafing-reader { margin: 0; padding: 0; box-sizing: content-box; font-size: 12px; color: #222; } #lf-panel { height: 27em; width: 48em; background-color: #f000; top: 50%; left: 50%; zIndex: 10; position: fixed; border: 1px solid #fff; display: flex; flex-flow: column nowrap; user-select: none; } #lf-toolbar { background: #aaa3; width: 100%; height: 18px; } .lf-item { padding: 0 0 0 1em; } .lf-btn { color: #00f7; } .lf-btn:hover { color: #00f; } #lf-content { background-color: #fff3; flex: 1; padding: 0 0.5em; overflow: hidden; } #lf-text { background-color: #f000, position: relative; } .lf-hidden { display: none; } `; const elements = {}; function create(tagName, id, ...clazz) { const tmp = document.createElement(tagName); if (id) { tmp.setAttribute('id', 'lf-' + id); } tmp.setAttribute('class', ['loafing-reader', ...(clazz.map(i => 'lf-' + i))].join(' ')); return tmp; } elements.panel = create('div', 'panel') elements.toolbar = create('div', 'toolbar'); elements.content = create('div', 'content'); elements.jump = create('span', 'btn-jump', 'item', 'btn'); elements.load = create('span', 'btn-load', 'item', 'btn'); elements.move = create('span', 'btn-move', 'item', 'btn'); elements.info = create('span', 'span-info', 'item'); elements.fileholder = create('input', undefined, 'hidden'); elements.text = create('div', 'text'); elements.jump.href = '#'; elements.jump.innerText = '[跳转]'; elements.load.href = '#'; elements.load.innerText = '[加载]'; elements.move.href = '#'; elements.move.innerText = '[移动]'; elements.fileholder.type = 'file'; elements.fileholder.accept = '.txt'; elements.info.innerText = '(无文件)'; document.documentElement.appendChild(elements.panel); elements.panel.appendChild(elements.toolbar); elements.panel.appendChild(elements.content); elements.toolbar.appendChild(elements.jump); elements.toolbar.appendChild(elements.load); elements.toolbar.appendChild(elements.move); elements.toolbar.appendChild(elements.info); elements.toolbar.appendChild(elements.fileholder); elements.content.appendChild(elements.text); // file handle const fileInfo = {}; function loadFile(filename, content) { clear(); fileInfo.fileName = filename.substring(0, filename.lastIndexOf('.')); fileInfo.content = content.split(/(?:\r\n|\n)/)//.filter(s=>/\s*/.test(s)); fileInfo.length = fileInfo.content.length; fileInfo.bookmark = 0; fileInfo.page = []; GM_setValue('lf_file_name', filename); GM_setValue('lf_file_content', content); GM_setValue('lf_bookmark', 0); jump(0); } // utils function updateInfo() { const filename = fileInfo.fileName; elements.info.innerText = `(${fileInfo.bookmark}/${fileInfo.length})-${filename}`; GM_setValue('lf_bookmark', fileInfo.bookmark); } function clear() { const ls = fileInfo.page; while (ls && ls.length > 0) { ls.pop().remove(); } } function render(mark, removeNumber, direction) { const ls = fileInfo.page; for (let i = 0; i < removeNumber; ++i) { if (direction) { ls.shift().remove(); } else { ls.pop().remove(); } } let i = mark; while (i < fileInfo.length && i >= 0 && elements.text.offsetHeight < elements.content.offsetHeight) { const p = create('div'); p.innerHTML = fileInfo.content[i] + ' '; if (direction) { elements.text.appendChild(p); ls.push(p); i++; } else { elements.text.insertBefore(p, elements.text.firstChild); ls.unshift(p); i--; if (i < 0) { let t = ls.length; while (t < fileInfo.length && elements.text.offsetHeight < elements.content.offsetHeight) { const p = create('div'); p.innerHTML = fileInfo.content[t] + ' '; elements.text.appendChild(p); ls.push(p); ++t } } } } return direction ? mark : (i + 1); } function jump(index) { let i = index; const ls = fileInfo.page; render(i, ls.length, true); fileInfo.bookmark = index; fileInfo.page = ls; updateInfo(); } function next() { const ls = fileInfo.page; if (fileInfo.bookmark + 1 >= fileInfo.length || ls.length === 0) { alert('已是最后一页'); return; } let i = fileInfo.bookmark + fileInfo.page.length; const s = Math.max(ls.length - 1, 1); render(i, s, true); fileInfo.bookmark += s; fileInfo.page = ls; updateInfo(); } function previous() { const ls = fileInfo.page; if (fileInfo.bookmark === 0 || ls.length === 0) { alert('已经是第一页'); return } let i = fileInfo.bookmark; const mk = render(i, ls.length, false); fileInfo.bookmark = mk; fileInfo.page = ls; updateInfo(); } // events elements.jump.addEventListener('click', function (e) { let value = prompt('跳转到?'); value = parseInt(value); if (!isNaN(value) && fileInfo.content && fileInfo.length >= value) { jump(value); } else { alert('输入有误,跳转失败'); } }); elements.fileholder.addEventListener('change', function (e) { const file = elements.fileholder.files[0]; const reader = new FileReader(); reader.readAsText(file); reader.onload = function () { loadFile(file.name, this.result); } }); elements.load.addEventListener('click', function (e) { elements.fileholder.click(); }); elements.content.addEventListener('contextmenu', function (e) { e.preventDefault(); }); elements.content.addEventListener('mousedown', function (e) { if (e.button === 0) { next(); } else if (e.button === 2) { previous(); } }); // move window let mouseMemory = [0, 0]; let moveWindow = false elements.move.addEventListener('mousedown', function (e) { mouseMemory = [e.screenX - mouseMemory[0], e.screenY - mouseMemory[1]]; moveWindow = !moveWindow; }); document.documentElement.addEventListener('mousemove', function (e) { if (moveWindow) { elements.panel.style.left = `calc(50% + ${e.screenX - mouseMemory[0]}px)`; elements.panel.style.top = `calc(50% + ${e.screenY - mouseMemory[1]}px)`; } }); GM_addStyle(cssText); // wake up & sleep document.onkeydown = function (event) { event = event || window.event if (event.shiftKey && (event.key === 'r' || event.key === 'R')) { elements.panel.style.visibility = 'visible'; if (!window.LOAFING_READER_INIT) { init(); window.LOAFING_READER_INIT = true; console.log('loafing-reader loaded.') } const bookmark = GM_getValue('lf_bookmark'); if (bookmark !== fileInfo.bookmark) { jump(bookmark); } } } elements.panel.addEventListener('mouseleave', function (event) { if (!moveWindow) { elements.panel.style.visibility = 'hidden'; } }) elements.panel.style.visibility = 'hidden'; // INIT window.LOAFING_READER_INIT = false; function init() { const lfFileName = GM_getValue('lf_file_name'); const lfFileContent = GM_getValue('lf_file_content'); const lfBookmark = GM_getValue('lf_bookmark', 0); if (lfFileName && lfFileContent) { loadFile(lfFileName, lfFileContent); } if (fileInfo.content) { jump(lfBookmark); } } })();