'
+ '
' + result.title + '
'
+ '
' + creationDateString + '
'
+ '
' + result.html + '
'
+'
';
notifiContent.innerHTML = entryHtml + notifiContent.innerHTML;
document.getElementsByClassName("notificationBtn")[0].classList.add("update");
}
}
// Shows the detailed information of the target element using its ID
function requestLongData(event, id) {
if (event.target.nodeName === "IMG") {
return;
}
var data = {"id": id};
$.ajax({
type : "POST",
url : "/api/news/detailed",
data: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
},
success : function(result) {
parseDetailedNotificationResults(result);
},
error : function(result) {
console.log(result);
}
});
}
// Parses the results of /api/news/detailed API calls and displays them
async function parseDetailedNotificationResults(result) {
$("#notification-detail-head").html(result.entry.title);
$("#notification-detail-body").html(result.entry.html);
$("#notificationModal").modal('show');
}
// Opens the short-informations for notifications
function toggleNotifications(event) {
let container = $('#notifications-container');
// Check if notification is opened already
if (!container.hasClass("hidden")) {
closeNotifications();
return;
}
// Prevent click event to pass through to the body
event.stopPropagation();
// Set the timestamp
localStorage.setItem("notification_timestamp", Math.floor(Date.now() / 1000));
container[0].classList.remove("hidden");
// Make clicks outside the element close it
$(document).one("click", function() {
closeNotifications();
container.off("click");
});
container.click(function(event){
event.stopPropagation();
});
}
// Closes the short-informations for notifications
function closeNotifications() {
document.getElementById("notifications-container").classList.add("hidden");
document.getElementsByClassName("notificationBtn")[0].classList.remove("update");
}
// Calls a page that displays (more-or-less) all past notifications
function showAllNotifications() {
Util.loadUrl(JotoTools.getPageUrl("news"));
}
================================================
FILE: html/assets/js/page/overlay/settings.js
================================================
/*
* This JS-File everything related to the settings overlay
*/
function Settings() { }
// Analytics. Use your own or leave empty
var analyticsUrl = '';
var analyticsAttributes = null;
// Default "language" settings
Settings.language = {
searchLang: { isCookie: true, id: "default_lang", dataType: "string", val: JotoTools.toJotobaLanguage(Cookies.get("default_lang") || navigator.language || navigator.userLanguage || "en-US") },
pageLang: { isCookie: true, id: "page_lang", dataType: "string", val: Cookies.get("page_lang") || "en-US" },
}
// Default "search" settings
Settings.search = {
alwaysShowEnglish: { isCookie: true, id: "show_english", dataType: "boolean", val: true },
showEnglishOnTop: { isCookie: true, id: "show_english_on_top", dataType: "boolean", val: false },
showExampleSentences: { isCookie: true, id: "show_sentences", dataType: "boolean", val: true },
showFurigana: { isCookie: true, id: "sentence_furigana", dataType: "boolean", val: true },
focusSearchbar: { isCookie: false, id: "focus_searchbar", dataType: "boolean", val: false },
selectSearchbarContent: { isCookie: false, id: "select_searchbar_content", dataType: "boolean", val: false },
itemsPerPage: { isCookie: true, id: "items_per_page", dataType: "int", val: 10 },
kanjiPerPage: { isCookie: true, id: "kanji_page_size", dataType: "int", val: 4 },
showFullGraph: { isCookie: false, id: "show_full_graph", dataType: "boolean", val: true },
}
// Default "display" settings
Settings.display = {
theme: { isCookie: false, id: "theme", dataType: "string", val: "light" },
kanjiAnimationSpeed: { isCookie: false, id: "kanji_speed", dataType: "float", val: 1 },
showKanjiOnLoad: { isCookie: false, id: "show_kanji_on_load", dataType: "boolean", val: true },
showKanjiNumbers: { isCookie: false, id: "show_kanji_numbers", dataType: "boolean", val: false },
}
// Default "other" settings
Settings.other = {
enableDoubleClickCopy: { isCookie: false, id: "dbl_click_copy", dataType: "boolean", val: true },
trackingAllowed: { isCookie: false, id: "tracking_allowed", dataType: "boolean", val: true },
firstVisit: { isCookie: false, id: "first_time", dataType: "boolean", val: true }
}
// Saves a settings-object into localStorage / Cookies
Settings.saveSettings = function (object) {
for (let [key, entry] of Object.entries(object)) {
if (entry.isCookie) {
Cookies.set(entry.id, entry.val, { path: '/', expires: 365 });
} else {
localStorage.setItem(entry.id, entry.val);
}
}
}
// Loads a settings-object from localStorage / Cookies
Settings.loadSettings = function (object) {
for (let [key, entry] of Object.entries(object)) {
let data = "";
// Try to get the data
if (entry.isCookie) {
data = Cookies.get(entry.id, entry.val);
} else {
data = localStorage.getItem(entry.id);
}
// Not found => ignore
if (!data) {
continue;
}
// Found => parse and overwrite
switch (entry.dataType) {
case "boolean":
object[key].val = Util.toBoolean(data);
break;
case "int":
object[key].val = parseInt(data);
break;
case "float":
object[key].val = parseFloat(data);
break;
default:
object[key].val = data;
}
}
}
// Alters a "language" setting and reloads if needed
Settings.alterLanguage = function (key, value, reloadPage) {
Settings.language[key].val = value;
Settings.saveSettings(Settings.language);
if (reloadPage) {
location.reload();
}
}
// Used for the Choices-Hook on function calls
alterLanguage_search = function (html, value) {
let reloadPage = window.location.href.includes("/search");
Settings.alterLanguage("searchLang", value, reloadPage);
}
// Used for the Choices-Hook on function calls
alterLanguage_page = function (html, value) {
Settings.alterLanguage("pageLang", value, true);
}
// Alters a "search" setting and reloads if needed
Settings.alterSearch = function (key, value, updateSub) {
Settings.search[key].val = value;
Settings.saveSettings(Settings.search);
if (updateSub) {
OverlaySettings.updateSubEntries();
}
}
// Alters a "display" setting and reloads if needed
Settings.alterDisplay = function (key, value) {
Settings.display[key].val = value;
Settings.saveSettings(Settings.display);
}
// Alters a "other" setting and reloads if needed
Settings.alterOther = function (key, value) {
Settings.other[key].val = value;
Settings.saveSettings(Settings.other);
}
// Opens the Settings Overlay and accepts cookie usage
Settings.trackingAccepted = function (manuallyCalled) {
if (manuallyCalled)
Util.showMessage("success", getText("SETTINGS_COOKIE_ACCEPT"));
Settings.alterOther("trackingAllowed", true);
loadAnalytics();
Util.setMdlCheckboxState("tracking_settings", true);
}
// Revokes the right to store user Cookies
Settings.trackingDeclined = function (manuallyCalled) {
if (manuallyCalled)
Util.showMessage("success", getText("SETTINGS_COOKIE_REJECT"));
Settings.alterOther("trackingAllowed", false);
Util.setMdlCheckboxState("tracking_settings", false);
}
// Special handling for tracking_allowed
Settings.onTrackingAcceptChange = function (allowed) {
if (allowed) {
Settings.trackingAccepted(true);
} else {
Settings.trackingDeclined(true);
}
}
// Prepare the settings overlay's data initially
async function prepareSettingsOverlay() {
// Prepare the Settings Overlay
OverlaySettings.updateDropdowns();
OverlaySettings.updateCheckboxes();
OverlaySettings.updateSubEntries();
OverlaySettings.updateSliders();
OverlaySettings.updateInputs();
};
// Load Settings on initial load
Util.awaitDocumentInteractive(() => {
Settings.loadSettings(Settings.search);
Settings.loadSettings(Settings.display);
Settings.loadSettings(Settings.other);
});
Util.awaitDocumentReady(() => {
Settings.loadSettings(Settings.language);
prepareSettingsOverlay();
// Add the info-icon on initial page load if needed
if (Settings.other.firstVisit.val) {
$(".infoBtn").addClass("new");
}
// Load analytics if allowed -> At this points any external source with high prio has already been loaded in and should have overwritten the analytics vars
if (Settings.other.trackingAllowed.val && analyticsUrl.length > 0) {
loadAnalytics();
}
});
function loadAnalytics() {
Util.awaitDocumentReady(() => {
Util.loadScript(analyticsUrl, true, analyticsAttributes, () => {
// Prepare any css-based events after the script is ready
let buttons = document.querySelectorAll(".p");
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', handleEvent);
}
function handleEvent(event) {
if (window.plausible) {
let attribute = event.target.getAttribute('data-p');
if (!attribute) return;
let eventData = attribute.split(/,(.+)/);
let events = [JSON.parse(eventData[0]), JSON.parse(eventData[1] || '{}')];
plausible(...events);
}
}
});
});
}
================================================
FILE: html/assets/js/page/overlay/settings_overlay.js
================================================
/** This JS file is used for the connection between the settings "backend" and "frontend" */
function OverlaySettings() {}
// Toggles a single element visible / hidden
var toggleSubEntry = function(id, show) {
if (show) {
$(id).removeClass("hidden");
} else {
$(id).addClass("hidden");
}
}
// Sets a slider to the given value
var setSliderEntry = function (sliderId, textId, value) {
$(sliderId).val(Settings.display.kanjiAnimationSpeed.val);
$(textId).html(Math.round(Settings.display.kanjiAnimationSpeed.val * 100) + "%");
}
// Sets a specific input's value
var setInput = function (id, value) {
let kanjiInput = $(id);
kanjiInput.val(value);
if (value) {
kanjiInput.parent().addClass("is-dirty");
}
}
// Updates all dropdowns
OverlaySettings.updateDropdowns = function() {
// "Language" page
document.querySelectorAll("#search-lang-select > .choices__item--choice").forEach((e) => {
if (e.dataset.value == Settings.language.searchLang.val) {
let choicesInner = e.parentElement.parentElement.parentElement.children[0].children;
choicesInner[0].children[0].innerHTML = e.innerHTML;
choicesInner[1].children[0].innerHTML = e.innerHTML;
}
});
document.querySelectorAll("#page-lang-select > .choices__item--choice").forEach((e) => {
if (e.dataset.value == Settings.language.pageLang.val) {
let choicesInner = e.parentElement.parentElement.parentElement.children[0].children;
choicesInner[0].children[0].innerHTML = e.innerHTML;
choicesInner[1].children[0].innerHTML = e.innerHTML;
}
});
}
// Updates all checkboxes
OverlaySettings.updateCheckboxes = function() {
// "Search" page
Util.setMdlCheckboxState("show_eng_settings", Settings.search.alwaysShowEnglish.val);
Util.setMdlCheckboxState("show_eng_on_top_settings", Settings.search.showEnglishOnTop.val);
Util.setMdlCheckboxState("show_example_sentences_settings", Settings.search.showExampleSentences.val);
Util.setMdlCheckboxState("show_sentence_furigana_settings", Settings.search.showFurigana.val);
Util.setMdlCheckboxState("focus_search_bar_settings", Settings.search.focusSearchbar.val);
Util.setMdlCheckboxState("select_searchbar_content_settings", Settings.search.selectSearchbarContent.val);
// "Display" page
Util.setMdlCheckboxState("use_dark_mode_settings", Settings.display.theme.val === "dark");
Util.setMdlCheckboxState("show_kanji_on_load_settings", Settings.display.showKanjiOnLoad.val);
Util.setMdlCheckboxState("show_kanji_numbers_settings", Settings.display.showKanjiNumbers.val);
// "Other" page
Util.setMdlCheckboxState("dbl_click_copy_settings", Settings.other.enableDoubleClickCopy.val);
Util.setMdlCheckboxState("tracking_settings", Settings.other.trackingAllowed.val);
}
// Updates all Sub entries
OverlaySettings.updateSubEntries = function() {
// "Search" page
toggleSubEntry("#eng_on_top_parent", Settings.search.alwaysShowEnglish.val);
toggleSubEntry("#select_searchbar_content_parent", Settings.search.focusSearchbar.val);
}
// Updates all sliders
OverlaySettings.updateSliders = function() {
// "Display" page
setSliderEntry("#show_anim_speed_settings", "#show_anim_speed_settings_slider", Settings.display.kanjiAnimationSpeed.val);
}
// Updates all inputs
OverlaySettings.updateInputs = function() {
setInput("#items_per_page_input", Settings.search.itemsPerPage.val);
setInput("#kanji_per_page_input", Settings.search.kanjiPerPage.val);
}
================================================
FILE: html/assets/js/page/sentencePage.js
================================================
// Toggles the given translation visible / invisible
function toggleTranslation(element) {
let parent = $(element.parentElement);
parent.find(".sentence-translation").toggle("hidden");
parent.find(".lang-separator").toggle("hidden");
parent.find(".sentence-toggle").toggleClass("hidden");
}
================================================
FILE: html/assets/js/page/wordPage.js
================================================
// Object reference for sentence reader
const sr = document.getElementById("sr");
// Enable sentence-example expander
$(".expander").on("click", (event) => {
event.target.classList.toggle("on");
event.target.parentElement.children[0].classList.toggle("collapsed");
});
// On first load and on every page resize: check where the expander-triangle is needed & whether sentence reader should be centered
hideUnusedExpanders();
centerSentenceReaderIfNeeded();
var screenWidth = $(window).width();
$(window).resize(() => {
// Mobile scrolling sends resize events because of the (dis-)appearing url input. Simple fix: ignore height changes.
if ($(window).width() == screenWidth) {
return;
}
screenWidth = $(window).width();
hideUnusedExpanders();
centerSentenceReaderIfNeeded();
});
// If the reader is overflown, remove the center to avoid weird style errors
function centerSentenceReaderIfNeeded() {
if (sr === undefined || sr === null)
return;
if (Util.checkOverflow(sr)) {
sr.parentElement.classList.add("no-center");
} else {
sr.parentElement.classList.remove("no-center");
}
}
// Scrolls the sentence reader onto the selected element
Util.awaitDocumentReady(scrollSentenceReaderIntoView);
function scrollSentenceReaderIntoView() {
let selected = $(".sentence-part.selected")[0];
if (selected !== undefined) {
$(".search-annotation").scrollLeft(selected.offsetLeft - $(".search-annotation")[0].offsetLeft);
$(".search-annotation").scrollTop(selected.offsetTop - $(".search-annotation")[0].offsetTop);
}
}
// Check if the expander-triangle should be hidden
function hideUnusedExpanders() {
$(".expander").each((i,e) => {
if (e.parentElement.children[0].scrollHeight < 40) {
e.classList.add("hidden");
} else {
e.classList.remove("hidden");
}
});
}
================================================
FILE: html/assets/js/qol.js
================================================
/**
* This JS-File contains some Quality of Life improvements for the website
*/
var shiftPressed = false;
// Prevent random dragging of