Libib - Custom status indicator style

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

目前为 2025-02-05 提交的版本。查看 最新版本

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