settings

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

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

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