Libib - Custom status indicator style

Set a custom color and style for libib.com item status indicator

目前為 2025-02-06 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Libib - Custom status indicator style
  3. // @name:it Libib - Stile indicatore stato personalizzato
  4. // @description Set a custom color and style for libib.com item status indicator
  5. // @description:it Modifica i colori e lo stile dell'indicatore dello stato di un oggetto di libib.com
  6. // @author JetpackCat
  7. // @namespace https://github.com/JetpackCat-IT/libib-custom-status-style
  8. // @supportURL https://github.com/JetpackCat-IT/libib-custom-status-style/issues
  9. // @icon https://github.com/JetpackCat-IT/libib-custom-status-style/raw/v1.0.0/img/icon_64.png
  10. // @version 1.1.0
  11. // @license GPL-3.0-or-later; https://raw.githubusercontent.com/JetpackCat-IT/libib-custom-status-style/master/LICENSE
  12. // @match https://www.libib.com/library
  13. // @icon https://www.libib.com/img/favicon.png
  14. // @run-at document-idle
  15. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  16. // @grant GM_getValue
  17. // @grant GM_setValue
  18. // @grant GM.getValue
  19. // @grant GM.setValue
  20. // ==/UserScript==
  21.  
  22. (function () {
  23. "use strict";
  24.  
  25. // Get libib sidebar menu. The settings button will be added to the sidebar
  26. const libib_sidebar_menu = document.getElementById("primary-menu");
  27.  
  28. // Create the element, it needs to be an <a> tag inside an <li> tag
  29. const settings_button_a = document.createElement("a");
  30. settings_button_a.appendChild(
  31. document.createTextNode("Libib status settings")
  32. );
  33.  
  34. // Create <li> element and insert <a> element inside
  35. const settings_button_li = document.createElement("li");
  36. settings_button_li.appendChild(settings_button_a);
  37.  
  38. // Assign click event handler to open the menu settings
  39. settings_button_li.addEventListener("click", function () {
  40. gmc.open();
  41. });
  42.  
  43. // Add <li> element to the sidebar
  44. libib_sidebar_menu.appendChild(settings_button_li);
  45.  
  46. // Create a container for the configuration elements
  47. const config_container = document.createElement("div");
  48. document.body.appendChild(config_container);
  49.  
  50. // Adapt container background color and shadow based on libib theme (dark/light)
  51. const is_dark_scheme = document.body.classList.contains("dark");
  52. let background_color = "#fefefe";
  53. let shadow_color = "#838383";
  54.  
  55. if (is_dark_scheme) {
  56. background_color = "#1b1b1b";
  57. shadow_color = "#e7e7e7";
  58. }
  59. const config_panel_css = `#libib_status_config{padding: 20px !important; background-color: ${background_color}; box-shadow: 0px 0px 9px 3px ${shadow_color}}; `;
  60.  
  61. let gmc = new GM_config({
  62. id: "libib_status_config", // The id used for this instance of GM_config
  63. title: "Script Settings", // Panel Title
  64. types: {
  65. // Create color input type
  66. color: {
  67. default: null,
  68. toNode: function () {
  69. var field = this.settings,
  70. value = this.value,
  71. id = this.id,
  72. create = this.create,
  73. slash = null,
  74. retNode = create("div", {
  75. className: "config_var",
  76. id: this.configId + "_" + id + "_var",
  77. title: field.title || "",
  78. });
  79.  
  80. // Create the field lable
  81. retNode.appendChild(
  82. create("label", {
  83. innerHTML: field.label,
  84. id: this.configId + "_" + id + "_field_label",
  85. for: this.configId + "_field_" + id,
  86. className: "field_label",
  87. })
  88. );
  89. // Create the actual input element
  90. var props = {
  91. id: this.configId + "_field_" + id,
  92. type: "color",
  93. value: value ?? "",
  94. };
  95. // Actually create and append the input element
  96. retNode.appendChild(create("input", props));
  97. return retNode;
  98. },
  99. toValue: function () {
  100. let input = document.getElementById(
  101. `${this.configId}_field_${this.id}`
  102. );
  103. return input.value;
  104. },
  105. reset: function () {
  106. let input = document.getElementById(
  107. `${this.configId}_field_${this.id}`
  108. );
  109. input.value = this.default;
  110. },
  111. },
  112. number: {
  113. default: null,
  114. toNode: function () {
  115. var field = this.settings,
  116. value = this.value,
  117. id = this.id,
  118. create = this.create,
  119. slash = null,
  120. retNode = create("div", {
  121. className: "config_var",
  122. id: this.configId + "_" + id + "_var",
  123. title: field.title || "",
  124. });
  125.  
  126. // Create the field lable
  127. retNode.appendChild(
  128. create("label", {
  129. innerHTML: field.label,
  130. id: this.configId + "_" + id + "_field_label",
  131. for: this.configId + "_field_" + id,
  132. className: "field_label",
  133. })
  134. );
  135. // Create the actual input element
  136. var props = {
  137. id: this.configId + "_field_" + id,
  138. type: "number",
  139. value: value ?? "",
  140. };
  141. // Actually create and append the input element
  142. retNode.appendChild(create("input", props));
  143. return retNode;
  144. },
  145. toValue: function () {
  146. let input = document.getElementById(
  147. `${this.configId}_field_${this.id}`
  148. );
  149. return input.value;
  150. },
  151. reset: function () {
  152. let input = document.getElementById(
  153. `${this.configId}_field_${this.id}`
  154. );
  155. input.value = this.default;
  156. },
  157. },
  158. },
  159. // Fields object
  160. fields: {
  161. // This is the id of the field
  162. type: {
  163. label: "Indicator type", // Appears next to field
  164. type: "radio", // Makes this setting a radio field
  165. options: ["Triangle", "Border"], // Default = triangle
  166. default: "Triangle", // Default value if user doesn't change it
  167. },
  168. // This is the id of the field
  169. trianglePosition: {
  170. label: "Triangle position", // Appears next to field
  171. type: "select", // Makes this setting a select field
  172. options: ["Top left", "Top right", "Bottom left", "Bottom right"],
  173. default: "Top left", // Default value if user doesn't change it
  174. },
  175. // This is the id of the field
  176. borderPosition: {
  177. label: "Border position", // Appears next to field
  178. type: "select", // Makes this setting a select field
  179. options: ["Top", "Bottom"],
  180. default: "Top", // Default value if user doesn't change it
  181. },
  182. // This is the id of the field
  183. borderHeight: {
  184. label: "Border height", // Appears next to field
  185. type: "number", // Makes this setting a select field
  186. default: 5, // Default value if user doesn't change it
  187. },
  188. // This is the id of the field
  189. colorNotBegun: {
  190. label: '"Not begun" Color', // Appears next to field
  191. type: "color", // Makes this setting a text field
  192. default: "#ffffff", // Default value if user doesn't change it
  193. },
  194. // This is the id of the field
  195. colorCompleted: {
  196. label: '"Completed" Color', // Appears next to field
  197. type: "color", // Makes this setting a text field
  198. default: "#76eb99", // Default value if user doesn't change it
  199. },
  200. // This is the id of the field
  201. colorProgress: {
  202. label: '"In progress" Color', // Appears next to field
  203. type: "color", // Makes this setting a text field
  204. default: "#ffec8a", // Default value if user doesn't change it
  205. },
  206. // This is the id of the field
  207. colorAbandoned: {
  208. label: '"Abandoned" Color', // Appears next to field
  209. type: "color", // Makes this setting a text field
  210. default: "#ff7a7a", // Default value if user doesn't change it
  211. },
  212. },
  213. css: config_panel_css,
  214. frame: config_container,
  215. // Callback functions object
  216. events: {
  217. init: function () {
  218. let css = generateCSS(this);
  219. setCustomStyle(css);
  220. },
  221. save: function () {
  222. let css = generateCSS(this);
  223. setCustomStyle(css);
  224. this.close();
  225. },
  226. },
  227. });
  228.  
  229. const generateCSS = function (GM_settings) {
  230. if (GM_settings == null) GM_settings = gmc;
  231.  
  232. const not_begun_color = GM_settings.get("colorNotBegun");
  233. const completed_color = GM_settings.get("colorCompleted");
  234. const in_progress_color = GM_settings.get("colorProgress");
  235. const abandoned_color = GM_settings.get("colorAbandoned");
  236.  
  237. let css_style = "";
  238. // Make libib buttons still clickable
  239. css_style += `
  240. .quick-edit-link{
  241. z-index: 10;
  242. }
  243. .batch-select{
  244. z-index: 10;
  245. }
  246. `;
  247. // Set the save, close and reset buttons color to white id dark mode
  248. css_style += `
  249. body.dark #libib_status_config_resetLink,body.dark #libib_status_config_saveBtn,body.dark #libib_status_config_closeBtn{
  250. color:white!important
  251. }`;
  252.  
  253. // Triangle style
  254. if (GM_settings.get("type") == "Triangle") {
  255. let triangle_position = GM_settings.get("trianglePosition");
  256. if (triangle_position == "Top left") {
  257. css_style += `
  258. .cover .completed.cover-wrapper::after {
  259. border-left-color: ${completed_color};
  260. border-top-color: ${completed_color};
  261. }
  262. .cover .in-progress.cover-wrapper::after {
  263. border-left-color: ${in_progress_color};
  264. border-top-color: ${in_progress_color};
  265. }
  266. .cover .abandoned.cover-wrapper::after {
  267. border-left-color: ${abandoned_color};
  268. border-top-color: ${abandoned_color};
  269. }
  270. .cover .not-begun.cover-wrapper::after {
  271. border-left-color: ${not_begun_color};
  272. border-top-color: ${not_begun_color};
  273. }
  274. `;
  275. } else if (triangle_position == "Top right") {
  276. css_style += `
  277. .cover .cover-wrapper::after{
  278. right: 0;
  279. left: auto;
  280. }
  281. .cover .completed.cover-wrapper::after {
  282. border-left-color: transparent;
  283. border-right-color: ${completed_color};
  284. border-top-color: ${completed_color};
  285. }
  286. .cover .in-progress.cover-wrapper::after {
  287. border-left-color: transparent;
  288. border-right-color: ${in_progress_color};
  289. border-top-color: ${in_progress_color};
  290. }
  291. .cover .abandoned.cover-wrapper::after {
  292. border-left-color: transparent;
  293. border-right-color: ${abandoned_color};
  294. border-top-color: ${abandoned_color};
  295. }
  296. .cover .not-begun.cover-wrapper::after {
  297. border-left-color: transparent;
  298. border-right-color: ${not_begun_color};
  299. border-top-color: ${not_begun_color};
  300. }
  301. `;
  302. } else if (triangle_position == "Bottom left") {
  303. css_style += `
  304. .cover .cover-wrapper::after{
  305. bottom: 0;
  306. top: auto;
  307. }
  308. .cover .completed.cover-wrapper::after {
  309. border-top-color: transparent;
  310. border-left-color: ${completed_color};
  311. border-bottom-color: ${completed_color};
  312. }
  313. .cover .in-progress.cover-wrapper::after {
  314. border-top-color: transparent;
  315. border-left-color: ${in_progress_color};
  316. border-bottom-color: ${in_progress_color};
  317. }
  318. .cover .abandoned.cover-wrapper::after {
  319. border-top-color: transparent;
  320. border-left-color: ${abandoned_color};
  321. border-bottom-color: ${abandoned_color};
  322. }
  323. .cover .not-begun.cover-wrapper::after {
  324. border-top-color: transparent;
  325. border-left-color: ${not_begun_color};
  326. border-bottom-color: ${not_begun_color};
  327. }
  328. `;
  329. } else if (triangle_position == "Bottom right") {
  330. css_style += `
  331. .cover .cover-wrapper::after{
  332. bottom: 0;
  333. top: auto;
  334. left: auto;
  335. right: 0;
  336. }
  337. .cover .completed.cover-wrapper::after {
  338. border-top-color: transparent;
  339. border-left-color: transparent;
  340. border-right-color: ${completed_color};
  341. border-bottom-color: ${completed_color};
  342. }
  343. .cover .in-progress.cover-wrapper::after {
  344. border-top-color: transparent;
  345. border-left-color: transparent;
  346. border-right-color: ${in_progress_color};
  347. border-bottom-color: ${in_progress_color};
  348. }
  349. .cover .abandoned.cover-wrapper::after {
  350. border-top-color: transparent;
  351. border-left-color: transparent;
  352. border-right-color: ${abandoned_color};
  353. border-bottom-color: ${abandoned_color};
  354. }
  355. .cover .not-begun.cover-wrapper::after {
  356. border-top-color: transparent;
  357. border-left-color: transparent;
  358. border-right-color: ${not_begun_color};
  359. border-bottom-color: ${not_begun_color};
  360. }
  361. `;
  362. }
  363. } else if (GM_settings.get("type") == "Border") {
  364. let border_position = GM_settings.get("borderPosition");
  365. let border_height = GM_settings.get("borderHeight");
  366. // The box-shadow prevents the click on the item, so it needs to be hidden on hover
  367. css_style += `
  368. .cover-wrapper {
  369. --shadow-y: ${
  370. border_position == "Top" ? `` : `-`
  371. }${border_height}px;
  372. }
  373. .cover-wrapper:hover::after {
  374. display:none!important;
  375. --shadow-y: 0px;
  376. transition: all 0.25s;
  377. transition-behavior: allow-discrete;
  378. }`;
  379.  
  380. css_style += `
  381. .cover .cover-wrapper::before, .cover .cover-wrapper::after {
  382. width: 100%;
  383. height: 100%;
  384. border-radius: 4px;
  385. display: block;
  386. border: none;
  387. z-index: 0;
  388. }
  389. .cover .completed.cover-wrapper::after {
  390. box-shadow: inset 0px var(--shadow-y) ${completed_color};
  391. }
  392. .cover .in-progress.cover-wrapper::after {
  393. box-shadow: inset 0px var(--shadow-y) ${in_progress_color};
  394. }
  395. .cover .abandoned.cover-wrapper::after {
  396. box-shadow: inset 0px var(--shadow-y) ${abandoned_color};
  397. }
  398. .cover .not-begun.cover-wrapper::after {
  399. box-shadow: inset 0px var(--shadow-y) ${not_begun_color};
  400. }
  401. `;
  402. }
  403. return css_style;
  404. };
  405.  
  406. const setCustomStyle = function (css) {
  407. // Remove existing style if present
  408. const existingStyle = document.getElementById(
  409. "libib-custom-status-indicator-style"
  410. );
  411. if (existingStyle != null) {
  412. existingStyle.remove();
  413. }
  414.  
  415. // Add style tag to document
  416. document.head.append(
  417. Object.assign(document.createElement("style"), {
  418. type: "text/css",
  419. id: "libib-custom-status-indicator-style",
  420. textContent: css,
  421. })
  422. );
  423. };
  424. })();