SidePanel

轻小说文库++的侧边工具栏

当前为 2022-08-17 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/449718/1082511/SidePanel.js

  1. /* eslint-disable no-multi-spaces */
  2. /* eslint-disable userscripts/no-invalid-headers */
  3. /* eslint-disable userscripts/no-invalid-grant */
  4.  
  5. // ==UserScript==
  6. // @name SidePanel
  7. // @namespace Wenku8++
  8. // @version 0.1.1
  9. // @description 轻小说文库++的侧边工具栏
  10. // @author PY-DNG
  11. // @license GPL-v3
  12. // @regurl https?://www\.wenku8\.net/.*
  13. // @require https://greasyfork.org/scripts/449412-basic-functions/code/Basic%20Functions.js?version=1080639
  14. // @grant none
  15. // ==/UserScript==
  16.  
  17. (function __MAIN__() {
  18. 'use strict';
  19.  
  20. const tippy = require('tippy');
  21.  
  22. exports = sideFunctions();
  23.  
  24. // Side functions area
  25. function sideFunctions() {
  26. const SPanel = new SidePanel();
  27. SPanel.create();
  28. SPanel.setPosition('bottom-right');
  29.  
  30. commonButtons();
  31. return SPanel;
  32.  
  33. function commonButtons() {
  34. // Button show/hide-all-buttons
  35. const btnShowHide = SPanel.add({
  36. faicon: 'fa-solid fa-down-left-and-up-right-to-center',
  37. className: 'accept-pointer',
  38. tip: '隐藏面板',
  39. onclick: (function() {
  40. let hidden = false;
  41. return (e) => {
  42. hidden = !hidden;
  43. btnShowHide.faicon.className = 'fa-solid ' + (hidden ? 'fa-up-right-and-down-left-from-center' : 'fa-down-left-and-up-right-to-center');
  44. btnShowHide.classList[hidden ? 'add' : 'remove']('low-opacity');
  45. btnShowHide.setAttribute('aria-label', (hidden ? '显示面板' : '隐藏面板'));
  46. SPanel.elements.panel.style.pointerEvents = hidden ? 'none' : 'auto';
  47. for (const button of SPanel.elements.buttons) {
  48. if (button === btnShowHide) {continue;}
  49. //button.style.display = hidden ? 'none' : 'block';
  50. button.style.pointerEvents = hidden ? 'none' : 'auto';
  51. button.style.opacity = hidden ? '0' : '1';
  52. }
  53. };
  54. }) ()
  55. });
  56.  
  57. // Button scroll-to-bottom
  58. const btnDown = SPanel.add({
  59. faicon: 'fa-solid fa-angle-down',
  60. tip: '转到底部',
  61. onclick: (e) => {
  62. const elms = [document.body.parentElement, $('#content'), $('#contentmain')];
  63.  
  64. for (const elm of elms) {
  65. elm && elm.scrollTo && elm.scrollTo(elm.scrollLeft, elm.scrollHeight);
  66. }
  67. }
  68. });
  69.  
  70. // Button scroll-to-top
  71. const btnUp = SPanel.add({
  72. faicon: 'fa-solid fa-angle-up',
  73. tip: '转到顶部',
  74. onclick: (e) => {
  75. const elms = [document.body.parentElement, $('#content'), $('#contentmain')];
  76.  
  77. for (const elm of elms) {
  78. elm && elm.scrollTo && elm.scrollTo(elm.scrollLeft, 0);
  79. }
  80. }
  81. });
  82.  
  83. // Darkmode
  84. /*
  85. const btnDarkmode = SPanel.add({
  86. faicon: 'fa-solid ' + (DMode.isActivated() ? 'fa-sun' : 'fa-moon'),
  87. tip: '明暗切换',
  88. onclick: (e) => {
  89. DMode.toggle();
  90. btnDarkmode.faicon.className = 'fa-solid ' + (DMode.isActivated() ? 'fa-sun' : 'fa-moon');
  91. }
  92. });
  93. */
  94.  
  95. // Refresh page
  96. const btnRefresh = SPanel.add({
  97. faicon: 'fa-solid fa-rotate-right',
  98. tip: '刷新页面',
  99. onclick: (e) => {
  100. location.href = location.href;
  101. }
  102. });
  103. }
  104. }
  105.  
  106. // Side-located control panel
  107. // Requirements: FontAwesome, tippy.js, addStyle
  108. // Use 'new' keyword
  109. function SidePanel() {
  110. // Public SP
  111. const SP = this;
  112. const elms = SP.elements = {};
  113.  
  114. // Private _SP
  115. // keys start with '_' shouldn't be modified
  116. const _SP = {
  117. _id: {
  118. css: 'sidepanel-style',
  119. usercss: 'sidepanel-style-user',
  120. panel: 'sidepanel-panel'
  121. },
  122. _class: {
  123. button: 'sidepanel-button'
  124. },
  125. _directions: ['left', 'right', 'top', 'bottom']
  126. };
  127.  
  128. addStyle('#sidepanel-panel {position: fixed; background-color: #00000000; padding: 0.5vmin; line-height: 3.5vmin; height: auto; display: flex; transition-duration: 0.3s; z-index: 9999999999;} #sidepanel-panel.right {right: 3vmin;} #sidepanel-panel.bottom {bottom: 3vmin; flex-direction: column-reverse;} #sidepanel-panel.left {left: 3vmin;} #sidepanel-panel.top {top: 3vmin; flex-direction: column;} .sidepanel-button {padding: 1vmin; margin: 0.5vmin; font-size: 3.5vmin; border-radius: 10%; text-align: center; color: #00000088; background-color: #FFFFFF88; box-shadow:3px 3px 2px #00000022; user-select: none; transition-duration: inherit;} .sidepanel-button:hover {color: #FFFFFFDD; background-color: #000000DD;}');
  129.  
  130. SP.create = function() {
  131. // Create panel
  132. const panel = elms.panel = document.createElement('div');
  133. panel.id = _SP._id.panel;
  134. SP.setPosition('bottom-right');
  135. document.body.appendChild(panel);
  136.  
  137. // Prepare buttons
  138. elms.buttons = [];
  139. }
  140.  
  141. // Insert a button to given index
  142. // details = {index, text, faicon, id, tip, className, onclick, listeners}, all optional
  143. // listeners = [..[..args]]. [..args] will be applied as button.addEventListener's args
  144. // faicon = 'fa-icon-name-classname fa-icon-style-classname', this arg stands for a FontAwesome icon to be inserted inside the botton
  145. // Returns the button(HTMLDivElement), including button.faicon(HTMLElement/HTMLSpanElement in firefox, <i>) if faicon is set
  146. SP.insert = function(details) {
  147. const index = details.index;
  148. const text = details.text;
  149. const faicon = details.faicon;
  150. const id = details.id;
  151. const tip = details.tip;
  152. const className = details.className;
  153. const onclick = details.onclick;
  154. const listeners = details.listeners || [];
  155.  
  156. const button = document.createElement('div');
  157. text && (button.innerHTML = text);
  158. id && (button.id = id);
  159. tip && setTooltip(button, tip); //settip(button, tip);
  160. className && (button.className = className);
  161. onclick && (button.onclick = onclick);
  162. if (faicon) {
  163. const i = document.createElement('i');
  164. i.className = faicon;
  165. button.faicon = i;
  166. button.appendChild(i);
  167. }
  168. for (const listener of listeners) {
  169. button.addEventListener.apply(button, listener);
  170. }
  171. button.classList.add(_SP._class.button);
  172.  
  173. elms.buttons = insertItem(elms.buttons, button, index);
  174. index < elms.buttons.length ? elms.panel.insertBefore(button, elms.panel.children[index]) : elms.panel.appendChild(button);
  175. return button;
  176. }
  177.  
  178. // Append a button
  179. SP.add = function(details) {
  180. details.index = elms.buttons.length;
  181. return SP.insert(details);
  182. }
  183.  
  184. // Remove a button
  185. SP.remove = function(arg) {
  186. let index, elm;
  187. if (arg instanceof HTMLElement) {
  188. elm = arg;
  189. index = elms.buttons.indexOf(elm);
  190. } else if (typeof(arg) === 'number') {
  191. index = arg;
  192. elm = elms.buttons[index];
  193. } else if (typeof(arg) === 'string') {
  194. elm = $(elms.panel, arg);
  195. index = elms.buttons.indexOf(elm);
  196. }
  197.  
  198. elms.buttons = delItem(elms.buttons, index);
  199. elm.parentElement.removeChild(elm);
  200. }
  201.  
  202. // Sets the display position by texts like 'right-bottom'
  203. SP.setPosition = function(pos) {
  204. const poses = _SP.direction = pos.split('-');
  205. const avails = _SP._directions;
  206.  
  207. // Available check
  208. if (poses.length !== 2) {return false;}
  209. for (const p of poses) {
  210. if (!avails.includes(p)) {return false;}
  211. }
  212.  
  213. // remove all others
  214. for (const p of avails) {
  215. elms.panel.classList.remove(p);
  216. }
  217.  
  218. // add new pos
  219. for (const p of poses) {
  220. elms.panel.classList.add(p);
  221. }
  222.  
  223. // Change tooltips' direction
  224. elms.buttons && elms.buttons.forEach(setTooltipDirection);
  225. }
  226.  
  227. // Gets the current display position
  228. SP.getPosition = function() {
  229. return _SP.direction.join('-');
  230. }
  231.  
  232. // Append a style text to document(<head>) with a <style> element
  233. // Replaces existing id-specificed <style>s
  234. function spAddStyle(css, id) {
  235. const style = document.createElement("style");
  236. id && (style.id = id);
  237. style.textContent = css;
  238. for (const elm of $All('#'+id)) {
  239. elm.parentElement && elm.parentElement.removeChild(elm);
  240. }
  241. document.head.appendChild(style);
  242. }
  243.  
  244. // Set a tooltip to the element
  245. function setTooltip(elm, text, direction='auto') {
  246. elm.tooltip = tippy(elm, {
  247. content: text,
  248. arrow: true,
  249. hideOnClick: false
  250. });
  251.  
  252. setTooltipDirection(elm, direction);
  253. }
  254.  
  255. function setTooltipDirection(elm, direction='auto') {
  256. direction === 'auto' && (direction = _SP.direction.includes('left') ? 'right' : 'left');
  257. if (!_SP._directions.includes(direction)) {throw new Error('setTooltip: invalid direction');}
  258.  
  259. // Tippy direction
  260. if (!elm.tooltip) {
  261. DoLog(LogLevel.Error, 'SidePanel.setTooltipDirection: Given elm has no tippy instance(elm.tooltip)');
  262. throw new Error('SidePanel.setTooltipDirection: Given elm has no tippy instance(elm.tooltip)');
  263. }
  264. elm.tooltip.setProps({
  265. placement: direction
  266. });
  267. }
  268.  
  269. // Del an item from an array using its index. Returns the array but can NOT modify the original array directly!!
  270. function delItem(arr, index) {
  271. arr = arr.slice(0, index).concat(arr.slice(index+1));
  272. return arr;
  273. }
  274.  
  275. // Insert an item into an array using given index. Returns the array but can NOT modify the original array directly!!
  276. function insertItem(arr, item, index) {
  277. arr = arr.slice(0, index).concat(item).concat(arr.slice(index));
  278. return arr;
  279. }
  280. }
  281. })();