heonian-webdic/main.js
2022-06-19 11:04:43 +02:00

567 lines
21 KiB
JavaScript

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 += `<ruby class="clickable" onclick='clickSearch(\"${letters[i]}\")'>${letters[i]}<rt>${heonianToRoman(letters[i])}</rt></ruby>`;
} else {
ruby += `<ruby>${letters[i]}<rt>${heonianToRoman(letters[i])}</rt></ruby>`;
}
}
}
//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 += `<ruby>${letters[i]}<rt>${heonianToRoman(letters[i])}</rt></ruby>`;
}
}
}
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 {
if (keys[i] == "") continue;
//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) {
if (type == "all") { word = "" };
if (type == "random") {
let random = Math.floor(Math.random() * Object.keys(json).length);
return [Object.keys(json)[random]];
}
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 += "<b class='capitalize'>" + json[results[i]][o]["type"] + "</b><br>";
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 += "<span class='result-number'>" + (o + 1) + "</span><span class='result-big'>" + json[results[i]][o]["meaning"] + "</span>";
//longer translation (below are not required, make sure to check for them)
if (json[results[i]][o]["translation"] != undefined) {
if (json[results[i]][o]["meaning"] != undefined && json[results[i]][o]["translation"].toLowerCase() != json[results[i]][o]["meaning"].toLowerCase()) {
meaning.innerHTML += "<br><span class='result-translation'>" + json[results[i]][o]["translation"] + "</span>";
}
}
//example
if (json[results[i]][o]["examples"] != undefined) {
let temp = ""
temp += "<details><summary>Examples</summary><p>";
for (let e in json[results[i]][o]["examples"]) {
temp += "<span class='heonian'>" + generateRuby(e, true) + "</span> - " + json[results[i]][o]["examples"][e] + "<br>";
}
temp += "</p></details>";
meaning.innerHTML += temp;
}
//antonyms, synonyms
if (json[results[i]][o]["antonyms"] != undefined) {
let temp = ""
temp += "<details class='result-nyms'><summary>Antonyms</summary><p>";
for (let e in json[results[i]][o]["antonyms"]) {
let r = json[results[i]][o]["antonyms"][e];
temp += "<span class='heonian'>" + generateRuby(r, true);
if (json[r] != undefined) {
temp += " - ";
for (let i in json[r]) {
if (i != 0) temp += ", ";
temp += json[r][i]["translation"];
}
}
temp += "</span><br>";
}
temp += "</p></details>";
meaning.innerHTML += temp;
}
if (json[results[i]][o]["synonyms"] != undefined) {
let temp = ""
temp += "<details class='result-nyms'><summary>Synonyms</summary><p>";
for (let e in json[results[i]][o]["synonyms"]) {
let r = json[results[i]][o]["synonyms"][e];
temp += "<span class='heonian'>" + generateRuby(r, true);
if (json[r] != undefined) {
temp += " - ";
for (let i in json[r]) {
if (i != 0) temp += ", ";
temp += json[r][i]["translation"];
}
}
temp += "</span><br>";
}
temp += "</p></details>";
meaning.innerHTML += temp;
}
//notes
if (json[results[i]][o]["notes"] != undefined) {
meaning.innerHTML += "<p class='result-notes'>" + json[results[i]][o]["notes"] + "</p>";
}
//(source, etc)
if (json[results[i]][o]["canon-etymology"] != undefined) {
meaning.innerHTML += "<details class='result-meta'><summary>Canon Etymology</summary><p>" + json[results[i]][o]["canon-etymology"] + "</p></details>";
}
if (json[results[i]][o]["meta-etymology"] != undefined) {
meaning.innerHTML += "<details class='result-meta'><summary>Meta Etymology</summary><p>" + json[results[i]][o]["meta-etymology"] + "</p></details>";
}
//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 += "<span class='heonian'>" + val + "</span><br>";
for (let i in types) {
if (i !== "") { header.innerHTML += "<span class='results-header-count'>" + i + "s - " + types[i] + "</span>"; }
}
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) {
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
console.log("localhost - not registering sw"); //because spamming removeSW() every time i make a change is annoying;
removeSW(); //just in case =w=
return;
}
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";
} 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.');
}
}
};
};
}).catch(error => {
console.error('Error during service worker registration:', error);
});
}
}
function removeSW() {
caches.keys().then(function(names) {
for (let name of names)
caches.delete(name);
}); //this doesnt actually seem to work :/ but . i'll leave it in for later fixing
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), {})