Jpdb custom components

Allows custom components for kanji

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