settings

轻小说文库++的脚本设置界面

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

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

  1. /* eslint-disable no-multi-spaces */
  2. /* eslint-disable no-implicit-globals */
  3. /* eslint-disable userscripts/no-invalid-headers */
  4. /* eslint-disable userscripts/no-invalid-grant */
  5.  
  6. // ==UserScript==
  7. // @name settings
  8. // @displayname 设置界面
  9. // @namespace Wenku8++
  10. // @version 0.3.1
  11. // @description 轻小说文库++的脚本设置界面
  12. // @author PY-DNG
  13. // @license GPL-v3
  14. // @regurl https?://www\.wenku8\.net/.*
  15. // @require https://greasyfork.org/scripts/449412-basic-functions/code/Basic%20Functions.js?version=1085783
  16. // @require https://greasyfork.org/scripts/449583-configmanager/code/ConfigManager.js?version=1085836
  17. // @grant GM_getValue
  18. // @grant GM_setValue
  19. // @grant GM_deleteValue
  20. // @grant GM_listValues
  21. // @grant ML_listModules
  22. // @grant ML_getModule
  23. // @grant ML_disableModule
  24. // @grant ML_enableModule
  25. // @grant ML_uninstallModule
  26. // @grant ML_moduleLoaded
  27. // @protect
  28. // ==/UserScript==
  29.  
  30. /*
  31. 计划任务:
  32. [ ] 模块列表排序显示
  33. [o] 不可用按钮灰选
  34. [o] 模块详细信息展示
  35. [o] 模块运行状态展示
  36. [?] 合并禁用/启用按钮
  37. [o] 按钮点击反馈
  38. */
  39.  
  40. (function __MAIN__() {
  41. const ASSETS = require('assets');
  42. const alertify = require('alertify');
  43. const tippy = require('tippy');
  44. const SPanel = require('SidePanel');
  45. const SettingPanel = require('SettingPanel');
  46. const mousetip = require('mousetip');
  47. const CONST = {
  48. Text: {
  49. Button: '设置',
  50. Title: '脚本设置',
  51. ModuleManage: '模块管理',
  52. OpenModuleDialog: '点此打开管理面板',
  53. ModuleSettings: '模块设置',
  54. Module: '模块',
  55. Operation: '操作',
  56. DisableModule: '禁用模块',
  57. EnableModule: '启用模块',
  58. NotDisablable: '不可禁用',
  59. UninstallModule: '卸载模块',
  60. NotUninstallable: '不可卸载',
  61. AlertTitle: '模块设置界面',
  62. NoLoadNoSettings: '模块并未在此页面上运行,无法获取设置',
  63. NoSettings: '该模块当前并没有提供设置选项',
  64. ModuleEnabled: '已启用</br>相关页面需要刷新后才能启动此模块',
  65. ModuleDisabled: '已禁用</br>相关页面需要刷新后才能停止此模块',
  66. ModuleUninstalled: '已卸载</br>相关页面需要刷新/关闭后才能彻底清除此模块',
  67. ModuleDisableFailed: '禁用失败</br>请检查该模块是否不可禁用',
  68. ModuleUninstallFailed: '卸载失败</br>请检查该模块是否不可卸载',
  69. ModuleDetail: '<span class="{CT}">名称:</span><span name="name"></span></br><span class="{CT}">描述:</span><span name="description"></span></br><span class="{CT}">版本:</span><span name="version"></span></br><span class="{CT}">作者:</span><span name="author"></span></br><span class="{CT}">版权:</span><span name="license"></span></br><span class="{CT}">来源:</span><span name="src"></span></br><span class="{CT}">是否已运行:</span><span name="loaded"></span></br><span class="{CT}">是否已启用:</span><span name="enabled"></span></br><span class="{CT}">是否可禁用:</span><span name="can_disable"></span></br><span class="{CT}">是否可卸载:</span><span name="can_uninstall"></span>'.replaceAll('{CT}', ASSETS.ClassName.Text),
  70. Boolean: {
  71. 'true': '是',
  72. 'false': '否'
  73. },
  74. },
  75. Faicon: {
  76. Info: 'fa-solid fa-circle-info'
  77. },
  78. Config_Ruleset: {
  79. 'version-key': 'config-version',
  80. 'ignores': ["LOCAL-CDN"],
  81. 'defaultValues': {
  82. //'config-key': {},
  83. },
  84. 'updaters': {
  85. /*'config-key': [
  86. function() {
  87. // This function contains updater for config['config-key'] from v0 to v1
  88. },
  89. function() {
  90. // This function contains updater for config['config-key'] from v1 to v2
  91. }
  92. ]*/
  93. }
  94. }
  95. };
  96.  
  97. const UMManager = new UserModuleManager();
  98. SPanel.add({
  99. faicon: 'fa-solid fa-gear',
  100. tip: CONST.Text.Button,
  101. onclick: UMManager.show
  102. });
  103.  
  104. exports = {
  105. isSettingPage: isSettingPage,
  106. insertLines: insertLines,
  107. registerSettings: UMManager.registerModuleSettings
  108. };
  109.  
  110. function main() {
  111. // Get elements
  112. const content = $('#content');
  113.  
  114. // Insert settings
  115. const title = [
  116. [{html: CONST.Text.Title, colSpan: 3, class: 'foot', key: 'settitle'}],
  117. [{html: CONST.Text.ModuleManage, colSpan: 1}, {html: CONST.Text.OpenModuleDialog, colSpan: 2, onclick: UMManager.show}],
  118. //[{html: CONST.Text.XXXX, colSpan: 1, key: 'xxxxxxxx'}, {html: CONST.Text.XXXX, colSpan: 2, key: 'xxxxxxxx'}],
  119. ]
  120. const elements = insertLines(title);
  121.  
  122. // scrollIntoView if need
  123. getUrlArgv('tosettings') === 'true' && elements.settitle.scrollIntoView();
  124. }
  125.  
  126. // Module manager user interface
  127. function UserModuleManager() {
  128. const UMM = this;
  129. const moduleSettingFuncs = {};
  130.  
  131. UMM.show = show;
  132.  
  133. UMM.registerModuleSettings = registerModuleSettings;
  134.  
  135. UMM.showModuleSettings = showModuleSettings;
  136.  
  137. function show() {
  138. //box.set('message', 'No implemented yet!').show();
  139. const modules = ML_listModules();
  140.  
  141. // Make panel
  142. const SetPanel = new SettingPanel.SettingPanel({
  143. header: CONST.Text.ModuleManage,
  144. tables: []
  145. });
  146.  
  147. // Make table
  148. const table = new SetPanel.PanelTable({});
  149.  
  150. // Make header
  151. table.appendRow({
  152. blocks: [{
  153. isHeader: true,
  154. colSpan: 1,
  155. width: '60%',
  156. innerText: CONST.Text.Module,
  157. },{
  158. isHeader: true,
  159. colSpan: 4,
  160. width: '40%',
  161. innerText: CONST.Text.Operation,
  162. }]
  163. });
  164.  
  165. // Make module rows
  166. for (const module of modules) {
  167. const id = module.identifier;
  168. const row = new SetPanel.PanelRow({
  169. blocks: [{
  170. // Module info
  171. colSpan: 1,
  172. rowSpan: 1,
  173. children: [
  174. (() => {
  175. const icon = $CrE('i');
  176. icon.className = CONST.Faicon.Info;
  177. icon.style.marginRight = '0.5em';
  178. icon.classList.add(ASSETS.ClassName.Text);
  179.  
  180. tippy(icon, {
  181. content: makeContent(),
  182. onTrigger: (instance, event) => {
  183. instance.setContent(makeContent());
  184. }
  185. });
  186. return icon;
  187.  
  188. function makeContent() {
  189. const module = ML_getModule(id);
  190. const status = {
  191. loaded: ML_moduleLoaded(id),
  192. system: module.flags & ASSETS.FLAG.SYSTEM,
  193. can_uninstall: !(module.flags & ASSETS.FLAG.NO_UNINSTALL),
  194. can_disable: !(module.flags & ASSETS.FLAG.NO_DISABLE),
  195. }
  196. const tip = $CrE('div');
  197. tip.innerHTML = CONST.Text.ModuleDetail;
  198. tip.childNodes.forEach((elm) => {
  199. if (!elm instanceof HTMLElement) {return;}
  200. const name = elm.getAttribute('name');
  201. if (name && module.hasOwnProperty(name) || status.hasOwnProperty(name)) {
  202. const info = module.hasOwnProperty(name) ? module : status;
  203. elm.innerText = ({
  204. string: (s) => (s),
  205. boolean: (b) => (CONST.Text.Boolean[b.toString()])
  206. })[typeof info[name]](info[name]);
  207. }
  208. });
  209.  
  210. return tip;
  211. }
  212. }) (),
  213. (() => {
  214. const span = $CrE('span');
  215. span.innerText = module.displayname || module.name;
  216. return span;
  217. }) (),
  218. ],
  219. },{
  220. // Module settings
  221. colSpan: 1,
  222. rowSpan: 1,
  223. children: [makeBtn({
  224. text: CONST.Text.ModuleSettings,
  225. onclick: showModuleSettings.bind(null, id)
  226. })]
  227. },{
  228. // Diable module
  229. colSpan: 1,
  230. rowSpan: 1,
  231. children: [makeBtn({
  232. text: canDisable(module) ? CONST.Text.DisableModule : CONST.Text.NotDisablable,
  233. onclick: function() {
  234. return ML_disableModule(id) ? 0 : 1;
  235. },
  236. disabled: !canDisable(module),
  237. alt: [CONST.Text.ModuleDisabled, CONST.Text.ModuleDisableFailed]
  238. })]
  239. },{
  240. // Enable module
  241. colSpan: 1,
  242. rowSpan: 1,
  243. children: [makeBtn({
  244. text: CONST.Text.EnableModule,
  245. onclick: ML_enableModule.bind(null, id),
  246. alt: CONST.Text.ModuleEnabled
  247. })]
  248. },{
  249. // Uninstall module
  250. colSpan: 1,
  251. rowSpan: 1,
  252. children: [makeBtn({
  253. text: canUninstall(module) ? CONST.Text.UninstallModule : CONST.Text.NotUninstallable,
  254. onclick: function() {
  255. ML_uninstallModule(id) ? 0 : 1;
  256. },
  257. disabled: !canUninstall(module),
  258. alt: [CONST.Text.ModuleUninstalled, CONST.Text.ModuleUninstallFailed]
  259. })]
  260. }]
  261. });
  262. table.appendRow(row);
  263. }
  264. SetPanel.appendTable(table);
  265.  
  266. function makeBtn(details) {
  267. // Get arguments
  268. let text, onclick, disabled, alt;
  269. text = details.text;
  270. onclick = details.onclick;
  271. disabled = details.disabled;
  272. alt = details.alt;
  273.  
  274. const span = $CrE('span');
  275. span.innerText = text;
  276. onclick && span.addEventListener('click', _onclick);
  277. span.classList.add(ASSETS.ClassName.Button);
  278. disabled && span.classList.add(ASSETS.ClassName.Disabled);
  279.  
  280. return span;
  281.  
  282. function _onclick() {
  283. const result = !disabled && onclick ? onclick() : 0;
  284. alertify.message(Array.isArray(alt) ? alt[result] : alt);
  285. }
  286. }
  287.  
  288. function canUninstall(module) {
  289. return !(module.flags & ASSETS.FLAG.NO_UNINSTALL);
  290. }
  291.  
  292. function canDisable(module) {
  293. return !(module.flags & ASSETS.FLAG.NO_DISABLE);
  294. }
  295. }
  296.  
  297. function registerModuleSettings(id, func) {
  298. moduleSettingFuncs[id] = func;
  299. }
  300.  
  301. function showModuleSettings(id) {
  302. const func = moduleSettingFuncs[id];
  303. if (typeof func === 'function') {
  304. func();
  305. return true;
  306. } else {
  307. if (!ML_moduleLoaded(id)) {
  308. alertify.alert(CONST.Text.AlertTitle, CONST.Text.NoLoadNoSettings);
  309. } else {
  310. alertify.alert(CONST.Text.AlertTitle, CONST.Text.NoSettings);
  311. }
  312. return false;
  313. }
  314. }
  315. }
  316.  
  317. function insertLines(lines, tbody) {
  318. !tbody && (tbody = $(content, 'table>tbody'));
  319. const elements = {};
  320. for (const line of lines) {
  321. const tr = $CrE('tr');
  322. for (const item of line) {
  323. const td = $CrE('td');
  324. item.html && (td.innerHTML = item.html);
  325. item.colSpan && (td.colSpan = item.colSpan);
  326. item.class && (td.className = item.class);
  327. item.id && (td.id = item.id);
  328. item.tiptitle && mousetip.settip(td, item.tiptitle);
  329. item.key && (elements[item.key] = td);
  330. if (item.onclick) {
  331. td.style.color = 'grey';
  332. td.style.textAlign = 'center';
  333. td.addEventListener('click', item.onclick);
  334. }
  335. td.style.padding = '3px';
  336. tr.appendChild(td);
  337. }
  338. tbody.appendChild(tr);
  339. }
  340. return elements;
  341. }
  342.  
  343. function isSettingPage(callback) {
  344. const page = getAPI()[0] === 'userdetail.php';
  345. page && callback && callback();
  346. return page;
  347. }
  348.  
  349. function htmlEncode(text) {
  350. const span = $CrE('div');
  351. span.innerText = text;
  352. return span.innerHTML;
  353. }
  354.  
  355. // Change location.href without reloading using history.pushState/replaceState
  356. function setPageUrl() {
  357. let win, url, push;
  358. switch (arguments.length) {
  359. case 1:
  360. win = window;
  361. url = arguments[0];
  362. push = false;
  363. break;
  364. case 2:
  365. win = arguments[0];
  366. url = arguments[1];
  367. push = false;
  368. break;
  369. case 3:
  370. win = arguments[0];
  371. url = arguments[1];
  372. push = arguments[2];
  373. }
  374. return win.history[push ? 'pushState' : 'replaceState']({modified: true, ...history.state}, '', url);
  375. }
  376. })();