您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
On script enabled mobile phones (touchscreen devices) creates a kana matrix input
// ==UserScript== // @name WK KanaMatrix // @namespace EthanMcCoy // @version 0.6 // @description On script enabled mobile phones (touchscreen devices) creates a kana matrix input // @author Ethan McCoy // @include *://www.wanikani.com/lesson/session* // @include *://www.wanikani.com/review/session* // @grant none // ==/UserScript== (function() { 'use strict'; var logEvent = function(eventName, message){ console.log("%cKM: %c" + eventName + "%c " + message, "background-color:black, color:white", "text-decoration:underline; text-decoration-color:red;", ""); }; // Some of these might only be used by constructCSS but some of them are used by other functions // All number types are units in pixels var buttonBkgColor = "#32323e;"; var buttonBorderColor = "black"; var buttonPetalColor = "transparent;"; var buttonTxtColor = "white;"; var buttonHt = 50; var footerHt = 25; var buttonBorderWd = 5; var buttonWithBorder = buttonHt + 2 * buttonBorderWd; var triangleColor = "#90dae077"; var petalWd = buttonHt * 0.23; var buttonFontSizeMain = buttonHt * (1 - 0.23 * 2); var petalTopPos = 2; var petalBottomPos = 1; var petalRightPos = 3; var handakutenLeftM = 45; // Define and call function so we can collapse it in the editor var constructCSS = function(){ var kbd_css = '#KMwrapper {'+ ' user-select: none;' + //Might stop the keyboard stealing focus on holding the button, can't replicate on PC so here's hoping. ' position: fixed;'+ ' left: 50%;'+ ' margin-left: -50%;'+ ' bottom:0px;'+ ' pointer-events: none;'+ ' height: ' + buttonWithBorder + 'px;'+ ' transition: height 0.5s;'+ ' width: 100%;'+ ' z-index: 999999;'+ '}'+ '#showBoard {'+ ' user-select: none;'+ ' top:0px;'+ ' z-index:10;'+ ' position:absolute;'+ ' background-color: purple;'+ ' opacity:1;'+ ' visibility:hidden;'+ '}'+ '#kanaBoard {'+ ' user-select: none;'+ ' top: ' + (buttonWithBorder) + 'px;'+ ' position:relative;'+ ' background-color: ' + buttonBorderColor + ';'+ ' height: ' + 4 * buttonWithBorder + footerHt + 'px;' + '}'+ '#kanaBoard, #showBoard{'+ ' width: 100%;'+ '}'+ '.groupCore, .changeState{'+ ' width: calc(25% - 10px);'+ ' height: ' + buttonHt + 'px;'+ ' line-height: ' + buttonHt + 'px;'+ ' border-width: ' + buttonBorderWd + 'px;' + ' border-style:solid;'+ '}'+ '.showButton{'+ ' background-color: ' + buttonBkgColor + ' color: ' + buttonTxtColor + ' width: calc((25% - ' + buttonBorderWd/1.5*2 + 'px) / 1.5);'+ ' height: ' + (buttonWithBorder/1.5 - buttonBorderWd/1.5*2) + 'px;' +//calc(90px / 1.5);'+ ' line-height: ' + (buttonWithBorder/1.5 - buttonBorderWd/1.5*2) + 'px;' +//calc(95px / 1.5);'+ ' border-width: ' + buttonBorderWd/1.5 + 'px;' + ' box-shadow: 5px 5px #88888877;'+ ' border-style:solid;'+ // Obviously this is a bit ironic, we should probably calculate it in javascript not css ' font-size: calc('+buttonFontSizeMain+'px / 1.5);'+ // Margin calculation is a bit of mystery, should check it to see how it works and if it's accurate ' margin: ' + (buttonWithBorder - buttonWithBorder/1.5)/2 + 'px calc((25% - 25% / 1.5) / 2 - ' + buttonBorderWd + 'px / 4);' + '}'+ '#spacebar {'+ ' height:' + (buttonHt+buttonBorderWd) * 2 +'px;'+ ' font-size:'+buttonFontSizeMain * 2 +'px;'+ ' float:right;' + '}'+ '#modify {'+ ' letter-spacing: normal;'+ '}'+ '#modify .buttonComps {' + ' font-size:' + buttonFontSizeMain * 2 + 'px;' + //64pt;' + ' margin-top:15px;' + ' width:100%;' + ' text-align:center;' + ' white-space: nowrap;' + '}' + '#modify .buttonComps:nth-child(1) {' + //handakuten ' text-align:center;' + ' text-indent:10%;' + '}' + '#backspace {'+ ' font-size:'+buttonFontSizeMain+'px;'+ '}'+ '#enter {'+ ' font-size:'+buttonFontSizeMain*2+'px;'+ '}'+ '.showPetal.leftPetal, .showPetal.rightPetal {'+ //' line-height: calc(95px / 1.5);'+ '}'+ '.showPetal.topPetal{'+ //' line-height: calc(25px / 1.5);'+ '}'+ '.showPetal.bottomPetal{'+ ' line-height: calc(17px / 1.5);'+ '}'+ '.showPetal{'+ ' color: ' + buttonTxtColor + ' font-size: calc(13pt / 1.5) !important;'+ '}'+ '.groupCore, .showButton {'+ ' position:relative;'+ ' overflow:hidden;'+ ' text-align:center;'+ ' vertical-align:middle;'+ ' display:inline-block;'+ '}'+ '.groupCore {'+ ' z-index:0;'+ ' border-color:' + buttonBorderColor + ';'+ ' background-color: ' + buttonBkgColor + ' color: ' + buttonTxtColor + ' font-size:'+buttonFontSizeMain+'px;'+ '}'+ '.changeState {'+ ' font-size: ' + buttonFontSizeMain + 'px;' + ' border-color:black;'+ ' background-color: ' + buttonBkgColor + ' color: ' + buttonTxtColor + ' display: block;'+ ' vertical-align:top;'+ ' display:inline-block;'+ ' position:relative;'+ '}'+ '.buttonComps {'+ ' position:absolute;'+ ' left:50%;'+ ' transform: translateX(-50%);'+ '}'+ '.groupFlower {'+ ' pointer-events: none;'+ '}'+ '.groupPetal {'+ ' pointer-events: none;'+ ' z-index:5;'+ ' position:absolute;'+ ' background-color: ' + buttonPetalColor + ' font-size: ' + petalWd + 'px;' + //23%;'+//13pt;'+ '}'+ '.topPetal {'+ ' line-height: ' + petalWd + 'px;' + //23%;'+ ' top: ' + petalTopPos + 'px;'+ ' height: ' + petalWd + 'px;' + //23%;'+ ' width: 100%;'+ '}'+ '.leftPetal {'+ //' line-height: ' + buttonHt + 'px;' + ' top:0;'+ ' left:0;'+ ' height: 100%;'+ ' width: 23%;' + //petalWd + 'px;' + // 23%;'+ '}'+ '.rightPetal {'+ //' line-height: ' + buttonHt + 'px;' + ' top:0;'+ ' right:' + petalRightPos + 'px;'+ ' height: 100%;'+ ' width: 23%;' + //petalWd + 'px;' + // 23%;'+ '}'+ '.bottomPetal {'+ ' line-height: ' + petalWd + 'px;' + // 23%;'+ ' bottom: ' + petalBottomPos + 'px;'+ ' height: ' + petalWd + 'px;' + // 23%;'+ ' width: 100%;'+ '}'+ '.arrow{'+ ' pointer-events: none;'+ ' width: 0; '+ ' height: 0; '+ ' position:absolute;'+ ' z-index:10;'+ ' visibility:hidden;'+ '}'+ '.arrow-up {'+ ' pointer-events: none;'+ ' bottom:0;'+ ' border-bottom: ' + buttonHt/2 + 'px solid ' + triangleColor + ';' + '}'+ '.arrow-down {'+ ' top:0;'+ ' border-top: ' + buttonHt/2 + 'px solid ' + triangleColor + ';' + '}'+ '.arrow-right {'+ ' top:0;'+ ' border-top: ' + buttonHt/2 + 'px solid transparent;' + ' border-bottom: ' + buttonHt/2 + 'px solid transparent;' + '}'+ '.arrow-left {'+ ' top:0;'+ ' right:0;'+ ' border-top: ' + buttonHt/2 + 'px solid transparent;' + ' border-bottom: ' + buttonHt/2 + 'px solid transparent;' + '}'+ '#caret {'+ ' opacity:1;shape-rendering: crispEdges;'+ '}'+ 'svg {'+ ' animation: 1s linear 0s infinite running blink;'+ '}'+ '@keyframes blink {'+ ' 50% {'+ ' stroke:black;'+ ' visibility: visible;'+ ' }'+ ' 100% {'+ ' visibility:hidden;'+ ' }'+ '}'; return kbd_css; }; // Define and call function so we can collapse it in the editor and refactor to build dynamically later var constructHTML = function(){ var kbd_html = '<div id="KMwrapper">'+ ' <div id="showBoard">'+ ' <div class="showButton" id="showA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal showPetal">う</div>'+ ' <div class="groupPetal leftPetal showPetal">い</div>'+ ' <div class="groupPetal rightPetal showPetal">え</div>'+ ' <div class="groupPetal bottomPetal showPetal">お</div>'+ ' あ'+ ' </div>'+ ' </div><div class="showButton" id="showKA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">く</div>'+ ' <div class="groupPetal leftPetal">き</div>'+ ' <div class="groupPetal rightPetal">け</div>'+ ' <div class="groupPetal bottomPetal">こ</div>'+ ' か'+ ' </div>'+ ' </div><div class="showButton" id="showSA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">す</div>'+ ' <div class="groupPetal leftPetal">し</div>'+ ' <div class="groupPetal rightPetal">せ</div>'+ ' <div class="groupPetal bottomPetal">そ</div>'+ ' さ'+ ' </div>'+ ' </div><div class="showButton">'+ ' </div><div class="showButton" id="showTA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">つ</div>'+ ' <div class="groupPetal leftPetal">ち</div>'+ ' <div class="groupPetal rightPetal">て</div>'+ ' <div class="groupPetal bottomPetal">と</div>'+ ' た'+ ' </div>'+ ' </div><div class="showButton" id="showNA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">ぬ</div>'+ ' <div class="groupPetal leftPetal">に</div>'+ ' <div class="groupPetal rightPetal">ね</div>'+ ' <div class="groupPetal bottomPetal">の</div>'+ ' な'+ ' </div>'+ ' </div><div class="showButton" id="showHA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">ふ</div>'+ ' <div class="groupPetal leftPetal">ひ</div>'+ ' <div class="groupPetal rightPetal">へ</div>'+ ' <div class="groupPetal bottomPetal">ほ</div>'+ ' は'+ ' </div>'+ ' </div><div class="showButton">'+ ' </div><div class="showButton" id="showMA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">む</div>'+ ' <div class="groupPetal leftPetal">み</div>'+ ' <div class="groupPetal rightPetal">め</div>'+ ' <div class="groupPetal bottomPetal">も</div>'+ ' ま'+ ' </div>'+ ' </div><div class="showButton" id="showYA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">ゆ</div>'+ ' <div class="groupPetal leftPetal">(</div>'+ ' <div class="groupPetal rightPetal">)</div>'+ ' <div class="groupPetal bottomPetal">よ</div>'+ ' や'+ ' </div>'+ ' </div><div class="showButton" id="showRA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">る</div>'+ ' <div class="groupPetal leftPetal">り</div>'+ ' <div class="groupPetal rightPetal">れ</div>'+ ' <div class="groupPetal bottomPetal">ろ</div>'+ ' ら'+ ' </div>'+ ' </div><div class="showButton">'+ ' </div><div class="showButton">'+ ' </div><div class="showButton" id="showWA">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">ん</div>'+ ' <div class="groupPetal leftPetal">を</div>'+ ' <div class="groupPetal rightPetal">ー</div>'+ ' <div class="groupPetal bottomPetal">~</div>'+ ' わ'+ ' </div>'+ ' </div><div class="showButton" id="showPUNC">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">?</div>'+ ' <div class="groupPetal leftPetal">。</div>'+ ' <div class="groupPetal rightPetal">!</div>'+ ' <div class="groupPetal bottomPetal">…</div>'+ ' 、'+ ' </div>'+ ' </div>'+ ' </div>'+ ' <div id="kanaBoard">'+ ' <div class="groupCore" id="a">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">う</div>'+ ' <div class="groupPetal leftPetal">い</div>'+ ' <div class="groupPetal rightPetal">え</div>'+ ' <div class="groupPetal bottomPetal">お</div>'+ ' あ'+ ' </div>'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div><div class="groupCore" id="ka">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">く</div>'+ ' <div class="groupPetal leftPetal">き</div>'+ ' <div class="groupPetal rightPetal">け</div>'+ ' <div class="groupPetal bottomPetal">こ</div>'+ ' か'+ ' </div>'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div><div class="groupCore" id="sa">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">す</div>'+ ' <div class="groupPetal leftPetal">し</div>'+ ' <div class="groupPetal rightPetal">せ</div>'+ ' <div class="groupPetal bottomPetal">そ</div>'+ ' さ'+ ' </div>'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div><div class="changeState" id="backspace">'+ ' <span class="buttonComps">⌫'+ ' </span>'+ ' </div><div class="groupCore" id="ta">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">つ</div>'+ ' <div class="groupPetal leftPetal">ち</div>'+ ' <div class="groupPetal rightPetal">て</div>'+ ' <div class="groupPetal bottomPetal">と</div>'+ ' た'+ ' </div>'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div><div class="groupCore" id="na">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">ぬ</div>'+ ' <div class="groupPetal leftPetal">に</div>'+ ' <div class="groupPetal rightPetal">ね</div>'+ ' <div class="groupPetal bottomPetal">の</div>'+ ' な'+ ' </div>'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div><div class="groupCore" id="ha">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">ふ</div>'+ ' <div class="groupPetal leftPetal">ひ</div>'+ ' <div class="groupPetal rightPetal">へ</div>'+ ' <div class="groupPetal bottomPetal">ほ</div>'+ ' は'+ ' </div>'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div><div class="changeState" id="spacebar">'+ ' <span class="buttonComps">⎵'+ ' </span>'+ ' </div><div class="groupCore" id="ma">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">む</div>'+ ' <div class="groupPetal leftPetal">み</div>'+ ' <div class="groupPetal rightPetal">め</div>'+ ' <div class="groupPetal bottomPetal">も</div>'+ ' ま'+ ' </div>'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div><div class="groupCore" id="ya">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">ゆ</div>'+ ' <div class="groupPetal leftPetal">(</div>'+ ' <div class="groupPetal rightPetal">)</div>'+ ' <div class="groupPetal bottomPetal">よ</div>'+ ' や'+ ' </div>'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div><div class="groupCore" id="ra">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">る</div>'+ ' <div class="groupPetal leftPetal">り</div>'+ ' <div class="groupPetal rightPetal">れ</div>'+ ' <div class="groupPetal bottomPetal">ろ</div>'+ ' ら'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div>'+ ' </div>'+ ' <div class="changeState" id="modify">'+ ' <span class="buttonComps">゙ ゚</span>'+ //' <span class="buttonComps"></span>'+ ' <span class="buttonComps" style="font-size:16pt;margin-top:15px">大⇔小</span>'+ ' </div><div class="groupCore" id="wa">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">ん</div>'+ ' <div class="groupPetal leftPetal">を</div>'+ ' <div class="groupPetal rightPetal">ー</div>'+ ' <div class="groupPetal bottomPetal">~</div>'+ ' わ'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div>'+ ' </div><div class="groupCore" id="punc">'+ ' <div class="groupFlower">'+ ' <div class="groupPetal topPetal">?</div>'+ ' <div class="groupPetal leftPetal">。</div>'+ ' <div class="groupPetal rightPetal">!</div>'+ ' <div class="groupPetal bottomPetal">…</div>'+ ' 、'+ ' <div class="arrow arrow-down"></div>'+ ' <div class="arrow arrow-right"></div>'+ ' <div class="arrow arrow-left"></div>'+ ' <div class="arrow arrow-up"></div>'+ ' </div>'+ ' </div><div class="changeState" id="enter">'+ ' <span class="buttonComps">⏎'+ ' </span>'+ ' </div>'+ ' '+ ' </div>'+ '</div>'; return kbd_html; }; // Assign global variable initial values var curItemKey = "currentItem"; var qTypeKey = "questionType"; var actQueueKey = "activeQueue"; var svgXmlNs = "http://www.w3.org/2000/svg"; var usrDiv = document.createElement("div"); var svgD = document.createElementNS(svgXmlNs, "svg"); svgD.setAttribute("xmlns", "http://www.w3.org/2000/svg"); var caret = document.createElementNS(svgXmlNs, "line"); // These are not CSS values, they are svg display instructions caret.setAttribute("id", "caret"); caret.setAttribute("x1", "2"); caret.setAttribute("x2", "2"); caret.setAttribute("y1", "9"); // This may depend on the screen and input size, so should be defined later caret.setAttribute("y2", "27"); // ditto ^ caret.setAttribute("stroke-width", "1"); svgD.appendChild(caret); var answerCopySpan = document.createElement("span"); // This is a CSS value, should maybe assign in CSS declaration? answerCopySpan.style.visibility = "hidden"; var updateValue = function(e) { logEvent(e.type, "updateValue on e.target"); answerCopySpan.innerText = e.target.value; }; var enableKbd = function(){ console.log("enableKbd %cArguments","color: purple", arguments); var usr = $("#user-response"); usr[0].style.caretColor = "transparent"; var fld = usr.parent(); // Div that covers the answer field and steals focus so the navtive mobile keyboard pisses off. //usrDiv.focus(); usrDiv.style.position = "absolute"; usrDiv.style.opacity = 1; // From @media section of input css so may need dynamic adjustment usrDiv.style.fontSize = "0.75em"; usrDiv.style.textAlign = "center"; usrDiv.style.backgroundColor="transparent"; // usrDiv.style.pointerEvents="none"; usrDiv.style.outline = "none"; fld.prepend(usrDiv); svgD.setAttribute("width", "3"); //Put span in the div so our fake caret will be around the right spot (after the text) answerCopySpan.innerText = usr.val(); usrDiv.appendChild(answerCopySpan); console.log("appending", svgD); usrDiv.appendChild(svgD); }; var groupings = { a:"あいうえお", ka:"かきくけこ", sa:"さしすせそ", ta:"たちつてと", na:"なにぬねの", ha:"はひふへほ", ma:"まみむめも", ya:"や(ゆ)よ", ra:"らりるれろ", wa:"わをんー~", punc:"、。?!…" }; var makeDiv = function(cl, id, text){ var result = document.createElement("div"); if (cl){ result.className = cl; } if (id){ result.id = id; } if (text){ result.appendChild(document.createTextNode(text)); } return result; }; var createNodeFromGrouping = function(v, i, a){ var result; if (i === 0){ //a - centre value: needs all the nodes result = makeDiv("groupFlower", false, a[0]); result.appendChild(makeDiv("groupPetal leftPetal showPetal", false, a[1])); result.appendChild(makeDiv("groupPetal topPetal showPetal", false, a[2])); result.appendChild(makeDiv("groupPetal rightPetal showPetal", false, a[3])); result.appendChild(makeDiv("groupPetal bottomPetal showPetal", false, a[4])); } else{ result = document.createTextNode(v); } return result; }; var buttonNodes = {}; var fillButtonNodes = function(buttonNodes){ for (var grouping in groupings){ var aiueoString = groupings[grouping]; // eg. buttonNodes["ka"] = [groupFlowerDiv, leftPetalDiv, topPetalDiv, rightPetalDiv, bottomPetalDiv] buttonNodes[grouping] = Array.prototype.map.call(aiueoString, createNodeFromGrouping); } console.log("buttonNodes", buttonNodes); }; // Just fills the object, which should be passed by reference so doesn't need to be reassigned fillButtonNodes(buttonNodes); var direction; var mouseDownCoords = []; var activeButton; var showButton; var clearArrowsBut = function(activeButton/** DivElement */, expt /** String */){ activeButton .getElementsByClassName("arrow-up")[0].style.visibility=(expt==="arrow-up"?"visible":"hidden"); activeButton .getElementsByClassName("arrow-left")[0].style.visibility=(expt==="arrow-left"?"visible":"hidden"); activeButton .getElementsByClassName("arrow-right")[0].style.visibility=(expt==="arrow-right"?"visible":"hidden"); activeButton .getElementsByClassName("arrow-down")[0].style.visibility=(expt==="arrow-down"?"visible":"hidden"); return activeButton; }; var repaintDisplay = function(showButton, direction){ console.log("%crunning repaintDisplay", "color:red", arguments); if (showButton){ while (showButton.childNodes[0]){ showButton.removeChild(showButton.childNodes[0]); } if (buttonNodes[activeButton.id]){ var noneNode = buttonNodes[activeButton.id][0]; // Expected to only have one node in each petal. var upNode = buttonNodes[activeButton.id][2]; var leftNode = buttonNodes[activeButton.id][1]; var rightNode = buttonNodes[activeButton.id][3]; var downNode = buttonNodes[activeButton.id][4]; switch (direction){ case "up": // Clear other arrow visibilities clearArrowsBut(activeButton, "arrow-down"); showButton.appendChild(upNode); break; case "left": //Clear other arrow visibilities clearArrowsBut(activeButton, "arrow-right"); showButton.appendChild(leftNode); break; case "right": //Clear other arrow visibilities clearArrowsBut(activeButton, "arrow-left"); showButton.appendChild(rightNode); break; case "down": //Clear other arrow visibilities clearArrowsBut(activeButton, "arrow-up"); showButton.appendChild(downNode); break; default: // Clear all triangles clearArrowsBut(activeButton); // Show default button showButton.appendChild(noneNode); break; } } } }; /** globalMouseMoveHandler Should only be active while mouse button is down (dragging) @todo convert to touchevents @param {MouseEvent} e - the native mousemove event */ var globalMouseMoveHandler = function(e){ logEvent(e.type, "globalMouseMoveHandler on " + e.target); var threshold = 30; //number of pixels before it's not the centre console.log("%cactiveButton.id", "color:green", activeButton.id); var pairId = "show"+activeButton.id.toUpperCase(); showButton = document.getElementById(pairId); console.log(pairId, showButton); if (mouseDownCoords.length){ var horizontalD = (e.clientX||e.touches[0].pageX)-mouseDownCoords[0]; var verticalD = (e.clientY||e.touches[0].pageY)-mouseDownCoords[1]; if (horizontalD > Math.max(threshold, Math.abs(verticalD))){ direction = "right"; } else if (horizontalD < Math.min(-threshold, -Math.abs(verticalD))){ direction = "left"; } else if (verticalD > Math.max(threshold, Math.abs(horizontalD))){ direction = "down"; } else if (verticalD < Math.min(-threshold, -Math.abs(horizontalD))){ direction = "up"; } else{ direction = "none"; } repaintDisplay(showButton, direction); // reset direction //direction = "none"; } //console.log("mousemove doc", e); }; var setUpMoveTracker = function(){ //document.addEventListener("mousemove", globalMouseMoveHandler); document.addEventListener("touchmove", globalMouseMoveHandler); }; var outputField = document.getElementById("user-response"); var getOutput = function(outputField){ return outputField.value; }; var setOutput = function(val, outputField){ console.log("setting", val); if (!outputField.disabled){ outputField.value = val; answerCopySpan.innerText = outputField.value; } }; var modifyOutput = function(val, outputField){ if (!outputField.disabled){ outputField.value += val; answerCopySpan.innerText = outputField.value; } }; var listenForMouseUp = function(){ var onMouseUp = function(e){ logEvent(e.type, "onMouseUp on "+e.target); //Get the elements showBoard pair var pairId = "show"+activeButton.id.toUpperCase(); //Hide the button on showBoard document.getElementById(pairId).style.visibility="hidden"; //Hide arrows clearArrowsBut(activeButton); // Remove globalMovementListener //document.removeEventListener("mousemove", globalMouseMoveHandler); document.removeEventListener("touchmove", globalMouseMoveHandler); // Remove self from listeners // using 'once' option for this purpose // Output chosen character to text // Get direction string position at release var dirId = 0; switch (direction){ case "left": dirId = 1; break; case "up": dirId = 2; break; case "right": dirId = 3; break; case "down": dirId = 4; break; } if (groupings[activeButton.id]){ var val = groupings[activeButton.id][dirId]; modifyOutput(val, outputField); } // Drags are okay but MouseClicks (that fire with the touch events) cause blur event to fire, this line prevents that. e.preventDefault(); }; document.addEventListener("touchend", onMouseUp, { once:true }); }; var initTouchEvent = function(elem){ var onMouseDown = function(e){ logEvent(e.type, "onMouseDown handler on "+e.target); // e.preventDefault(); direction = "none"; activeButton = e.target; //Get the elements showBoard pair var pairId = "show"+activeButton.id.toUpperCase(); //Show the button on showBoard var showButton = document.getElementById(pairId); if (showButton){ showButton.style.visibility="visible"; repaintDisplay(showButton, direction); //Store coordinates of click mouseDownCoords = [e.clientX||e.touches[0].pageX, e.clientY||e.touches[0].pageY]; setUpMoveTracker();//mousemove listenForMouseUp();//mouseup } }; elem.addEventListener("touchstart", onMouseDown); }; /** Performs a character rotation for Kana */ var rotateChar = function(char){ var lookup = [ "あいうえおかきくけこさしすせそたちつてとはひふへほばびぶべぼやゆよわぁぃぅぇぉがぎぐげござじずぜぞだぢっづでどぱぴぷぺぽゃゅょゎ", "ぁぃぅぇぉがぎぐげござじずぜぞだぢっでどばびぶべぼぱぴぷぺぽゃゅょゎあいうえおかきくけこさしすせそたちづつてとはひふへほやゆよわ" ]; console.log(lookup); return lookup[1][lookup[0].indexOf(char)]||char; }; //$(document).ready(function(){ // loadingScreen.remove is called after page is rendered, so sizes should be set by then // It is also called when a new question is up, although there is nothing to remove, so we probably don't want to run it the other times var runCounter = 0; var kanaMxKbd = { // Initial status status: "hidden", // Add relevant html and css on page load inject: function(){ console.log("%cKM: %cInjecting Code into DOM", "color:white; background-color: black", "color:red; background-color: black"); $("#reviews").append(constructHTML()); enableKbd(); var oldEvaluate = answerChecker.evaluate; // TODO Mutation observers answerChecker.evaluate = function(){ logEvent("answerEvaluate", "Important 'event', but currently shimming when we should probably mutationObserve"); var result = oldEvaluate.apply(answerChecker, arguments); // Answer submitted, so the field should be disabled (but might not be set yet?) if (!result.exception && true||$("#user-response")[0].disabled){ console.log("%cAnswer Submitted (reviews)", "background-color:pink"); kanaMxKbd.disableKanaMatrixOnly(); } return result; }; // Observe input field var inputObserver = new MutationObserver(function(mutationsList, observer){ console.log("%cDOM ready for measurement", "background-color:pink"); observer.disconnect(); kanaMxKbd.resize(); }); inputObserver.observe($("#user-response")[0], {attributes:true}); }, // Measure elements and resize divs resize: function(){ console.log("%cKM: %cMeasuring widths and resizing element components", "color:white; background-color: black", "color:orange; background-color: black"); var usrDivwidth = $("#user-response")[0].scrollWidth; var usrDivheight = $("#user-response")[0].scrollHeight; console.log("%cKM: %cResize div cover: " + usrDivwidth + "px x " + usrDivheight + "px", "color:white; background-color: black", "color:orange;"); usrDiv.style.width = usrDivwidth+"px"; usrDiv.style.height = usrDivheight+"px"; svgD.setAttribute("height", usrDivheight); svgD.setAttribute( "viewBox", "0 0 3 " + usrDivheight); // Resize the triangles to a percentage of the document width $(".arrow").each(setTriangleWidths); //this.contextEnable(); }, // Enable keyboard (question is a reading) enable: function(){ console.log("%cKM: %cShowing and activating the KanaMatrix", "color:white; background-color: black", "color:yellow; background-color: black"); if ($("#user-response")[0]){ // Hide the native text caret by making it transparent $("#user-response")[0].style.caretColor = "transparent"; // Remove answerfield from tabIndex $("#user-response")[0].setAttribute("tabIndex", -1); } // Add usrDiv (covering answerfield) to tabIndex usrDiv.setAttribute("tabIndex", 0); // enable pointerevents usrDiv.style.pointerEvents = ""; // Give usrDiv focus to hide native kbd usrDiv.focus(); // show kanamatrix this.show(); }, // Answer submitted, don't want any field enabled disableKanaMatrixOnly: function(){ console.log("%cKM: %cDisabling the KanaMatrix, but not hiding it", "color:white; background-color: black", "color:lightgreen; background-color: black"); kanaMxKbd.hideCaret(); usrDiv.setAttribute("tabIndex", -1); usrDiv.style.pointerEvents = "none"; }, // Disable keyboard (question is a meaning) disable: function(){ console.log("%cKM: %cHiding the KanaMatrix", "color:white; background-color: black", "color:lightblue; background-color: black"); if ($("#user-response")[0]){ //Show the native text caret by clearing caretColor $("#user-response")[0].style.caretColor = "";//so I know it works // Remove usrDiv from tabIndex usrDiv.setAttribute("tabIndex", -1); // Ignore pointerevents usrDiv.style.pointerEvents = "none"; // Add answerfield back to tabIndex $("#user-response")[0].setAttribute("tabIndex", 0); // Give answerfield focus $("#user-response")[0].focus(); } //Hide the kanaBoard this.hide(); }, // Enable or disable based on questionType contextEnable: function(e){ // String from jStorage indicates what key was changed console.log("%cKM: %cChecking circumstances to determine which action to take", "color:white; background-color: black", "color:violet; background-color: black"); if ($("#user-response")[0].disabled && e !== curItemKey){ // init was called when an answer has been submitted, probably resized window console.log("%cCould have been called after a resize, or when questionType changes on lessons: ", "color:darkblue", e); this.disableKanaMatrixOnly(); } else if ($.jStorage.get(qTypeKey)==="reading"){ this.enable(); } else{ this.disable(); } }, // Reveal keyboard through animation show: function(){ this.status = "shown"; this.showCaret(); // Named function so it is easy to remove var transitionCnclFn = function(e){ logEvent(e.type, "transitionCnclFn on "+e.target); }; if ($("#KMwrapper")[0]){ $("#KMwrapper")[0].style.height = (5 * buttonWithBorder + footerHt) + "px"; // Does nothing, but still should remove when we know whats going on, even with the 'once' setting, it may get added more often than it is executed and removed $("#KMwrapper")[0].addEventListener("transitioncancel", transitionCnclFn, { 'once':true }); $("#KMwrapper")[0].addEventListener("transitionend", function(e){ logEvent(e.type, "anonymous function on "+e.target); $("#KMwrapper")[0].removeEventListener("transitioncancel", transitionCnclFn); }, { 'once':true }); } }, // Hide keyboard through animation hide: function(){ this.status = "hidden"; this.hideCaret(); if ($("#KMwrapper")[0]){ $("#KMwrapper")[0].style.height = buttonWithBorder + "px"; } }, showCaret: function(){ caret.style.visibility = ""; }, hideCaret: function(){ caret.style.visibility = "hidden"; } }; var main = function(e){ logEvent(e.type, "main on "+e.target); console.log("%cKM: %cmain%c - run once and only once when the quiz is about to start.", "background-color:black; color:white;", "text-decoration:underline", ""); runCounter++; // Inject only once per page load if (runCounter === 1){ // Stop mousedowns from blurring the field when we want to keep kanaMatrix up. document.body.addEventListener("mousedown", function(e){ logEvent(e.type, "anonymous function on "+e.target + ". Aimed at stopping the blur event on the inputfield when the usrDiv is clicked, or the key held down too long. WIP"); //e.preventDefault(); }); kanaMxKbd.inject(); //Assign listeners $("#user-response")[0].addEventListener('input', updateValue); var kanaBoard = document.getElementById("kanaBoard"); kanaBoard.addEventListener("touchmove", function(e){ logEvent(e.type, "anonymous function on "+e.target); e.preventDefault(); }, false); var kanaButtons = kanaBoard.getElementsByClassName("groupCore"); for (var key in kanaButtons) if (kanaButtons.hasOwnProperty(key)){ initTouchEvent(kanaButtons[key]); } var kanaModifyButtonHandler = function(e){ logEvent(e.type, "kanaModifyButtonHandler on "+e.target); //Get current output (last character) var output = getOutput(outputField); var lastChar = output[output.length-1]; if(lastChar){ output = output.substr(0,output.length-1)+rotateChar(lastChar); setOutput(output, outputField); } // prevent event from blurring usrDiv e.preventDefault(); }; document.getElementById("modify").addEventListener("touchstart", kanaModifyButtonHandler); var backspaceButtonHandler = function(e){ logEvent(e.type, "backspaceButtonHandler on "+e.target); //Get current output (last character) var output = getOutput(outputField); var lastChar = output[output.length-1]; if(lastChar){ output = output.substr(0,output.length-1); setOutput(output, outputField); } // prevent event from blurring usrDiv e.preventDefault(); }; document.getElementById("backspace").addEventListener("touchstart", backspaceButtonHandler); var spaceBarHandler = function(e){ logEvent(e.type, "spaceBarHandler on "+e.target); //Get current output (last character) var output = getOutput(outputField); var lastChar = output[output.length-1]; if(lastChar){ output += " "; //Japanese space char, I don't think it will be used though setOutput(output, outputField); } // prevent event from blurring usrDiv e.preventDefault(); }; document.getElementById("spacebar").addEventListener("touchstart", spaceBarHandler); var enterHandler = function(e){ logEvent(e.type, "enterHandler on "+e.target); e.preventDefault(); return $("#answer-form button").click(); }; document.getElementById("enter").addEventListener("touchstart", enterHandler); // Disable matrix while in transition $("#KMwrapper")[0].addEventListener("transitionstart", function(){ logEvent(e.type, "anonymous function on "+e.target); $("#kanaBoard")[0].style.pointerEvents = "none"; }); // Enable matrix when in place $("#KMwrapper")[0].addEventListener("transitionend", function(){ logEvent(e.type, "anonymous function on "+e.target); $("#kanaBoard")[0].style.pointerEvents = "auto"; }); } else{ console.log("%cKM: Already initialized, this function has been called " + runCounter + " time(s)", "color:grey"); } }; // Commands that require the DOM to be active var init = function(){ $("head").append("<style>"+constructCSS()+"</style>"); var lessonOrReview = ($("#reviews").length?"r":$("#lessons").length?"l":false); if (lessonOrReview === "r"){ // $.jStorage.listenKeyChange("questionType") fires before document.readyState is "complete" in reviews // Code is therefore not injected yet. if (document.readyState === 'complete') main({type:"invoked by code (reviews)"}); else window.addEventListener('load', main, false); } else if (lessonOrReview === "l"){ $("#lessons").append(constructHTML()); curItemKey ="l/currentQuizItem"; qTypeKey ="l/"+qTypeKey; actQueueKey = "l/"+actQueueKey; $.jStorage.listenKeyChange(curItemKey, function(e, a){ console.log("KM: initiating, because "+e+ " has just been "+ a); main({type:"invoked by code (lessons)"}); }); } }; init(); // Borders don't accept percentages so we need to calculate he pixel width of the buttons ourselves var setTriangleWidths = function(ix, triElem){ var docWidth = window.document.body.clientWidth; var numButtons = 4; var triangleWidth = (docWidth - numButtons * buttonBorderWd * 2) / numButtons / 2; triElem.style.borderLeft = triangleWidth + "px solid " + ($(triElem).hasClass("arrow-right")? triangleColor : "transparent"); triElem.style.borderRight = triangleWidth + "px solid " + ($(triElem).hasClass("arrow-left")? triangleColor : "transparent"); return triElem; }; //**** Listeners ****// // Did the answer field get focus? usrDiv.addEventListener("focus", function(e){ logEvent(e.type, "anonymous function on "+e.target); kanaMxKbd.show(); }); console.log("KM: Just added focus listener to ", usrDiv); // Did the answer field lose focus? usrDiv.addEventListener("blur", function(e){ logEvent(e.type, "anonymous function on "+e.target); logEvent(e.type, "relatedTarget "+e.relatedTarget); // I suspect the lesson code is focusing on the button element after some time, causing a blur on the div, // check if relatedTarget is the input element and we are in a reading var lessonOrReview = ($("#reviews").length?"r":$("#lessons").length?"l":false); if (lessonOrReview === "l"){ if (e.relatedTarget === $("#answer-form button")[0]){ //Take back focus console.log("KM: Taking back focus from '#answer-form button'"); e.target.focus(); } else if (e.relatedTarget === $("#user-response")[0]){ //Take back focus console.log("KM: Taking back focus from '#user-input'"); e.target.focus(); } else{ kanaMxKbd.hide(); } } else{ kanaMxKbd.hide(); } }); // Listening for currentItem to change as trigger for enabling or disabling the matrix // In Reviews when first loading, this happens before the elements are given a size to measure // In Lessons it occurs after the page has $.jStorage.listenKeyChange(curItemKey, function(e){ logEvent(arguments[1], "anonymous function on "+arguments[0]); console.log("%cKM:", "color:white; background-color: black", arguments); // clear span so cursor returns to the centre answerCopySpan.innerText = ""; kanaMxKbd.contextEnable(e); }); /* loadingScreen.__remove = loadingScreen.remove; loadingScreen.remove = function(){ runCounter++; if (runCounter === 1){ } return loadingScreen.__remove.apply(this, arguments); }*/ // }); // fix all the divs on resize (hopefully) window.addEventListener("resize", function(e){ logEvent(e.type, "anonymous function on "+e.target); kanaMxKbd.resize(); //enableKbd(); //kanaMxKbd.contextEnable(e); }); //for testing window.kanaMxKbd = kanaMxKbd; })();