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