// bug: if you fuck up input really badly, you might get a ??? unicode to print. // it successfully breaks everything. however, im not managing to repro it so... // (shruggie) // let h; window.onload = () => { document.querySelectorAll(".ime").forEach(input => { h = new HeonianIME(input); }); } class HeonianIME { constructor(what) { this.input = []; this.currentWord = []; this.selected = -1; this.inputState = 0; //STARTING, COMPOSTING, TRAILING, Fucking Nothing, ...cleanup this.stateState = 0; //depends on the state this.inputFull = []; this.inputCurrent = ""; this.ourHtml; this.oldText = []; this.hVowels = { //standalone, composing, trailing "a": ["0", "5", "A"], "e": ["1", "6", "B"], "i": ["2", "7", "C"], "o": ["3", "8", "D"], "u": ["4", "9", "E"] }; this.hConsonants = { //0 = standalone, 1 = composing, 2-6 = vowels "g": "01", "s": "02", "r": "03", "n": "04", "c": "05", "m": "06", "j": "07", "f": "08", "t": "09", "k": "0A", "w": "0B", "l": "0C", "p": "0D", "b": "0E", "d": "0F", "h": "10", }; this.hVowelsK = Object.keys(this.hVowels) this.hConsonantsK = Object.keys(this.hConsonants); this.hConsonantsR = this.inverse(this.hConsonants); this.ourHtml = what; this.oldText = [what.value.slice(0, what.selectionStart), what.value.slice(what.selectionStart)]; what.addEventListener("keydown", (key) => { this.imeDown(key, what); }); what.addEventListener("mousedown", (e) => { this.mouse(e, what); }); } mouse(e, what) { e.preventDefault(); what.focus(); this.imeUpdate(what); } imeDetach() { //how do we detach this without . uh, let renderText = ""; this.input.forEach((w) => { renderText += w.join(""); }) if (this.selected == -1) { this.ourHtml.setSelectionRange(this.oldText[0].length+renderText.length, this.oldText[0].length+renderText.length); } else { if (this.input.join("") != "") { let from = 0; for (let x = 0; x < this.selected; x++) { from += this.input[x].join("").length; } this.ourHtml.setSelectionRange(this.oldText[0].length+from, this.oldText[0].length+from); } } this.imeDown = ()=>{}; this.mouse = ()=>{}; //close enough? } imePush() { //basically equavlient to pressing enter. if (this.selected == -1) { if (this.currentWord.join() == "") { //blame js :) let s = document.createElement("p"); s.innerText = this.input.join(""); document.body.appendChild(s); this.input = []; this.selected = -1; } else { this.input.push(this.currentWord); this.currentWord = []; } this.imeReset(); } else { if (this.selected + 1 >= this.input.length) { this.selected = -1; } else { this.selected += 1; } this.imeReset(true); } } imeDown(keyEvent, inputField) { keyEvent.preventDefault(); switch (keyEvent.key) { case "Backspace": if (this.selected == -1) { if (this.currentWord.join("") == "") { this.currentWord = this.input.pop() || []; } else { this.imeRestore(this.currentWord); // this.currentWord.pop(); } } else { this.imeRestore(this.input[this.selected]); // this.input[this.selected].pop(); if (this.input[this.selected].join("") == "") { this.input.splice(this.selected, 1); this.imeMove("left"); } } break; case "Enter": this.imePush() break; case "Space": //adds a ', on second press . an actual space break; case "ArrowLeft": this.imeMove("left"); break; case "ArrowRight": this.imeMove("right"); break; case "ArrowDown": this.imeMove("start"); break; case "ArrowUp": this.imeMove("end"); break; case "Escape": this.imeDetach(); return; default: this.imeInput(keyEvent.key); if (this.selected == -1) { this.currentWord = this.inputFull; } else { this.input[this.selected] = this.inputFull; } break; } this.imeUpdate(inputField); } imeUpdate(inputField) { let renderText = ""; this.input.forEach((w) => { renderText += w.join(""); }) inputField.value = this.oldText[0] + renderText + this.currentWord.join("") + this.oldText[1]; if (this.selected == -1) { inputField.setSelectionRange(this.oldText[0].length+renderText.length, this.oldText[0].length+renderText.length + this.currentWord.join("").length); } else { if (this.input.join("") != "") { let from = 0; for (let x = 0; x < this.selected; x++) { from += this.input[x].join("").length; } let to = from + this.input[this.selected].join("").length; inputField.setSelectionRange(this.oldText[0].length+from, this.oldText[0].length+to); } } } clamp(min, max, value) { return Math.max(min, Math.min(max, value)); } imeMove(direction) { let d = 1; if (this.currentWord.join("") != "") { this.input.push(this.currentWord); this.currentWord = []; d = 2; } switch (direction) { case "start": this.selected = 0; break; case "end": this.selected = -1; break; case "left": if (this.selected == -1) this.selected = this.input.length; this.selected -= 1 * d; if (this.selected != -1) { this.selected = this.clamp(0, this.input.length, this.selected); //is there even a point in clamping it here,,,, } break; case "right": this.selected += 1 * d; this.selected = this.clamp(0, this.input.length, this.selected); //tbh same here, like. //oh well ig ???/ if (this.selected == this.input.length) { this.selected = -1; } break; } this.imeReset(true); } getUnicodeVowel(vowel, sot) { if (sot == "standalone") sot = 0; if (sot == "composing") sot = 1; if (sot == "trailing") sot = 2; return String.fromCharCode(parseInt("E00" + this.hVowels[vowel][sot], 16)); } getUnicodeConsonant(consonant, scv) { if (scv == "standalone") { scv = 0; } else if (scv == "trailing") { scv = 1; } else { scv = Number(this.hVowels[scv][0]) + 2 } return String.fromCharCode(parseInt("E" + this.hConsonants[consonant] + "" + scv, 16)); } inverse(obj) { var retobj = {}; for (var key in obj) { retobj[obj[key]] = key; } return retobj; } imeReset(soft = false) { this.stateState = 0; this.inputState = 0; this.inputFull = []; this.inputCurrent = ""; if (!soft) { if (this.selected == -1) { this.currentWord = []; } else { this.input[this.selected] = []; } } } imeInfo(decHex) { try { let hex = decHex.codePointAt(0).toString(16).split(""); hex[1] = hex[1].toString(16);//parseInt(hex[1], 16); hex[2] = hex[2].toString(16);//parseInt(hex[2], 16); hex[3] = hex[3].toString(16);//parseInt(hex[3], 16); let ka = (hex[1] == 0 && hex[2] == 0) ? "a" : "k"; if (ka == "k") { if (hex[3] == 1) { //trailing return ([ka, "trailing"]) } else if (hex[3] == 0) { //standalone //full reset / no restore necessary return ([ka, "standalone"]) } else { //composing return ([ka, "composing"]) } } else { if (hex[3] <= 8) { //standalone return ([ka, "standalone"]) } else { //trailing return ([ka, "trailing"]) } } } catch { return ""; } } imeChange(inState, stState, pop, array) { this.inputState = inState; this.stateState = stState; if (pop) { array.pop(); } this.inputFull = array; } // @remi@snug.moe: // we made a backspace key that works!!!!!!! // @meduelelateta@sweet.succubi.services: // ari fell asleep imeRestore(array) { let hex = array[array.length - 1].codePointAt(0).toString(16).split(""); hex[1] = hex[1].toString(16);//parseInt(hex[1], 16); hex[2] = hex[2].toString(16);//parseInt(hex[2], 16); hex[3] = hex[3].toString(16);//parseInt(hex[3], 16); let previous = this.imeInfo(array[array.length - 2])[1]; let ka = (hex[1] == 0 && hex[2] == 0) ? "a" : "k"; if (ka == "k") { if (hex[3] == 1) { //trailing if (previous == "trailing") { //trailing twice // console.log("2.1"); this.imeChange(2, 1, true, array); } else { //trailing once // console.log("2.0"); this.imeChange(2, 0, true, array); } } else if (hex[3] == 0) { if (array.length != 1) { //0.1, i think? // console.log("0.1"); this.imeChange(0, 1, true, array); } else { //standalone // console.log("full reset"); this.imeReset(); } } else { //composing if (previous == "standalone") { //0.1?? //recreate?? last digit??? let c = this.hConsonantsR[[hex[1], hex[2]].join("").toUpperCase()]; array.pop(); array.push(this.getUnicodeConsonant(c, "standalone")); this.inputCurrent = c; // console.log("0.1"); this.imeChange(0, 1, false, array); } else { //0.2???? fuck it lets try and find out ig //recreate?? last digit??? let c = this.hConsonantsR[[hex[1], hex[2]].join("").toUpperCase()]; array.pop(); array.push(this.getUnicodeConsonant(c, "standalone")); this.inputCurrent = c; // console.log("0.2"); this.imeChange(0, 2, false, array); } } } else { if (hex[3] <= 8) { //standalone // console.log("full reset"); this.imeReset(); } else { //trailing if (previous == "trailing") { //trailing twice // console.log("2.1"); this.imeChange(2, 1, true, array); } else { //trailing once // console.log("2.0"); this.imeChange(2, 0, true, array); } } } } imeInput(key) { key = key.toLowerCase(); if (!this.hConsonantsK.includes(key) && !this.hVowelsK.includes(key)) return; switch (this.inputState) { case 0: //starting if (this.stateState == 0) { if (this.hVowelsK.includes(key)) { this.inputFull.push(this.getUnicodeVowel(key, "standalone")); this.inputState = 2; } else { this.inputFull.push(this.getUnicodeConsonant(key, "standalone")); this.stateState = 1; this.inputCurrent = key; } break; } else if (this.stateState == 1) { if (this.hVowelsK.includes(key)) { this.inputState = 1; this.stateState = 0; } else { this.inputFull.push(this.getUnicodeConsonant(key, "trailing")); this.inputCurrent = key; this.stateState = 2; break; } } else if (this.stateState == 2) { if (this.hVowelsK.includes(key)) { this.inputState = 1; this.stateState = 0; } else break; } case 1: //composing if (this.hVowelsK.includes(key)) { this.inputState = 2; this.inputFull.pop(); this.inputFull.push(this.getUnicodeConsonant(this.inputCurrent, key)); this.inputCurrent = ""; } break; case 2: //trailing if (this.hVowelsK.includes(key)) { if (this.stateState == 1 && this.inputCurrent != "") { this.inputFull.pop(); let ic = this.inputCurrent; let k = key; this.imePush(); this.inputFull.pop(); this.inputFull.push(this.getUnicodeConsonant(ic, k)); this.inputCurrent = ""; this.inputState = 2; } else { this.inputFull.push(this.getUnicodeVowel(key, "trailing")); this.inputCurrent = ""; this.stateState += 1; } } else { this.inputFull.push(this.getUnicodeConsonant(key, "trailing")); this.inputCurrent = key; this.stateState += 1; } if (this.stateState == 2) { this.stateState = 0; this.inputState = 3; }; break; case 3: //go to next word uwu if (this.hVowelsK.includes(key)) { this.inputFull.pop(); let ic = this.inputCurrent; let k = key; this.imePush(); this.inputFull.pop(); this.inputFull.push(this.getUnicodeConsonant(ic, k)); this.inputCurrent = ""; this.inputState = 2; } else { this.imePush(); this.imeInput(key); } break; } } }