settings

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

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

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/450210/1094681/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.8
  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. [x] 模块详细信息展示
  35. [x] 模块运行状态展示
  36. [?] 合并禁用/启用按钮
  37. [x] 按钮点击反馈
  38. [x] 更改设置图标位置
  39. [-] 模块卸载后删除模块行
  40. */
  41.  
  42. (function __MAIN__() {
  43. const ASSETS = require('assets');
  44. const alertify = require('alertify');
  45. const tippy = require('tippy');
  46. const SPanel = require('SidePanel');
  47. const SettingPanel = require('SettingPanel');
  48. const mousetip = require('mousetip');
  49. const CONST = {
  50. Text: {
  51. Button: '设置',
  52. Title: '脚本设置',
  53. ModuleManage: '模块管理',
  54. OpenModuleDialog: '点此打开管理面板',
  55. ModuleSettings: '模块设置',
  56. Module: '模块',
  57. Operation: '操作',
  58. DisableModule: '禁用模块',
  59. EnableModule: '启用模块',
  60. NotDisablable: '不可禁用',
  61. UninstallModule: '卸载模块',
  62. NotUninstallable: '不可卸载',
  63. AlertTitle: '模块设置界面',
  64. NoLoadNoSettings: '模块并未在此页面上运行,无法获取设置',
  65. NoSettings: '该模块当前并没有提供设置选项',
  66. ModuleEnabled: '已启用</br>相关页面需要刷新后才能启动此模块',
  67. ModuleDisabled: '已禁用</br>相关页面需要刷新后才能停止此模块',
  68. ModuleUninstalled: '已卸载</br>相关页面需要刷新/关闭后才能彻底清除此模块',
  69. ModuleDisableFailed: '禁用失败</br>请检查该模块是否不可禁用',
  70. ModuleUninstallFailed: '卸载失败</br>请检查该模块是否不可卸载',
  71. RememberSaving: '修改设置/恢复设置后记得点击保存哦:)',
  72. ModuleMissing: '模块不见了?!</br>您似乎没有安装这个模块哦…是不是之前在别的标签页里把它卸载了?',
  73. 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),
  74. Boolean: {
  75. 'true': '是',
  76. 'false': '否'
  77. },
  78. },
  79. Faicon: {
  80. Info: 'fa-solid fa-circle-info'
  81. },
  82. Config_Ruleset: {
  83. 'version-key': 'config-version',
  84. 'ignores': ["LOCAL-CDN"],
  85. 'defaultValues': {
  86. //'config-key': {},
  87. },
  88. 'updaters': {
  89. /*'config-key': [
  90. function() {
  91. // This function contains updater for config['config-key'] from v0 to v1
  92. },
  93. function() {
  94. // This function contains updater for config['config-key'] from v1 to v2
  95. }
  96. ]*/
  97. }
  98. }
  99. };
  100.  
  101. const UMManager = new UserModuleManager();
  102. SPanel.insert({
  103. index: 1,
  104. faicon: 'fa-solid fa-gear',
  105. tip: CONST.Text.Button,
  106. onclick: UMManager.show
  107. });
  108.  
  109. exports = {
  110. isSettingPage: isSettingPage,
  111. insertLines: insertLines,
  112. registerSettings: UMManager.registerModuleSettings
  113. };
  114.  
  115. function main() {
  116. // Get elements
  117. const content = $('#content');
  118.  
  119. // Insert settings
  120. const title = [
  121. [{html: CONST.Text.Title, colSpan: 3, class: 'foot', key: 'settitle'}],
  122. [{html: CONST.Text.ModuleManage, colSpan: 1}, {html: CONST.Text.OpenModuleDialog, colSpan: 2, onclick: UMManager.show}],
  123. //[{html: CONST.Text.XXXX, colSpan: 1, key: 'xxxxxxxx'}, {html: CONST.Text.XXXX, colSpan: 2, key: 'xxxxxxxx'}],
  124. ]
  125. const elements = insertLines(title);
  126.  
  127. // scrollIntoView if need
  128. getUrlArgv('tosettings') === 'true' && elements.settitle.scrollIntoView();
  129. }
  130.  
  131. // Module manager user interface
  132. function UserModuleManager() {
  133. const UMM = this;
  134. const moduleSettingFuncs = {};
  135.  
  136. UMM.show = show;
  137.  
  138. UMM.registerModuleSettings = registerModuleSettings;
  139.  
  140. UMM.showModuleSettings = showModuleSettings;
  141.  
  142. function show() {
  143. //box.set('message', 'No implemented yet!').show();
  144. const modules = ML_listModules();
  145.  
  146. // Make panel
  147. const SetPanel = new SettingPanel.SettingPanel({
  148. header: CONST.Text.ModuleManage,
  149. tables: []
  150. });
  151.  
  152. // Make table
  153. const table = new SetPanel.PanelTable({});
  154.  
  155. // Make header
  156. table.appendRow({
  157. blocks: [{
  158. isHeader: true,
  159. colSpan: 1,
  160. width: '60%',
  161. innerText: CONST.Text.Module,
  162. },{
  163. isHeader: true,
  164. colSpan: 4,
  165. width: '40%',
  166. innerText: CONST.Text.Operation,
  167. }]
  168. });
  169.  
  170. // Make module rows
  171. for (const module of modules) {
  172. const id = module.identifier;
  173. const row = new SetPanel.PanelRow({
  174. blocks: [{
  175. // Module info
  176. colSpan: 1,
  177. rowSpan: 1,
  178. children: [
  179. (() => {
  180. const icon = $CrE('i');
  181. icon.className = CONST.Faicon.Info;
  182. icon.style.marginRight = '0.5em';
  183. icon.classList.add(ASSETS.ClassName.Text);
  184.  
  185. tippy(icon, {
  186. content: makeContent(),
  187. onTrigger: (instance, event) => {
  188. instance.setContent(makeContent());
  189. }
  190. });
  191. return icon;
  192.  
  193. function makeContent() {
  194. const module = ML_getModule(id);
  195. if (!module) {
  196. alertify.alert(CONST.Text.AlertTitle, CONST.Text.ModuleMissing);
  197. row.remove();
  198. return false;
  199. }
  200. const status = {
  201. loaded: ML_moduleLoaded(id),
  202. system: module.flags & ASSETS.FLAG.SYSTEM,
  203. can_uninstall: !(module.flags & ASSETS.FLAG.NO_UNINSTALL),
  204. can_disable: !(module.flags & ASSETS.FLAG.NO_DISABLE),
  205. }
  206. const tip = $CrE('div');
  207. tip.innerHTML = CONST.Text.ModuleDetail;
  208. tip.childNodes.forEach((elm) => {
  209. if (!elm instanceof HTMLElement) {return;}
  210. const name = elm.getAttribute('name');
  211. if (name && module.hasOwnProperty(name) || status.hasOwnProperty(name)) {
  212. const info = module.hasOwnProperty(name) ? module : status;
  213. elm.innerText = ({
  214. string: (s) => (s),
  215. boolean: (b) => (CONST.Text.Boolean[b.toString()])
  216. })[typeof info[name]](info[name]);
  217. }
  218. });
  219.  
  220. return tip;
  221. }
  222. }) (),
  223. (() => {
  224. const span = $CrE('span');
  225. span.innerText = module.displayname || module.name;
  226. return span;
  227. }) (),
  228. ],
  229. },{
  230. // Module settings
  231. colSpan: 1,
  232. rowSpan: 1,
  233. children: [makeBtn({
  234. text: CONST.Text.ModuleSettings,
  235. onclick: function() {
  236. return showModuleSettings(id) ? 0 : 1;
  237. },
  238. alt: [CONST.Text.RememberSaving, null]
  239. })]
  240. },{
  241. // Diable module
  242. colSpan: 1,
  243. rowSpan: 1,
  244. children: [makeBtn({
  245. text: canDisable(module) ? CONST.Text.DisableModule : CONST.Text.NotDisablable,
  246. onclick: function() {
  247. if (!ML_getModule(id)) {
  248. alertify.alert(CONST.Text.AlertTitle, CONST.Text.ModuleMissing);
  249. row.remove();
  250. return false;
  251. }
  252. return ML_disableModule(id) ? 0 : 1;
  253. },
  254. disabled: !canDisable(module),
  255. alt: [CONST.Text.ModuleDisabled, CONST.Text.ModuleDisableFailed]
  256. })]
  257. },{
  258. // Enable module
  259. colSpan: 1,
  260. rowSpan: 1,
  261. children: [makeBtn({
  262. text: CONST.Text.EnableModule,
  263. onclick: function () {
  264. if (!ML_getModule(id)) {
  265. alertify.alert(CONST.Text.AlertTitle, CONST.Text.ModuleMissing);
  266. row.remove();
  267. return false;
  268. }
  269. ML_enableModule(id);
  270. },
  271. alt: CONST.Text.ModuleEnabled
  272. })]
  273. },{
  274. // Uninstall module
  275. colSpan: 1,
  276. rowSpan: 1,
  277. children: [makeBtn({
  278. text: canUninstall(module) ? CONST.Text.UninstallModule : CONST.Text.NotUninstallable,
  279. onclick: function() {
  280. if (!ML_getModule(id)) {
  281. alertify.alert(CONST.Text.AlertTitle, CONST.Text.ModuleMissing);
  282. row.remove();
  283. return false;
  284. }
  285. const success = ML_uninstallModule(id);
  286. success && row.remove();
  287. return success ? 0 : 1;
  288. },
  289. disabled: !canUninstall(module),
  290. alt: [CONST.Text.ModuleUninstalled, CONST.Text.ModuleUninstallFailed]
  291. })]
  292. }]
  293. });
  294. table.appendRow(row);
  295. }
  296. SetPanel.appendTable(table);
  297.  
  298. function makeBtn(details) {
  299. // Get arguments
  300. let text, onclick, disabled, alt;
  301. text = details.text;
  302. onclick = details.onclick;
  303. disabled = details.disabled;
  304. alt = details.alt;
  305.  
  306. const span = $CrE('span');
  307. span.innerText = text;
  308. onclick && span.addEventListener('click', _onclick);
  309. span.classList.add(ASSETS.ClassName.Button);
  310. disabled && span.classList.add(ASSETS.ClassName.Disabled);
  311.  
  312. return span;
  313.  
  314. function _onclick() {
  315. const result = !disabled && onclick ? onclick() : 0;
  316. const alt_content = alt && ![null, false].includes(result) && (Array.isArray(alt) ? alt[result] : alt);
  317. alt_content && alertify.message(alt_content);
  318. }
  319. }
  320.  
  321. function canUninstall(module) {
  322. return !(module.flags & ASSETS.FLAG.NO_UNINSTALL);
  323. }
  324.  
  325. function canDisable(module) {
  326. return !(module.flags & ASSETS.FLAG.NO_DISABLE);
  327. }
  328. }
  329.  
  330. function registerModuleSettings(id, func) {
  331. moduleSettingFuncs[id] = func;
  332. }
  333.  
  334. function showModuleSettings(id) {
  335. const func = moduleSettingFuncs[id];
  336. if (typeof func === 'function') {
  337. func();
  338. return true;
  339. } else {
  340. if (!ML_moduleLoaded(id)) {
  341. alertify.alert(CONST.Text.AlertTitle, CONST.Text.NoLoadNoSettings);
  342. } else {
  343. alertify.alert(CONST.Text.AlertTitle, CONST.Text.NoSettings);
  344. }
  345. return false;
  346. }
  347. }
  348. }
  349.  
  350. function insertLines(lines, tbody) {
  351. !tbody && (tbody = $(content, 'table>tbody'));
  352. const elements = {};
  353. for (const line of lines) {
  354. const tr = $CrE('tr');
  355. for (const item of line) {
  356. const td = $CrE('td');
  357. item.html && (td.innerHTML = item.html);
  358. item.colSpan && (td.colSpan = item.colSpan);
  359. item.class && (td.className = item.class);
  360. item.id && (td.id = item.id);
  361. item.tiptitle && mousetip.settip(td, item.tiptitle);
  362. item.key && (elements[item.key] = td);
  363. if (item.onclick) {
  364. td.style.color = 'grey';
  365. td.style.textAlign = 'center';
  366. td.addEventListener('click', item.onclick);
  367. }
  368. td.style.padding = '3px';
  369. tr.appendChild(td);
  370. }
  371. tbody.appendChild(tr);
  372. }
  373. return elements;
  374. }
  375.  
  376. function isSettingPage(callback) {
  377. const page = getAPI()[0] === 'userdetail.php';
  378. page && callback && callback();
  379. return page;
  380. }
  381.  
  382. function htmlEncode(text) {
  383. const span = $CrE('div');
  384. span.innerText = text;
  385. return span.innerHTML;
  386. }
  387.  
  388. // Change location.href without reloading using history.pushState/replaceState
  389. function setPageUrl() {
  390. let win, url, push;
  391. switch (arguments.length) {
  392. case 1:
  393. win = window;
  394. url = arguments[0];
  395. push = false;
  396. break;
  397. case 2:
  398. win = arguments[0];
  399. url = arguments[1];
  400. push = false;
  401. break;
  402. case 3:
  403. win = arguments[0];
  404. url = arguments[1];
  405. push = arguments[2];
  406. }
  407. return win.history[push ? 'pushState' : 'replaceState']({modified: true, ...history.state}, '', url);
  408. }
  409. })();