commit 78eb25a2add1fcd0b20491418152b3e0f7ec5ee7 Author: remi Date: Sat May 7 15:20:44 2022 +0200 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..7bac698 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Heonian IME + +ime made in js for heonian + +how to use: + +* have an input or a textarea +* probably put heonian font on it. generally a good idea +* `let heonian = new HeonianIME(your_input);` diff --git a/ime.js b/ime.js new file mode 100644 index 0000000..bdac89a --- /dev/null +++ b/ime.js @@ -0,0 +1,472 @@ +// 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": + //TODO LMAO + 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; + } + } +} \ No newline at end of file