Last active
August 23, 2019 19:29
-
-
Save tryforceful/5a2682f76b359ba283913eaebe34f8e6 to your computer and use it in GitHub Desktop.
WaniKani: Clickable Jisho.org Link (Tampermonkey Script)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name WaniKani: Clickable Jisho.org Link & Popover | |
// @namespace http://tryforceful.com/ | |
// @version 0.2.5 | |
// @description Clickable Jisho.org Link & Popover | |
// @author tryforceful | |
// @match htt*://www.wanikani.com/review/session | |
// @match htt*://www.wanikani.com/lesson/session | |
// @grant GM_xmlhttpRequest | |
// @grant GM_addStyle | |
// @run-at document-end | |
// @connect cors-anywhere.herokuapp.com | |
// @connect jisho.org | |
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js | |
// @require https://gist.githubusercontent.com/tryforceful/77ffe9e6951b862744b9a51bb6df9a83/raw/a976201b5f7c61909bbb68a9b8a1cdb4db4a767a/tryf-just-popover-tooltip-bootstrap.js | |
// ==/UserScript== | |
// For showing logs: | |
const verbose = false; | |
GM_addStyle(" .popover { color: black; } div#character a {color:unset; text-decoration:none; cursor: pointer } .kunyomi {color: red;} .onyomi {color: blue;}"); | |
// streamlined Bootstrap, just Popover & Tooltip | |
GM_addStyle(` | |
/*! | |
* Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/) | |
*//*! | |
* Bootstrap v3.4.1 (https://getbootstrap.com/) | |
* Copyright 2011-2019 Twitter, Inc. | |
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | |
*//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;opacity:0}.tooltip.in{opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#2e2e2e}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#2e2e2e}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#2e2e2e}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#2e2e2e}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#2e2e2e}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#2e2e2e}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#2e2e2e}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#2e2e2e}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#2e2e2e;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden} | |
`); | |
//from https://stackoverflow.com/questions/3219758/detect-changes-in-the-dom | |
const observeDOM = (function(){ | |
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; | |
return function( obj, callback ){ | |
if( !obj || !obj.nodeType === 1 ) return; // validation | |
if( MutationObserver ){ | |
// define a new observer | |
var obs = new MutationObserver(function(mutations, observer){ | |
callback(mutations); | |
}) | |
// have the observer observe foo for changes in children | |
obs.observe( obj, { childList:true, subtree:true }); | |
} | |
else if( window.addEventListener ){ | |
obj.addEventListener('DOMNodeInserted', callback, false); | |
obj.addEventListener('DOMNodeRemoved', callback, false); | |
} | |
} | |
})(); | |
function setPopover (title, content, anchorDiv, divForPlacement) { | |
$(anchorDiv).attr({ | |
'data-toggle': 'popover', | |
'title': title, | |
'data-content': content | |
}); | |
$(anchorDiv).popover({ | |
trigger: 'hover', | |
html: true, | |
container: divForPlacement, | |
placement: 'right' | |
}); | |
} | |
const addLink = (function () { | |
const PAGE_TYPES = Object.freeze({ | |
UNKNOWN: 0, | |
REVIEW: 1, | |
LESSON: 2, | |
}); | |
let currentPage = PAGE_TYPES.UNKNOWN; | |
if (/\/review\/./.test(document.URL)) { | |
currentPage = PAGE_TYPES.REVIEW; | |
} else if (/\/lesson/.test(document.URL)) { | |
currentPage = PAGE_TYPES.LESSON; | |
} | |
if(currentPage == PAGE_TYPES.UNKNOWN) | |
{ | |
// We don't know what kind of page we're on. | |
// To be safe, just exit, returning an empty fn. | |
return () => {}; | |
} | |
let previousJapaneseText = ''; // this acts as a state tracker to prevent infinite recursion | |
let f = 0; | |
return function() { | |
'use strict'; | |
console.log("Add Link was called"); | |
let elemAroundJapaneseText = currentPage == PAGE_TYPES.REVIEW ? | |
document.querySelector("div#question div#character span") : | |
document.querySelector("header div#main-info div#character") //LESSON | |
let elemWithTypeOfVocabClassName = currentPage == PAGE_TYPES.REVIEW ? | |
document.querySelector("div#question div#character") : | |
document.querySelector("header div#main-info") //LESSON | |
let addedADiv = elemAroundJapaneseText.querySelector("a"); | |
let japaneseText = addedADiv ? addedADiv.innerText : elemAroundJapaneseText.innerText; | |
// console.log('----------------') | |
// console.log('previousJapaneseText',previousJapaneseText) | |
// console.log('elemAroundJapaneseText',elemAroundJapaneseText) | |
// console.log('elemAroundJapaneseText.innerText',elemAroundJapaneseText.innerText) | |
// console.log('elemAroundJapaneseText.innerHTML',elemAroundJapaneseText.innerHTML) | |
// console.log('elemWithTypeOfVocabClassName',elemWithTypeOfVocabClassName) | |
// console.log('japaneseText',japaneseText) | |
// console.log('addedADiv',addedADiv) | |
// console.log("test 1: prev v char_content",previousJapaneseText, (z ? z : elemAroundJapaneseText.innerText), previousJapaneseText != (z ? z : elemAroundJapaneseText.innerText)); | |
// console.log("test 1: prev v japanesetext",previousJapaneseText, japaneseText, previousJapaneseText != japaneseText); | |
if(f >= 100) return; | |
if(previousJapaneseText != japaneseText || addedADiv == null) | |
{ | |
if(verbose) console.log("We made it past the gatekeeper"); | |
f++; | |
if(elemAroundJapaneseText.querySelector("img") != null) | |
{ | |
//this is not a 'real' character; it's an <img>, so don't process | |
//just remove the <a> from the previous character | |
if(addedADiv) { | |
$(addedADiv).unwrap(); | |
} | |
return; | |
} | |
//set historical state so we know when to proceed | |
previousJapaneseText = japaneseText; | |
//make it a kanji-style search | |
const isKanji = ["kanji", "radical"].includes( elemWithTypeOfVocabClassName.className ); | |
if(verbose) console.log('we made it here', "[", addedADiv, "]") | |
if(addedADiv == null) | |
{ | |
//make the inner link | |
let theLink = document.createElement("a"); | |
theLink.setAttribute("href", "http://www.jisho.org/search/" + japaneseText + (isKanji ? "%20%23kanji" : "")); | |
theLink.setAttribute("target", "_blank"); | |
theLink.setAttribute("data-innerText", japaneseText); | |
$(elemAroundJapaneseText).wrapInner(theLink); | |
//now this should come back | |
addedADiv = elemAroundJapaneseText.querySelector("a"); | |
if(verbose) console.log('we made it here 2', addedADiv, theLink ) | |
} | |
else { | |
//just manipulate the inner link | |
$(addedADiv).attr({ | |
"href": "http://www.jisho.org/search/" + japaneseText + (isKanji ? "%20%23kanji" : ""), | |
"data-innerText": japaneseText | |
}) | |
if(verbose) console.log('we made it here 3', "[", addedADiv, "]") | |
} | |
if(isKanji) { | |
const callb_kanjiapi = (resp) => { | |
if(verbose) console.log(resp.response); | |
let engResults = '<p>' + resp.response.meanings.join(', ') + '</p>'; | |
let jpnReading = '', | |
onReading = '', | |
kunReading = ''; | |
if(resp.response.on_readings.length) { | |
onReading = `<span class='onyomi'>` + resp.response.on_readings.join(', ') + '</span>'; | |
jpnReading += onReading; | |
} | |
if(resp.response.kun_readings.length) { | |
if(!!onReading) { | |
jpnReading += '<br/>' | |
} | |
kunReading = `<span class='kunyomi'>` + resp.response.kun_readings.join(', ') + '</span>'; | |
jpnReading += kunReading; | |
} | |
setPopover (jpnReading, engResults, addedADiv, elemAroundJapaneseText); | |
} | |
let params = { | |
method: 'GET', | |
url: 'https://cors-anywhere.herokuapp.com/https://kanjiapi.dev/v1/kanji/' + japaneseText, | |
headers: {'X-Requested-With': 'XMLHttpRequest'}, | |
responseType: 'json', | |
onload: callb_kanjiapi, | |
onerror: (x) => {console.error(x);}, | |
}; | |
GM_xmlhttpRequest(params); | |
} else { | |
const callb_jisho = (resp) => { | |
if(verbose) console.log(resp.response.data[0]); | |
let engResults, jpnReading; | |
if(resp.response.data[0]) { | |
engResults = resp.response.data[0].senses.map(x => '<p>'+x.english_definitions.join(', ')+'</p>').join(''); | |
jpnReading = [...new Set(resp.response.data[0].japanese.map(x => x.reading))].join(', '); | |
} | |
//console.log(engResults); | |
setPopover (jpnReading, engResults, addedADiv, elemAroundJapaneseText); | |
} | |
let params = { | |
method: 'GET', | |
url: 'https://cors-anywhere.herokuapp.com/https://jisho.org/api/v1/search/words?keyword=' + japaneseText, | |
headers: {'X-Requested-With': 'XMLHttpRequest'}, | |
responseType: 'json', | |
onload: callb_jisho, | |
onerror: (x) => {console.error(x);}, | |
}; | |
GM_xmlhttpRequest(params); | |
} | |
} | |
}; | |
})(); | |
observeDOM(document.querySelector("div#character"), addLink); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment