let json = null; let searchDictionary = {}; let header = null; let main = null; let ime = null; function toggleIME() { if (ime == null) { ime = new HeonianIME(header.querySelector("input")); header.querySelector("#search button").style.color = "var(--fg)"; } else { ime.imeDetach(); ime = null; header.querySelector("#search button").style.color = "var(--grey)"; } } function animateHeader(inout = false) { //todo: debounce this if (inout) { header.classList.add("header-animation-reverse"); header.classList.remove("header-animation"); main.classList.add("main-animation-reverse"); setTimeout(() => { header.classList.add("fullscreen"); main.style.opacity = "0"; main.classList.remove("main-animation-reverse"); main.innerHTML = ""; }, 500); setTimeout(() => { header.classList.remove("header-animation-reverse"); }, 1000); } else { header.classList.add("header-animation"); header.classList.remove("header-animation-reverse"); setTimeout(() => { header.classList.remove("fullscreen"); main.classList.add("main-animation"); }, 500); setTimeout(() => { header.classList.remove("header-animation"); main.style.opacity = "1"; main.classList.remove("main-animation"); }, 1000); } //somewhere here we animate in the actual page too, but it.. doesnt exist yet } // on space animateHeader() document.addEventListener("keydown", (e) => { //on / key press if (e.keyCode === 191) { e.preventDefault(); header.querySelector("input").focus(); } if (e.keyCode === 27) { //if search box not focused, and if we're not on the home page, go back to home page if (!header.querySelector("input").matches(":focus") && !header.classList.contains("fullscreen")) { e.preventDefault(); goHome(); } else if (header.querySelector("input").matches(":focus") && ime != null) { toggleIME(); } else if (header.querySelector("input").matches(":focus")) { header.querySelector("input").value = ""; } } if (e.keyCode === 13) { if (header.querySelector("input").matches(":focus") && ime == null) { //search e.preventDefault(); if (header.classList.contains("fullscreen")) { animateHeader(); } doSearch(); } } }); function stripWord(word, space = false) { return space == false ? word.replace(/[^a-zA-Z-]/gu, "").toLowerCase() : word.replace(/[^a-zA-Z-\s]/gu, "").toLowerCase(); } heonianVowels = { "-1": "", "0": "", "1": "a", "2": "e", "3": "i", "4": "o", "5": "u", "a": "a", "b": "e", "c": "i", "d": "o", "e": "u" } heonianVowelsSeparate = { "0": "a", "1": "e", "2": "i", "3": "o", "4": "u", "a": "a", "b": "e", "c": "i", "d": "o", "e": "u" } heonianConsonants = { "00": "", "01": "g", "02": "sh", "03": "r", "04": "ny", "05": "ch", "06": "m", "07": "j", "07": "y", "08": "f", "09": "t", "0a": "k", "0b": "w", "0c": "l", "0d": "p", "0e": "b", "0f": "d", "10": "h", }; function heonianToRoman(text, strict = false) { let roman = ""; for (let i = 0; i < text.length; i++) { let h = text.codePointAt(i); if (h >= 57344 && h <= 57606) { h = text.codePointAt(i).toString(16).split(""); if (h[1] == 0 && h[2] == 0) { //if vowel roman += heonianVowelsSeparate[h[3]]; } else { //if consonant if (h[1] == 0 && h[2] == 6 && h[3] == 1) { roman += "n"; } else { roman += heonianConsonants[h[1] + "" + h[2]] + heonianVowels[h[3] - 1]; } } } else if (strict == false) { roman += text.charAt(i); } } return roman; } function processVerb(word) { //basically, turn welikanyapash into liku //and uhhhhhhhhhhh maybe. return as an object with. all the things . used, for info return word; //but ... not yet uwu } function isHeonian(word) { letters = word.split(""); for (let i = 0; i < letters.length; i++) { if (letters[i].codePointAt(0) < 57344 || letters[i].codePointAt(0) > 57606) { return false; } } return true; } function generateRuby(word, addLinks = false) { ruby = ""; if (addLinks == true) { //split by space let split = word.split(" "); for (let i = 0; i < split.length; i++) { letters = word.split(""); for (let i = 0; i < letters.length; i++) { if (letters[i] == " ") { ruby += " "; } else if (json[letters[i]] != undefined) { ruby += `${letters[i]}${heonianToRoman(letters[i])}`; } else { ruby += `${letters[i]}${heonianToRoman(letters[i])}`; } } } //somehow figure out how to get links in } else { letters = word.split(""); for (let i = 0; i < letters.length; i++) { if (letters[i] == " ") { ruby += " "; } else { ruby += `${letters[i]}${heonianToRoman(letters[i])}`; } } } return ruby; } function loadDictionary() { fetch("./heonian-resources/wordlist.json").then((e) => { if (e.status === 200) { //convert to json lmao e.json().then((e) => { json = e; let values = Object.values(json); let keys = Object.keys(json); //prepare search. maybe async this if we're loading a specific word? for (let i = 0; i < keys.length; i++) { try { //create array searchDictionary[keys[i]] = []; //add: word // searchDictionary[keys[i]].push(keys[i]); //add: romanized word searchDictionary[keys[i]].push(stripWord(heonianToRoman(keys[i]))); //what if i add it here. searchDictionary[keys[i]].push(stripWord(heonianToRoman(keys[i]).replace("dsh", "z"))); //add: translations, meanings for (let o = 0; o < values[i].length; o++) { if (values[i][o].hasOwnProperty("translation")) { searchDictionary[keys[i]].push(stripWord(values[i][o]["translation"], true)) }; if (values[i][o].hasOwnProperty("meaning")) searchDictionary[keys[i]].push(stripWord(values[i][o]["meaning"], true)); } } catch (e) { console.log(keys[i] + " failed to load :/"); } } //ok, we're all ready! document.querySelector("header input").placeholder = "search"; }); } else { alert("yeah something went horribly wrong loading the wordlist so uh,,, certified ike moment"); return; } }); } function search(word, type) { console.log(type); if (type == "all") { word = "" }; if (type == "random") { let random = Math.floor(Math.random() * Object.keys(json).length); return [Object.keys(json)[random]]; } console.log(word); let words = word.split(" "); for (let i = 0; i < words.length; i++) { words[i] = stripWord(words[i]); words[i] = heonianToRoman(words[i]); words[i] = processVerb(words[i]); } word = words.join(""); console.log("debug - search: " + word); let result = []; for (let key in searchDictionary) { for (let value in searchDictionary[key]) { if (searchDictionary[key][value].includes(word)) { if (value == 0) { result.push(key); break; } else { //if its a description (i.e: includes a space) //do startsWith or something instead to prevent //false positive search results let t = false; let d = searchDictionary[key][value].split(" ") for (let w in d) { if (d[w].startsWith(word)) { t = true; } } if (t) { result.push(key); break; } } } else if (words.length > 1 && stripWord(searchDictionary[key][value]).includes(word)) { result.push(key); break; //can still produce false positives, but... very low chance //i'll fix it once it starts happening... //(just... re-creating the search tems to actually have spaces should do the trick, right?) //(yeah, i'll wanna . strip spaces to not be more than one in a row and all that but. H) //idk. this should be fine for now, and possibly, ever } } } return result; } function clickSearch(word) { header.querySelector("input").value = word; doSearch(); //TODO: scroll to top of page or w/e. //maybe dosearch should do that instead //minds too hazy for this rn though... } function doSearch(state = true) { let val = header.querySelector("input").value; if (val == "") { main.innerHTML = "enter some search terms..."; return; } //Manage search methods let type = "normal"; if (val.includes("show:random")) { type = "random"; } else if (val.includes("show:all")) { type = "all"; } //Results let results = search(val, type); if (results.length == 0) { main.innerHTML = "no results (todo: cool form)"; } else { main.innerHTML = ""; /* [words - 420 found / expressions - 69 found / wahtever else - ygettheidea] (click one to filter to just those) */ let types = {} for (let i = 0; i < results.length; i++) { let result = document.createElement("div"); result.classList.add("result"); // 1. add word in heo + romanized ruby as header let header = document.createElement("span"); header.classList.add("result-header"); header.innerHTML = generateRuby(results[i]); // 2. add tags [tags like. common word, slang, formal???, type, etc] // hhh how do i do this part, what tags. would i add. would this be. per word, or per definition? // 3. formal/nonformal versions, inflections button // could try to work on this though // 4. audio clip if found // 5. render all meanings of word /* short english translation, followed by . long description/explanation of word maybe an example? usage? and. source/who made it/etc repeat for all meanings of word, */ result.appendChild(header); let last = ""; for (let o = 0; o < json[results[i]].length; o++) { let meaning = document.createElement("div"); meaning.classList.add("result-meaning"); //ripping off jisho: Bold, word type (*required) if (last != json[results[i]][o]["type"]) meaning.innerHTML += "" + json[results[i]][o]["type"] + "
"; last = json[results[i]][o]["type"]; if (types[json[results[i]][o]["type"]] == undefined) { types[json[results[i]][o]["type"]] = 1; } else { types[json[results[i]][o]["type"]]++; } //number, meaning (*required) meaning.innerHTML += "" + (o + 1) + "" + json[results[i]][o]["meaning"] + ""; //longer translation (below are not required, make sure to check for them) if (json[results[i]][o]["translation"] != undefined && json[results[i]][o]["translation"].toLowerCase() != json[results[i]][o]["meaning"].toLowerCase()) { meaning.innerHTML += "
" + json[results[i]][o]["translation"] + ""; } //example if (json[results[i]][o]["examples"] != undefined) { let temp = "" temp += "
Examples

"; for (let e in json[results[i]][o]["examples"]) { temp += "" + generateRuby(e, true) + " - " + json[results[i]][o]["examples"][e] + "
"; } temp += "

"; meaning.innerHTML += temp; } //antonyms, synonyms if (json[results[i]][o]["antonyms"] != undefined) { let temp = "" temp += "
Antonyms

"; for (let e in json[results[i]][o]["antonyms"]) { let r = json[results[i]][o]["antonyms"][e]; temp += "" + generateRuby(r, true); if (json[r] != undefined) { temp += " - "; for (let i in json[r]) { if (i != 0) temp += ", "; temp += json[r][i]["translation"]; } } temp += "
"; } temp += "

"; meaning.innerHTML += temp; } if (json[results[i]][o]["synonyms"] != undefined) { let temp = "" temp += "
Synonyms

"; for (let e in json[results[i]][o]["synonyms"]) { let r = json[results[i]][o]["synonyms"][e]; temp += "" + generateRuby(r, true); if (json[r] != undefined) { temp += " - "; for (let i in json[r]) { if (i != 0) temp += ", "; temp += json[r][i]["translation"]; } } temp += "
"; } temp += "

"; meaning.innerHTML += temp; } //notes if (json[results[i]][o]["notes"] != undefined) { meaning.innerHTML += "

" + json[results[i]][o]["notes"] + "

"; } //(source, etc) if (json[results[i]][o]["canon-etymology"] != undefined) { meaning.innerHTML += "
Canon Etymology

" + json[results[i]][o]["canon-etymology"] + "

"; } if (json[results[i]][o]["meta-etymology"] != undefined) { meaning.innerHTML += "
Meta Etymology

" + json[results[i]][o]["meta-etymology"] + "

"; } //todo result.appendChild(meaning); //y, yeah. } main.appendChild(result); } types = sortObject(types); let header = document.createElement("div"); header.classList.add("results-header"); //When using search methods, accomodate search text. switch (type) { //show:all case "all": val = "Showing all words"; break; case "random": val = "Showing random word: " + results[0]; break; //OMG A WORD default: val = "search results for: " + val; break; } header.innerHTML += "" + val + "
"; for (let i in types) { if (i !== "") { header.innerHTML += "" + i + "s - " + types[i] + ""; } } main.prepend(header); } const url = new URL(window.location); url.searchParams.set('s', val); if (state == true) history.pushState(val, "", url); } function goHome(state = true) { animateHeader(true); const url = new URL(window.location); url.searchParams.delete('s'); if (state == true) history.pushState(null, "", url); } async function registerSW() { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('./sw.js').then(registration => { registration.onupdatefound = () => { const installingWorker = registration.installing; if (installingWorker == null) { return; } installingWorker.onstatechange = () => { if (installingWorker.state === 'installed') { if (navigator.serviceWorker.controller) { // At this point, the updated precached content has been fetched, // but the previous service worker will still serve the older // content until all client tabs are closed. header.querySelector("#update").style.display = "block"; // Execute callback if (config && config.onUpdate) { config.onUpdate(registration); } } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. console.log('Content is cached for offline use.'); // Execute callback if (config && config.onSuccess) { config.onSuccess(registration); } } } }; }; }).catch(error => { console.error('Error during service worker registration:', error); }); } } function removeSW() { navigator.serviceWorker.getRegistrations().then(function(registrations) { for(let registration of registrations) { registration.unregister() }}); //dev use only!! or whatever... i just copypasted this from SO } window.onload = () => { header = document.querySelector("header"); main = document.querySelector("main"); header.querySelector("#search button").onclick = () => { toggleIME(); }; header.querySelector("span.heonian").onclick = () => { if (!header.classList.contains("fullscreen")) { goHome(); } }; header.querySelector("#showAllWords").onclick = () => { if (header.classList.contains("fullscreen")) { animateHeader(false); } header.querySelector("input").value = "show:all"; doSearch(true); }; header.querySelector("#randomWord").onclick = () => { if (header.classList.contains("fullscreen")) { animateHeader(false); } header.querySelector("input").value = "show:random"; doSearch(true); }; loadDictionary(); window.addEventListener('popstate', (e) => { if (e.state == null) { if (!header.classList.contains("fullscreen")) { goHome(false); } } else { if (header.classList.contains("fullscreen")) { animateHeader(false); } header.querySelector("input").value = e.state; doSearch(false); } }); registerSW(); //also check if ?s is there . for hecks sake (todo) (TODO!!!!!!) } const sortObject = obj => Object.keys(obj).sort().reduce((res, key) => (res[key] = obj[key], res), {})