Jpdb custom components

Allows custom components for kanji

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

  1. // ==UserScript==
  2. // @name Jpdb custom components
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3.1
  5. // @description Allows custom components for kanji
  6. // @author Calonca
  7. // @match https://jpdb.io/kanji/*
  8. // @match https://jpdb.io/review
  9. // @match https://jpdb.io/edit_mnemonic*
  10. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  11. // @require http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js
  12. // @license GPLv2
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. //#Utility functions
  19. //
  20. function getCCList(){//returns custom component array from mnemonic
  21. let mnemonic = document.getElementsByClassName("mnemonic")[0];
  22. if (mnemonic == null){
  23. return [];
  24. }else {
  25. let componetsPharagraph =
  26. Array.from(mnemonic.childNodes)
  27. .filter(p=>p.nodeName=="P")
  28. .map(p=>p.innerHTML)
  29. .find(t=>t.includes("Composed of: "));
  30. if (componetsPharagraph){
  31. return componetsPharagraph.substring(componetsPharagraph.indexOf(':') + 2).split(",");
  32. }else return [];
  33. }
  34. }
  35.  
  36. /**
  37. * @param {String} HTML representing a single element
  38. * @return {Element}
  39. */
  40. function htmlToElement(html) {
  41. var template = document.createElement('template');
  42. html = html.trim(); // Never return a text node of whitespace as the result
  43. template.innerHTML = html;
  44. return template.content.firstChild;
  45. }
  46.  
  47. //#Dom update functions
  48. //
  49. function updateComponents(){
  50.  
  51. const componentsList = document.getElementsByClassName("subsection-composed-of-kanji")[0].getElementsByClassName("subsection")[0].childNodes;
  52. const componentsLen = componentsList.length;
  53.  
  54. let customSpellingList = getCCList();
  55. const customSpellingLen = customSpellingList.length;
  56. if (customSpellingLen>0){
  57. let nodesToRemove = componentsLen-customSpellingLen;
  58. //Removes nodes if the custom componets are less then the original ones
  59. for (let i=0;i<nodesToRemove;i++){
  60. componentsList[0].parentNode.removeChild(componentsList[customSpellingLen])
  61. }
  62. //Replaces the kanji/radical/spelling and the keyword/description
  63. for (let i=0;i<customSpellingLen;i++){
  64.  
  65. if (i>=componentsLen){//Duplicates nodes if we have more custom componets then the original ones
  66. componentsList[0].parentNode.appendChild(componentsList[0].cloneNode(true));
  67. }
  68. let plain = componentsList[i].querySelector(".spelling .plain")
  69. plain.innerHTML = customSpellingList[i];
  70. plain.href = "/kanji/"+customSpellingList[i]+"#a"
  71.  
  72. //Gets the keyword for each component
  73. const URL = plain.href;
  74. fetch(URL)
  75. .then(res => res.text())
  76. .then(text => {
  77. //Creates a new html document from requested page
  78. var parser = new DOMParser();
  79. var doc = parser.parseFromString(text, "text/html");
  80.  
  81. let desc = doc.querySelector(".vbox.gap .subsection").innerHTML;
  82. componentsList[i].querySelector(".description").innerHTML = desc;
  83. }).catch(err => console.log(err));
  84.  
  85. }
  86. }
  87. }
  88.  
  89. function addEditButton(){
  90. let div =document.querySelector("a[href*='/edit_mnemonic']").parentNode.parentNode;
  91. let memEditButton = div.querySelector(".subsection-label a");
  92. let coSubsectionLabel = document.querySelector(".subsection-composed-of-kanji .subsection-label");
  93. let memEditButtonClone = memEditButton.cloneNode(true);
  94. memEditButtonClone.href += "&co=1";//This parameter is used to differentiate if a user is editing a mnemonic or a component. It is only present when editing components.
  95.  
  96. coSubsectionLabel.appendChild(htmlToElement('<span style="opacity: 0.5">*</span>'));
  97. coSubsectionLabel.appendChild(htmlToElement('&nbsp;'));
  98. coSubsectionLabel.appendChild(memEditButtonClone);
  99.  
  100. }
  101.  
  102. function updateMemLine(){
  103. if (getCCList().length==0){
  104. let textArea = document.getElementsByTagName("textarea")[0];
  105. let componentsStr = "\n\nComposed of: "+Array.from(document.querySelectorAll(".spelling .plain")).map(a=>a.innerHTML).join(',');
  106. textArea.innerHTML += componentsStr;
  107. }
  108. }
  109.  
  110. function updateComponentsAndAddButton (){
  111. updateComponents();
  112. addEditButton();
  113. }
  114.  
  115. //#Running different functions based on the current page
  116. //
  117. if(window.location.href.indexOf("https://jpdb.io/kanji") > -1) {
  118. updateComponentsAndAddButton();
  119. } else if (window.location.href.indexOf("https://jpdb.io/review") > -1){//If reviewing wait for mnemonic to show up before adding the components
  120. waitForKeyElements (
  121. ".mnemonic"
  122. , updateComponentsAndAddButton
  123. );
  124. } else if (window.location.href.indexOf("https://jpdb.io/edit_mnemonic") > -1){
  125. const urlParams = new URLSearchParams(document.location.href);
  126. if (urlParams.get('co')=="1"){
  127. updateMemLine();
  128. }
  129. }
  130. })();
  131.  
  132.  
  133. //The following function is used by the script.
  134. //I Put it here instead of requires due to GreasyFolk rules, the original can be found at https://gist.github.com/BrockA/2625891
  135. /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
  136. that detects and handles AJAXed content.
  137.  
  138. Usage example:
  139.  
  140. waitForKeyElements (
  141. "div.comments"
  142. , commentCallbackFunction
  143. );
  144.  
  145. //--- Page-specific function to do what we want when the node is found.
  146. function commentCallbackFunction (jNode) {
  147. jNode.text ("This comment changed by waitForKeyElements().");
  148. }
  149.  
  150. IMPORTANT: This function requires your script to have loaded jQuery.
  151. */
  152. function waitForKeyElements (
  153. selectorTxt, /* Required: The jQuery selector string that
  154. specifies the desired element(s).
  155. */
  156. actionFunction, /* Required: The code to run when elements are
  157. found. It is passed a jNode to the matched
  158. element.
  159. */
  160. bWaitOnce, /* Optional: If false, will continue to scan for
  161. new elements even after the first match is
  162. found.
  163. */
  164. iframeSelector /* Optional: If set, identifies the iframe to
  165. search.
  166. */
  167. ) {
  168. var targetNodes, btargetsFound;
  169.  
  170. if (typeof iframeSelector == "undefined")
  171. targetNodes = $(selectorTxt);
  172. else
  173. targetNodes = $(iframeSelector).contents ()
  174. .find (selectorTxt);
  175.  
  176. if (targetNodes && targetNodes.length > 0) {
  177. btargetsFound = true;
  178. /*--- Found target node(s). Go through each and act if they
  179. are new.
  180. */
  181. targetNodes.each ( function () {
  182. var jThis = $(this);
  183. var alreadyFound = jThis.data ('alreadyFound') || false;
  184.  
  185. if (!alreadyFound) {
  186. //--- Call the payload function.
  187. var cancelFound = actionFunction (jThis);
  188. if (cancelFound)
  189. btargetsFound = false;
  190. else
  191. jThis.data ('alreadyFound', true);
  192. }
  193. } );
  194. }
  195. else {
  196. btargetsFound = false;
  197. }
  198.  
  199. //--- Get the timer-control variable for this selector.
  200. var controlObj = waitForKeyElements.controlObj || {};
  201. var controlKey = selectorTxt.replace (/[^\w]/g, "_");
  202. var timeControl = controlObj [controlKey];
  203.  
  204. //--- Now set or clear the timer as appropriate.
  205. if (btargetsFound && bWaitOnce && timeControl) {
  206. //--- The only condition where we need to clear the timer.
  207. clearInterval (timeControl);
  208. delete controlObj [controlKey]
  209. }
  210. else {
  211. //--- Set a timer, if needed.
  212. if ( ! timeControl) {
  213. timeControl = setInterval ( function () {
  214. waitForKeyElements ( selectorTxt,
  215. actionFunction,
  216. bWaitOnce,
  217. iframeSelector
  218. );
  219. },
  220. 300
  221. );
  222. controlObj [controlKey] = timeControl;
  223. }
  224. }
  225. waitForKeyElements.controlObj = controlObj;
  226. }
  227.