// ==UserScript==
// @name 摸鱼小说阅读器 Loafing-Reader
// @namespace hanayabuki-loafing-reader
// @version 1.3
// @description 内嵌浏览器里用来上班摸鱼看小说
// @author HanaYabuki
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @noframes
// ==/UserScript==
(function () {
const cssText = `
:root {
--lf-color: #222;
--lf-toolbar-background-color: #aaa3;
--lf-content-background-color: #fff3;
--lf-btn-color: #00a;
--lf-btn-color-hover: #00f;
}
[lf-theme='dark'] {
--lf-color: #ddd;
--lf-toolbar-background-color: #5553;
--lf-content-background-color: #2223;
--lf-btn-color: #aa0;
--lf-btn-color-hover: #ff0;
}
.loafing-reader {
margin: 0; padding: 0;
box-sizing: content-box;
font-size: 12px;
color: var(--lf-color);
}
#lf-panel {
height: 27em;
width: 48em;
background-color: #f000;
top: 50%; left: 50%;
z-index: 10;
position: fixed;
display: flex;
flex-flow: column nowrap;
user-select: none;
backdrop-filter: blur(1px);
}
#lf-toolbar {
background: var(--lf-toolbar-background-color);
width: 100%; height: 18px;
}
.lf-item {
padding: 0 0 0 1em;
}
.lf-btn {
color: var(--lf-btn-color);
}
.lf-btn:hover {
color: var(--lf-btn-color-hover);
}
#lf-content {
background-color: var(--lf-content-background-color);
flex: 1;
padding: 0 0.5em;
overflow: hidden;
}
#lf-text {
background-color: #f000,
position: relative;
}
.lf-hidden {
display: none;
}
#lf-trigger {
position: fixed;
top: 0;
left: 0;
width: 20px;
height: 20px;
background: linear-gradient(-45deg, transparent 14px, pink 0);
z-index: 16777271;
}
`;
const elements = {};
// create element
function ce(tagName, id, children = [], ...clazz) {
const tmp = document.createElement(tagName);
tmp.setAttribute('id', 'lf-' + id);
tmp.setAttribute('class', ['loafing-reader', ...(clazz.map(i => 'lf-' + i))].join(' '));
children.forEach(i => tmp.appendChild(i));
return elements[id] = tmp;
}
ce('div', 'panel', [
ce('div', 'toolbar', [
ce('input', 'fileholder', [], 'hidden'),
ce('span', 'jump', [], 'item', 'btn'),
ce('span', 'load', [], 'item', 'btn'),
ce('span', 'move', [], 'item', 'btn'),
ce('span', 'info', [], 'item'),
ce('span', 'color', [], 'item', 'btn'),
],),
ce('div', 'content', [
ce('div', 'text', [])
]),
]);
ce('div', 'trigger', [], 'trigger');
elements.jump.innerText = '[跳转]';
elements.load.innerText = '[加载]';
elements.move.innerText = '[移动]';
elements.fileholder.type = 'file';
elements.fileholder.accept = '.txt';
elements.info.innerText = '(无文件)';
elements.color.innerText = '[主题]';
document.documentElement.appendChild(elements.panel);
document.documentElement.appendChild(elements.trigger);
// 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);
}
// css
GM_addStyle(cssText);
const themes = ['light', 'dark'];
let themeId = 1;
elements.color.addEventListener('click', function () {
elements.panel.setAttribute('lf-theme', themes.at(themeId));
themeId = (themeId + 1) % themes.length;
});
// 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 = ce('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 = ce('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('跳转到?', fileInfo.bookmark);
value = parseInt(value);
if (!isNaN(value) && fileInfo.content && fileInfo.length >= value) {
jump(value);
}
else {
alert('输入有误,跳转失败');
}
});
let charset = "utf-8";
elements.fileholder.addEventListener('change', function (e) {
const file = elements.fileholder.files[0];
const reader = new FileReader();
reader.readAsText(file, charset);
reader.onload = function () {
loadFile(file.name, this.result);
}
});
elements.load.addEventListener('click', function (e) {
charset = prompt("选择文件编码格式", charset)
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.clientX - mouseMemory[0], e.clientY - mouseMemory[1]];
moveWindow = !moveWindow;
});
document.documentElement.addEventListener('mousemove', function (e) {
if (moveWindow) {
elements.panel.style.left = `calc(50% + ${e.clientX - mouseMemory[0]}px)`;
elements.panel.style.top = `calc(50% + ${e.clientY - mouseMemory[1]}px)`;
}
});
// wake up & sleep down
document.onkeydown = function (event) {
event = event || window.event
if (event.altKey && (event.key === 'r' || event.key === 'R')) {
wakeUp();
}
}
elements.panel.addEventListener('mouseleave', function (event) {
sleepDown();
})
elements.panel.style.visibility = 'hidden';
elements.trigger.addEventListener('click', function (event) {
wakeUp();
})
function wakeUp() {
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);
}
}
function sleepDown() {
if (!moveWindow) {
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);
}
}
})();