summaryrefslogtreecommitdiffstats
path: root/assets/js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--assets/js/about.js23
-rw-r--r--assets/js/absences.js245
-rw-r--r--assets/js/app.js.bvr76
-rw-r--r--assets/js/changelog.js27
-rw-r--r--assets/js/chats.js504
-rw-r--r--assets/js/grades.js235
-rw-r--r--assets/js/gradings.js190
-rw-r--r--assets/js/gsec.js676
-rw-r--r--assets/js/initialize.js35
-rw-r--r--assets/js/jitsi.js20
-rw-r--r--assets/js/lang/bundle.js513
-rw-r--r--assets/js/lib/fullcalendar/core/main.min.js (renamed from js/lib/fullcalendar/core/main.min.js)0
-rw-r--r--assets/js/lib/fullcalendar/daygrid/main.min.js (renamed from js/lib/fullcalendar/daygrid/main.min.js)0
-rw-r--r--assets/js/lib/fullcalendar/timegrid/main.min.js (renamed from js/lib/fullcalendar/timegrid/main.min.js)0
-rw-r--r--assets/js/lib/he.js345
-rw-r--r--assets/js/lib/jquery.min.js (renamed from js/lib/jquery.min.js)0
-rw-r--r--assets/js/lib/localforage.min.js (renamed from js/lib/localforage.min.js)0
-rw-r--r--assets/js/lib/materialize.min.js (renamed from js/lib/materialize.min.js)0
-rw-r--r--assets/js/lib/mergedeep.js31
-rw-r--r--assets/js/lib/sjcl.js61
-rw-r--r--assets/js/lib/themes.js188
-rw-r--r--assets/js/lib/xss.js1613
-rw-r--r--assets/js/login.js59
-rw-r--r--assets/js/logout.js5
-rw-r--r--assets/js/meals.js393
-rw-r--r--assets/js/messaging.js650
-rw-r--r--assets/js/privacypolicy.js (renamed from js/privacypolicy.js)0
-rw-r--r--assets/js/settings.js130
-rw-r--r--assets/js/setup-storage.js44
-rw-r--r--assets/js/teachers.js150
-rw-r--r--assets/js/timetable.js302
-rw-r--r--assets/js/tos.js (renamed from js/tos.js)0
32 files changed, 6515 insertions, 0 deletions
diff --git a/assets/js/about.js b/assets/js/about.js
new file mode 100644
index 0000000..7056b06
--- /dev/null
+++ b/assets/js/about.js
@@ -0,0 +1,23 @@
+/**
+ * Redirects user to login page if it's not logged int
+ */
+async function checkLogin() {
+ localforage.getItem("logged_in").then(function (value) {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch(function (err) {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+ checkLogin();
+
+ // Setup side menu
+ const menus = document.querySelectorAll(".side-menu");
+ M.Sidenav.init(menus, { edge: "right", draggable: true });
+});
diff --git a/assets/js/absences.js b/assets/js/absences.js
new file mode 100644
index 0000000..5fbc622
--- /dev/null
+++ b/assets/js/absences.js
@@ -0,0 +1,245 @@
+// const API_ENDPOINT = "https://gimb.tk/test.php"; // deprecated
+// const API_ENDPOINT = "http://localhost:5000/test.php";
+
+/**
+ * Redirects user to login page if it's not logged int
+ */
+async function checkLogin() {
+ localforage.getItem("logged_in").then(function (value) {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch(function (err) {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+/**
+ * Sets visibility of the loading bar
+ * @param {boolean} state Desired visibility
+ */
+function setLoading(state) {
+ if (state) {
+ $("#loading-bar").removeClass("hidden");
+ } else {
+ $("#loading-bar").addClass("hidden");
+ }
+}
+
+/**
+ * Loads absences from API and displays them
+ * @param {boolean} forceRefresh If true, cached absences are ignored
+ */
+async function loadAbsences(forceRefresh = false) {
+ setLoading(true);
+ // Load required data
+ let promisesToRun = [
+ localforage.getItem("username").then(function (value) {
+ username = value;
+ }),
+ localforage.getItem("password").then(function (value) {
+ password = value;
+ }),
+ localforage.getItem("absences").then(function (value) {
+ absences = value;
+ })
+ ];
+ await Promise.all(promisesToRun);
+ // If we don't have a list of absences, query it
+ if (absences == null || absences == {} || forceRefresh) {
+ try {
+ let gsecInstance = new gsec();
+ await gsecInstance.login(username, password);
+ let date = {};
+ date.from = $("#datepicker-from").val().split(".");
+ date.till = $("#datepicker-to").val().split(".");
+ Object.keys(date).map((key) => {
+ date[key] = new Date(Date.parse(date[key].reverse().join("-")));
+ });
+ gsecInstance.fetchAbsences().then( (fetchedAbsences) => {
+ fetchedAbsences.sort((a, b) => {
+ // Turn your strings into dates, and then subtract them
+ // to get a value that is either negative, positive, or zero.
+ return new Date(b.date) - new Date(a.date);
+ });
+
+ var fromKey = fetchedAbsences.findIndex((procEl) => {
+ if (procEl.date.getTime() >= date.from.getTime()) {
+ return true;
+ }
+ });
+
+ var tillKey = fetchedAbsences.findIndex((procEl) => {
+ if (procEl.date.getTime() > date.till.getTime()) {
+ return true;
+ }
+ });
+
+ // Both were -1, but we increased fromKey and decreased tillKey
+ // Means no absences in the provided timeframe
+ if (fromKey === 0 && tillKey === -2) {
+ fetchedAbsences = [];
+ } else {
+ fetchedAbsences = fetchedAbsences.slice(fromKey, tillKey);
+ }
+
+ absences = fetchedAbsences;
+ localforage.setItem("absences", fetchedAbsences).then(() => {
+ displayData();
+ setLoading(false);
+ });
+ setLoading(false);
+ }).catch( (err) => {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ });
+ } catch (err) {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ }
+ } else {
+ displayData();
+ setLoading(false);
+ }
+}
+
+/**
+ * Display absences data - called by loadAbsences
+ */
+function displayData() {
+ absences.forEach(absence => {
+ let li = document.createElement("li");
+
+ // dateString comes from bundle.js
+ let dateStringValue = dateString.longFormatted(absence["date"]);
+
+ let header = document.createElement("div");
+ header.className = "collapsible-header";
+ header.innerText = dateStringValue;
+
+ let body = document.createElement("div");
+ body.className = "collapsible-body";
+
+ let body_table = document.createElement("table");
+ body_table.className = "highlight";
+
+ let body_table_tbody = document.createElement("tbody");
+
+ Object.keys(absence.subjects).forEach(lesson => {
+
+ let absenceLessonObject = absence["subjects"][lesson];
+
+ let subjectRow = document.createElement("tr");
+ let subjectLessonIcon = document.createElement("td");
+ let subjectLessonText = document.createElement("td");
+ subjectLessonText.innerText = `${S("lesson")} ${lesson}`;
+
+ let subjectLessonIconInner = document.createElement("i");
+ subjectLessonIconInner.className = "material-icons";
+
+ switch (absenceLessonObject["status"]) {
+ case 0:
+ subjectLessonIconInner.innerText = "schedule";
+ break;
+ case 1:
+ subjectLessonIconInner.innerText = "check_circle_outline";
+ break;
+ case 2:
+ subjectLessonIconInner.innerText = "error_outline";
+ break;
+ case 3:
+ subjectLessonIconInner.innerText = "not_interested";
+ break;
+ }
+
+ subjectLessonIcon.appendChild(subjectLessonIconInner);
+
+ let subjectName = document.createElement("td");
+ subjectName.innerText = `${S(gseAbsenceTypes[absenceLessonObject["status"]])} : ${absenceLessonObject["subject"]}`;
+ subjectRow.appendChild(subjectLessonIcon);
+ subjectRow.appendChild(subjectLessonText);
+ subjectRow.appendChild(subjectName);
+ body_table_tbody.appendChild(subjectRow);
+ });
+
+ body_table.appendChild(body_table_tbody);
+ body.appendChild(body_table);
+
+ li.appendChild(header);
+ li.appendChild(body);
+ $("#absences-col").append(li);
+ });
+}
+
+/**
+ * Clear all displayed absences
+ */
+function clearAbsences() {
+ const table = document.getElementById("absences-col");
+ while (table.firstChild) {
+ table.removeChild(table.firstChild);
+ }
+}
+
+/**
+ * Force reloading of absences
+ */
+function refreshAbsences() {
+ clearAbsences();
+ loadAbsences(true);
+}
+
+/**
+ * Setup date pickers (from date and to date)
+ */
+function setupPickers() {
+ // Setup pickers
+ var dateObject = new Date();
+
+ let elems = document.querySelectorAll('#datepicker-to');
+ let options = {
+ autoClose: true,
+ format: "dd.mm.yyyy",
+ defaultDate: dateObject,
+ setDefaultDate: true,
+ firstDay: 1,
+ onSelect: refreshAbsences
+ }
+
+ M.Datepicker.init(elems, options);
+
+ dateObject.setDate(dateObject.getDate() - 14);
+
+ elems = document.querySelectorAll('#datepicker-from');
+ options = {
+ autoClose: true,
+ format: "dd.mm.yyyy",
+ defaultDate: dateObject,
+ setDefaultDate: true,
+ firstDay: 1,
+ onSelect: refreshAbsences
+ }
+ M.Datepicker.init(elems, options);
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+ checkLogin();
+ loadAbsences(true);
+
+ // Setup refresh handler
+ $("#refresh-icon").click(function () {
+ refreshAbsences();
+ });
+
+ setupPickers();
+
+ let collectionElem = document.querySelectorAll('.collapsible');
+ M.Collapsible.init(collectionElem, {});
+
+ // Setup side menu
+ const menus = document.querySelectorAll('.side-menu');
+ M.Sidenav.init(menus, { edge: 'right', draggable: true });
+});
diff --git a/assets/js/app.js.bvr b/assets/js/app.js.bvr
new file mode 100644
index 0000000..a75505e
--- /dev/null
+++ b/assets/js/app.js.bvr
@@ -0,0 +1,76 @@
+<@?i global@>
+const app_version = "<@?g app_version@>";
+const previous_commit = "<@?g latest_commit@>";
+
+if ("serviceWorker" in navigator) {
+ navigator.serviceWorker.register("/sw.js")
+ .then(() => { })
+ .catch((err) => console.log("Service worker registration failed", err));
+}
+
+// Listen to messages from service workers.
+if (navigator.serviceWorker) {
+ navigator.serviceWorker.addEventListener('message', (event) => {
+ if (event.data.msg === "install") {
+ window.location.replace("/index.html");
+ }
+ });
+}
+
+/**
+ * Displays a user-friendly text to the user and
+ * detailed text to developer (console)
+ * @param {string} usermsg User-friendly message
+ * @param {string} devmsg Developer-friendly message
+ */
+async function UIAlert(usermsg, devmsg) {
+ if (true) { // če bo kakšen dev switch?
+ M.toast( { html: usermsg } );
+ console.log(`[BežiApp UIAlert] ${usermsg} ${devmsg}`);
+ } else {
+ M.toast( { html: `${usermsg} ${devmsg}` } );
+ }
+}
+
+/**
+ * Handles GSEC error - notifies the user and prints a console message
+ * @param {Object} err GSEC error object
+ */
+function gsecErrorHandlerUI(err) {
+ console.log(`gsecErrorHanderUI: handling ${err}`);
+ if(err == GSEC_ERR_NET || err == GSEC_ERR_NET_POSTBACK_GET ||
+ err == GSEC_ERR_NET_POSTBACK_POST) {
+
+ UIAlert( D("gsecErrNet") );
+ } else if(err == GSEC_ERR_LOGIN) {
+ UIAlert( D("gsecErrLogin") );
+ localforage.setItem("logged_in", false).then( () => {
+ window.location.replace("/index.html");
+ });
+ } else {
+ UIAlert( D("gsecErrOther") );
+ }
+}
+
+
+var error_report_function = async function (msg, url, lineNo, columnNo, error) {
+ localforage.getItem("errorReporting").then((value) => {
+ let selectedE = value;
+ if(value == null || value.length < 1) {
+ selectedE = "on";
+ }
+ if(selectedE == "on") {
+ var data = {};
+ data.error = {"msg": msg, "url": url, "line": lineNo, "column": columnNo, "obj": error};
+ data.client = {"ua": navigator.userAgent, "app_version": app_version, "previous_commit": previous_commit};
+ data.type = "error";
+ $.post("https://beziapp-report.gimb.tk/", data);
+ } else {
+ console.log("error not reported as reporting is disabled!");
+ }
+ }).catch(() => {});
+ return false;
+}
+
+window.onerror = error_report_function;
+window.onunhandledrejection = error_report_function;
diff --git a/assets/js/changelog.js b/assets/js/changelog.js
new file mode 100644
index 0000000..ccc3493
--- /dev/null
+++ b/assets/js/changelog.js
@@ -0,0 +1,27 @@
+/**
+ * Redirects user to login page if it's not logged int
+ */
+async function checkLogin() {
+ localforage.getItem("logged_in").then(function (value) {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch(function (err) {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+ checkLogin();
+
+ // Setup back button
+ $("#nav-back-button").click(function () {
+ window.location.replace("/pages/about.html");
+ });
+
+ var elems = document.querySelectorAll(".collapsible");
+ M.Collapsible.init(elems, {});
+}); \ No newline at end of file
diff --git a/assets/js/chats.js b/assets/js/chats.js
new file mode 100644
index 0000000..daf10ab
--- /dev/null
+++ b/assets/js/chats.js
@@ -0,0 +1,504 @@
+// const API_ENDPOINT = "https://gimb.tk/test.php";
+const DIRECTORY_URL = "/directory.json";
+const CHATS_BEGIN_TAG = "<!-- ba-ctlmsg-chat-begin -->";
+const CHATS_END_TAG = "<!-- ba-ctlmsg-chat-end -->";
+const CHAT_REGEX = /<!-- ba-ctlmsg-chat-begin -->([\S\s]+)<!-- ba-ctlmsg-chat-end -->/g;
+const CHATS_SUBJECT_PREFIX = "ba-ctlmsg-chat-";
+
+// "Global" object for name directory
+let directory = null;
+let currentlyChattingWith = null; // msgid
+let sogovornik = null; // name
+let firstPageOfMessages = null; // so we can test if new messages ever arrive
+
+/**
+ * Redirects user to login page if it's not logged int
+ */
+async function checkLogin() {
+ localforage.getItem("logged_in").then(function (value) {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch(function (err) {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+/**
+ * Find the matching key for a provided value in an object
+ * @param {object} object Object to search
+ * @param {object} value Value to find the matching key for
+ * @returns {object} Key
+ */
+function getKeyByValue(object, value) {
+ return Object.keys(object).find(key => object[key] === value);
+}
+
+// -----------HTML HELPERS-----------
+/**
+ * Encode HTML entities
+ * @param {string} value Value to encode
+ * @returns {string} Encoded value
+ */
+function htmlEncode(value) {
+ /**
+ Create a in-memory element, set its inner text
+ (which is automatically encoded)
+ Then grab the encoded contents back out.
+ The element never exists on the DOM.
+ **/
+ return $("<textarea/>").text(value).html();
+}
+
+/**
+ * Decode HTML entities
+ * @param {string} value Value to decode
+ * @returns {string} Decoded value
+ */
+function htmlDecode(value) {
+ return $("<textarea/>").html(value).text();
+}
+// ---------------------------------
+
+// Try to fetch name:id directory
+/**
+ * Fetch name:id directory and populate it in the app
+ */
+function loadDirectory() {
+ $.ajax({
+ url: DIRECTORY_URL,
+ crossDomain: true,
+
+ dataType: "json",
+ cache: false,
+ type: "GET",
+
+ success: (data) => {
+ // If we were able to retrieve it, update the saved directory
+ localforage.setItem("directory", data);
+ directory = data;
+ // Populate autocomplete
+ populateAutocomplete();
+ },
+
+ error: () => {
+ // Otherwise, try to retrieve stored directory
+ localforage.getItem("directory").then((storedDirectory) => {
+ if (storedDirectory === null) {
+ // If unable, set directory to null
+ // (so other functions know that we don't have it)
+ UIAlert(
+ D("nameDirectoryNotSet"),
+ "loadDirectory(): stored_directory === null"
+ );
+ directory = null;
+ // Disable send button
+ $("#msg-send").prop("disabled", true);
+ } else {
+ directory = storedDirectory;
+ // Populate autocomplete
+ populateAutocomplete();
+ }
+ });
+ }
+ });
+}
+
+/**
+ * Populate autocomplete DOM object with the values
+ * fetched from the server
+ */
+function populateAutocomplete() {
+ let elems = document.querySelectorAll('.autocomplete-fullname');
+ /**
+ če se uporablja globalna var directory, ki je shranjena
+ kot objekt (vedno shranjen kot reference), bo pri let x=y
+ x le pointer na object y in se bo spremenil z spremembo
+ "originala". spodnja stvar itak ni preveč efficent, loop čez
+ vseh 7000 ljudi bi lahko delal težave...
+ kakšen Object.keys bi bila boljša varianta ampak raje napišem
+ tale komentar... idk, to se mi je zdelo uporabno ampak sedaj obžalujem
+ samo guglal sem "copying an object js"
+ **/
+
+ let autocompleteEntries = Object.assign({}, directory);
+ for (const [key] of Object.entries(autocompleteEntries)) {
+ autocompleteEntries[key] = null;
+ }
+
+ M.Autocomplete.init(elems, {
+ data: autocompleteEntries,
+ onAutocomplete: validateName,
+ minLength: 0
+ });
+
+ if (window.location.hash.length > 1) {
+ $("#full-name").val(decodeURIComponent(window.location.hash.substring(1)));
+ } else {
+ $("#full-name").val(getUrlParameter("m"));
+ }
+
+ M.updateTextFields();
+ validateName();
+}
+
+/**
+ * Toggle loading bar state
+ * @param {boolean} state Desired state
+ */
+function setLoading(state) {
+ if (state) {
+ $("#loading-bar").removeClass("hidden");
+ } else {
+ $("#loading-bar").addClass("hidden");
+ }
+}
+
+/**
+ * Send a message to the recipient
+ * @param {number} recipientNumber Recipient's id
+ * @param {string} body Message body
+ */
+async function sendMessage(recipientNumber = null, body = null) {
+ // == catches null & undefined
+ if (recipientNumber == null) {
+ recipientNumber = directory[sogovornik];
+ }
+
+ if (body == null) {
+ body = $("#msg-body").val();
+ }
+
+ if (body.length > 180) {
+ throw new RangeError(
+ "sendMessage(): message is longer than 180 characters."
+ );
+ }
+
+ let promisesToRun = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ ];
+ setLoading(true);
+ Promise.all(promisesToRun).then(() => {
+ try {
+ let gsecInstance = new gsec();
+ gsecInstance.login(username, password).then( () => {
+ gsecInstance.sendMessage(
+ recipientNumber,
+ CHATS_SUBJECT_PREFIX + body,
+ S("chatExternalInfo") + CHATS_BEGIN_TAG + body + CHATS_END_TAG
+ ).then(() => {
+
+ addMessage(0, body);
+ setLoading(false);
+
+ }).catch((err) => {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ });
+ });
+ } catch (err) {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ }
+ });
+}
+
+// datePlacement:
+/**
+ * Add message to the UI
+ * @param {boolean} isSender Indicates if current user is sender of the message
+ * @param {string} body Message body
+ * @param {number} [datePlacement=0] 0=append bubble to end, 1=append bubble to start.
+ * @param {Date} [messageDate=null] Timestamp of the message
+ */
+function addMessage(isSender, body, datePlacement=0, messageDate=null) {
+ var whos = isSender == 0 ? "mine": "yours";
+
+ var timestamp = Date.now();
+ if (messageDate instanceof Date) {
+ timestamp = messageDate.getTime();
+ }
+
+ if (typeof messageDate == "number") {
+ timestamp = messageDate;
+ }
+
+ var chatarea = document.getElementsByClassName("chat")[0];
+ var alreadyMessages = chatarea.querySelectorAll(".message");
+ var textnode = document.createTextNode(body);
+
+ var bubblenode = document.createElement("div");
+ bubblenode.setAttribute("data-date", timestamp);
+ bubblenode.appendChild(textnode);
+
+ var istaOseba;
+ var messagesnest;
+ if (chatarea.childElementCount == 0) {
+ bubblenode.className = "message last";
+ messagesnest = document.createElement("div");
+ istaOseba = false;
+ } else {
+
+ if (datePlacement == 0 || timestamp > Number(alreadyMessages[alreadyMessages.length - 1].getAttribute("data-date"))) {
+
+ datePlacement = 0;
+ console.log(alreadyMessages[0].getAttribute("data-date"));
+ bubblenode.className = "message last";
+ if (chatarea.children.item(chatarea.children.length - 1).classList.contains(whos)) { // ista oseba
+ istaOseba = true;
+ messagesnest = chatarea.children.item(chatarea.children.length - 1);
+ messagesnest.children.item(messagesnest.children.length - 1).classList.remove("last");
+ } else {
+ istaOseba = false;
+ messagesnest = document.createElement("div");
+ }
+
+ } else if (datePlacement == 1 || timestamp < Number(alreadyMessages[0].getAttribute("data-date"))) {
+
+ datePlacement = 1;
+ console.log(alreadyMessages[0].getAttribute("data-date"));
+ if(chatarea.children.item(0).classList.contains(whos)) { // ista oseba
+ bubblenode.className = "message";
+ istaOseba = true;
+ messagesnest = chatarea.children.item(0);
+ } else {
+ bubblenode.className = "message last";
+ istaOseba = false;
+ messagesnest = document.createElement("div");
+ }
+
+ } else { // auto place (slower, so 0 or 1 are options
+
+ console.log("if3");
+ for (var iter = 0; iter < alreadyMessages.length - 2; iter++) { // (-2 zato, ker potem iter+1 ne obstaja pri zadnjem elementu)
+ if (Number(alreadyMessages[iter].getAttribute("data-date")) < timestamp
+ && Number(alreadyMessages[iter+1].getAttribute("data-date")) > timestamp) {
+
+ var zgornjiIsti = alreadyMessages[iter].parentElement.classList.contains(whos);
+ var spodnjiIsti = alreadyMessages[iter+1].parentElement.classList.contains(whos);
+ console.log([zgornjiIsti, spodnjiIsti]);
+
+ if (zgornjiIsti && spodnjiIsti) {
+ messagesnest = alreadyMessages[iter].parentElement;
+ bubblenode.className = "message";
+ messagesnest.insertBefore(bubblenode, alreadyMessages[iter+1]);
+ return;
+ }
+
+ if (zgornjiIsti && !spodnjiIsti) {
+ messagesnest = alreadyMessages[iter].parentElement;
+ bubblenode.className = "message last";
+ messagesnest.children.item(messagesnest.childElementCount - 1).classList.remove("last");
+ messagesnest.appendChild(bubblenode);
+ return;
+ }
+
+ if (!zgornjiIsti && spodnjiIsti) {
+ messagesnest = alreadyMessages[iter+1].parentElement;
+ bubblenode.className = "message";
+ messagesnest.insertBefore(bubblenode, alreadyMessages[iter+1]);
+ return;
+ }
+
+ throw new RangeError("This should not happen!");
+ }
+ }
+ throw new RangeError("This should not happen!");
+
+ }
+ }
+
+ // autodetect date is not present here anymore
+ messagesnest.className = whos + " messages";
+ if (datePlacement == 0) {
+ messagesnest.appendChild(bubblenode);
+ } else {
+ messagesnest.prepend(bubblenode);
+ }
+
+ if (!istaOseba) {
+ if (datePlacement == 0) {
+ chatarea.appendChild(messagesnest);
+ } else{
+ chatarea.prepend(messagesnest);
+ }
+ }
+}
+
+async function validateName() {
+ if (directory !== null) {
+ $("#full-name").prop("disabled", false);
+ if ($("#full-name").val() in directory) {
+ $("#full-name").addClass("valid");
+ $("#full-name").removeClass("invalid");
+ $("#chat-recipient-select-btn").prop("disabled", false);
+ $("#msg-body").prop("disabled", false);
+ } else {
+ $("#full-name").addClass("invalid");
+ $("#full-name").removeClass("valid");
+ $("#chat-recipient-select-btn").prop("disabled", true);
+ $("#msg-body").prop("disabled", true);
+ $("#msg-body").val("");
+ }
+ } else {
+ $("#chat-recipient-select-btn").prop("disabled", true);
+ $("#full-name").val(D("nameDirectoryNotSet"));
+ $("#full-name").prop("disabled", true);
+ $("#msg-body").val("");
+ }
+}
+
+async function clearMessages() {
+ $(".chat").eq(0).html("");
+}
+
+function getUrlParameter(sParam) {
+ const url_params = new URLSearchParams(window.location.search);
+ const found_param = url_params.get(sParam);
+ return found_param;
+}
+
+async function updateSendButton() {
+ if ($("#msg-body").val().length == 0) {
+ $("#msg-send").prop("disabled", true);
+ } else {
+ $("#msg-send").prop("disabled", false);
+ }
+}
+
+async function setRecipient(name = null) {
+ if (name == null || name == false || name == undefined) {
+ name = $("#full-name").val();
+ } else if (typeof name != "number") {
+ throw new TypeError('Hello from setRecipient(): name can only be string or null!');
+ }
+
+ if (!Object.keys(directory).includes(name)) {
+ UIAlert(D("recipientNotInDirectory"));
+ throw new RangeError("Hello from setRecipient(): name is not in directory");
+ }
+
+ var modal = document.querySelectorAll("#directory-side-menu")[0];
+ var modalInstance = M.Sidenav.getInstance(modal);
+ modalInstance.close();
+
+ $(".msg-chattingWith").eq(0).html(name);
+ sogovornik = name;
+ currentlyChattingWith = directory[name];
+
+ $("#chat-mustSelectRecipient").hide();
+
+ updateSendButton();
+ clearMessages(); // <-- do when recipient selected
+ prepareAndStartFetchingMessages(); // <-- same
+}
+
+async function prepareAndStartFetchingMessages() {
+ if (currentlyChattingWith === 0 || (currentlyChattingWith >= 1 && currentlyChattingWith < 69420)) {
+ $("#msg-body").prop("disabled", false);
+ await clearMessages();
+ startFetchingMessages();
+ } else {
+ var modal = document.querySelectorAll("#directory-side-menu")[0];
+ var modalInstance = M.Sidenav.getInstance(modal);
+ modalInstance.open();
+ }
+}
+
+async function startFetchingMessages() {
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ ];
+
+ setLoading(true);
+ await Promise.all(promises_to_run);
+ try {
+ let gsecInstance = new gsec();
+ await gsecInstance.login(username, password);
+ for (var category = 0; category <= 1; category++) {
+ let lastpage = await gsecInstance.fetchMessagesLastPageNumber(category);
+ startLoadingMessagesForCategory(gsecInstance, category, lastpage);
+ }
+ } catch (err) {
+ gsecErrorHandlerUI(err);
+ }
+ setLoading(false);
+}
+
+async function startLoadingMessagesForCategory(gsecInstance, category, lastpage) {
+ for (var page = 1; page <= lastpage; page++) {
+ var gsecMsgList = await gsecInstance.fetchMessagesList(category, page);
+ if (category == 0) {
+ whom = 1;
+ } else {
+ whom = 0;
+ }
+ renderMessages(gsecMsgList, whom, 1);
+ }
+}
+
+async function renderMessages(gsecMsgList, whom, order = 1) { // order: 1=newest>olest 0=oldest>newest 2=autodetect (todo-not implemented)
+ for (const message of gsecMsgList) { // whom: 0=me 1=you
+ if (message.subject.startsWith(CHATS_SUBJECT_PREFIX)) {
+ console.log(message);
+ addMessage(whom, message.subject.substring(CHATS_SUBJECT_PREFIX.length), 2, message.date.getTime);
+ }
+ }
+}
+
+function setupEventListeners() {
+ $("#chat-recipient-select-btn").click(() => {
+ setRecipient();
+ });
+
+ $("#msg-send").click(() => {
+ sendMessage();
+ });
+
+ $("#full-name").on("input", () => {
+ validateName();
+ });
+
+ $("#msg-body").on("input", () => {
+ updateSendButton();
+ });
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+
+ checkLogin();
+ loadDirectory();
+
+ setupEventListeners();
+
+ updateSendButton();
+
+ // var receivedmessages = null;
+
+ M.updateTextFields();
+
+ // Setup side menu
+ const menus = document.querySelectorAll(".side-menu");
+ M.Sidenav.init(menus, { edge: "right", draggable: true });
+ let elems = document.querySelectorAll(".modal");
+ M.Modal.init(elems, {});
+
+ // Setup side modal
+ const modals = document.querySelectorAll(".side-modal");
+ M.Sidenav.init(modals, { edge: "left", draggable: false });
+ prepareAndStartFetchingMessages(); // just opens modal, as there is no recipient selected
+}); \ No newline at end of file
diff --git a/assets/js/grades.js b/assets/js/grades.js
new file mode 100644
index 0000000..aff62e4
--- /dev/null
+++ b/assets/js/grades.js
@@ -0,0 +1,235 @@
+// const API_ENDPOINT = "https://gimb.tk/test.php"; // deprecated
+let checkbox_state = false;
+var grades;
+async function checkLogin() {
+ localforage.getItem("logged_in").then((value) => {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch((err) => {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+// Set loading bar visibility
+function setLoading(state) {
+ if (state) {
+ $("#loading-bar").removeClass("hidden");
+ } else {
+ $("#loading-bar").addClass("hidden");
+ }
+}
+
+async function loadGrades(force_refresh = false) {
+ setLoading(true);
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ localforage.getItem("grades").then((value) => {
+ grades = value;
+ })
+ ];
+ await Promise.all(promises_to_run);
+ // If we don't have a list of grades, fetch it
+ if (grades == null || grades == [] || force_refresh) {
+ try {
+
+ let gsecInstance = new gsec();
+ await gsecInstance.login(username, password);
+
+ gsecInstance.fetchGrades().then( (value) => {
+ grades = value;
+ localforage.setItem("grades", value).then(() => {
+ displayGrades();
+ setLoading(false);
+ });
+ setLoading(false);
+ }).catch( (err) => {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ });
+
+ } catch (err) {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ }
+
+ } else {
+ displayGrades();
+ setLoading(false);
+ }
+}
+
+function displayGrades() {
+ let grades_by_subject = {};
+ grades.forEach((grade, index) => {
+ if (!(grade["subject"] in grades_by_subject)) {
+ grades_by_subject[grade["subject"]] = [];
+ }
+
+ let grade_object = {
+ date: dateString.longFormatted(grade["date"]),
+ teacher: grade["teacher"],
+ subject: grade["subject"],
+ title: grade["name"][0],
+ type: grade["name"][1],
+ term: grade["name"][2],
+ grade: grade["grade"],
+ temporary: grade["temporary"],
+ index: index
+ }
+ grades_by_subject[grade["subject"]].push(grade_object);
+
+ });
+
+ let root_element = document.getElementById("grades-collapsible");
+ Object.keys(grades_by_subject).forEach((subject) => {
+ // Create root element for a subject entry
+ let subject_entry = document.createElement("li");
+ // Create subject collapsible header
+ let subject_header = document.createElement("div");
+ subject_header.classList.add("collapsible-header");
+ subject_header.classList.add("collapsible-header-root");
+ // Create header text element
+ let subject_header_text = document.createElement("span");
+ subject_header_text.innerText = subject;
+ // Create collection for displaying individuals grades
+ let subject_body = document.createElement("div");
+ subject_body.className = "collapsible-body";
+ let subject_body_root = document.createElement("ul");
+ subject_body_root.className = "collection";
+ // Setup variables for calculating average
+ let grade_sum = 0;
+ let grade_tot = 0;
+ grades_by_subject[subject].forEach((grade) => {
+ // Create element for individual grade
+ let grade_node = document.createElement("li");
+ grade_node.className = "collection-item";
+ grade_node.classList.add("collection-item")
+ grade_node.classList.add("grade-node");
+ grade_node.dataset["index"] = grade["index"];
+ let grade_node_div = document.createElement("div");
+ // Node for date and subject text
+ let grade_text = document.createElement("span");
+ // Node for the actual number
+ let grade_number = document.createElement("div");
+ grade_number.className = "secondary-content";
+ // Apply different style, if the grade is temporary
+ if (grade["temporary"]) {
+ // Styling for text
+ let grade_text_italic = document.createElement("i");
+ grade_text_italic.innerText = grade["date"] + " - " + grade["title"];
+ grade_text.appendChild(grade_text_italic);
+ // Styling for number
+ let grade_number_italic = document.createElement("i");
+ grade_number_italic.innerText = grade["grade"].toString();
+ grade_number.appendChild(grade_number_italic);
+ } else {
+ // Text
+ grade_text.innerText = grade["date"] + " - " + grade["title"];
+ // Number
+ grade_number.innerText = grade["grade"].toString();
+ }
+ grade_node_div.appendChild(grade_text);
+ grade_node_div.appendChild(grade_number);
+ grade_node.appendChild(grade_node_div);
+ // Count the grade only if it's not temporary or explicitly enabled
+ if (!grade["temporary"] || !checkbox_state) {
+ grade_sum += grade["grade"];
+ grade_tot += 1;
+ }
+ subject_body_root.appendChild(grade_node);
+ });
+ let grade_average = (grade_tot === 0) ? "N/A" : (Math.round(((grade_sum / grade_tot) + Number.EPSILON) * 100) / 100);
+ let subject_header_average = document.createElement("div");
+ subject_header_average.className = "collapsible-header-right";
+ subject_header_average.innerText = grade_average.toString();
+ subject_header.appendChild(subject_header_text);
+ subject_header.appendChild(subject_header_average);
+ subject_body.append(subject_body_root);
+ subject_entry.append(subject_header);
+ subject_entry.append(subject_body);
+ root_element.append(subject_entry);
+ });
+ $("#grades-collapsible").append(root_element);
+ refreshClickHandlers();
+}
+
+function clearGrades() {
+ const table = document.getElementById("grades-collapsible");
+ while (table.firstChild) {
+ table.removeChild(table.firstChild);
+ }
+}
+
+function refreshGrades(force) {
+ clearGrades();
+ loadGrades(force);
+}
+
+function refreshClickHandlers() {
+ $("#grades-collapsible").find(".collection-item.grade-node").click(function () {
+ let grade_obj = grades[parseInt(this.dataset["index"])];
+ document.getElementById("grade-header").innerText = grade_obj["subject"] + ": " + grade_obj["grade"];
+ document.getElementById("grade-date").innerText = dateString.longFormatted(grade_obj["date"]);
+ document.getElementById("grade-title").innerText = grade_obj["name"][0];
+ document.getElementById("grade-type").innerText = S("type") + ": " + grade_obj["name"][1];
+ let term_element = document.getElementById("grade-term");
+ if (grade_obj["name"][2] !== "") {
+ term_element.innerText = S("term") + ": " + grade_obj["name"][2];
+ term_element.style["display"] = "";
+ } else {
+ term_element.style["display"] = "none";
+ }
+ document.getElementById("grade-teacher").innerText = S("teacher") + ": " + grade_obj["teacher"];
+ let temporary_object = document.getElementById("grade-temporary");
+ let temporary_object_root = document.getElementById("grade-temporary-root");
+ if (grade_obj["temporary"]) {
+ temporary_object.innerText = "(" + S("temporary") + ")";
+ temporary_object_root.style["display"] = "";
+ } else {
+ temporary_object_root.style["display"] = "none";
+ }
+ const modal = document.querySelectorAll('.side-modal')[0];
+ M.Sidenav.getInstance(modal).open();
+ });
+}
+
+// Initialization code
+document.addEventListener("DOMContentLoaded", async () => {
+ checkLogin();
+
+ let coll_elem = document.querySelectorAll('.collapsible');
+ M.Collapsible.init(coll_elem, {});
+
+ // Setup refresh handler
+ $("#refresh-icon").click(function () {
+ refreshGrades(true);
+ });
+
+ // Setup checkbox handler
+ $("#permanent-grades-checkbox").change(function () {
+ checkbox_state = this.checked;
+ refreshGrades(false);
+ });
+ let elems = document.querySelectorAll('.modal');
+ M.Modal.init(elems, {});
+
+ // Setup side menu
+ const menus = document.querySelectorAll('.side-menu');
+ M.Sidenav.init(menus, { edge: 'right', draggable: true });
+
+ // Setup side modal
+ const modals = document.querySelectorAll('.side-modal');
+ M.Sidenav.init(modals, { edge: 'left', draggable: false });
+
+ clearGrades();
+ await loadGrades();
+});
diff --git a/assets/js/gradings.js b/assets/js/gradings.js
new file mode 100644
index 0000000..b5bf7b5
--- /dev/null
+++ b/assets/js/gradings.js
@@ -0,0 +1,190 @@
+// const API_ENDPOINT = "https://gimb.tk/test.php"; // deprecated
+var calendar_obj = null;
+var gradings;
+async function checkLogin() {
+ localforage.getItem("logged_in").then((value) => {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch((err) => {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+// Set loading bar visibility
+function setLoading(state) {
+ if (state) {
+ $("#loading-bar").removeClass("hidden");
+ } else {
+ $("#loading-bar").addClass("hidden");
+ }
+}
+
+// GET COLOR FROM STRING
+function hashCode(str) { // java String#hashCode
+ var hash = 0;
+ for (var i = 0; i < str.length; i++) {
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ return hash;
+}
+
+function intToRGB(i) {
+ var c = (i & 0x00FFFFFF)
+ .toString(16)
+ .toUpperCase();
+
+ return "00000".substring(0, 6 - c.length) + c;
+}
+
+// http://www.w3.org/TR/AERT#color-contrast
+function getForegroundFromBackground(background_color) {
+ let color_hex = background_color.replace("#", "");
+ let rgb = [
+ parseInt(color_hex.substring(0, 2), 16),
+ parseInt(color_hex.substring(2, 4), 16),
+ parseInt(color_hex.substring(4, 6), 16)
+ ];
+ let o = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000);
+ if (o > 180) {
+ return "#000000";
+ } else {
+ return "#ffffff";
+ }
+}
+
+function getHexColorFromString(str) {
+ return "#" + intToRGB(hashCode(str));
+}
+// --------------------------------------------------
+
+function getDateString() {
+ let date = new Date();
+
+ let year_str = date.getFullYear();
+ let month_str = date.getMonth() + 1
+ month_str = month_str.toString().padStart(2, "0");
+ let day_str = date.getDate();
+ day_str = day_str.toString().padStart(2, "0");
+
+ let date_string = year_str + "-" + month_str + "-" + day_str;
+ return date_string;
+}
+
+async function loadGradings(force_refresh = false) {
+ setLoading(true);
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ localforage.getItem("gradings").then((value) => {
+ gradings = value;
+ })
+ ];
+ await Promise.all(promises_to_run);
+ if (gradings == null || gradings == [] || gradings == -1 || force_refresh) {
+ try {
+ let gsecInstance = new gsec();
+ await gsecInstance.login(username, password);
+ gsecInstance.fetchGradings().then( (value) => {
+ gradings = value;
+ localforage.setItem("gradings", value).then(() => {
+ displayData();
+ setLoading(false);
+ });
+ setLoading(false);
+ }).catch( (err) => {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ });
+ } catch (err) {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ }
+ } else {
+ displayData();
+ setLoading(false);
+ }
+}
+
+function displayData() {
+ let transformed_gradings = [];
+ gradings.forEach((element, index) => {
+ let bg_color = getHexColorFromString(element["acronym"]);
+ let fg_color = getForegroundFromBackground(bg_color);
+ let grading_object = {
+ start: element["date"].toISOString().substring(0, 10), // če se da direktno date object, se doda še 1a zraven (prefixa tajtlu) (verjetno 1am ura)
+ title: element["acronym"],
+ id: index.toString(),
+ backgroundColor: bg_color,
+ textColor: fg_color
+ };
+ transformed_gradings.push(grading_object);
+ });
+ calendar_obj.removeAllEvents();
+ calendar_obj.addEventSource(transformed_gradings);
+}
+
+function gradingClickHandler(eventClickInfo) {
+ let grading_id = parseInt(eventClickInfo.event.id);
+ let grading_subject = gradings[grading_id]["subject"];
+ let grading_date_obj = gradings[grading_id]["date"];
+ let grading_date = dateString.longFormatted(grading_date_obj);
+ let grading_description = gradings[grading_id]["description"];
+ $("#grading-subject").text(grading_subject);
+ $("#grading-date").text(grading_date);
+ $("#grading-description").text(grading_description);
+ const modal = document.querySelectorAll(".side-modal")[0];
+ M.Sidenav.getInstance(modal).open();
+}
+/*
+ function setupPickers() {
+ // Setup pickers, todo (adding an event), to be stored in messages
+ var date_object = new Date();
+ let elems = document.querySelectorAll('#datepicker-add');
+ let options = {
+ autoClose: true,
+ format: "dd.mm.yyyy",
+ defaultDate: date_object,
+ setDefaultDate: true,
+ firstDay: 1
+ }
+ let instances = M.Datepicker.init(elems, options);
+ instances = M.Datepicker.init(elems, options);
+ }
+*/
+
+document.addEventListener("DOMContentLoaded", () => {
+ checkLogin();
+ // Calendar setup
+ var calendarEl = document.getElementById("calendar");
+ calendar_obj = new FullCalendar.Calendar(calendarEl, {
+ firstDay: 1,
+ plugins: ["dayGrid"],
+ defaultDate: getDateString(),
+ navLinks: false,
+ editable: false,
+ events: [],
+ eventClick: gradingClickHandler,
+ height: "parent"
+ });
+ calendar_obj.render();
+ // setupPickers(); // todo (adding an event), to be stored in messages
+ loadGradings(true);
+ // Setup refresh handler
+ $("#refresh-icon").click(() => {
+ loadGradings(true);
+ });
+ // Setup side menu
+ const menus = document.querySelectorAll(".side-menu");
+ M.Sidenav.init(menus, { edge: "right", draggable: true });
+ // Setup side modal
+ const modals = document.querySelectorAll('.side-modal');
+ M.Sidenav.init(modals, { edge: 'left', draggable: false });
+});
diff --git a/assets/js/gsec.js b/assets/js/gsec.js
new file mode 100644
index 0000000..19e190f
--- /dev/null
+++ b/assets/js/gsec.js
@@ -0,0 +1,676 @@
+// tab = 2 || any spaces; use tabs
+// not tested yet -- NOTE: document.createElement is xssy, use DOMParser!
+var gseAbsenceTypes = ["notProcessed", "authorizedAbsence", "unauthorizedAbsence", "doesNotCount"];
+
+function getStringBetween(string, start, end) {
+ return string.split(start).pop().split(end)[0];
+}
+
+function stripHtml(html) {
+ var tmp = document.createElement("DIV");
+ tmp.innerHTML = html;
+ return tmp.textContent || tmp.innerText || "";
+}
+
+function slDayToInt(inputString) { // wtf
+ let fourChars = inputString.substring(1, 5);
+ let fourCharDays = ["oned", "orek", "reda", "etrt", "etek", "obot", "edel"];
+ return fourCharDays.indexOf(fourChars);
+}
+
+const GSE_URL = "https://zgimsis.gimb.tk/gse/";
+const GSEC_ERR_NET = "GSEC NETWORK ERROR (ajax error)";
+const GSEC_ERR_NET_POSTBACK_GET = "GSEC NETWORK ERROR (ajax error) in postback GET"
+const GSEC_ERR_NET_POSTBACK_POST = "GSEC NETWORK ERROR (ajax error) in postback POST"
+const GSEC_MSGTYPE_RECEIVED = 0;
+const GSEC_MSGTYPE_SENT = 1;
+const GSEC_MSGTYPE_DELETED = 2;
+const GSEC_ERR_LOGIN = "GSEC LOGIN ERROR";
+const GSEC_NO_ABSENCES = "noAbsences";
+const GSEC_MSGTYPES = ["msgReceived", "msgSent", "msgDeleted"];
+
+class gsec {
+
+ constructor() {
+ }
+
+ parseAndPost(inputHTML, params, formId = null, useDiffAction = null) {
+ return new Promise((resolve, reject) => {
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(inputHTML, "text/html");
+
+ var form;
+ if (formId == null) {
+ form = parsed.getElementsByTagName("form")[0];
+ } else {
+ form = parsed.getElementById(formId);
+ }
+
+ var otherParams = $(form).serializeArray();
+ for (const input of otherParams) {
+ if (!(input.name in params)) {
+ params[input.name] = input.value; // so we don't overwrite existing values
+ }
+ }
+
+ var action;
+ if (useDiffAction == null || useDiffAction == false) {
+ action = new URL($(form).attr("action"), GSE_URL); // absolute == relative + base
+ } else {
+ action = useDiffAction;
+ }
+
+ $.ajax({
+ xhrFields: {
+ withCredentials: true
+ },
+ crossDomain: true,
+ url: action,
+ cache: false,
+ type: "POST",
+ data: params,
+ dataType: "text",
+ success: (postData, textStatus, xhr) => {
+ resolve({data: postData, textStatus: textStatus, code: xhr.status});
+ },
+ error: () => {
+ reject(new Error(GSEC_ERR_NET_POSTBACK_POST));
+ }
+ });
+ });
+ }
+
+ postback(getUrl, params = {}, formId = null, useDiffAction = null) {
+ return new Promise( (resolve, reject) => {
+ $.ajax({
+ xhrFields: {
+ withCredentials: true
+ },
+ crossDomain: true,
+ url: getUrl,
+ cache: false,
+ type: "GET",
+ dataType: "html",
+ success: (data) => {
+ if (useDiffAction == true) {
+ useDiffAction = getUrl;
+ }
+ this.parseAndPost(data, params, formId, useDiffAction).then((value) => {
+ resolve(value);
+ });
+ },
+ error: () => {
+ reject(new Error(GSEC_ERR_NET_POSTBACK_GET));
+ }
+ });
+ });
+ }
+
+ login(usernameToLogin, passwordToLogin) {
+ return new Promise((resolve, reject) => {
+ var dataToSend = {
+ "edtGSEUserId": usernameToLogin,
+ "edtGSEUserPassword": passwordToLogin,
+ "btnLogin": "Prijava"
+ };
+ this.postback(GSE_URL + "Logon.aspx", dataToSend, null, true).then((response) => {
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(response.data, "text/html");
+ if (response.code === 302) {
+ resolve(true);
+ } else {
+ if (parsed.getElementById("lblMsg")) { // če obstaja lblMsg (napaka pri prijavi)
+ reject(new Error(GSEC_ERR_LOGIN));
+ } else if (!(parsed.getElementById("ctl00_lblLoginName"))) { // če ni ctl00_lblLoginName nismo na Default.aspx
+ reject(new Error(GSEC_ERR_LOGIN));
+ } else {
+ resolve(parsed.getElementById("ctl00_lblLoginName").innerHTML); // vrne ime dijaka, to je lahko uporabno
+ }
+ }
+ });
+ });
+ }
+
+ fetchSessionData() {
+ return new Promise((resolve, reject) => {
+ $.ajax({
+ xhrFields: {
+ withCredentials: true
+ },
+ crossDomain: true,
+ url: GSE_URL + "WS_Gim/wsGimSisUtils.asmx/GetSessionData",
+ type: "POST",
+ dataType: "json",
+ cache: false,
+ contentType: "application/json",
+ data: "{}",
+ processData: false,
+ success: (data, textStatus, xhr) => {
+ var podatki = {};
+ podatki[0] = data.d.split(", ")[0];
+ podatki[1] = data.d.split(", ")[1];
+ podatki["username"] = data.d.split(", ")[1];
+ podatki[2] = data.d.split(", ")[2];
+ podatki[3] = data.d.split(", ")[3];
+ podatki["sessionCookie"] = data.d.split(", ")[3];
+ podatki[4] = data.d.split(", ")[4];
+ resolve({"data": podatki, "textStatus": textStatus, "code": xhr.status});
+ },
+ error: () => {
+ reject(new Error(false));
+ }
+ });
+ });
+ }
+
+ fetchTeachersDirectory() {
+ return new Promise((resolve, reject) => {
+ var currentDate = new Date();
+ // če je že po avgustu (september), uporabimo trenutno letnico
+ // če še ni avgust uporabimo preteklo leto/letnico
+ // skratka uporabi se prvi sklop številk v šolskem letu TOLE(XXXX/yyyy)
+ var letnica = currentDate.getFullYear();
+ if (currentDate.getMonth() < 7) {
+ letnica--;
+ }
+
+ $.ajax({
+ xhrFields: {
+ withCredentials: true
+ },
+ crossDomain: true,
+ url: GSE_URL + "Page_Gim/Uporabnik/modSporociloPrejemniki.aspx/NajdiOsebePrejemniki",
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json",
+ cache: false,
+ data: JSON.stringify({
+ "aIdOsebeRe": "",
+ "aIdSolskoLeto": Number(letnica).toString(),
+ "aMsgType": "null",
+ "aIdType": "null",
+ "aIdUcitelj": "",
+ "aFilter": null
+ }),
+ // note about the nulls, "null"s and ""s: this is actually how zgimsisext2016 javascripts sends it.
+ processData: false,
+ success: (data) => {
+ var teachersDirectory = data.d.split(";"); // data.d je "12434=Ime Primek (učitelj);75353=Ime Drugega Priimek (učitelj)";
+ teachersDirectory.pop(); // pop, ker se string konča z ;
+ var formatted = {};
+ teachersDirectory.forEach((v) => {
+ formatted[v.split("=")[1].split(" (")[0]] = v.split("=")[0];
+ });
+ resolve(formatted);
+ },
+ error: () => {
+ reject(new Error(false));
+ }
+ });
+ });
+ }
+
+ fetchTimetable(datum = null) {
+
+ const SUBJECT_REGEX = /\((.+?)\)/;
+ const ABKURZUNG_REGEX = /^(.+?) \(/;
+
+ var dataToSend = datum == null ? {} : {
+ "ctl00$ContentPlaceHolder1$wkgDnevnik_edtGridSelectDate": `${datum.getDate()}.${Number(datum.getMonth()+1)}.${datum.getFullYear()}`
+ };
+
+ return new Promise((resolve) => {
+ var urnik = { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6:{} } ;
+ this.postback(GSE_URL+"Page_Gim/Ucenec/DnevnikUcenec.aspx", dataToSend, null, true).then((response) => {
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(response.data, "text/html");
+ for (const urnikElement of parsed.querySelectorAll('*[id^="ctl00_ContentPlaceHolder1_wkgDnevnik_btnCell_"]')) {
+ var subFields = urnikElement.id.split("_");
+ var period = subFields[4];
+ var day = subFields[5];
+ var desc = $(urnikElement).attr("title").split("\n");
+ var subject = SUBJECT_REGEX.exec(desc[1])[1];
+ var abkurzung = ABKURZUNG_REGEX.exec(desc[1])[1];
+ var razred = desc[2];
+ var teacher = desc[3];
+ var place = desc[4];
+ urnik[day][period] = {
+ "subject": subject,
+ "acronym": abkurzung,
+ "class": razred,
+ "teacher": teacher,
+ "place": place
+ };
+ }
+ resolve(urnik);
+ });
+ });
+ }
+
+ fetchGradings() {
+
+ const DESC_REGEX = /\((.+?)\)/m;
+ const SUBJECT_REGEX = /^(.+?) \(/m;
+
+ return new Promise((resolve) => {
+ var gradings = [];
+ this.postback(GSE_URL + "Page_Gim/Ucenec/IzpitiUcenec.aspx", {}, null, true).then( (response) => {
+
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(response.data, "text/html");
+
+ var rowElements = parsed.getElementsByTagName("table")[0].getElementsByTagName("tbody")[0].getElementsByTagName("tr");
+
+ for (const row of rowElements) {
+ var subFields = row.getElementsByTagName("td");
+ var date = subFields[0].innerHTML.trim().split(".");
+ var dateObj = new Date(date[2]+"-"+date[1]+"-"+date[0]);
+ var rowSpan = subFields[1].getElementsByTagName("span")[0];
+ var abkurzung = "";
+
+ if (rowSpan) {
+ abkurzung = rowSpan.innerHTML.trim();
+ }
+
+ rowSpan.remove(); // magic
+ var subject = SUBJECT_REGEX.exec(subFields[1].innerHTML)[1].trim();
+ var desc = DESC_REGEX.exec(subFields[1].innerHTML)[1];
+
+ gradings.push({
+ "date": dateObj,
+ "acronym": abkurzung,
+ "subject": subject,
+ "description": desc
+ });
+ }
+ resolve(gradings);
+ });
+ });
+ }
+
+ fetchTeachers() { // razrednika ne vrne kot razrednika, če le-ta uči še en predmet. razlog: razrednik je napisan dvakrat, drugič se prepiše. Ne da se mi popravljat.
+
+ const SUBJECT_REGEX = /^(.+?(?= \()|.+(?! \())/; // For some reason, JS doesn't support conditional regex
+ const ABKURZUNG_REGEX = /\((.+)\)/;
+ const DAY_REGEX = /^(.+?), /m;
+ const PERIOD_REGEX = /, (\d+?)\. ura/;
+ const TIME_RANGE_REGEX = /\((.+?) - (.+?)\)/;
+
+ return new Promise((resolve) => {
+ var Teachers = {};
+
+ this.postback(GSE_URL + "Page_Gim/Ucenec/UciteljskiZbor.aspx", {}, null, true).then((response) => {
+
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(response.data, "text/html");
+
+ var rowElements = parsed.getElementsByTagName("table")[0].getElementsByTagName("tbody")[0].getElementsByTagName("tr");
+
+ for (const row of rowElements) {
+ var subFields = row.getElementsByTagName("td");
+ var name = stripHtml(subFields[0].innerHTML); // razrednik je namreč bold tekst!
+ var subjectStrings = subFields[2].innerHTML.split("<br>");
+ var subjects = {};
+
+ for (const subjectString of subjectStrings) {
+ var subjectName = SUBJECT_REGEX.exec(stripHtml(subjectString))[1];
+ var abkurzung = ABKURZUNG_REGEX.exec(stripHtml(subjectString));
+ abkurzung = abkurzung == null ? subjectName : abkurzung[1];
+ subjects[abkurzung] = subjectName;
+ }
+
+ try {
+ var TP = {};
+ TP.day = slDayToInt(DAY_REGEX.exec(subFields[3].innerHTML)[1]);
+ TP.period = Number(PERIOD_REGEX.exec(subFields[3].innerHTML)[1]);
+ var time_range_matches = TIME_RANGE_REGEX.exec(subFields[3].innerHTML);
+ TP.from = time_range_matches[1];
+ TP.till = time_range_matches[2];
+ if (TP.day < 0) { // indexOf vrne -1, če v arrayu ne najde dneva (&nbsp;)
+ TP = false;
+ }
+ } catch (error) {
+ var TP = {};
+ TP.day = slDayToInt(subFields[3].innerHTML.split(", ")[0]);
+ TP.period = Number( subFields[3].innerHTML.split(", ").pop().split(". ura")[0] );
+ TP.from = subFields[3].innerHTML.split("(").pop().split(")")[0].split(" - ")[0];
+ TP.till = subFields[3].innerHTML.split("(").pop().split(")")[0].split(" - ")[1];
+ if (TP.day < 0) { // indexOf vrne -1, če v arrayu ne najde dneva (&nbsp;)
+ TP = false;
+ }
+ }
+
+ Teachers[name] = { "subjects" : subjects , "tpMeetings" : TP };
+ }
+ resolve(Teachers);
+ });
+ });
+ }
+
+ sendMessage(recipient, subject = " ", body = " ") {
+ return new Promise((resolve) => {
+ var dataToSend = {
+ "ctl00$ModalMasterBody$edtPrejemniki": "",
+ "ctl00$ModalMasterBody$edtZadeva": subject,
+ "ctl00$ModalMasterBody$edtBesediloExt": he.encode(body, {useNamedReferences: true}),
+ "__EVENTTARGET": "ctl00$ModalMasterBody$btnDogodekShrani",
+ "__EVENTARGUMENT": "",
+ "ctl00$ModalMasterBody$hfPrejemniki": recipient
+ };
+ this.postback(GSE_URL+"Page_Gim/Uporabnik/modSporocilo.aspx?params=", dataToSend, null, true).then(() => {
+ resolve(null);
+ });
+ });
+ }
+
+ deleteMessage(id) {
+ return new Promise((resolve, reject) => {
+ $.ajax({
+ xhrFields: {
+ withCredentials: true
+ },
+ crossDomain: true,
+ url: GSE_URL + "Page_Gim/Uporabnik/Sporocila.aspx/DeleteMessage",
+ type: "POST",
+ dataType: "json",
+ cache: false,
+ contentType: "application/json",
+ data: JSON.stringify({
+ "aIdSporocilo": id.split("|")[0],
+ "aIdZapis": id.split("|")[1]
+ }),
+ processData: false,
+ success: (data) => {
+ if(data.d == true) {
+ resolve(true);
+ } else {
+ reject(new Error(false));
+ }
+ },
+ error: () => {
+ reject(new Error(GSEC_ERR_NET));
+ }
+ });
+ });
+ }
+
+ fetchAbsences(fromDate = null, tillDate = null) { // navedba datumov je deprecated. Internet je dovolj hiter za poslat maksimalno 4160 ur (16 ur/dan, 5 dni/ted, 52 ted/leto)
+
+ const SUBJECT_LIST_REGEX = /(.+? \(<span class="opr\d">\dP?<\/span>\))(?:,\s*|$)/g;
+ const FIELDS_REGEX = /^(.+?) \(<span class="opr(\d)">(\dP?)<\/span>\)/;
+
+ return new Promise((resolve) => {
+ if (!(fromDate instanceof Date) || !(tillDate instanceof Date)) {
+ tillDate = new Date(Date.UTC(9999, 11, 30)); // overkill? Of course not, cez 8000 let bo ta app se vedno top shit
+ fromDate = new Date(Date.UTC(1, 1, 1)); // i don't think so
+ }
+
+ var dataToBeSent = {
+ "ctl00$ContentPlaceHolder1$edtDatZacetka": `${fromDate.getDay()}.${fromDate.getMonth()}.${fromDate.getFullYear()}`,
+ "ctl00$ContentPlaceHolder1$edtDatKonca": `${tillDate.getDay()}.${tillDate.getMonth()}.${tillDate.getFullYear()}`,
+ };
+
+ this.postback(GSE_URL+"Page_Gim/Ucenec/IzostankiUcenec.aspx", dataToBeSent, null, true).then((response) => {
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(response.data, "text/html");
+
+ try {
+ var rowElements = parsed.getElementById("ctl00_ContentPlaceHolder1_gvwIzostankiGroup").getElementsByTagName("tbody")[0].getElementsByTagName("tr");
+ } catch (err) {
+ resolve(GSEC_NO_ABSENCES);
+ }
+
+ var absences = [];
+ for (const izostanek of rowElements) {
+ var subFields = izostanek.getElementsByTagName("td");
+ var date = subFields[0].innerHTML.trim().split(".");
+ var dateObj = new Date(Date.parse(`${date[2]}-${date[1]}-${date[0]}`));
+
+ var subjects = [];
+ subFields[2].innerHTML.match(SUBJECT_LIST_REGEX).forEach((subject) => {
+ subjects.push(subject);
+ });
+
+ var absencesBySubject = {};
+
+ for (const subject of subjects) {
+ const matched_info = FIELDS_REGEX.exec(subject);
+
+ var subjectName = matched_info[1];
+ var status = Number(matched_info[2]);
+ // statusi so: 0: ni obdelano, 1: opravičeno, 2: neopravičeno, 3: ne šteje, uporabi S(gseAbsenceTypes[num]) za i18n prevod
+ // Ce je v "stevilki" P, gre za popoldansko uro -> +7 ur
+ var period = matched_info[3];
+ period = period.includes("P") ? Number(period.replace("P", "")) + 7 : Number(period);
+ absencesBySubject[period] = {status: status, subject: subjectName};
+ }
+ absences.push({subjects: absencesBySubject, date: dateObj});
+ }
+ resolve(absences);
+ });
+ });
+ }
+
+ fetchGrades() {
+ var grades = [];
+ return new Promise((resolve, reject) => {
+ $.ajax({
+ xhrFields: {
+ withCredentials: true
+ },
+ crossDomain: true,
+ url: GSE_URL + "Page_Gim/Ucenec/OceneUcenec.aspx",
+ cache: false,
+ type: "GET",
+ dataType: "html",
+ processData: false,
+
+ success: (data) => {
+
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(data, "text/html");
+
+ let gradeSpans = parsed.getElementsByClassName("txtVOcObd");
+ for (const grade of gradeSpans) {
+ var ist = grade.getElementsByTagName("span")[0].getAttribute("title").split("\n");
+ var date = ist[0].split(": ")[1].trim().split(".");
+ var dateObj = new Date(Date.parse(`${date[2]}-${date[1]}-${date[0]}`));
+ var teacher = ist[1].split(": ")[1].trim();
+ var subject = ist[2].split(": ")[1].trim();
+ var name = [];
+
+ name.push(ist[3].split(": ")[1].trim())
+ name.push(ist[4].split(": ")[1].trim())
+ name.push(ist[5].split(": ")[1].trim())
+
+ var gradeNumber = Number(grade.getElementsByTagName("span")[0].innerHTML);
+ var temporary = grade.getElementsByTagName("span")[0].classList.contains("ocVmesna");
+
+ var gradeToAdd = {
+ "date": dateObj,
+ "teacher": teacher,
+ "subject": subject,
+ "name": name,
+ "temporary": temporary,
+ "grade": gradeNumber
+ };
+
+ if (grade.getElementsByTagName("span").length > 1) {
+ if(grade.getElementsByTagName("span")[1].classList.contains("ocVmesna")) {
+ gradeToAdd["temporary"] = true;
+ } else {
+ gradeToAdd["temporary"] = false;
+ }
+ gradeToAdd["grade"] = Number(grade.getElementsByTagName("span")[1].innerHTML);
+ gradeToAdd["oldgrade"] = Number(grade.getElementsByTagName("span")[0].innerHTML);
+ }
+ grades.push(gradeToAdd);
+
+ }
+ resolve(grades);
+ },
+ error: () => {
+ reject(new Error(GSEC_ERR_NET));
+ }
+ });
+ });
+ }
+
+ fetchMessageOld(selectId) { // ne dela, glej fix spodaj (fetchMessage)
+
+ const TIME_REGEX = / \(.+ (.+?)\)/;
+ const DATE_REGEX = / \(.+? /;
+ const SENDER_REGEX = /^(.+?) \(/;
+
+ var message;
+ return new Promise((resolve) => {
+ var dataToBeSent = {
+ "__EVENTTARGET": "ctl00$ContentPlaceHolder1$gvwSporocila",
+ "__EVENTARGUMENT": "Select$" + selectId
+ };
+
+ this.postback(GSE_URL+"Page_Gim/Uporabnik/Sporocila.aspx", dataToBeSent, null, true).then((response) => {
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(response.data, "text/html");
+ let subject = parsed.getElementsByClassName("msgSubjectS")[0].innerHTML.trim();
+ let body = parsed.getElementsByClassName("gCursorAuto")[0].innerHTML.trim();
+ let sender = SENDER_REGEX.exec(parsed.querySelectorAll("[id$=Label7]")[0].innerHTML)[1];
+ let recipient = parsed.querySelectorAll("[id$=Label8]")[0].innerHTML;
+ var date = DATE_REGEX.exec(parsed.querySelectorAll("[id$=Label7]")[0].innerHTML)[1];
+ var tume = TIME_REGEX.exec(parsed.querySelectorAll("[id$=Label7]")[0].innerHTML)[1]; // "tume"!
+ var dateObj = new Date(Date.parse(`${date[2]}-${date[1]}-${date[0]} ${tume}`)); // "tume"!
+ var msgId = parsed.getElementById("ctl00_ContentPlaceHolder1_hfIdSporocilo").getAttribute("value");
+ message = {
+ "subject": subject,
+ "body": body,
+ "sender": sender,
+ "recipient": recipient,
+ "date": dateObj,
+ "msgId": msgId
+ };
+ resolve(message);
+ });
+ });
+ }
+
+ fetchMessagesLastPageNumber(category = GSEC_MSGTYPE_RECEIVED) {
+ var msgCategory = GSEC_MSGTYPES[category];
+ return new Promise((resolve) => {
+ var dataToBeSent = {
+ "ctl00$ContentPlaceHolder1$ddlPrikaz": msgCategory,
+ "__EVENTARGUMENT": "Page$Last",
+ "__EVENTTARGET": "ctl00$ContentPlaceHolder1$gvwSporocila"
+ };
+ this.postback(GSE_URL+"Page_Gim/Uporabnik/Sporocila.aspx", dataToBeSent, null, true).then((response) => {
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(response.data, "text/html");
+ let currentPage;
+ if (parsed.getElementsByClassName("pager").length == 0) { // pager is not shown, there is only page one.
+ currentPage = 1;
+ } else {
+ currentPage = Number(parsed.getElementsByClassName("pager")[0].getElementsByTagName("span")[0].innerHTML);
+ }
+ resolve(currentPage);
+ });
+ });
+ }
+
+ fetchMessagesList(category = GSEC_MSGTYPE_RECEIVED, pageNumber = 1, outputResponse = false) { // outputResponse je probably za debug
+
+ const DATE_REGEX = /(\d+?).(\d+?).(\d+?) /; // I'm lazy
+
+ var msgCategory = GSEC_MSGTYPES[category];
+ var messages = [];
+ var requestURI = GSE_URL + "Page_Gim/Uporabnik/Sporocila.aspx";
+ return new Promise((resolve) => {
+ var dataToBeSent = {
+ "ctl00$ContentPlaceHolder1$ddlPrikaz": msgCategory,
+ "__EVENTARGUMENT": "Page$" + pageNumber,
+ "__EVENTTARGET": "ctl00$ContentPlaceHolder1$gvwSporocila"
+ };
+ this.postback(requestURI, dataToBeSent, null, true).then((response) => {
+ if (outputResponse === true) {
+ response.url = requestURI;
+ resolve(response);
+ }
+
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(response.data, "text/html");
+ let messageElements = parsed.getElementById("ctl00_ContentPlaceHolder1_gvwSporocila").getElementsByTagName("tbody")[0].getElementsByTagName("td");
+ for (const messageElement of messageElements) {
+ let msgId = messageElement.getElementsByTagName("input")[0].value;
+ var date = DATE_REGEX.exec(messageElement.getElementsByClassName("msgSubDate")[0].innerHTML);
+ var today = new Date();
+
+ if (date[3] == undefined || date[2].length < 1) {
+ date[3] = today.getFullYear();
+ }
+
+ if (date[2] == undefined || date[2].length < 1) {
+ date[2] = today.getMonth() + 1;
+ date[1] = today.getDate();
+ }
+
+ var tume = messageElement.getElementsByClassName("msgSubDate")[0].innerHTML.split(" ")[1];
+ if(tume == null || tume.length < 1) {
+ tume = messageElement.getElementsByClassName("msgSubDate")[0].innerHTML;
+ }
+ var dateStringToParse = `${date[2]}-${date[1]}-${date[0]} ${tume}`;
+ var dateObj = new Date(Date.parse(dateStringToParse)); // "tume"!
+ var person = messageElement.getElementsByClassName("msgDir")[0].innerHTML;
+ var subject = messageElement.getElementsByClassName("msgSubject")[0].innerHTML;
+ messages.push({
+ "date": dateObj,
+ "sender": person,
+ "subject": subject,
+ "msgId": msgId
+ });
+ }
+ resolve(messages);
+ });
+ });
+ }
+
+ fetchMessage(category = GSEC_MSGTYPE_RECEIVED, pageNumber = 1, messageNumberOnPage = 0) {
+
+ const TIME_REGEX = / \(.+ (.+?)\)/;
+ const DATE_REGEX = / \((\d+?).(\d+?).(\d+?) /;
+ const SENDER_REGEX = /^(.+?) \(/;
+
+ return new Promise((resolve) => {
+ this.fetchMessagesList(category, pageNumber, true).then( (value) => {
+ this.parseAndPost(
+ value.data,
+ {
+ "__EVENTTARGET": "ctl00$ContentPlaceHolder1$gvwSporocila",
+ "__EVENTARGUMENT": "Select$" + messageNumberOnPage
+ },
+ null,
+ value.url
+ ).then((response) => {
+
+ let parser = new DOMParser();
+ let parsed = parser.parseFromString(response.data, "text/html");
+ let subject = parsed.getElementsByClassName("msgSubjectS")[0].innerHTML.trim();
+ let body = parsed.getElementsByClassName("gCursorAuto")[0].innerHTML.trim();
+ let sender = SENDER_REGEX.exec(parsed.querySelectorAll("[id$=Label7]")[0].innerHTML)[1];
+ let recipient = parsed.querySelectorAll("[id$=Label8]")[0].innerHTML;
+ var date = DATE_REGEX.exec(parsed.querySelectorAll("[id$=Label7]")[0].innerHTML);
+ var tume = TIME_REGEX.exec(parsed.querySelectorAll("[id$=Label7]")[0].innerHTML)[1]; // "tume"!
+ var dateObj = new Date(Date.parse(`${date[3]}-${date[2]}-${date[1]} ${tume}`)); // "tume"!
+ var msgId = parsed.getElementById("ctl00_ContentPlaceHolder1_hfIdSporocilo").getAttribute("value");
+ var message = {
+ "subject": subject,
+ "body": body,
+ "sender": sender,
+ "recipient": recipient,
+ "date": dateObj,
+ "msgId": msgId
+ };
+ resolve(message);
+
+ });
+ });
+ });
+ }
+}
diff --git a/assets/js/initialize.js b/assets/js/initialize.js
new file mode 100644
index 0000000..cb5d210
--- /dev/null
+++ b/assets/js/initialize.js
@@ -0,0 +1,35 @@
+function getUrlParameter(sParam) {
+ const url_params = new URLSearchParams(window.location.search);
+ const found_param = url_params.get(sParam);
+ return found_param === null ? "" : found_param;
+}
+
+
+localforage.getItem("logged_in")
+ .then(
+ function (value) {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value == null) {
+ // Setup the storage if it doesn't exist
+ setupStorage(true);
+ window.location.replace("/login.html");
+ } else if (value === false) {
+ // If storage exists, but user isn't logged in, redirect to login
+ window.location.replace("/login.html");
+ } else {
+ // User is logged in, execute appropriate action
+
+ if (getUrlParameter("m") !== "") {
+ window.location.replace("/pages/messaging.html#" + getUrlParameter("m"));
+ } else {
+ window.location.replace("/pages/timetable.html");
+ }
+
+ }
+ }
+ ).catch(
+ function (err) {
+ console.log(err);
+ }
+ );
diff --git a/assets/js/jitsi.js b/assets/js/jitsi.js
new file mode 100644
index 0000000..8dd55ab
--- /dev/null
+++ b/assets/js/jitsi.js
@@ -0,0 +1,20 @@
+async function checkLogin() {
+ localforage.getItem("logged_in").then(function (value) {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch(function (err) {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+ checkLogin();
+
+ // Setup side menu
+ const menus = document.querySelectorAll(".side-menu");
+ M.Sidenav.init(menus, { edge: "right", draggable: true });
+});
diff --git a/assets/js/lang/bundle.js b/assets/js/lang/bundle.js
new file mode 100644
index 0000000..8270527
--- /dev/null
+++ b/assets/js/lang/bundle.js
@@ -0,0 +1,513 @@
+// there's an DoS backdoor in BežiApp because of this (-:<
+var chosenLang;
+var dateString = {
+ day: (danv) => {
+ let dnevitedna = [S("sunday"), S("monday"), S("tuesday"), S("wednesday"), S("thursday"), S("friday"), S("saturday")];
+ return dnevitedna[danv];
+ },
+ month: (mesl) => {
+ let mesecileta = [S("january"), S("february"), S("march"), S("april"), S("may"), S("june"), S("july"), S("august"), S("september"), S("october"), S("november"), S("december")];
+ return mesecileta[mesl];
+ },
+ longFormatted: (dateObject) => {
+ return dateString.day(dateObject.getDay())+", "+(dateObject.getDate())+". "+dateString.month(dateObject.getMonth())+" "+dateObject.getFullYear();
+ }
+};
+async function refreshLangDOM() {
+ let promises_to_runn = [
+ localforage.getItem("chosenLang").then( (value) => {
+ chosenLang = value;
+ })
+ // localforage.getItem("chosenCapitalize").then( (value) => { // poor unused code
+ // chosenCapitalize = value;
+ // })
+ ];
+ await Promise.all(promises_to_runn);
+ // this could be done nicer. p. s.: lahko bi se uporablil x-s in x-S za razločitev med capitalize in !capitalize queryselectorall ni case sensitive za imena elementov
+ let stringContainerss = document.querySelectorAll("x-sl:not(.langFinished)");
+ for (i = 0; i < stringContainerss.length; i++) {
+ stringContainerss[i].innerHTML = s(stringContainerss[i].innerHTML);
+ stringContainerss[i].classList.add("langFinished");
+ stringContainerss[i].hidden = false;
+ }
+ let stringContainersd = document.querySelectorAll("x-dl:not(.langFinished)");
+ for (i = 0; i < stringContainersd.length; i++) {
+ stringContainersd[i].innerHTML = d(stringContainersd[i].innerHTML);
+ stringContainersd[i].classList.add("langFinished");
+ stringContainersd[i].hidden = false;
+ }
+ let stringContainersS = document.querySelectorAll("x-su:not(.langFinished)");
+ for (i = 0; i < stringContainersS.length; i++) {
+ stringContainersS[i].innerHTML = S(stringContainersS[i].innerHTML);
+ stringContainersS[i].classList.add("langFinished");
+ stringContainersS[i].hidden = false;
+ }
+ let stringContainersD = document.querySelectorAll("x-du:not(.langFinished)");
+ for (i = 0; i < stringContainersD.length; i++) {
+ stringContainersD[i].innerHTML = D(stringContainersD[i].innerHTML);
+ stringContainersD[i].classList.add("langFinished");
+ stringContainersD[i].hidden = false;
+ }
+}
+async function setLangConfigAndReload() {
+ let promises_to_run = [
+ /* localforage.setItem("chosenCapitalize", true), */ // F for unused code
+ localforage.setItem("chosenLang", "en")
+ ];
+ await Promise.all(promises_to_run);
+ window.location.reload();
+}
+window.addEventListener("DOMContentLoaded", () => {
+ localforage.getItem("chosenLang").then( (value) => {
+ if(value == null) {
+ setLangConfigAndReload();
+ } else {
+ chosenLang = value;
+ }
+ });
+ refreshLangDOM();
+});
+
+
+const capitalize = (s) => {
+ if (typeof s !== 'string') return ''
+ return s.charAt(0).toUpperCase() + s.slice(1)
+}
+var s = function(whatString) {
+ return getLang.s(whatString);
+};
+var d = function(whatString) {
+ return getLang.d(whatString);
+};
+var S = function(whatString) {
+ return getLang.S(whatString);
+};
+var D = function(whatString) {
+ return getLang.D(whatString);
+};
+var getLang = { // language object
+ s: function(whatString) { // get string
+ return langstrings[chosenLang][whatString];
+ },
+ S: function(whatString) { // get capitalized string
+ return capitalize(langstrings[chosenLang][whatString]);
+ },
+ d: function(whatString) { // add a dot and get string
+ if(langstrings[chosenLang][whatString].slice(-1) != ".") {
+ return langstrings[chosenLang][whatString]+".";
+ } else {
+ return langstrings[chosenLang][whatString];
+ }
+ },
+ D: function(whatString) { // add a dot and get capitalized string
+ if(langstrings[chosenLang][whatString].slice(-1) != ".") {
+ return capitalize(langstrings[chosenLang][whatString]+".");
+ } else {
+ return capitalize(langstrings[chosenLang][whatString]);
+ }
+ },
+}
+var langstrings = {
+ en: {
+ miscTranslationLanguage: "English",
+ miscTranslationAuthors: "Rok Štular",
+ "": "",
+ // date
+ monday: "monday",
+ tuesday: "tuesday",
+ wednesday: "wednesday",
+ thursday: "thursday",
+ friday: "friday",
+ saturday: "saturday",
+ sunday: "sunday",
+ am: "am",
+ pm: "pm",
+ january: "january",
+ february: "february",
+ march: "march",
+ april: "april",
+ may: "may",
+ june: "june",
+ july: "july",
+ august: "august",
+ september: "september",
+ october: "october",
+ november: "november",
+ december: "december",
+ // login
+ username: "username",
+ password: "password",
+ signIn: "sign in",
+ bySigningInYouAgreeTo: "by signing in, you agree to",
+ theToS: "the terms and conditions",
+ and: "and",
+ thePrivacyPolicy: "the privacy policy",
+ loginFailed: "login failed",
+ browserNotSupported: "bežiapp won't work on your device, unless you update your Internet browser",
+ // index
+ timetable: "timetable",
+ gradings: "gradings",
+ grades: "grades",
+ teachers: "teachers",
+ absences: "absences",
+ messaging: "messaging",
+ meals: "meals",
+ about: "about",
+ logout: "logout",
+ settings: "settings",
+ // timetable
+ noPeriods: "no periods in selected week",
+ // gradings
+ requestFailed: "request failed",
+ noInternetConnection: "no internet connection",
+ // grades
+ temporary: "temporary",
+ useOnlyPermanentGrades: "use only permanent grades",
+ useOnlyPermanentGradesNote1: "if checked, only permanent grades will be used in the average grade calculation",
+ useOnlyPermanentGradesNote2: "if left unchecked, the calculation will include every available grade",
+ type: "type",
+ term: "term",
+ teacher: "teacher",
+ // teachers
+ name: "name",
+ schoolSubject: "subject",
+ tpMeetings: "TP meetings",
+ // absences
+ from: "from",
+ to: "to",
+ cancel: "cancel",
+ ok: "ok",
+ noAbsences: "no absences in the chosen time period",
+ lesson: "lesson",
+ notProcessed: "not processed",
+ authorizedAbsence: "authorized",
+ unauthorizedAbsence: "unauthorized",
+ doesNotCount: "does not count",
+ // messaging
+ loadingMessages: "Loading messages...",
+ sendAMessage: "send a message",
+ recipient: "recipient",
+ messageSubject: "subject",
+ messageBody: "message body",
+ removeImages: "remove images",
+ note: "note",
+ largeImagesNote: "GimB servers don't like large messages, so only very small images may be attached or your message will not be delivered",
+ attachedImages: "attached images",
+ encryptMessage: "Encrypt message",
+ passwordForE2EE: "password for encrypting the message",
+ messages: "messages",
+ received: "received",
+ sent: "sent",
+ deleted: "deleted",
+ messageStorageUsed: "message storage used in this folder",
+ maxMessagesNote: "you can only have 120 messages per message folder, older messages will not be shown. Remember to delete read and sent messages regulary to avoid any issues.",
+ loadMessageBody: "load message body",
+ thisMessageWasEncrypted: "this message was encrypted",
+ enterPassword: "enter password",
+ decrypt: "decrypt",
+ nameDirectoryNotSet: "name directory not set, sending unavailable",
+ errorFetchingMessages: "error fetching messages",
+ unableToReceiveTheMessage: "unable to receive the message",
+ unableToDeleteTheMessage: "unable to delete the message",
+ messageWasProbablySent: "message was probably sent, check the Sent folder to be sure",
+ errorSendingMessage: "error sending message",
+ imageAddedAsAnAttachment: "image added as an attachment",
+ unableToReadDirectory: "unable to read directory of people",
+ messageCouldNotBeSent: "message could to be sent",
+ incorrectPassword: "incorrect password",
+ // chats
+ chat: "chat",
+ chattingWith: "chatting with",
+ noMessages: "no messages",
+ stillLoading: "loading is still in progress",
+ directory: "directory",
+ select: "select",
+ mustSelectRecipient: "you have to select a recipient before chatting. Open directory on the left side by clicking on the top left addressbook button and select a recipient in order to start chatting with them",
+ recipientNotInDirectory: "recipient is not in directory.",
+ chatExternalInfo: "you have just received a chat. Chats are not supported by GimSIS, so you must reply by changing the subject to something else. Chat body: ",
+ // meals
+ loginError: "login error",
+ loginToLopolis: "login to Lopolis",
+ loginToLopolisNote: "it seems like you're not currently logged in to eRestavracija, so this form has been presented to you. You have a different username and password combination used for applying and opting out of of menus. In order to use this feature, you have to log in with your Lopolis account.",
+ logInToLopolis: "log in to Lopolis",
+ logOutFromLopolis: "log out from Lopolis",
+ readOnly: "read only",
+ usage: "usage",
+ mealsUsageNote: "click on a date to open the collapsible menu with choices and click on a specific meal to select it. Reload the meals when you're done and check the entries.",
+ lunchesNote: "app was not tested with lunches in mind. Meals probably won't work with lunches and having a lunch subscription may even break its functionality.",
+ mealNotShownNote: "if a meal is not present in the meals collapsible field, this does not necessarily mean it does not exist. Meals that haven't been altered by you and are unchangable (read-only) are not shown for clarity.",
+ mealsContributeNote: "you are welcome to contribute to the LopolisAPI project and add features, such as checkouts.",
+ authenticationError: "authentication error",
+ lopolisAPIConnectionError: "LopolisAPI server connection error",
+ errorGettingMenus: "error getting menus",
+ errorUnexpectedResponse: "error: unexpected response",
+ requestForAuthenticationFailed: "request for authentication failed",
+ credentialsMatch: "credentials match",
+ errorSettingMeals: "error setting meals",
+ mealSet: "meal set! Reload meals to be sure",
+ selected: "selected",
+ // about
+ version: "version",
+ authors: "authors",
+ translatorsForThisLanguage: "translators for this language",
+ whatIsNew: "what's new",
+ whatsNew: "what's new",
+ reportABug: "report a bug",
+ sendASuggestion: "send a suggestion",
+ instagram: "instagram",
+ // changelog
+ changelog: "changelog",
+ // terms and conditions
+ termsOfUse: "terms of use",
+ termsOfUseDescription: "as a condition of use, you promise not to use the BežiApp (App or application) and its related infrastructure (API, hosting service) for any purpose that is unlawful or prohibited by these Terms, or any other purpose not reasonably intended by the authors of the App. By way of example, and not as a limitation, you agree not to use the App",
+ termsOfUseHarass: "to abuse, harass, threaten, impersonate or intimidate any person",
+ termsOfUsePost: "to post or transmit, or cause to be posted or transmitted, any Content that is libelous, defamatory, obscene, pornographic, abusive, offensive, profane or that infringes any copyright or other right of any person",
+ termsOfUseCommunicate: "to communicate with the App developers or other users in abusive or offensive manner",
+ termsOfUsePurpose: "for any purpose that is not permitted under the laws of the jurisdiction where you use the App",
+ termsOfUseExploit: "to post or transmit, or cause to be posted or transmitted, any Communication designed or intended to obtain password, account or private information of any App user",
+ termsOfUseSpam: "to create or transmit unwanted “spam” to any person or any URL",
+ termsOfUseModify: "you may also not reverse engineer, modify or redistribute the app without written consent from the developers",
+ terminationOfServices: "termination of services",
+ terminationOfServicesDescriptions: "the developers of the App may terminate your access to the App without any prior warning or notice for any of the following reasons",
+ terminationOfServicesBreaching: "breaching the Terms of Service",
+ terminationOfServicesRequest: "receiving a formal request from authorities of Gimnazija Bežigrad administration requesting termination of your access to the App",
+ limitationOfLiability: "limitation of Liability",
+ limitationOfLiabilityContent: "the developers of the App provide no warranty; You expressly acknowledge and agree that the use of the licensed application is at your sole risk. To the maximum extent permited by applicable law, the licensed application and any services performed of provided by the licensed application are provided “as is” and “as available”, with all faults and without warranty of any kind, and licensor hereby disclaims all warranties and conditions with respect to the licensed application and any services, either express, implied or statutory, including, but not limited to, the implied warranties and/or conditions of merchantability, of satisfactory quality, of fitness for a particular purpose, of accuracy, of quiet enjoyment, and of noninfringement of third-party rights. No oral or written information or advice given by licensor or its authorized representative shall create a warranty. Should the licensed application or services prove defective, you assume the entire cost of all necessary servicing, repair or correction. Some jurisdictions do not allow the exclusion of the implied warranties or limitations on applicable statutory rights of a customer, so the above exclusion may not apply to you.",
+ tosAreEffectiveAsOf: "the Terms of Service are effective as of",
+ // privacy policy
+ privacyImportant: "your privacy is important to us. It is the developers' policy to respect your privacy regarding any information we may collect from you through our app, BežiApp.",
+ privacyOnlyAskedWhen: "we only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why we’re collecting it and how it will be used.",
+ privacyDataCollection: "we only retain collected information for as long as necessary to provide you with your requested service. What data we store, we’ll protect within commercially acceptable means to prevent loss and theft, as well as unauthorized access, disclosure, copying, use or modification.",
+ privacySharingData: "we don’t share any personally identifying information publicly or with third-parties, except when required to by law",
+ privacyExternalSites: "our app may link to external sites that are not operated by us. Please be aware that we have no control over the content and practices of these sites, and cannot accept responsibility or liability for their respective privacy policies.",
+ privacyRefuse: "you are free to refuse our request for your personal information, with the understanding that we may be unable to provide you with some of your desired services.",
+ privacyAcceptWithUse: "your continued use of our website will be regarded as acceptance of our practices around privacy and personal information. If you have any questions about how we handle user data and personal information, feel free to contact us.",
+ privacyEffectiveAsOf: "this policy is effective as of",
+ // settings
+ language: "language",
+ selectLanguage: "select desired language",
+ languageSet: "language set, open another page for the changes to take effect",
+ theme: "theme",
+ themeLight: "light theme (default)",
+ themeDark: "dark theme",
+ themeNight: "night theme",
+ selectTheme: "select a theme",
+ triggerWarning: "the following switch enables additional settings, which some people may: disagree with, find annoying, be offended by them. By enabling the switch, you agree that you won't be triggered by any of the additional options and will not asociate any of the authors and/or their personal beliefs and opinions with additional options.",
+ triggerAgreement: "i agree with terms and conditions stated above",
+ triggerWarningSet: "additional settings toggled",
+ additionalOptions: "additional settings",
+ themeSet: "theme set, open another page for the changes to take effect",
+ errorReportingSet: "error reporting preference set",
+ errorReporting: "error reporting",
+ on: "on",
+ off: "off",
+ selectErrorReporting: "should error reports be submitted to the developers?",
+ // gsec
+ gsecErrNet: "GimSIS connection error",
+ gsecErrLogin: "GimSIS login error (bad password?), try logging out",
+ gsecErrOther: "GimSIS unknown error, try logging out",
+ // videoconferences
+ videoconferences: "GimB meet"
+ },
+ sl: {
+ miscTranslationLanguage: "slovenščina",
+ miscTranslationAuthors: "Anton Luka Šijanec",
+ "": "",
+ // date
+ monday: "ponedeljek",
+ tuesday: "torek",
+ wednesday: "sreda",
+ thursday: "četrtek",
+ friday: "petek",
+ saturday: "sobota",
+ sunday: "nedelja",
+ am: "dop.",
+ pm: "pop.",
+ january: "januar",
+ february: "februar",
+ march: "marec",
+ april: "april",
+ may: "maj",
+ june: "junij",
+ july: "julij",
+ august: "avgust",
+ september: "september",
+ october: "oktober",
+ november: "november",
+ december: "december",
+ // login
+ username: "uporabniško ime",
+ password: "geslo",
+ signIn: "prijava",
+ bySigningInYouAgreeTo: "s prijavo se strinjate s",
+ theToS: "pogoji uporabe (v angleščini)",
+ and: "in",
+ thePrivacyPolicy: "politika zasebnosti (v angleščini)",
+ loginFailed: "prijava je spodletela",
+ browserNotSupported: "BežiApp ne bo deloval na vaši napravi, če ne posodobite vašega Internetnega brskalnika",
+ // timetable
+ noPeriods: "ni ur v izbranem tednu",
+ // index
+ timetable: "urnik",
+ gradings: "ocenjevanja",
+ grades: "ocene",
+ teachers: "profesorji",
+ absences: "izostanki",
+ messaging: "sporočanje",
+ meals: "obroki",
+ about: "o",
+ logout: "odjava",
+ settings: "nastavitve",
+ // gradings
+ requestFailed: "zahteva spodletela",
+ noInternetConnection: "ni povezave s spletom",
+ // grades
+ temporary: "začasno",
+ useOnlyPermanentGrades: "uporabi le stalne ocene",
+ useOnlyPermanentGradesNote1: "če je označeno, bodo za izračun povprečja uporabljene le stalne ocene",
+ useOnlyPermanentGradesNote2: "če pa je polje neoznačeno, pa se ob izračunu povprečne ocene upoštevajo vse ocene",
+ type: "tip",
+ term: "rok",
+ teacher: "profesor",
+ // teachers
+ name: "ime",
+ schoolSubject: "predmet",
+ tpMeetings: "govorilne ure",
+ // absences
+ from: "od",
+ to: "do",
+ cancel: "preklic",
+ ok: "v redu",
+ noAbsences: "ni izostankov v izbranem časovnem obdobju",
+ lesson: "ura",
+ notProcessed: "ni obdelano",
+ authorizedAbsence: "opravičeno",
+ unauthorizedAbsence: "neopravičeno",
+ doesNotCount: "ne šteje",
+ // messaging
+ loadingMessages: "Nalagam sporočila...",
+ sendAMessage: "pošlji sporočilo",
+ recipient: "prejemnik",
+ messageSubject: "zadeva",
+ messageBody: "telo",
+ removeImages: "odstrani slike",
+ note: "opomba",
+ largeImagesNote: "GimB strežniki ne marajo velikih sporočil, zato lahko pošiljate le zelo majhne slike, v nasprotnem primeru sporočilo ne bo dostavljeno",
+ attachedImages: "pripete slike",
+ encryptMessage: "Šifriraj sporočilo",
+ passwordForE2EE: "geslo za šifriranje sporočila",
+ messages: "sporočila",
+ received: "prejeta",
+ sent: "poslana",
+ deleted: "izbrisana",
+ messageStorageUsed: "zasedenost shrambe sporočil v tej mapi",
+ maxMessagesNote: "v vsaki mapi imate lahko največ 120 sporočil. Starejša sporočila ne bodo prikazana. Redno brišite sporočila, da se izognete morebitnim težavam.",
+ loadMessageBody: "naloži telo sporočila",
+ thisMessageWasEncrypted: "to sporočilo je šifrirano",
+ enterPassword: "vnesite geslo",
+ decrypt: "dešifriraj",
+ nameDirectoryNotSet: "imenik ni nastavljen, pošiljanje ni mogoče",
+ errorFetchingMessages: "sporočil ni bilo mogoče prenesti",
+ unableToReceiveTheMessage: "sporočila ni bilo mogoče prenesti",
+ unableToDeleteTheMessage: "sporočila ni bilo mogoče izbrisati",
+ messageWasProbablySent: "sporočilo je bilo verjetno poslano, prepričajte se in preverite mapo s poslanimi sporočili",
+ errorSendingMessage: "sporočila ni bilo mogoče poslati",
+ imageAddedAsAnAttachment: "slika dodana kot priloga",
+ unableToReadDirectory: "imenika ni bilo mogoče prebrati",
+ messageCouldNotBeSent: "sporočila ni bilo mogoče poslati",
+ incorrectPassword: "nepravilno geslo",
+ // chats
+ chat: "klepet",
+ chattingWith: "klepet z osebo",
+ noMessages: "ni sporočil",
+ stillLoading: "nalaganje še poteka",
+ directory: "imenik",
+ select: "izberi",
+ mustSelectRecipient: "pred klepetom morate izbrati sogovornika. Odprite imenik (meni na levi strani) s pritiskom na gumb \"imenik\" zgoraj desno in izberite sogovornika.",
+ recipientNotInDirectory: "izbrane osebe ni v imeniku",
+ chatExternalInfo: "dobili ste kratko sporočilo v standardu, ki ga GimSIS ne podpira. Pri odgovarjanju spremenite zadevo. Vsebina sporočila: ",
+ // meals
+ loginError: "napaka pri prijavi",
+ loginToLopolis: "prijava v Lopolis",
+ loginToLopolisNote: "izgleda, da niste prijavljeni v eRestavracijo, zato se vam je prikazal prijavni obrazec. Za uporavljanje s prehrano se uporablja druga kombinacija uporabniškega imena in gesla, zato se prijavite s svojimi Lopolis prijavnimi podatki za nadaljevanje.",
+ logInToLopolis: "prijava v Lopolis",
+ logOutFromLopolis: "odjava iz Lopolisa",
+ readOnly: "samo za branje",
+ usage: "uporaba",
+ mealsUsageNote: "kliknite na datum za prikaz menijev, nato pa si enega izberite s klikom na ime menija. Po nastavitvi menijev ponovno naložite menije in se prepričajte o pravilnih nastavitvah.",
+ lunchesNote: "aplikacija ni testirana za naročanje na koslila, zato verjetno to ne deluje. Če ste naročeni na kosila lahko naročanje na menije sploh ne deluje ali pa deluje narobe.",
+ mealNotShownNote: "če nek dan manjka med meniji, to verjetno pomeni, da ni več spremenljiv in zanj niste ročno spremenili menija",
+ mealsContributeNote: "vabimo vas k urejanju LopolisAPI programa za upravljanje z meniji.",
+ authenticationError: "napaka avtentikacije",
+ lopolisAPIConnectionError: "napaka povezave na LopolisAPI strežnik",
+ errorGettingMenus: "napaka branja menijev",
+ errorUnexpectedResponse: "napaka: nepričakovan odgovor",
+ requestForAuthenticationFailed: "zahteva za avtentikacijo ni uspela",
+ credentialsMatch: "prijavni podatki so pravilni",
+ errorSettingMeals: "napaka pri nastavljanju menijev",
+ mealSet: "obrok nastavljen! osvežite obroke in se prepričajte sami",
+ selected: "izbrano",
+ // about
+ version: "različica",
+ authors: "avtorji",
+ translatorsForThisLanguage: "prevajalci izbranega jezika",
+ whatIsNew: "kaj je novega",
+ whatsNew: "kaj je novega",
+ reportABug: "prijavite napako",
+ sendASuggestion: "pošljite pripombo/predlog/pohvalo/pritožbo",
+ instagram: "instagram",
+ // changelog
+ changelog: "dnevnik sprememb",
+ // terms and conditions
+ termsOfUse: "terms of use",
+ termsOfUseDescription: "as a condition of use, you promise not to use the BežiApp (App or application) and its related infrastructure (API, hosting service) for any purpose that is unlawful or prohibited by these Terms, or any other purpose not reasonably intended by the authors of the App. By way of example, and not as a limitation, you agree not to use the App",
+ termsOfUseHarass: "to abuse, harass, threaten, impersonate or intimidate any person",
+ termsOfUsePost: "to post or transmit, or cause to be posted or transmitted, any Content that is libelous, defamatory, obscene, pornographic, abusive, offensive, profane or that infringes any copyright or other right of any person",
+ termsOfUseCommunicate: "to communicate with the App developers or other users in abusive or offensive manner",
+ termsOfUsePurpose: "for any purpose that is not permitted under the laws of the jurisdiction where you use the App",
+ termsOfUseExploit: "to post or transmit, or cause to be posted or transmitted, any Communication designed or intended to obtain password, account or private information of any App user",
+ termsOfUseSpam: "to create or transmit unwanted “spam” to any person or any URL",
+ termsOfUseModify: "you may also not reverse engineer, modify or redistribute the app without written consent from the developers",
+ terminationOfServices: "termination of services",
+ terminationOfServicesDescriptions: "the developers of the App may terminate your access to the App without any prior warning or notice for any of the following reasons",
+ terminationOfServicesBreaching: "breaching the Terms of Service",
+ terminationOfServicesRequest: "receiving a formal request from authorities of Gimnazija Bežigrad administration requesting termination of your access to the App",
+ limitationOfLiability: "limitation of Liability",
+ limitationOfLiabilityContent: "the developers of the App provide no warranty; You expressly acknowledge and agree that the use of the licensed application is at your sole risk. To the maximum extent permited by applicable law, the licensed application and any services performed of provided by the licensed application are provided “as is” and “as available”, with all faults and without warranty of any kind, and licensor hereby disclaims all warranties and conditions with respect to the licensed application and any services, either express, implied or statutory, including, but not limited to, the implied warranties and/or conditions of merchantability, of satisfactory quality, of fitness for a particular purpose, of accuracy, of quiet enjoyment, and of noninfringement of third-party rights. No oral or written information or advice given by licensor or its authorized representative shall create a warranty. Should the licensed application or services prove defective, you assume the entire cost of all necessary servicing, repair or correction. Some jurisdictions do not allow the exclusion of the implied warranties or limitations on applicable statutory rights of a customer, so the above exclusion may not apply to you.",
+ tosAreEffectiveAsOf: "the Terms of Service are effective as of",
+ // privacy policy
+ privacyImportant: "your privacy is important to us. It is the developers' policy to respect your privacy regarding any information we may collect from you through our app, BežiApp.",
+ privacyOnlyAskedWhen: "we only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why we’re collecting it and how it will be used.",
+ privacyDataCollection: "we only retain collected information for as long as necessary to provide you with your requested service. What data we store, we’ll protect within commercially acceptable means to prevent loss and theft, as well as unauthorized access, disclosure, copying, use or modification.",
+ privacySharingData: "we don’t share any personally identifying information publicly or with third-parties, except when required to by law",
+ privacyExternalSites: "our app may link to external sites that are not operated by us. Please be aware that we have no control over the content and practices of these sites, and cannot accept responsibility or liability for their respective privacy policies.",
+ privacyRefuse: "you are free to refuse our request for your personal information, with the understanding that we may be unable to provide you with some of your desired services.",
+ privacyAcceptWithUse: "your continued use of our website will be regarded as acceptance of our practices around privacy and personal information. If you have any questions about how we handle user data and personal information, feel free to contact us.",
+ privacyEffectiveAsOf: "this policy is effective as of",
+ // settings
+ language: "jezik",
+ selectLanguage: "izberite željen jezik",
+ languageSet: "jezik nastavljen, odprite neko drugo stran da se pokažejo spremembe",
+ theme: "izgled",
+ themeLight: "svetel izgled (privzeto)",
+ themeDark: "temen izgled",
+ themeNight: "nočni izgled",
+ themeSet: "izgled nastavljen, odprite neko drugo stran da se spremembe uveljavijo",
+ selectTheme: "izberite željen izgled",
+ errorReportingSet: "nastavitev pošiljanja napak izbrana",
+ errorReporting: "pošiljanje napak",
+ on: "vklopljeno",
+ off: "izklopljeno",
+ selectErrorReporting: "ali naj so napake v aplikaciji posredovane razvijalcem?",
+ triggerWarning: "spodnji gumb omogoči dodatne možnosti, ki lahko razburijo/vznevoljijo nekatere uporabnike. Če omogočite stikalo se strinjate, da avtorjev in/ali njihovih osebnih prepričanj ne boste povezovali s katerokoli od dodatnih omogočenih možnosti.",
+ triggerAgreement: "strinjam se z zgoraj navedenimi pogoji",
+ triggerWarningSet: "spremenili ste stanje dodatnih nastavitev",
+ additionalOptions: "dodatne nastavitve",
+ // gsec
+ gsecErrNet: "napaka povezave na GimSIS",
+ gsecErrLogin: "prijava v GimSIS ni uspela (napačno geslo?), poskusite se odjaviti",
+ gsecErrOther: "neznana napaka GimSISa, poskusite se odjaviti",
+ // videoconferences
+ videoconferences: "GimB konference"
+ }
+}
diff --git a/js/lib/fullcalendar/core/main.min.js b/assets/js/lib/fullcalendar/core/main.min.js
index 6b72fff..6b72fff 100644
--- a/js/lib/fullcalendar/core/main.min.js
+++ b/assets/js/lib/fullcalendar/core/main.min.js
diff --git a/js/lib/fullcalendar/daygrid/main.min.js b/assets/js/lib/fullcalendar/daygrid/main.min.js
index 2f4c0d4..2f4c0d4 100644
--- a/js/lib/fullcalendar/daygrid/main.min.js
+++ b/assets/js/lib/fullcalendar/daygrid/main.min.js
diff --git a/js/lib/fullcalendar/timegrid/main.min.js b/assets/js/lib/fullcalendar/timegrid/main.min.js
index 13c3433..13c3433 100644
--- a/js/lib/fullcalendar/timegrid/main.min.js
+++ b/assets/js/lib/fullcalendar/timegrid/main.min.js
diff --git a/assets/js/lib/he.js b/assets/js/lib/he.js
new file mode 100644
index 0000000..65ad0a3
--- /dev/null
+++ b/assets/js/lib/he.js
@@ -0,0 +1,345 @@
+/*! https://mths.be/he v1.2.0 by @mathias | MIT license */
+;(function(root) {
+
+ // Detect free variables `exports`.
+ var freeExports = typeof exports == 'object' && exports;
+
+ // Detect free variable `module`.
+ var freeModule = typeof module == 'object' && module &&
+ module.exports == freeExports && module;
+
+ // Detect free variable `global`, from Node.js or Browserified code,
+ // and use it as `root`.
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ // All astral symbols.
+ var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
+ // All ASCII symbols (not just printable ASCII) except those listed in the
+ // first column of the overrides table.
+ // https://html.spec.whatwg.org/multipage/syntax.html#table-charref-overrides
+ var regexAsciiWhitelist = /[\x01-\x7F]/g;
+ // All BMP symbols that are not ASCII newlines, printable ASCII symbols, or
+ // code points listed in the first column of the overrides table on
+ // https://html.spec.whatwg.org/multipage/syntax.html#table-charref-overrides.
+ var regexBmpWhitelist = /[\x01-\t\x0B\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g;
+
+ var regexEncodeNonAscii = /<\u20D2|=\u20E5|>\u20D2|\u205F\u200A|\u219D\u0338|\u2202\u0338|\u2220\u20D2|\u2229\uFE00|\u222A\uFE00|\u223C\u20D2|\u223D\u0331|\u223E\u0333|\u2242\u0338|\u224B\u0338|\u224D\u20D2|\u224E\u0338|\u224F\u0338|\u2250\u0338|\u2261\u20E5|\u2264\u20D2|\u2265\u20D2|\u2266\u0338|\u2267\u0338|\u2268\uFE00|\u2269\uFE00|\u226A\u0338|\u226A\u20D2|\u226B\u0338|\u226B\u20D2|\u227F\u0338|\u2282\u20D2|\u2283\u20D2|\u228A\uFE00|\u228B\uFE00|\u228F\u0338|\u2290\u0338|\u2293\uFE00|\u2294\uFE00|\u22B4\u20D2|\u22B5\u20D2|\u22D8\u0338|\u22D9\u0338|\u22DA\uFE00|\u22DB\uFE00|\u22F5\u0338|\u22F9\u0338|\u2933\u0338|\u29CF\u0338|\u29D0\u0338|\u2A6D\u0338|\u2A70\u0338|\u2A7D\u0338|\u2A7E\u0338|\u2AA1\u0338|\u2AA2\u0338|\u2AAC\uFE00|\u2AAD\uFE00|\u2AAF\u0338|\u2AB0\u0338|\u2AC5\u0338|\u2AC6\u0338|\u2ACB\uFE00|\u2ACC\uFE00|\u2AFD\u20E5|[\xA0-\u0113\u0116-\u0122\u0124-\u012B\u012E-\u014D\u0150-\u017E\u0192\u01B5\u01F5\u0237\u02C6\u02C7\u02D8-\u02DD\u0311\u0391-\u03A1\u03A3-\u03A9\u03B1-\u03C9\u03D1\u03D2\u03D5\u03D6\u03DC\u03DD\u03F0\u03F1\u03F5\u03F6\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E\u045F\u2002-\u2005\u2007-\u2010\u2013-\u2016\u2018-\u201A\u201C-\u201E\u2020-\u2022\u2025\u2026\u2030-\u2035\u2039\u203A\u203E\u2041\u2043\u2044\u204F\u2057\u205F-\u2063\u20AC\u20DB\u20DC\u2102\u2105\u210A-\u2113\u2115-\u211E\u2122\u2124\u2127-\u2129\u212C\u212D\u212F-\u2131\u2133-\u2138\u2145-\u2148\u2153-\u215E\u2190-\u219B\u219D-\u21A7\u21A9-\u21AE\u21B0-\u21B3\u21B5-\u21B7\u21BA-\u21DB\u21DD\u21E4\u21E5\u21F5\u21FD-\u2205\u2207-\u2209\u220B\u220C\u220F-\u2214\u2216-\u2218\u221A\u221D-\u2238\u223A-\u2257\u2259\u225A\u225C\u225F-\u2262\u2264-\u228B\u228D-\u229B\u229D-\u22A5\u22A7-\u22B0\u22B2-\u22BB\u22BD-\u22DB\u22DE-\u22E3\u22E6-\u22F7\u22F9-\u22FE\u2305\u2306\u2308-\u2310\u2312\u2313\u2315\u2316\u231C-\u231F\u2322\u2323\u232D\u232E\u2336\u233D\u233F\u237C\u23B0\u23B1\u23B4-\u23B6\u23DC-\u23DF\u23E2\u23E7\u2423\u24C8\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2550-\u256C\u2580\u2584\u2588\u2591-\u2593\u25A1\u25AA\u25AB\u25AD\u25AE\u25B1\u25B3-\u25B5\u25B8\u25B9\u25BD-\u25BF\u25C2\u25C3\u25CA\u25CB\u25EC\u25EF\u25F8-\u25FC\u2605\u2606\u260E\u2640\u2642\u2660\u2663\u2665\u2666\u266A\u266D-\u266F\u2713\u2717\u2720\u2736\u2758\u2772\u2773\u27C8\u27C9\u27E6-\u27ED\u27F5-\u27FA\u27FC\u27FF\u2902-\u2905\u290C-\u2913\u2916\u2919-\u2920\u2923-\u292A\u2933\u2935-\u2939\u293C\u293D\u2945\u2948-\u294B\u294E-\u2976\u2978\u2979\u297B-\u297F\u2985\u2986\u298B-\u2996\u299A\u299C\u299D\u29A4-\u29B7\u29B9\u29BB\u29BC\u29BE-\u29C5\u29C9\u29CD-\u29D0\u29DC-\u29DE\u29E3-\u29E5\u29EB\u29F4\u29F6\u2A00-\u2A02\u2A04\u2A06\u2A0C\u2A0D\u2A10-\u2A17\u2A22-\u2A27\u2A29\u2A2A\u2A2D-\u2A31\u2A33-\u2A3C\u2A3F\u2A40\u2A42-\u2A4D\u2A50\u2A53-\u2A58\u2A5A-\u2A5D\u2A5F\u2A66\u2A6A\u2A6D-\u2A75\u2A77-\u2A9A\u2A9D-\u2AA2\u2AA4-\u2AB0\u2AB3-\u2AC8\u2ACB\u2ACC\u2ACF-\u2ADB\u2AE4\u2AE6-\u2AE9\u2AEB-\u2AF3\u2AFD\uFB00-\uFB04]|\uD835[\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDD6B]/g;
+ var encodeMap = {'\xAD':'shy','\u200C':'zwnj','\u200D':'zwj','\u200E':'lrm','\u2063':'ic','\u2062':'it','\u2061':'af','\u200F':'rlm','\u200B':'ZeroWidthSpace','\u2060':'NoBreak','\u0311':'DownBreve','\u20DB':'tdot','\u20DC':'DotDot','\t':'Tab','\n':'NewLine','\u2008':'puncsp','\u205F':'MediumSpace','\u2009':'thinsp','\u200A':'hairsp','\u2004':'emsp13','\u2002':'ensp','\u2005':'emsp14','\u2003':'emsp','\u2007':'numsp','\xA0':'nbsp','\u205F\u200A':'ThickSpace','\u203E':'oline','_':'lowbar','\u2010':'dash','\u2013':'ndash','\u2014':'mdash','\u2015':'horbar',',':'comma',';':'semi','\u204F':'bsemi',':':'colon','\u2A74':'Colone','!':'excl','\xA1':'iexcl','?':'quest','\xBF':'iquest','.':'period','\u2025':'nldr','\u2026':'mldr','\xB7':'middot','\'':'apos','\u2018':'lsquo','\u2019':'rsquo','\u201A':'sbquo','\u2039':'lsaquo','\u203A':'rsaquo','"':'quot','\u201C':'ldquo','\u201D':'rdquo','\u201E':'bdquo','\xAB':'laquo','\xBB':'raquo','(':'lpar',')':'rpar','[':'lsqb',']':'rsqb','{':'lcub','}':'rcub','\u2308':'lceil','\u2309':'rceil','\u230A':'lfloor','\u230B':'rfloor','\u2985':'lopar','\u2986':'ropar','\u298B':'lbrke','\u298C':'rbrke','\u298D':'lbrkslu','\u298E':'rbrksld','\u298F':'lbrksld','\u2990':'rbrkslu','\u2991':'langd','\u2992':'rangd','\u2993':'lparlt','\u2994':'rpargt','\u2995':'gtlPar','\u2996':'ltrPar','\u27E6':'lobrk','\u27E7':'robrk','\u27E8':'lang','\u27E9':'rang','\u27EA':'Lang','\u27EB':'Rang','\u27EC':'loang','\u27ED':'roang','\u2772':'lbbrk','\u2773':'rbbrk','\u2016':'Vert','\xA7':'sect','\xB6':'para','@':'commat','*':'ast','/':'sol','undefined':null,'&':'amp','#':'num','%':'percnt','\u2030':'permil','\u2031':'pertenk','\u2020':'dagger','\u2021':'Dagger','\u2022':'bull','\u2043':'hybull','\u2032':'prime','\u2033':'Prime','\u2034':'tprime','\u2057':'qprime','\u2035':'bprime','\u2041':'caret','`':'grave','\xB4':'acute','\u02DC':'tilde','^':'Hat','\xAF':'macr','\u02D8':'breve','\u02D9':'dot','\xA8':'die','\u02DA':'ring','\u02DD':'dblac','\xB8':'cedil','\u02DB':'ogon','\u02C6':'circ','\u02C7':'caron','\xB0':'deg','\xA9':'copy','\xAE':'reg','\u2117':'copysr','\u2118':'wp','\u211E':'rx','\u2127':'mho','\u2129':'iiota','\u2190':'larr','\u219A':'nlarr','\u2192':'rarr','\u219B':'nrarr','\u2191':'uarr','\u2193':'darr','\u2194':'harr','\u21AE':'nharr','\u2195':'varr','\u2196':'nwarr','\u2197':'nearr','\u2198':'searr','\u2199':'swarr','\u219D':'rarrw','\u219D\u0338':'nrarrw','\u219E':'Larr','\u219F':'Uarr','\u21A0':'Rarr','\u21A1':'Darr','\u21A2':'larrtl','\u21A3':'rarrtl','\u21A4':'mapstoleft','\u21A5':'mapstoup','\u21A6':'map','\u21A7':'mapstodown','\u21A9':'larrhk','\u21AA':'rarrhk','\u21AB':'larrlp','\u21AC':'rarrlp','\u21AD':'harrw','\u21B0':'lsh','\u21B1':'rsh','\u21B2':'ldsh','\u21B3':'rdsh','\u21B5':'crarr','\u21B6':'cularr','\u21B7':'curarr','\u21BA':'olarr','\u21BB':'orarr','\u21BC':'lharu','\u21BD':'lhard','\u21BE':'uharr','\u21BF':'uharl','\u21C0':'rharu','\u21C1':'rhard','\u21C2':'dharr','\u21C3':'dharl','\u21C4':'rlarr','\u21C5':'udarr','\u21C6':'lrarr','\u21C7':'llarr','\u21C8':'uuarr','\u21C9':'rrarr','\u21CA':'ddarr','\u21CB':'lrhar','\u21CC':'rlhar','\u21D0':'lArr','\u21CD':'nlArr','\u21D1':'uArr','\u21D2':'rArr','\u21CF':'nrArr','\u21D3':'dArr','\u21D4':'iff','\u21CE':'nhArr','\u21D5':'vArr','\u21D6':'nwArr','\u21D7':'neArr','\u21D8':'seArr','\u21D9':'swArr','\u21DA':'lAarr','\u21DB':'rAarr','\u21DD':'zigrarr','\u21E4':'larrb','\u21E5':'rarrb','\u21F5':'duarr','\u21FD':'loarr','\u21FE':'roarr','\u21FF':'hoarr','\u2200':'forall','\u2201':'comp','\u2202':'part','\u2202\u0338':'npart','\u2203':'exist','\u2204':'nexist','\u2205':'empty','\u2207':'Del','\u2208':'in','\u2209':'notin','\u220B':'ni','\u220C':'notni','\u03F6':'bepsi','\u220F':'prod','\u2210':'coprod','\u2211':'sum','+':'plus','\xB1':'pm','\xF7':'div','\xD7':'times','<':'lt','\u226E':'nlt','<\u20D2':'nvlt','=':'equals','\u2260':'ne','=\u20E5':'bne','\u2A75':'Equal','>':'gt','\u226F':'ngt','>\u20D2':'nvgt','\xAC':'not','|':'vert','\xA6':'brvbar','\u2212':'minus','\u2213':'mp','\u2214':'plusdo','\u2044':'frasl','\u2216':'setmn','\u2217':'lowast','\u2218':'compfn','\u221A':'Sqrt','\u221D':'prop','\u221E':'infin','\u221F':'angrt','\u2220':'ang','\u2220\u20D2':'nang','\u2221':'angmsd','\u2222':'angsph','\u2223':'mid','\u2224':'nmid','\u2225':'par','\u2226':'npar','\u2227':'and','\u2228':'or','\u2229':'cap','\u2229\uFE00':'caps','\u222A':'cup','\u222A\uFE00':'cups','\u222B':'int','\u222C':'Int','\u222D':'tint','\u2A0C':'qint','\u222E':'oint','\u222F':'Conint','\u2230':'Cconint','\u2231':'cwint','\u2232':'cwconint','\u2233':'awconint','\u2234':'there4','\u2235':'becaus','\u2236':'ratio','\u2237':'Colon','\u2238':'minusd','\u223A':'mDDot','\u223B':'homtht','\u223C':'sim','\u2241':'nsim','\u223C\u20D2':'nvsim','\u223D':'bsim','\u223D\u0331':'race','\u223E':'ac','\u223E\u0333':'acE','\u223F':'acd','\u2240':'wr','\u2242':'esim','\u2242\u0338':'nesim','\u2243':'sime','\u2244':'nsime','\u2245':'cong','\u2247':'ncong','\u2246':'simne','\u2248':'ap','\u2249':'nap','\u224A':'ape','\u224B':'apid','\u224B\u0338':'napid','\u224C':'bcong','\u224D':'CupCap','\u226D':'NotCupCap','\u224D\u20D2':'nvap','\u224E':'bump','\u224E\u0338':'nbump','\u224F':'bumpe','\u224F\u0338':'nbumpe','\u2250':'doteq','\u2250\u0338':'nedot','\u2251':'eDot','\u2252':'efDot','\u2253':'erDot','\u2254':'colone','\u2255':'ecolon','\u2256':'ecir','\u2257':'cire','\u2259':'wedgeq','\u225A':'veeeq','\u225C':'trie','\u225F':'equest','\u2261':'equiv','\u2262':'nequiv','\u2261\u20E5':'bnequiv','\u2264':'le','\u2270':'nle','\u2264\u20D2':'nvle','\u2265':'ge','\u2271':'nge','\u2265\u20D2':'nvge','\u2266':'lE','\u2266\u0338':'nlE','\u2267':'gE','\u2267\u0338':'ngE','\u2268\uFE00':'lvnE','\u2268':'lnE','\u2269':'gnE','\u2269\uFE00':'gvnE','\u226A':'ll','\u226A\u0338':'nLtv','\u226A\u20D2':'nLt','\u226B':'gg','\u226B\u0338':'nGtv','\u226B\u20D2':'nGt','\u226C':'twixt','\u2272':'lsim','\u2274':'nlsim','\u2273':'gsim','\u2275':'ngsim','\u2276':'lg','\u2278':'ntlg','\u2277':'gl','\u2279':'ntgl','\u227A':'pr','\u2280':'npr','\u227B':'sc','\u2281':'nsc','\u227C':'prcue','\u22E0':'nprcue','\u227D':'sccue','\u22E1':'nsccue','\u227E':'prsim','\u227F':'scsim','\u227F\u0338':'NotSucceedsTilde','\u2282':'sub','\u2284':'nsub','\u2282\u20D2':'vnsub','\u2283':'sup','\u2285':'nsup','\u2283\u20D2':'vnsup','\u2286':'sube','\u2288':'nsube','\u2287':'supe','\u2289':'nsupe','\u228A\uFE00':'vsubne','\u228A':'subne','\u228B\uFE00':'vsupne','\u228B':'supne','\u228D':'cupdot','\u228E':'uplus','\u228F':'sqsub','\u228F\u0338':'NotSquareSubset','\u2290':'sqsup','\u2290\u0338':'NotSquareSuperset','\u2291':'sqsube','\u22E2':'nsqsube','\u2292':'sqsupe','\u22E3':'nsqsupe','\u2293':'sqcap','\u2293\uFE00':'sqcaps','\u2294':'sqcup','\u2294\uFE00':'sqcups','\u2295':'oplus','\u2296':'ominus','\u2297':'otimes','\u2298':'osol','\u2299':'odot','\u229A':'ocir','\u229B':'oast','\u229D':'odash','\u229E':'plusb','\u229F':'minusb','\u22A0':'timesb','\u22A1':'sdotb','\u22A2':'vdash','\u22AC':'nvdash','\u22A3':'dashv','\u22A4':'top','\u22A5':'bot','\u22A7':'models','\u22A8':'vDash','\u22AD':'nvDash','\u22A9':'Vdash','\u22AE':'nVdash','\u22AA':'Vvdash','\u22AB':'VDash','\u22AF':'nVDash','\u22B0':'prurel','\u22B2':'vltri','\u22EA':'nltri','\u22B3':'vrtri','\u22EB':'nrtri','\u22B4':'ltrie','\u22EC':'nltrie','\u22B4\u20D2':'nvltrie','\u22B5':'rtrie','\u22ED':'nrtrie','\u22B5\u20D2':'nvrtrie','\u22B6':'origof','\u22B7':'imof','\u22B8':'mumap','\u22B9':'hercon','\u22BA':'intcal','\u22BB':'veebar','\u22BD':'barvee','\u22BE':'angrtvb','\u22BF':'lrtri','\u22C0':'Wedge','\u22C1':'Vee','\u22C2':'xcap','\u22C3':'xcup','\u22C4':'diam','\u22C5':'sdot','\u22C6':'Star','\u22C7':'divonx','\u22C8':'bowtie','\u22C9':'ltimes','\u22CA':'rtimes','\u22CB':'lthree','\u22CC':'rthree','\u22CD':'bsime','\u22CE':'cuvee','\u22CF':'cuwed','\u22D0':'Sub','\u22D1':'Sup','\u22D2':'Cap','\u22D3':'Cup','\u22D4':'fork','\u22D5':'epar','\u22D6':'ltdot','\u22D7':'gtdot','\u22D8':'Ll','\u22D8\u0338':'nLl','\u22D9':'Gg','\u22D9\u0338':'nGg','\u22DA\uFE00':'lesg','\u22DA':'leg','\u22DB':'gel','\u22DB\uFE00':'gesl','\u22DE':'cuepr','\u22DF':'cuesc','\u22E6':'lnsim','\u22E7':'gnsim','\u22E8':'prnsim','\u22E9':'scnsim','\u22EE':'vellip','\u22EF':'ctdot','\u22F0':'utdot','\u22F1':'dtdot','\u22F2':'disin','\u22F3':'isinsv','\u22F4':'isins','\u22F5':'isindot','\u22F5\u0338':'notindot','\u22F6':'notinvc','\u22F7':'notinvb','\u22F9':'isinE','\u22F9\u0338':'notinE','\u22FA':'nisd','\u22FB':'xnis','\u22FC':'nis','\u22FD':'notnivc','\u22FE':'notnivb','\u2305':'barwed','\u2306':'Barwed','\u230C':'drcrop','\u230D':'dlcrop','\u230E':'urcrop','\u230F':'ulcrop','\u2310':'bnot','\u2312':'profline','\u2313':'profsurf','\u2315':'telrec','\u2316':'target','\u231C':'ulcorn','\u231D':'urcorn','\u231E':'dlcorn','\u231F':'drcorn','\u2322':'frown','\u2323':'smile','\u232D':'cylcty','\u232E':'profalar','\u2336':'topbot','\u233D':'ovbar','\u233F':'solbar','\u237C':'angzarr','\u23B0':'lmoust','\u23B1':'rmoust','\u23B4':'tbrk','\u23B5':'bbrk','\u23B6':'bbrktbrk','\u23DC':'OverParenthesis','\u23DD':'UnderParenthesis','\u23DE':'OverBrace','\u23DF':'UnderBrace','\u23E2':'trpezium','\u23E7':'elinters','\u2423':'blank','\u2500':'boxh','\u2502':'boxv','\u250C':'boxdr','\u2510':'boxdl','\u2514':'boxur','\u2518':'boxul','\u251C':'boxvr','\u2524':'boxvl','\u252C':'boxhd','\u2534':'boxhu','\u253C':'boxvh','\u2550':'boxH','\u2551':'boxV','\u2552':'boxdR','\u2553':'boxDr','\u2554':'boxDR','\u2555':'boxdL','\u2556':'boxDl','\u2557':'boxDL','\u2558':'boxuR','\u2559':'boxUr','\u255A':'boxUR','\u255B':'boxuL','\u255C':'boxUl','\u255D':'boxUL','\u255E':'boxvR','\u255F':'boxVr','\u2560':'boxVR','\u2561':'boxvL','\u2562':'boxVl','\u2563':'boxVL','\u2564':'boxHd','\u2565':'boxhD','\u2566':'boxHD','\u2567':'boxHu','\u2568':'boxhU','\u2569':'boxHU','\u256A':'boxvH','\u256B':'boxVh','\u256C':'boxVH','\u2580':'uhblk','\u2584':'lhblk','\u2588':'block','\u2591':'blk14','\u2592':'blk12','\u2593':'blk34','\u25A1':'squ','\u25AA':'squf','\u25AB':'EmptyVerySmallSquare','\u25AD':'rect','\u25AE':'marker','\u25B1':'fltns','\u25B3':'xutri','\u25B4':'utrif','\u25B5':'utri','\u25B8':'rtrif','\u25B9':'rtri','\u25BD':'xdtri','\u25BE':'dtrif','\u25BF':'dtri','\u25C2':'ltrif','\u25C3':'ltri','\u25CA':'loz','\u25CB':'cir','\u25EC':'tridot','\u25EF':'xcirc','\u25F8':'ultri','\u25F9':'urtri','\u25FA':'lltri','\u25FB':'EmptySmallSquare','\u25FC':'FilledSmallSquare','\u2605':'starf','\u2606':'star','\u260E':'phone','\u2640':'female','\u2642':'male','\u2660':'spades','\u2663':'clubs','\u2665':'hearts','\u2666':'diams','\u266A':'sung','\u2713':'check','\u2717':'cross','\u2720':'malt','\u2736':'sext','\u2758':'VerticalSeparator','\u27C8':'bsolhsub','\u27C9':'suphsol','\u27F5':'xlarr','\u27F6':'xrarr','\u27F7':'xharr','\u27F8':'xlArr','\u27F9':'xrArr','\u27FA':'xhArr','\u27FC':'xmap','\u27FF':'dzigrarr','\u2902':'nvlArr','\u2903':'nvrArr','\u2904':'nvHarr','\u2905':'Map','\u290C':'lbarr','\u290D':'rbarr','\u290E':'lBarr','\u290F':'rBarr','\u2910':'RBarr','\u2911':'DDotrahd','\u2912':'UpArrowBar','\u2913':'DownArrowBar','\u2916':'Rarrtl','\u2919':'latail','\u291A':'ratail','\u291B':'lAtail','\u291C':'rAtail','\u291D':'larrfs','\u291E':'rarrfs','\u291F':'larrbfs','\u2920':'rarrbfs','\u2923':'nwarhk','\u2924':'nearhk','\u2925':'searhk','\u2926':'swarhk','\u2927':'nwnear','\u2928':'toea','\u2929':'tosa','\u292A':'swnwar','\u2933':'rarrc','\u2933\u0338':'nrarrc','\u2935':'cudarrr','\u2936':'ldca','\u2937':'rdca','\u2938':'cudarrl','\u2939':'larrpl','\u293C':'curarrm','\u293D':'cularrp','\u2945':'rarrpl','\u2948':'harrcir','\u2949':'Uarrocir','\u294A':'lurdshar','\u294B':'ldrushar','\u294E':'LeftRightVector','\u294F':'RightUpDownVector','\u2950':'DownLeftRightVector','\u2951':'LeftUpDownVector','\u2952':'LeftVectorBar','\u2953':'RightVectorBar','\u2954':'RightUpVectorBar','\u2955':'RightDownVectorBar','\u2956':'DownLeftVectorBar','\u2957':'DownRightVectorBar','\u2958':'LeftUpVectorBar','\u2959':'LeftDownVectorBar','\u295A':'LeftTeeVector','\u295B':'RightTeeVector','\u295C':'RightUpTeeVector','\u295D':'RightDownTeeVector','\u295E':'DownLeftTeeVector','\u295F':'DownRightTeeVector','\u2960':'LeftUpTeeVector','\u2961':'LeftDownTeeVector','\u2962':'lHar','\u2963':'uHar','\u2964':'rHar','\u2965':'dHar','\u2966':'luruhar','\u2967':'ldrdhar','\u2968':'ruluhar','\u2969':'rdldhar','\u296A':'lharul','\u296B':'llhard','\u296C':'rharul','\u296D':'lrhard','\u296E':'udhar','\u296F':'duhar','\u2970':'RoundImplies','\u2971':'erarr','\u2972':'simrarr','\u2973':'larrsim','\u2974':'rarrsim','\u2975':'rarrap','\u2976':'ltlarr','\u2978':'gtrarr','\u2979':'subrarr','\u297B':'suplarr','\u297C':'lfisht','\u297D':'rfisht','\u297E':'ufisht','\u297F':'dfisht','\u299A':'vzigzag','\u299C':'vangrt','\u299D':'angrtvbd','\u29A4':'ange','\u29A5':'range','\u29A6':'dwangle','\u29A7':'uwangle','\u29A8':'angmsdaa','\u29A9':'angmsdab','\u29AA':'angmsdac','\u29AB':'angmsdad','\u29AC':'angmsdae','\u29AD':'angmsdaf','\u29AE':'angmsdag','\u29AF':'angmsdah','\u29B0':'bemptyv','\u29B1':'demptyv','\u29B2':'cemptyv','\u29B3':'raemptyv','\u29B4':'laemptyv','\u29B5':'ohbar','\u29B6':'omid','\u29B7':'opar','\u29B9':'operp','\u29BB':'olcross','\u29BC':'odsold','\u29BE':'olcir','\u29BF':'ofcir','\u29C0':'olt','\u29C1':'ogt','\u29C2':'cirscir','\u29C3':'cirE','\u29C4':'solb','\u29C5':'bsolb','\u29C9':'boxbox','\u29CD':'trisb','\u29CE':'rtriltri','\u29CF':'LeftTriangleBar','\u29CF\u0338':'NotLeftTriangleBar','\u29D0':'RightTriangleBar','\u29D0\u0338':'NotRightTriangleBar','\u29DC':'iinfin','\u29DD':'infintie','\u29DE':'nvinfin','\u29E3':'eparsl','\u29E4':'smeparsl','\u29E5':'eqvparsl','\u29EB':'lozf','\u29F4':'RuleDelayed','\u29F6':'dsol','\u2A00':'xodot','\u2A01':'xoplus','\u2A02':'xotime','\u2A04':'xuplus','\u2A06':'xsqcup','\u2A0D':'fpartint','\u2A10':'cirfnint','\u2A11':'awint','\u2A12':'rppolint','\u2A13':'scpolint','\u2A14':'npolint','\u2A15':'pointint','\u2A16':'quatint','\u2A17':'intlarhk','\u2A22':'pluscir','\u2A23':'plusacir','\u2A24':'simplus','\u2A25':'plusdu','\u2A26':'plussim','\u2A27':'plustwo','\u2A29':'mcomma','\u2A2A':'minusdu','\u2A2D':'loplus','\u2A2E':'roplus','\u2A2F':'Cross','\u2A30':'timesd','\u2A31':'timesbar','\u2A33':'smashp','\u2A34':'lotimes','\u2A35':'rotimes','\u2A36':'otimesas','\u2A37':'Otimes','\u2A38':'odiv','\u2A39':'triplus','\u2A3A':'triminus','\u2A3B':'tritime','\u2A3C':'iprod','\u2A3F':'amalg','\u2A40':'capdot','\u2A42':'ncup','\u2A43':'ncap','\u2A44':'capand','\u2A45':'cupor','\u2A46':'cupcap','\u2A47':'capcup','\u2A48':'cupbrcap','\u2A49':'capbrcup','\u2A4A':'cupcup','\u2A4B':'capcap','\u2A4C':'ccups','\u2A4D':'ccaps','\u2A50':'ccupssm','\u2A53':'And','\u2A54':'Or','\u2A55':'andand','\u2A56':'oror','\u2A57':'orslope','\u2A58':'andslope','\u2A5A':'andv','\u2A5B':'orv','\u2A5C':'andd','\u2A5D':'ord','\u2A5F':'wedbar','\u2A66':'sdote','\u2A6A':'simdot','\u2A6D':'congdot','\u2A6D\u0338':'ncongdot','\u2A6E':'easter','\u2A6F':'apacir','\u2A70':'apE','\u2A70\u0338':'napE','\u2A71':'eplus','\u2A72':'pluse','\u2A73':'Esim','\u2A77':'eDDot','\u2A78':'equivDD','\u2A79':'ltcir','\u2A7A':'gtcir','\u2A7B':'ltquest','\u2A7C':'gtquest','\u2A7D':'les','\u2A7D\u0338':'nles','\u2A7E':'ges','\u2A7E\u0338':'nges','\u2A7F':'lesdot','\u2A80':'gesdot','\u2A81':'lesdoto','\u2A82':'gesdoto','\u2A83':'lesdotor','\u2A84':'gesdotol','\u2A85':'lap','\u2A86':'gap','\u2A87':'lne','\u2A88':'gne','\u2A89':'lnap','\u2A8A':'gnap','\u2A8B':'lEg','\u2A8C':'gEl','\u2A8D':'lsime','\u2A8E':'gsime','\u2A8F':'lsimg','\u2A90':'gsiml','\u2A91':'lgE','\u2A92':'glE','\u2A93':'lesges','\u2A94':'gesles','\u2A95':'els','\u2A96':'egs','\u2A97':'elsdot','\u2A98':'egsdot','\u2A99':'el','\u2A9A':'eg','\u2A9D':'siml','\u2A9E':'simg','\u2A9F':'simlE','\u2AA0':'simgE','\u2AA1':'LessLess','\u2AA1\u0338':'NotNestedLessLess','\u2AA2':'GreaterGreater','\u2AA2\u0338':'NotNestedGreaterGreater','\u2AA4':'glj','\u2AA5':'gla','\u2AA6':'ltcc','\u2AA7':'gtcc','\u2AA8':'lescc','\u2AA9':'gescc','\u2AAA':'smt','\u2AAB':'lat','\u2AAC':'smte','\u2AAC\uFE00':'smtes','\u2AAD':'late','\u2AAD\uFE00':'lates','\u2AAE':'bumpE','\u2AAF':'pre','\u2AAF\u0338':'npre','\u2AB0':'sce','\u2AB0\u0338':'nsce','\u2AB3':'prE','\u2AB4':'scE','\u2AB5':'prnE','\u2AB6':'scnE','\u2AB7':'prap','\u2AB8':'scap','\u2AB9':'prnap','\u2ABA':'scnap','\u2ABB':'Pr','\u2ABC':'Sc','\u2ABD':'subdot','\u2ABE':'supdot','\u2ABF':'subplus','\u2AC0':'supplus','\u2AC1':'submult','\u2AC2':'supmult','\u2AC3':'subedot','\u2AC4':'supedot','\u2AC5':'subE','\u2AC5\u0338':'nsubE','\u2AC6':'supE','\u2AC6\u0338':'nsupE','\u2AC7':'subsim','\u2AC8':'supsim','\u2ACB\uFE00':'vsubnE','\u2ACB':'subnE','\u2ACC\uFE00':'vsupnE','\u2ACC':'supnE','\u2ACF':'csub','\u2AD0':'csup','\u2AD1':'csube','\u2AD2':'csupe','\u2AD3':'subsup','\u2AD4':'supsub','\u2AD5':'subsub','\u2AD6':'supsup','\u2AD7':'suphsub','\u2AD8':'supdsub','\u2AD9':'forkv','\u2ADA':'topfork','\u2ADB':'mlcp','\u2AE4':'Dashv','\u2AE6':'Vdashl','\u2AE7':'Barv','\u2AE8':'vBar','\u2AE9':'vBarv','\u2AEB':'Vbar','\u2AEC':'Not','\u2AED':'bNot','\u2AEE':'rnmid','\u2AEF':'cirmid','\u2AF0':'midcir','\u2AF1':'topcir','\u2AF2':'nhpar','\u2AF3':'parsim','\u2AFD':'parsl','\u2AFD\u20E5':'nparsl','\u266D':'flat','\u266E':'natur','\u266F':'sharp','\xA4':'curren','\xA2':'cent','$':'dollar','\xA3':'pound','\xA5':'yen','\u20AC':'euro','\xB9':'sup1','\xBD':'half','\u2153':'frac13','\xBC':'frac14','\u2155':'frac15','\u2159':'frac16','\u215B':'frac18','\xB2':'sup2','\u2154':'frac23','\u2156':'frac25','\xB3':'sup3','\xBE':'frac34','\u2157':'frac35','\u215C':'frac38','\u2158':'frac45','\u215A':'frac56','\u215D':'frac58','\u215E':'frac78','\uD835\uDCB6':'ascr','\uD835\uDD52':'aopf','\uD835\uDD1E':'afr','\uD835\uDD38':'Aopf','\uD835\uDD04':'Afr','\uD835\uDC9C':'Ascr','\xAA':'ordf','\xE1':'aacute','\xC1':'Aacute','\xE0':'agrave','\xC0':'Agrave','\u0103':'abreve','\u0102':'Abreve','\xE2':'acirc','\xC2':'Acirc','\xE5':'aring','\xC5':'angst','\xE4':'auml','\xC4':'Auml','\xE3':'atilde','\xC3':'Atilde','\u0105':'aogon','\u0104':'Aogon','\u0101':'amacr','\u0100':'Amacr','\xE6':'aelig','\xC6':'AElig','\uD835\uDCB7':'bscr','\uD835\uDD53':'bopf','\uD835\uDD1F':'bfr','\uD835\uDD39':'Bopf','\u212C':'Bscr','\uD835\uDD05':'Bfr','\uD835\uDD20':'cfr','\uD835\uDCB8':'cscr','\uD835\uDD54':'copf','\u212D':'Cfr','\uD835\uDC9E':'Cscr','\u2102':'Copf','\u0107':'cacute','\u0106':'Cacute','\u0109':'ccirc','\u0108':'Ccirc','\u010D':'ccaron','\u010C':'Ccaron','\u010B':'cdot','\u010A':'Cdot','\xE7':'ccedil','\xC7':'Ccedil','\u2105':'incare','\uD835\uDD21':'dfr','\u2146':'dd','\uD835\uDD55':'dopf','\uD835\uDCB9':'dscr','\uD835\uDC9F':'Dscr','\uD835\uDD07':'Dfr','\u2145':'DD','\uD835\uDD3B':'Dopf','\u010F':'dcaron','\u010E':'Dcaron','\u0111':'dstrok','\u0110':'Dstrok','\xF0':'eth','\xD0':'ETH','\u2147':'ee','\u212F':'escr','\uD835\uDD22':'efr','\uD835\uDD56':'eopf','\u2130':'Escr','\uD835\uDD08':'Efr','\uD835\uDD3C':'Eopf','\xE9':'eacute','\xC9':'Eacute','\xE8':'egrave','\xC8':'Egrave','\xEA':'ecirc','\xCA':'Ecirc','\u011B':'ecaron','\u011A':'Ecaron','\xEB':'euml','\xCB':'Euml','\u0117':'edot','\u0116':'Edot','\u0119':'eogon','\u0118':'Eogon','\u0113':'emacr','\u0112':'Emacr','\uD835\uDD23':'ffr','\uD835\uDD57':'fopf','\uD835\uDCBB':'fscr','\uD835\uDD09':'Ffr','\uD835\uDD3D':'Fopf','\u2131':'Fscr','\uFB00':'fflig','\uFB03':'ffilig','\uFB04':'ffllig','\uFB01':'filig','fj':'fjlig','\uFB02':'fllig','\u0192':'fnof','\u210A':'gscr','\uD835\uDD58':'gopf','\uD835\uDD24':'gfr','\uD835\uDCA2':'Gscr','\uD835\uDD3E':'Gopf','\uD835\uDD0A':'Gfr','\u01F5':'gacute','\u011F':'gbreve','\u011E':'Gbreve','\u011D':'gcirc','\u011C':'Gcirc','\u0121':'gdot','\u0120':'Gdot','\u0122':'Gcedil','\uD835\uDD25':'hfr','\u210E':'planckh','\uD835\uDCBD':'hscr','\uD835\uDD59':'hopf','\u210B':'Hscr','\u210C':'Hfr','\u210D':'Hopf','\u0125':'hcirc','\u0124':'Hcirc','\u210F':'hbar','\u0127':'hstrok','\u0126':'Hstrok','\uD835\uDD5A':'iopf','\uD835\uDD26':'ifr','\uD835\uDCBE':'iscr','\u2148':'ii','\uD835\uDD40':'Iopf','\u2110':'Iscr','\u2111':'Im','\xED':'iacute','\xCD':'Iacute','\xEC':'igrave','\xCC':'Igrave','\xEE':'icirc','\xCE':'Icirc','\xEF':'iuml','\xCF':'Iuml','\u0129':'itilde','\u0128':'Itilde','\u0130':'Idot','\u012F':'iogon','\u012E':'Iogon','\u012B':'imacr','\u012A':'Imacr','\u0133':'ijlig','\u0132':'IJlig','\u0131':'imath','\uD835\uDCBF':'jscr','\uD835\uDD5B':'jopf','\uD835\uDD27':'jfr','\uD835\uDCA5':'Jscr','\uD835\uDD0D':'Jfr','\uD835\uDD41':'Jopf','\u0135':'jcirc','\u0134':'Jcirc','\u0237':'jmath','\uD835\uDD5C':'kopf','\uD835\uDCC0':'kscr','\uD835\uDD28':'kfr','\uD835\uDCA6':'Kscr','\uD835\uDD42':'Kopf','\uD835\uDD0E':'Kfr','\u0137':'kcedil','\u0136':'Kcedil','\uD835\uDD29':'lfr','\uD835\uDCC1':'lscr','\u2113':'ell','\uD835\uDD5D':'lopf','\u2112':'Lscr','\uD835\uDD0F':'Lfr','\uD835\uDD43':'Lopf','\u013A':'lacute','\u0139':'Lacute','\u013E':'lcaron','\u013D':'Lcaron','\u013C':'lcedil','\u013B':'Lcedil','\u0142':'lstrok','\u0141':'Lstrok','\u0140':'lmidot','\u013F':'Lmidot','\uD835\uDD2A':'mfr','\uD835\uDD5E':'mopf','\uD835\uDCC2':'mscr','\uD835\uDD10':'Mfr','\uD835\uDD44':'Mopf','\u2133':'Mscr','\uD835\uDD2B':'nfr','\uD835\uDD5F':'nopf','\uD835\uDCC3':'nscr','\u2115':'Nopf','\uD835\uDCA9':'Nscr','\uD835\uDD11':'Nfr','\u0144':'nacute','\u0143':'Nacute','\u0148':'ncaron','\u0147':'Ncaron','\xF1':'ntilde','\xD1':'Ntilde','\u0146':'ncedil','\u0145':'Ncedil','\u2116':'numero','\u014B':'eng','\u014A':'ENG','\uD835\uDD60':'oopf','\uD835\uDD2C':'ofr','\u2134':'oscr','\uD835\uDCAA':'Oscr','\uD835\uDD12':'Ofr','\uD835\uDD46':'Oopf','\xBA':'ordm','\xF3':'oacute','\xD3':'Oacute','\xF2':'ograve','\xD2':'Ograve','\xF4':'ocirc','\xD4':'Ocirc','\xF6':'ouml','\xD6':'Ouml','\u0151':'odblac','\u0150':'Odblac','\xF5':'otilde','\xD5':'Otilde','\xF8':'oslash','\xD8':'Oslash','\u014D':'omacr','\u014C':'Omacr','\u0153':'oelig','\u0152':'OElig','\uD835\uDD2D':'pfr','\uD835\uDCC5':'pscr','\uD835\uDD61':'popf','\u2119':'Popf','\uD835\uDD13':'Pfr','\uD835\uDCAB':'Pscr','\uD835\uDD62':'qopf','\uD835\uDD2E':'qfr','\uD835\uDCC6':'qscr','\uD835\uDCAC':'Qscr','\uD835\uDD14':'Qfr','\u211A':'Qopf','\u0138':'kgreen','\uD835\uDD2F':'rfr','\uD835\uDD63':'ropf','\uD835\uDCC7':'rscr','\u211B':'Rscr','\u211C':'Re','\u211D':'Ropf','\u0155':'racute','\u0154':'Racute','\u0159':'rcaron','\u0158':'Rcaron','\u0157':'rcedil','\u0156':'Rcedil','\uD835\uDD64':'sopf','\uD835\uDCC8':'sscr','\uD835\uDD30':'sfr','\uD835\uDD4A':'Sopf','\uD835\uDD16':'Sfr','\uD835\uDCAE':'Sscr','\u24C8':'oS','\u015B':'sacute','\u015A':'Sacute','\u015D':'scirc','\u015C':'Scirc','\u0161':'scaron','\u0160':'Scaron','\u015F':'scedil','\u015E':'Scedil','\xDF':'szlig','\uD835\uDD31':'tfr','\uD835\uDCC9':'tscr','\uD835\uDD65':'topf','\uD835\uDCAF':'Tscr','\uD835\uDD17':'Tfr','\uD835\uDD4B':'Topf','\u0165':'tcaron','\u0164':'Tcaron','\u0163':'tcedil','\u0162':'Tcedil','\u2122':'trade','\u0167':'tstrok','\u0166':'Tstrok','\uD835\uDCCA':'uscr','\uD835\uDD66':'uopf','\uD835\uDD32':'ufr','\uD835\uDD4C':'Uopf','\uD835\uDD18':'Ufr','\uD835\uDCB0':'Uscr','\xFA':'uacute','\xDA':'Uacute','\xF9':'ugrave','\xD9':'Ugrave','\u016D':'ubreve','\u016C':'Ubreve','\xFB':'ucirc','\xDB':'Ucirc','\u016F':'uring','\u016E':'Uring','\xFC':'uuml','\xDC':'Uuml','\u0171':'udblac','\u0170':'Udblac','\u0169':'utilde','\u0168':'Utilde','\u0173':'uogon','\u0172':'Uogon','\u016B':'umacr','\u016A':'Umacr','\uD835\uDD33':'vfr','\uD835\uDD67':'vopf','\uD835\uDCCB':'vscr','\uD835\uDD19':'Vfr','\uD835\uDD4D':'Vopf','\uD835\uDCB1':'Vscr','\uD835\uDD68':'wopf','\uD835\uDCCC':'wscr','\uD835\uDD34':'wfr','\uD835\uDCB2':'Wscr','\uD835\uDD4E':'Wopf','\uD835\uDD1A':'Wfr','\u0175':'wcirc','\u0174':'Wcirc','\uD835\uDD35':'xfr','\uD835\uDCCD':'xscr','\uD835\uDD69':'xopf','\uD835\uDD4F':'Xopf','\uD835\uDD1B':'Xfr','\uD835\uDCB3':'Xscr','\uD835\uDD36':'yfr','\uD835\uDCCE':'yscr','\uD835\uDD6A':'yopf','\uD835\uDCB4':'Yscr','\uD835\uDD1C':'Yfr','\uD835\uDD50':'Yopf','\xFD':'yacute','\xDD':'Yacute','\u0177':'ycirc','\u0176':'Ycirc','\xFF':'yuml','\u0178':'Yuml','\uD835\uDCCF':'zscr','\uD835\uDD37':'zfr','\uD835\uDD6B':'zopf','\u2128':'Zfr','\u2124':'Zopf','\uD835\uDCB5':'Zscr','\u017A':'zacute','\u0179':'Zacute','\u017E':'zcaron','\u017D':'Zcaron','\u017C':'zdot','\u017B':'Zdot','\u01B5':'imped','\xFE':'thorn','\xDE':'THORN','\u0149':'napos','\u03B1':'alpha','\u0391':'Alpha','\u03B2':'beta','\u0392':'Beta','\u03B3':'gamma','\u0393':'Gamma','\u03B4':'delta','\u0394':'Delta','\u03B5':'epsi','\u03F5':'epsiv','\u0395':'Epsilon','\u03DD':'gammad','\u03DC':'Gammad','\u03B6':'zeta','\u0396':'Zeta','\u03B7':'eta','\u0397':'Eta','\u03B8':'theta','\u03D1':'thetav','\u0398':'Theta','\u03B9':'iota','\u0399':'Iota','\u03BA':'kappa','\u03F0':'kappav','\u039A':'Kappa','\u03BB':'lambda','\u039B':'Lambda','\u03BC':'mu','\xB5':'micro','\u039C':'Mu','\u03BD':'nu','\u039D':'Nu','\u03BE':'xi','\u039E':'Xi','\u03BF':'omicron','\u039F':'Omicron','\u03C0':'pi','\u03D6':'piv','\u03A0':'Pi','\u03C1':'rho','\u03F1':'rhov','\u03A1':'Rho','\u03C3':'sigma','\u03A3':'Sigma','\u03C2':'sigmaf','\u03C4':'tau','\u03A4':'Tau','\u03C5':'upsi','\u03A5':'Upsilon','\u03D2':'Upsi','\u03C6':'phi','\u03D5':'phiv','\u03A6':'Phi','\u03C7':'chi','\u03A7':'Chi','\u03C8':'psi','\u03A8':'Psi','\u03C9':'omega','\u03A9':'ohm','\u0430':'acy','\u0410':'Acy','\u0431':'bcy','\u0411':'Bcy','\u0432':'vcy','\u0412':'Vcy','\u0433':'gcy','\u0413':'Gcy','\u0453':'gjcy','\u0403':'GJcy','\u0434':'dcy','\u0414':'Dcy','\u0452':'djcy','\u0402':'DJcy','\u0435':'iecy','\u0415':'IEcy','\u0451':'iocy','\u0401':'IOcy','\u0454':'jukcy','\u0404':'Jukcy','\u0436':'zhcy','\u0416':'ZHcy','\u0437':'zcy','\u0417':'Zcy','\u0455':'dscy','\u0405':'DScy','\u0438':'icy','\u0418':'Icy','\u0456':'iukcy','\u0406':'Iukcy','\u0457':'yicy','\u0407':'YIcy','\u0439':'jcy','\u0419':'Jcy','\u0458':'jsercy','\u0408':'Jsercy','\u043A':'kcy','\u041A':'Kcy','\u045C':'kjcy','\u040C':'KJcy','\u043B':'lcy','\u041B':'Lcy','\u0459':'ljcy','\u0409':'LJcy','\u043C':'mcy','\u041C':'Mcy','\u043D':'ncy','\u041D':'Ncy','\u045A':'njcy','\u040A':'NJcy','\u043E':'ocy','\u041E':'Ocy','\u043F':'pcy','\u041F':'Pcy','\u0440':'rcy','\u0420':'Rcy','\u0441':'scy','\u0421':'Scy','\u0442':'tcy','\u0422':'Tcy','\u045B':'tshcy','\u040B':'TSHcy','\u0443':'ucy','\u0423':'Ucy','\u045E':'ubrcy','\u040E':'Ubrcy','\u0444':'fcy','\u0424':'Fcy','\u0445':'khcy','\u0425':'KHcy','\u0446':'tscy','\u0426':'TScy','\u0447':'chcy','\u0427':'CHcy','\u045F':'dzcy','\u040F':'DZcy','\u0448':'shcy','\u0428':'SHcy','\u0449':'shchcy','\u0429':'SHCHcy','\u044A':'hardcy','\u042A':'HARDcy','\u044B':'ycy','\u042B':'Ycy','\u044C':'softcy','\u042C':'SOFTcy','\u044D':'ecy','\u042D':'Ecy','\u044E':'yucy','\u042E':'YUcy','\u044F':'yacy','\u042F':'YAcy','\u2135':'aleph','\u2136':'beth','\u2137':'gimel','\u2138':'daleth'};
+
+ var regexEscape = /["&'<>`]/g;
+ var escapeMap = {
+ '"': '&quot;',
+ '&': '&amp;',
+ '\'': '&#x27;',
+ '<': '&lt;',
+ // See https://mathiasbynens.be/notes/ambiguous-ampersands: in HTML, the
+ // following is not strictly necessary unless it’s part of a tag or an
+ // unquoted attribute value. We’re only escaping it to support those
+ // situations, and for XML support.
+ '>': '&gt;',
+ // In Internet Explorer ≤ 8, the backtick character can be used
+ // to break out of (un)quoted attribute values or HTML comments.
+ // See http://html5sec.org/#102, http://html5sec.org/#108, and
+ // http://html5sec.org/#133.
+ '`': '&#x60;'
+ };
+
+ var regexInvalidEntity = /&#(?:[xX][^a-fA-F0-9]|[^0-9xX])/;
+ var regexInvalidRawCodePoint = /[\0-\x08\x0B\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
+ var regexDecode = /&(CounterClockwiseContourIntegral|DoubleLongLeftRightArrow|ClockwiseContourIntegral|NotNestedGreaterGreater|NotSquareSupersetEqual|DiacriticalDoubleAcute|NotRightTriangleEqual|NotSucceedsSlantEqual|NotPrecedesSlantEqual|CloseCurlyDoubleQuote|NegativeVeryThinSpace|DoubleContourIntegral|FilledVerySmallSquare|CapitalDifferentialD|OpenCurlyDoubleQuote|EmptyVerySmallSquare|NestedGreaterGreater|DoubleLongRightArrow|NotLeftTriangleEqual|NotGreaterSlantEqual|ReverseUpEquilibrium|DoubleLeftRightArrow|NotSquareSubsetEqual|NotDoubleVerticalBar|RightArrowLeftArrow|NotGreaterFullEqual|NotRightTriangleBar|SquareSupersetEqual|DownLeftRightVector|DoubleLongLeftArrow|leftrightsquigarrow|LeftArrowRightArrow|NegativeMediumSpace|blacktriangleright|RightDownVectorBar|PrecedesSlantEqual|RightDoubleBracket|SucceedsSlantEqual|NotLeftTriangleBar|RightTriangleEqual|SquareIntersection|RightDownTeeVector|ReverseEquilibrium|NegativeThickSpace|longleftrightarrow|Longleftrightarrow|LongLeftRightArrow|DownRightTeeVector|DownRightVectorBar|GreaterSlantEqual|SquareSubsetEqual|LeftDownVectorBar|LeftDoubleBracket|VerticalSeparator|rightleftharpoons|NotGreaterGreater|NotSquareSuperset|blacktriangleleft|blacktriangledown|NegativeThinSpace|LeftDownTeeVector|NotLessSlantEqual|leftrightharpoons|DoubleUpDownArrow|DoubleVerticalBar|LeftTriangleEqual|FilledSmallSquare|twoheadrightarrow|NotNestedLessLess|DownLeftTeeVector|DownLeftVectorBar|RightAngleBracket|NotTildeFullEqual|NotReverseElement|RightUpDownVector|DiacriticalTilde|NotSucceedsTilde|circlearrowright|NotPrecedesEqual|rightharpoondown|DoubleRightArrow|NotSucceedsEqual|NonBreakingSpace|NotRightTriangle|LessEqualGreater|RightUpTeeVector|LeftAngleBracket|GreaterFullEqual|DownArrowUpArrow|RightUpVectorBar|twoheadleftarrow|GreaterEqualLess|downharpoonright|RightTriangleBar|ntrianglerighteq|NotSupersetEqual|LeftUpDownVector|DiacriticalAcute|rightrightarrows|vartriangleright|UpArrowDownArrow|DiacriticalGrave|UnderParenthesis|EmptySmallSquare|LeftUpVectorBar|leftrightarrows|DownRightVector|downharpoonleft|trianglerighteq|ShortRightArrow|OverParenthesis|DoubleLeftArrow|DoubleDownArrow|NotSquareSubset|bigtriangledown|ntrianglelefteq|UpperRightArrow|curvearrowright|vartriangleleft|NotLeftTriangle|nleftrightarrow|LowerRightArrow|NotHumpDownHump|NotGreaterTilde|rightthreetimes|LeftUpTeeVector|NotGreaterEqual|straightepsilon|LeftTriangleBar|rightsquigarrow|ContourIntegral|rightleftarrows|CloseCurlyQuote|RightDownVector|LeftRightVector|nLeftrightarrow|leftharpoondown|circlearrowleft|SquareSuperset|OpenCurlyQuote|hookrightarrow|HorizontalLine|DiacriticalDot|NotLessGreater|ntriangleright|DoubleRightTee|InvisibleComma|InvisibleTimes|LowerLeftArrow|DownLeftVector|NotSubsetEqual|curvearrowleft|trianglelefteq|NotVerticalBar|TildeFullEqual|downdownarrows|NotGreaterLess|RightTeeVector|ZeroWidthSpace|looparrowright|LongRightArrow|doublebarwedge|ShortLeftArrow|ShortDownArrow|RightVectorBar|GreaterGreater|ReverseElement|rightharpoonup|LessSlantEqual|leftthreetimes|upharpoonright|rightarrowtail|LeftDownVector|Longrightarrow|NestedLessLess|UpperLeftArrow|nshortparallel|leftleftarrows|leftrightarrow|Leftrightarrow|LeftRightArrow|longrightarrow|upharpoonleft|RightArrowBar|ApplyFunction|LeftTeeVector|leftarrowtail|NotEqualTilde|varsubsetneqq|varsupsetneqq|RightTeeArrow|SucceedsEqual|SucceedsTilde|LeftVectorBar|SupersetEqual|hookleftarrow|DifferentialD|VerticalTilde|VeryThinSpace|blacktriangle|bigtriangleup|LessFullEqual|divideontimes|leftharpoonup|UpEquilibrium|ntriangleleft|RightTriangle|measuredangle|shortparallel|longleftarrow|Longleftarrow|LongLeftArrow|DoubleLeftTee|Poincareplane|PrecedesEqual|triangleright|DoubleUpArrow|RightUpVector|fallingdotseq|looparrowleft|PrecedesTilde|NotTildeEqual|NotTildeTilde|smallsetminus|Proportional|triangleleft|triangledown|UnderBracket|NotHumpEqual|exponentiale|ExponentialE|NotLessTilde|HilbertSpace|RightCeiling|blacklozenge|varsupsetneq|HumpDownHump|GreaterEqual|VerticalLine|LeftTeeArrow|NotLessEqual|DownTeeArrow|LeftTriangle|varsubsetneq|Intersection|NotCongruent|DownArrowBar|LeftUpVector|LeftArrowBar|risingdotseq|GreaterTilde|RoundImplies|SquareSubset|ShortUpArrow|NotSuperset|quaternions|precnapprox|backepsilon|preccurlyeq|OverBracket|blacksquare|MediumSpace|VerticalBar|circledcirc|circleddash|CircleMinus|CircleTimes|LessGreater|curlyeqprec|curlyeqsucc|diamondsuit|UpDownArrow|Updownarrow|RuleDelayed|Rrightarrow|updownarrow|RightVector|nRightarrow|nrightarrow|eqslantless|LeftCeiling|Equilibrium|SmallCircle|expectation|NotSucceeds|thickapprox|GreaterLess|SquareUnion|NotPrecedes|NotLessLess|straightphi|succnapprox|succcurlyeq|SubsetEqual|sqsupseteq|Proportion|Laplacetrf|ImaginaryI|supsetneqq|NotGreater|gtreqqless|NotElement|ThickSpace|TildeEqual|TildeTilde|Fouriertrf|rmoustache|EqualTilde|eqslantgtr|UnderBrace|LeftVector|UpArrowBar|nLeftarrow|nsubseteqq|subsetneqq|nsupseteqq|nleftarrow|succapprox|lessapprox|UpTeeArrow|upuparrows|curlywedge|lesseqqgtr|varepsilon|varnothing|RightFloor|complement|CirclePlus|sqsubseteq|Lleftarrow|circledast|RightArrow|Rightarrow|rightarrow|lmoustache|Bernoullis|precapprox|mapstoleft|mapstodown|longmapsto|dotsquare|downarrow|DoubleDot|nsubseteq|supsetneq|leftarrow|nsupseteq|subsetneq|ThinSpace|ngeqslant|subseteqq|HumpEqual|NotSubset|triangleq|NotCupCap|lesseqgtr|heartsuit|TripleDot|Leftarrow|Coproduct|Congruent|varpropto|complexes|gvertneqq|LeftArrow|LessTilde|supseteqq|MinusPlus|CircleDot|nleqslant|NotExists|gtreqless|nparallel|UnionPlus|LeftFloor|checkmark|CenterDot|centerdot|Mellintrf|gtrapprox|bigotimes|OverBrace|spadesuit|therefore|pitchfork|rationals|PlusMinus|Backslash|Therefore|DownBreve|backsimeq|backprime|DownArrow|nshortmid|Downarrow|lvertneqq|eqvparsl|imagline|imagpart|infintie|integers|Integral|intercal|LessLess|Uarrocir|intlarhk|sqsupset|angmsdaf|sqsubset|llcorner|vartheta|cupbrcap|lnapprox|Superset|SuchThat|succnsim|succneqq|angmsdag|biguplus|curlyvee|trpezium|Succeeds|NotTilde|bigwedge|angmsdah|angrtvbd|triminus|cwconint|fpartint|lrcorner|smeparsl|subseteq|urcorner|lurdshar|laemptyv|DDotrahd|approxeq|ldrushar|awconint|mapstoup|backcong|shortmid|triangle|geqslant|gesdotol|timesbar|circledR|circledS|setminus|multimap|naturals|scpolint|ncongdot|RightTee|boxminus|gnapprox|boxtimes|andslope|thicksim|angmsdaa|varsigma|cirfnint|rtriltri|angmsdab|rppolint|angmsdac|barwedge|drbkarow|clubsuit|thetasym|bsolhsub|capbrcup|dzigrarr|doteqdot|DotEqual|dotminus|UnderBar|NotEqual|realpart|otimesas|ulcorner|hksearow|hkswarow|parallel|PartialD|elinters|emptyset|plusacir|bbrktbrk|angmsdad|pointint|bigoplus|angmsdae|Precedes|bigsqcup|varkappa|notindot|supseteq|precneqq|precnsim|profalar|profline|profsurf|leqslant|lesdotor|raemptyv|subplus|notnivb|notnivc|subrarr|zigrarr|vzigzag|submult|subedot|Element|between|cirscir|larrbfs|larrsim|lotimes|lbrksld|lbrkslu|lozenge|ldrdhar|dbkarow|bigcirc|epsilon|simrarr|simplus|ltquest|Epsilon|luruhar|gtquest|maltese|npolint|eqcolon|npreceq|bigodot|ddagger|gtrless|bnequiv|harrcir|ddotseq|equivDD|backsim|demptyv|nsqsube|nsqsupe|Upsilon|nsubset|upsilon|minusdu|nsucceq|swarrow|nsupset|coloneq|searrow|boxplus|napprox|natural|asympeq|alefsym|congdot|nearrow|bigstar|diamond|supplus|tritime|LeftTee|nvinfin|triplus|NewLine|nvltrie|nvrtrie|nwarrow|nexists|Diamond|ruluhar|Implies|supmult|angzarr|suplarr|suphsub|questeq|because|digamma|Because|olcross|bemptyv|omicron|Omicron|rotimes|NoBreak|intprod|angrtvb|orderof|uwangle|suphsol|lesdoto|orslope|DownTee|realine|cudarrl|rdldhar|OverBar|supedot|lessdot|supdsub|topfork|succsim|rbrkslu|rbrksld|pertenk|cudarrr|isindot|planckh|lessgtr|pluscir|gesdoto|plussim|plustwo|lesssim|cularrp|rarrsim|Cayleys|notinva|notinvb|notinvc|UpArrow|Uparrow|uparrow|NotLess|dwangle|precsim|Product|curarrm|Cconint|dotplus|rarrbfs|ccupssm|Cedilla|cemptyv|notniva|quatint|frac35|frac38|frac45|frac56|frac58|frac78|tridot|xoplus|gacute|gammad|Gammad|lfisht|lfloor|bigcup|sqsupe|gbreve|Gbreve|lharul|sqsube|sqcups|Gcedil|apacir|llhard|lmidot|Lmidot|lmoust|andand|sqcaps|approx|Abreve|spades|circeq|tprime|divide|topcir|Assign|topbot|gesdot|divonx|xuplus|timesd|gesles|atilde|solbar|SOFTcy|loplus|timesb|lowast|lowbar|dlcorn|dlcrop|softcy|dollar|lparlt|thksim|lrhard|Atilde|lsaquo|smashp|bigvee|thinsp|wreath|bkarow|lsquor|lstrok|Lstrok|lthree|ltimes|ltlarr|DotDot|simdot|ltrPar|weierp|xsqcup|angmsd|sigmav|sigmaf|zeetrf|Zcaron|zcaron|mapsto|vsupne|thetav|cirmid|marker|mcomma|Zacute|vsubnE|there4|gtlPar|vsubne|bottom|gtrarr|SHCHcy|shchcy|midast|midcir|middot|minusb|minusd|gtrdot|bowtie|sfrown|mnplus|models|colone|seswar|Colone|mstpos|searhk|gtrsim|nacute|Nacute|boxbox|telrec|hairsp|Tcedil|nbumpe|scnsim|ncaron|Ncaron|ncedil|Ncedil|hamilt|Scedil|nearhk|hardcy|HARDcy|tcedil|Tcaron|commat|nequiv|nesear|tcaron|target|hearts|nexist|varrho|scedil|Scaron|scaron|hellip|Sacute|sacute|hercon|swnwar|compfn|rtimes|rthree|rsquor|rsaquo|zacute|wedgeq|homtht|barvee|barwed|Barwed|rpargt|horbar|conint|swarhk|roplus|nltrie|hslash|hstrok|Hstrok|rmoust|Conint|bprime|hybull|hyphen|iacute|Iacute|supsup|supsub|supsim|varphi|coprod|brvbar|agrave|Supset|supset|igrave|Igrave|notinE|Agrave|iiiint|iinfin|copysr|wedbar|Verbar|vangrt|becaus|incare|verbar|inodot|bullet|drcorn|intcal|drcrop|cularr|vellip|Utilde|bumpeq|cupcap|dstrok|Dstrok|CupCap|cupcup|cupdot|eacute|Eacute|supdot|iquest|easter|ecaron|Ecaron|ecolon|isinsv|utilde|itilde|Itilde|curarr|succeq|Bumpeq|cacute|ulcrop|nparsl|Cacute|nprcue|egrave|Egrave|nrarrc|nrarrw|subsup|subsub|nrtrie|jsercy|nsccue|Jsercy|kappav|kcedil|Kcedil|subsim|ulcorn|nsimeq|egsdot|veebar|kgreen|capand|elsdot|Subset|subset|curren|aacute|lacute|Lacute|emptyv|ntilde|Ntilde|lagran|lambda|Lambda|capcap|Ugrave|langle|subdot|emsp13|numero|emsp14|nvdash|nvDash|nVdash|nVDash|ugrave|ufisht|nvHarr|larrfs|nvlArr|larrhk|larrlp|larrpl|nvrArr|Udblac|nwarhk|larrtl|nwnear|oacute|Oacute|latail|lAtail|sstarf|lbrace|odblac|Odblac|lbrack|udblac|odsold|eparsl|lcaron|Lcaron|ograve|Ograve|lcedil|Lcedil|Aacute|ssmile|ssetmn|squarf|ldquor|capcup|ominus|cylcty|rharul|eqcirc|dagger|rfloor|rfisht|Dagger|daleth|equals|origof|capdot|equest|dcaron|Dcaron|rdquor|oslash|Oslash|otilde|Otilde|otimes|Otimes|urcrop|Ubreve|ubreve|Yacute|Uacute|uacute|Rcedil|rcedil|urcorn|parsim|Rcaron|Vdashl|rcaron|Tstrok|percnt|period|permil|Exists|yacute|rbrack|rbrace|phmmat|ccaron|Ccaron|planck|ccedil|plankv|tstrok|female|plusdo|plusdu|ffilig|plusmn|ffllig|Ccedil|rAtail|dfisht|bernou|ratail|Rarrtl|rarrtl|angsph|rarrpl|rarrlp|rarrhk|xwedge|xotime|forall|ForAll|Vvdash|vsupnE|preceq|bigcap|frac12|frac13|frac14|primes|rarrfs|prnsim|frac15|Square|frac16|square|lesdot|frac18|frac23|propto|prurel|rarrap|rangle|puncsp|frac25|Racute|qprime|racute|lesges|frac34|abreve|AElig|eqsim|utdot|setmn|urtri|Equal|Uring|seArr|uring|searr|dashv|Dashv|mumap|nabla|iogon|Iogon|sdote|sdotb|scsim|napid|napos|equiv|natur|Acirc|dblac|erarr|nbump|iprod|erDot|ucirc|awint|esdot|angrt|ncong|isinE|scnap|Scirc|scirc|ndash|isins|Ubrcy|nearr|neArr|isinv|nedot|ubrcy|acute|Ycirc|iukcy|Iukcy|xutri|nesim|caret|jcirc|Jcirc|caron|twixt|ddarr|sccue|exist|jmath|sbquo|ngeqq|angst|ccaps|lceil|ngsim|UpTee|delta|Delta|rtrif|nharr|nhArr|nhpar|rtrie|jukcy|Jukcy|kappa|rsquo|Kappa|nlarr|nlArr|TSHcy|rrarr|aogon|Aogon|fflig|xrarr|tshcy|ccirc|nleqq|filig|upsih|nless|dharl|nlsim|fjlig|ropar|nltri|dharr|robrk|roarr|fllig|fltns|roang|rnmid|subnE|subne|lAarr|trisb|Ccirc|acirc|ccups|blank|VDash|forkv|Vdash|langd|cedil|blk12|blk14|laquo|strns|diams|notin|vDash|larrb|blk34|block|disin|uplus|vdash|vBarv|aelig|starf|Wedge|check|xrArr|lates|lbarr|lBarr|notni|lbbrk|bcong|frasl|lbrke|frown|vrtri|vprop|vnsup|gamma|Gamma|wedge|xodot|bdquo|srarr|doteq|ldquo|boxdl|boxdL|gcirc|Gcirc|boxDl|boxDL|boxdr|boxdR|boxDr|TRADE|trade|rlhar|boxDR|vnsub|npart|vltri|rlarr|boxhd|boxhD|nprec|gescc|nrarr|nrArr|boxHd|boxHD|boxhu|boxhU|nrtri|boxHu|clubs|boxHU|times|colon|Colon|gimel|xlArr|Tilde|nsime|tilde|nsmid|nspar|THORN|thorn|xlarr|nsube|nsubE|thkap|xhArr|comma|nsucc|boxul|boxuL|nsupe|nsupE|gneqq|gnsim|boxUl|boxUL|grave|boxur|boxuR|boxUr|boxUR|lescc|angle|bepsi|boxvh|varpi|boxvH|numsp|Theta|gsime|gsiml|theta|boxVh|boxVH|boxvl|gtcir|gtdot|boxvL|boxVl|boxVL|crarr|cross|Cross|nvsim|boxvr|nwarr|nwArr|sqsup|dtdot|Uogon|lhard|lharu|dtrif|ocirc|Ocirc|lhblk|duarr|odash|sqsub|Hacek|sqcup|llarr|duhar|oelig|OElig|ofcir|boxvR|uogon|lltri|boxVr|csube|uuarr|ohbar|csupe|ctdot|olarr|olcir|harrw|oline|sqcap|omacr|Omacr|omega|Omega|boxVR|aleph|lneqq|lnsim|loang|loarr|rharu|lobrk|hcirc|operp|oplus|rhard|Hcirc|orarr|Union|order|ecirc|Ecirc|cuepr|szlig|cuesc|breve|reals|eDDot|Breve|hoarr|lopar|utrif|rdquo|Umacr|umacr|efDot|swArr|ultri|alpha|rceil|ovbar|swarr|Wcirc|wcirc|smtes|smile|bsemi|lrarr|aring|parsl|lrhar|bsime|uhblk|lrtri|cupor|Aring|uharr|uharl|slarr|rbrke|bsolb|lsime|rbbrk|RBarr|lsimg|phone|rBarr|rbarr|icirc|lsquo|Icirc|emacr|Emacr|ratio|simne|plusb|simlE|simgE|simeq|pluse|ltcir|ltdot|empty|xharr|xdtri|iexcl|Alpha|ltrie|rarrw|pound|ltrif|xcirc|bumpe|prcue|bumpE|asymp|amacr|cuvee|Sigma|sigma|iiint|udhar|iiota|ijlig|IJlig|supnE|imacr|Imacr|prime|Prime|image|prnap|eogon|Eogon|rarrc|mdash|mDDot|cuwed|imath|supne|imped|Amacr|udarr|prsim|micro|rarrb|cwint|raquo|infin|eplus|range|rangd|Ucirc|radic|minus|amalg|veeeq|rAarr|epsiv|ycirc|quest|sharp|quot|zwnj|Qscr|race|qscr|Qopf|qopf|qint|rang|Rang|Zscr|zscr|Zopf|zopf|rarr|rArr|Rarr|Pscr|pscr|prop|prod|prnE|prec|ZHcy|zhcy|prap|Zeta|zeta|Popf|popf|Zdot|plus|zdot|Yuml|yuml|phiv|YUcy|yucy|Yscr|yscr|perp|Yopf|yopf|part|para|YIcy|Ouml|rcub|yicy|YAcy|rdca|ouml|osol|Oscr|rdsh|yacy|real|oscr|xvee|andd|rect|andv|Xscr|oror|ordm|ordf|xscr|ange|aopf|Aopf|rHar|Xopf|opar|Oopf|xopf|xnis|rhov|oopf|omid|xmap|oint|apid|apos|ogon|ascr|Ascr|odot|odiv|xcup|xcap|ocir|oast|nvlt|nvle|nvgt|nvge|nvap|Wscr|wscr|auml|ntlg|ntgl|nsup|nsub|nsim|Nscr|nscr|nsce|Wopf|ring|npre|wopf|npar|Auml|Barv|bbrk|Nopf|nopf|nmid|nLtv|beta|ropf|Ropf|Beta|beth|nles|rpar|nleq|bnot|bNot|nldr|NJcy|rscr|Rscr|Vscr|vscr|rsqb|njcy|bopf|nisd|Bopf|rtri|Vopf|nGtv|ngtr|vopf|boxh|boxH|boxv|nges|ngeq|boxV|bscr|scap|Bscr|bsim|Vert|vert|bsol|bull|bump|caps|cdot|ncup|scnE|ncap|nbsp|napE|Cdot|cent|sdot|Vbar|nang|vBar|chcy|Mscr|mscr|sect|semi|CHcy|Mopf|mopf|sext|circ|cire|mldr|mlcp|cirE|comp|shcy|SHcy|vArr|varr|cong|copf|Copf|copy|COPY|malt|male|macr|lvnE|cscr|ltri|sime|ltcc|simg|Cscr|siml|csub|Uuml|lsqb|lsim|uuml|csup|Lscr|lscr|utri|smid|lpar|cups|smte|lozf|darr|Lopf|Uscr|solb|lopf|sopf|Sopf|lneq|uscr|spar|dArr|lnap|Darr|dash|Sqrt|LJcy|ljcy|lHar|dHar|Upsi|upsi|diam|lesg|djcy|DJcy|leqq|dopf|Dopf|dscr|Dscr|dscy|ldsh|ldca|squf|DScy|sscr|Sscr|dsol|lcub|late|star|Star|Uopf|Larr|lArr|larr|uopf|dtri|dzcy|sube|subE|Lang|lang|Kscr|kscr|Kopf|kopf|KJcy|kjcy|KHcy|khcy|DZcy|ecir|edot|eDot|Jscr|jscr|succ|Jopf|jopf|Edot|uHar|emsp|ensp|Iuml|iuml|eopf|isin|Iscr|iscr|Eopf|epar|sung|epsi|escr|sup1|sup2|sup3|Iota|iota|supe|supE|Iopf|iopf|IOcy|iocy|Escr|esim|Esim|imof|Uarr|QUOT|uArr|uarr|euml|IEcy|iecy|Idot|Euml|euro|excl|Hscr|hscr|Hopf|hopf|TScy|tscy|Tscr|hbar|tscr|flat|tbrk|fnof|hArr|harr|half|fopf|Fopf|tdot|gvnE|fork|trie|gtcc|fscr|Fscr|gdot|gsim|Gscr|gscr|Gopf|gopf|gneq|Gdot|tosa|gnap|Topf|topf|geqq|toea|GJcy|gjcy|tint|gesl|mid|Sfr|ggg|top|ges|gla|glE|glj|geq|gne|gEl|gel|gnE|Gcy|gcy|gap|Tfr|tfr|Tcy|tcy|Hat|Tau|Ffr|tau|Tab|hfr|Hfr|ffr|Fcy|fcy|icy|Icy|iff|ETH|eth|ifr|Ifr|Eta|eta|int|Int|Sup|sup|ucy|Ucy|Sum|sum|jcy|ENG|ufr|Ufr|eng|Jcy|jfr|els|ell|egs|Efr|efr|Jfr|uml|kcy|Kcy|Ecy|ecy|kfr|Kfr|lap|Sub|sub|lat|lcy|Lcy|leg|Dot|dot|lEg|leq|les|squ|div|die|lfr|Lfr|lgE|Dfr|dfr|Del|deg|Dcy|dcy|lne|lnE|sol|loz|smt|Cup|lrm|cup|lsh|Lsh|sim|shy|map|Map|mcy|Mcy|mfr|Mfr|mho|gfr|Gfr|sfr|cir|Chi|chi|nap|Cfr|vcy|Vcy|cfr|Scy|scy|ncy|Ncy|vee|Vee|Cap|cap|nfr|scE|sce|Nfr|nge|ngE|nGg|vfr|Vfr|ngt|bot|nGt|nis|niv|Rsh|rsh|nle|nlE|bne|Bfr|bfr|nLl|nlt|nLt|Bcy|bcy|not|Not|rlm|wfr|Wfr|npr|nsc|num|ocy|ast|Ocy|ofr|xfr|Xfr|Ofr|ogt|ohm|apE|olt|Rho|ape|rho|Rfr|rfr|ord|REG|ang|reg|orv|And|and|AMP|Rcy|amp|Afr|ycy|Ycy|yen|yfr|Yfr|rcy|par|pcy|Pcy|pfr|Pfr|phi|Phi|afr|Acy|acy|zcy|Zcy|piv|acE|acd|zfr|Zfr|pre|prE|psi|Psi|qfr|Qfr|zwj|Or|ge|Gg|gt|gg|el|oS|lt|Lt|LT|Re|lg|gl|eg|ne|Im|it|le|DD|wp|wr|nu|Nu|dd|lE|Sc|sc|pi|Pi|ee|af|ll|Ll|rx|gE|xi|pm|Xi|ic|pr|Pr|in|ni|mp|mu|ac|Mu|or|ap|Gt|GT|ii);|&(Aacute|Agrave|Atilde|Ccedil|Eacute|Egrave|Iacute|Igrave|Ntilde|Oacute|Ograve|Oslash|Otilde|Uacute|Ugrave|Yacute|aacute|agrave|atilde|brvbar|ccedil|curren|divide|eacute|egrave|frac12|frac14|frac34|iacute|igrave|iquest|middot|ntilde|oacute|ograve|oslash|otilde|plusmn|uacute|ugrave|yacute|AElig|Acirc|Aring|Ecirc|Icirc|Ocirc|THORN|Ucirc|acirc|acute|aelig|aring|cedil|ecirc|icirc|iexcl|laquo|micro|ocirc|pound|raquo|szlig|thorn|times|ucirc|Auml|COPY|Euml|Iuml|Ouml|QUOT|Uuml|auml|cent|copy|euml|iuml|macr|nbsp|ordf|ordm|ouml|para|quot|sect|sup1|sup2|sup3|uuml|yuml|AMP|ETH|REG|amp|deg|eth|not|reg|shy|uml|yen|GT|LT|gt|lt)(?!;)([=a-zA-Z0-9]?)|&#([0-9]+)(;?)|&#[xX]([a-fA-F0-9]+)(;?)|&([0-9a-zA-Z]+)/g;
+ var decodeMap = {'aacute':'\xE1','Aacute':'\xC1','abreve':'\u0103','Abreve':'\u0102','ac':'\u223E','acd':'\u223F','acE':'\u223E\u0333','acirc':'\xE2','Acirc':'\xC2','acute':'\xB4','acy':'\u0430','Acy':'\u0410','aelig':'\xE6','AElig':'\xC6','af':'\u2061','afr':'\uD835\uDD1E','Afr':'\uD835\uDD04','agrave':'\xE0','Agrave':'\xC0','alefsym':'\u2135','aleph':'\u2135','alpha':'\u03B1','Alpha':'\u0391','amacr':'\u0101','Amacr':'\u0100','amalg':'\u2A3F','amp':'&','AMP':'&','and':'\u2227','And':'\u2A53','andand':'\u2A55','andd':'\u2A5C','andslope':'\u2A58','andv':'\u2A5A','ang':'\u2220','ange':'\u29A4','angle':'\u2220','angmsd':'\u2221','angmsdaa':'\u29A8','angmsdab':'\u29A9','angmsdac':'\u29AA','angmsdad':'\u29AB','angmsdae':'\u29AC','angmsdaf':'\u29AD','angmsdag':'\u29AE','angmsdah':'\u29AF','angrt':'\u221F','angrtvb':'\u22BE','angrtvbd':'\u299D','angsph':'\u2222','angst':'\xC5','angzarr':'\u237C','aogon':'\u0105','Aogon':'\u0104','aopf':'\uD835\uDD52','Aopf':'\uD835\uDD38','ap':'\u2248','apacir':'\u2A6F','ape':'\u224A','apE':'\u2A70','apid':'\u224B','apos':'\'','ApplyFunction':'\u2061','approx':'\u2248','approxeq':'\u224A','aring':'\xE5','Aring':'\xC5','ascr':'\uD835\uDCB6','Ascr':'\uD835\uDC9C','Assign':'\u2254','ast':'*','asymp':'\u2248','asympeq':'\u224D','atilde':'\xE3','Atilde':'\xC3','auml':'\xE4','Auml':'\xC4','awconint':'\u2233','awint':'\u2A11','backcong':'\u224C','backepsilon':'\u03F6','backprime':'\u2035','backsim':'\u223D','backsimeq':'\u22CD','Backslash':'\u2216','Barv':'\u2AE7','barvee':'\u22BD','barwed':'\u2305','Barwed':'\u2306','barwedge':'\u2305','bbrk':'\u23B5','bbrktbrk':'\u23B6','bcong':'\u224C','bcy':'\u0431','Bcy':'\u0411','bdquo':'\u201E','becaus':'\u2235','because':'\u2235','Because':'\u2235','bemptyv':'\u29B0','bepsi':'\u03F6','bernou':'\u212C','Bernoullis':'\u212C','beta':'\u03B2','Beta':'\u0392','beth':'\u2136','between':'\u226C','bfr':'\uD835\uDD1F','Bfr':'\uD835\uDD05','bigcap':'\u22C2','bigcirc':'\u25EF','bigcup':'\u22C3','bigodot':'\u2A00','bigoplus':'\u2A01','bigotimes':'\u2A02','bigsqcup':'\u2A06','bigstar':'\u2605','bigtriangledown':'\u25BD','bigtriangleup':'\u25B3','biguplus':'\u2A04','bigvee':'\u22C1','bigwedge':'\u22C0','bkarow':'\u290D','blacklozenge':'\u29EB','blacksquare':'\u25AA','blacktriangle':'\u25B4','blacktriangledown':'\u25BE','blacktriangleleft':'\u25C2','blacktriangleright':'\u25B8','blank':'\u2423','blk12':'\u2592','blk14':'\u2591','blk34':'\u2593','block':'\u2588','bne':'=\u20E5','bnequiv':'\u2261\u20E5','bnot':'\u2310','bNot':'\u2AED','bopf':'\uD835\uDD53','Bopf':'\uD835\uDD39','bot':'\u22A5','bottom':'\u22A5','bowtie':'\u22C8','boxbox':'\u29C9','boxdl':'\u2510','boxdL':'\u2555','boxDl':'\u2556','boxDL':'\u2557','boxdr':'\u250C','boxdR':'\u2552','boxDr':'\u2553','boxDR':'\u2554','boxh':'\u2500','boxH':'\u2550','boxhd':'\u252C','boxhD':'\u2565','boxHd':'\u2564','boxHD':'\u2566','boxhu':'\u2534','boxhU':'\u2568','boxHu':'\u2567','boxHU':'\u2569','boxminus':'\u229F','boxplus':'\u229E','boxtimes':'\u22A0','boxul':'\u2518','boxuL':'\u255B','boxUl':'\u255C','boxUL':'\u255D','boxur':'\u2514','boxuR':'\u2558','boxUr':'\u2559','boxUR':'\u255A','boxv':'\u2502','boxV':'\u2551','boxvh':'\u253C','boxvH':'\u256A','boxVh':'\u256B','boxVH':'\u256C','boxvl':'\u2524','boxvL':'\u2561','boxVl':'\u2562','boxVL':'\u2563','boxvr':'\u251C','boxvR':'\u255E','boxVr':'\u255F','boxVR':'\u2560','bprime':'\u2035','breve':'\u02D8','Breve':'\u02D8','brvbar':'\xA6','bscr':'\uD835\uDCB7','Bscr':'\u212C','bsemi':'\u204F','bsim':'\u223D','bsime':'\u22CD','bsol':'\\','bsolb':'\u29C5','bsolhsub':'\u27C8','bull':'\u2022','bullet':'\u2022','bump':'\u224E','bumpe':'\u224F','bumpE':'\u2AAE','bumpeq':'\u224F','Bumpeq':'\u224E','cacute':'\u0107','Cacute':'\u0106','cap':'\u2229','Cap':'\u22D2','capand':'\u2A44','capbrcup':'\u2A49','capcap':'\u2A4B','capcup':'\u2A47','capdot':'\u2A40','CapitalDifferentialD':'\u2145','caps':'\u2229\uFE00','caret':'\u2041','caron':'\u02C7','Cayleys':'\u212D','ccaps':'\u2A4D','ccaron':'\u010D','Ccaron':'\u010C','ccedil':'\xE7','Ccedil':'\xC7','ccirc':'\u0109','Ccirc':'\u0108','Cconint':'\u2230','ccups':'\u2A4C','ccupssm':'\u2A50','cdot':'\u010B','Cdot':'\u010A','cedil':'\xB8','Cedilla':'\xB8','cemptyv':'\u29B2','cent':'\xA2','centerdot':'\xB7','CenterDot':'\xB7','cfr':'\uD835\uDD20','Cfr':'\u212D','chcy':'\u0447','CHcy':'\u0427','check':'\u2713','checkmark':'\u2713','chi':'\u03C7','Chi':'\u03A7','cir':'\u25CB','circ':'\u02C6','circeq':'\u2257','circlearrowleft':'\u21BA','circlearrowright':'\u21BB','circledast':'\u229B','circledcirc':'\u229A','circleddash':'\u229D','CircleDot':'\u2299','circledR':'\xAE','circledS':'\u24C8','CircleMinus':'\u2296','CirclePlus':'\u2295','CircleTimes':'\u2297','cire':'\u2257','cirE':'\u29C3','cirfnint':'\u2A10','cirmid':'\u2AEF','cirscir':'\u29C2','ClockwiseContourIntegral':'\u2232','CloseCurlyDoubleQuote':'\u201D','CloseCurlyQuote':'\u2019','clubs':'\u2663','clubsuit':'\u2663','colon':':','Colon':'\u2237','colone':'\u2254','Colone':'\u2A74','coloneq':'\u2254','comma':',','commat':'@','comp':'\u2201','compfn':'\u2218','complement':'\u2201','complexes':'\u2102','cong':'\u2245','congdot':'\u2A6D','Congruent':'\u2261','conint':'\u222E','Conint':'\u222F','ContourIntegral':'\u222E','copf':'\uD835\uDD54','Copf':'\u2102','coprod':'\u2210','Coproduct':'\u2210','copy':'\xA9','COPY':'\xA9','copysr':'\u2117','CounterClockwiseContourIntegral':'\u2233','crarr':'\u21B5','cross':'\u2717','Cross':'\u2A2F','cscr':'\uD835\uDCB8','Cscr':'\uD835\uDC9E','csub':'\u2ACF','csube':'\u2AD1','csup':'\u2AD0','csupe':'\u2AD2','ctdot':'\u22EF','cudarrl':'\u2938','cudarrr':'\u2935','cuepr':'\u22DE','cuesc':'\u22DF','cularr':'\u21B6','cularrp':'\u293D','cup':'\u222A','Cup':'\u22D3','cupbrcap':'\u2A48','cupcap':'\u2A46','CupCap':'\u224D','cupcup':'\u2A4A','cupdot':'\u228D','cupor':'\u2A45','cups':'\u222A\uFE00','curarr':'\u21B7','curarrm':'\u293C','curlyeqprec':'\u22DE','curlyeqsucc':'\u22DF','curlyvee':'\u22CE','curlywedge':'\u22CF','curren':'\xA4','curvearrowleft':'\u21B6','curvearrowright':'\u21B7','cuvee':'\u22CE','cuwed':'\u22CF','cwconint':'\u2232','cwint':'\u2231','cylcty':'\u232D','dagger':'\u2020','Dagger':'\u2021','daleth':'\u2138','darr':'\u2193','dArr':'\u21D3','Darr':'\u21A1','dash':'\u2010','dashv':'\u22A3','Dashv':'\u2AE4','dbkarow':'\u290F','dblac':'\u02DD','dcaron':'\u010F','Dcaron':'\u010E','dcy':'\u0434','Dcy':'\u0414','dd':'\u2146','DD':'\u2145','ddagger':'\u2021','ddarr':'\u21CA','DDotrahd':'\u2911','ddotseq':'\u2A77','deg':'\xB0','Del':'\u2207','delta':'\u03B4','Delta':'\u0394','demptyv':'\u29B1','dfisht':'\u297F','dfr':'\uD835\uDD21','Dfr':'\uD835\uDD07','dHar':'\u2965','dharl':'\u21C3','dharr':'\u21C2','DiacriticalAcute':'\xB4','DiacriticalDot':'\u02D9','DiacriticalDoubleAcute':'\u02DD','DiacriticalGrave':'`','DiacriticalTilde':'\u02DC','diam':'\u22C4','diamond':'\u22C4','Diamond':'\u22C4','diamondsuit':'\u2666','diams':'\u2666','die':'\xA8','DifferentialD':'\u2146','digamma':'\u03DD','disin':'\u22F2','div':'\xF7','divide':'\xF7','divideontimes':'\u22C7','divonx':'\u22C7','djcy':'\u0452','DJcy':'\u0402','dlcorn':'\u231E','dlcrop':'\u230D','dollar':'$','dopf':'\uD835\uDD55','Dopf':'\uD835\uDD3B','dot':'\u02D9','Dot':'\xA8','DotDot':'\u20DC','doteq':'\u2250','doteqdot':'\u2251','DotEqual':'\u2250','dotminus':'\u2238','dotplus':'\u2214','dotsquare':'\u22A1','doublebarwedge':'\u2306','DoubleContourIntegral':'\u222F','DoubleDot':'\xA8','DoubleDownArrow':'\u21D3','DoubleLeftArrow':'\u21D0','DoubleLeftRightArrow':'\u21D4','DoubleLeftTee':'\u2AE4','DoubleLongLeftArrow':'\u27F8','DoubleLongLeftRightArrow':'\u27FA','DoubleLongRightArrow':'\u27F9','DoubleRightArrow':'\u21D2','DoubleRightTee':'\u22A8','DoubleUpArrow':'\u21D1','DoubleUpDownArrow':'\u21D5','DoubleVerticalBar':'\u2225','downarrow':'\u2193','Downarrow':'\u21D3','DownArrow':'\u2193','DownArrowBar':'\u2913','DownArrowUpArrow':'\u21F5','DownBreve':'\u0311','downdownarrows':'\u21CA','downharpoonleft':'\u21C3','downharpoonright':'\u21C2','DownLeftRightVector':'\u2950','DownLeftTeeVector':'\u295E','DownLeftVector':'\u21BD','DownLeftVectorBar':'\u2956','DownRightTeeVector':'\u295F','DownRightVector':'\u21C1','DownRightVectorBar':'\u2957','DownTee':'\u22A4','DownTeeArrow':'\u21A7','drbkarow':'\u2910','drcorn':'\u231F','drcrop':'\u230C','dscr':'\uD835\uDCB9','Dscr':'\uD835\uDC9F','dscy':'\u0455','DScy':'\u0405','dsol':'\u29F6','dstrok':'\u0111','Dstrok':'\u0110','dtdot':'\u22F1','dtri':'\u25BF','dtrif':'\u25BE','duarr':'\u21F5','duhar':'\u296F','dwangle':'\u29A6','dzcy':'\u045F','DZcy':'\u040F','dzigrarr':'\u27FF','eacute':'\xE9','Eacute':'\xC9','easter':'\u2A6E','ecaron':'\u011B','Ecaron':'\u011A','ecir':'\u2256','ecirc':'\xEA','Ecirc':'\xCA','ecolon':'\u2255','ecy':'\u044D','Ecy':'\u042D','eDDot':'\u2A77','edot':'\u0117','eDot':'\u2251','Edot':'\u0116','ee':'\u2147','efDot':'\u2252','efr':'\uD835\uDD22','Efr':'\uD835\uDD08','eg':'\u2A9A','egrave':'\xE8','Egrave':'\xC8','egs':'\u2A96','egsdot':'\u2A98','el':'\u2A99','Element':'\u2208','elinters':'\u23E7','ell':'\u2113','els':'\u2A95','elsdot':'\u2A97','emacr':'\u0113','Emacr':'\u0112','empty':'\u2205','emptyset':'\u2205','EmptySmallSquare':'\u25FB','emptyv':'\u2205','EmptyVerySmallSquare':'\u25AB','emsp':'\u2003','emsp13':'\u2004','emsp14':'\u2005','eng':'\u014B','ENG':'\u014A','ensp':'\u2002','eogon':'\u0119','Eogon':'\u0118','eopf':'\uD835\uDD56','Eopf':'\uD835\uDD3C','epar':'\u22D5','eparsl':'\u29E3','eplus':'\u2A71','epsi':'\u03B5','epsilon':'\u03B5','Epsilon':'\u0395','epsiv':'\u03F5','eqcirc':'\u2256','eqcolon':'\u2255','eqsim':'\u2242','eqslantgtr':'\u2A96','eqslantless':'\u2A95','Equal':'\u2A75','equals':'=','EqualTilde':'\u2242','equest':'\u225F','Equilibrium':'\u21CC','equiv':'\u2261','equivDD':'\u2A78','eqvparsl':'\u29E5','erarr':'\u2971','erDot':'\u2253','escr':'\u212F','Escr':'\u2130','esdot':'\u2250','esim':'\u2242','Esim':'\u2A73','eta':'\u03B7','Eta':'\u0397','eth':'\xF0','ETH':'\xD0','euml':'\xEB','Euml':'\xCB','euro':'\u20AC','excl':'!','exist':'\u2203','Exists':'\u2203','expectation':'\u2130','exponentiale':'\u2147','ExponentialE':'\u2147','fallingdotseq':'\u2252','fcy':'\u0444','Fcy':'\u0424','female':'\u2640','ffilig':'\uFB03','fflig':'\uFB00','ffllig':'\uFB04','ffr':'\uD835\uDD23','Ffr':'\uD835\uDD09','filig':'\uFB01','FilledSmallSquare':'\u25FC','FilledVerySmallSquare':'\u25AA','fjlig':'fj','flat':'\u266D','fllig':'\uFB02','fltns':'\u25B1','fnof':'\u0192','fopf':'\uD835\uDD57','Fopf':'\uD835\uDD3D','forall':'\u2200','ForAll':'\u2200','fork':'\u22D4','forkv':'\u2AD9','Fouriertrf':'\u2131','fpartint':'\u2A0D','frac12':'\xBD','frac13':'\u2153','frac14':'\xBC','frac15':'\u2155','frac16':'\u2159','frac18':'\u215B','frac23':'\u2154','frac25':'\u2156','frac34':'\xBE','frac35':'\u2157','frac38':'\u215C','frac45':'\u2158','frac56':'\u215A','frac58':'\u215D','frac78':'\u215E','frasl':'\u2044','frown':'\u2322','fscr':'\uD835\uDCBB','Fscr':'\u2131','gacute':'\u01F5','gamma':'\u03B3','Gamma':'\u0393','gammad':'\u03DD','Gammad':'\u03DC','gap':'\u2A86','gbreve':'\u011F','Gbreve':'\u011E','Gcedil':'\u0122','gcirc':'\u011D','Gcirc':'\u011C','gcy':'\u0433','Gcy':'\u0413','gdot':'\u0121','Gdot':'\u0120','ge':'\u2265','gE':'\u2267','gel':'\u22DB','gEl':'\u2A8C','geq':'\u2265','geqq':'\u2267','geqslant':'\u2A7E','ges':'\u2A7E','gescc':'\u2AA9','gesdot':'\u2A80','gesdoto':'\u2A82','gesdotol':'\u2A84','gesl':'\u22DB\uFE00','gesles':'\u2A94','gfr':'\uD835\uDD24','Gfr':'\uD835\uDD0A','gg':'\u226B','Gg':'\u22D9','ggg':'\u22D9','gimel':'\u2137','gjcy':'\u0453','GJcy':'\u0403','gl':'\u2277','gla':'\u2AA5','glE':'\u2A92','glj':'\u2AA4','gnap':'\u2A8A','gnapprox':'\u2A8A','gne':'\u2A88','gnE':'\u2269','gneq':'\u2A88','gneqq':'\u2269','gnsim':'\u22E7','gopf':'\uD835\uDD58','Gopf':'\uD835\uDD3E','grave':'`','GreaterEqual':'\u2265','GreaterEqualLess':'\u22DB','GreaterFullEqual':'\u2267','GreaterGreater':'\u2AA2','GreaterLess':'\u2277','GreaterSlantEqual':'\u2A7E','GreaterTilde':'\u2273','gscr':'\u210A','Gscr':'\uD835\uDCA2','gsim':'\u2273','gsime':'\u2A8E','gsiml':'\u2A90','gt':'>','Gt':'\u226B','GT':'>','gtcc':'\u2AA7','gtcir':'\u2A7A','gtdot':'\u22D7','gtlPar':'\u2995','gtquest':'\u2A7C','gtrapprox':'\u2A86','gtrarr':'\u2978','gtrdot':'\u22D7','gtreqless':'\u22DB','gtreqqless':'\u2A8C','gtrless':'\u2277','gtrsim':'\u2273','gvertneqq':'\u2269\uFE00','gvnE':'\u2269\uFE00','Hacek':'\u02C7','hairsp':'\u200A','half':'\xBD','hamilt':'\u210B','hardcy':'\u044A','HARDcy':'\u042A','harr':'\u2194','hArr':'\u21D4','harrcir':'\u2948','harrw':'\u21AD','Hat':'^','hbar':'\u210F','hcirc':'\u0125','Hcirc':'\u0124','hearts':'\u2665','heartsuit':'\u2665','hellip':'\u2026','hercon':'\u22B9','hfr':'\uD835\uDD25','Hfr':'\u210C','HilbertSpace':'\u210B','hksearow':'\u2925','hkswarow':'\u2926','hoarr':'\u21FF','homtht':'\u223B','hookleftarrow':'\u21A9','hookrightarrow':'\u21AA','hopf':'\uD835\uDD59','Hopf':'\u210D','horbar':'\u2015','HorizontalLine':'\u2500','hscr':'\uD835\uDCBD','Hscr':'\u210B','hslash':'\u210F','hstrok':'\u0127','Hstrok':'\u0126','HumpDownHump':'\u224E','HumpEqual':'\u224F','hybull':'\u2043','hyphen':'\u2010','iacute':'\xED','Iacute':'\xCD','ic':'\u2063','icirc':'\xEE','Icirc':'\xCE','icy':'\u0438','Icy':'\u0418','Idot':'\u0130','iecy':'\u0435','IEcy':'\u0415','iexcl':'\xA1','iff':'\u21D4','ifr':'\uD835\uDD26','Ifr':'\u2111','igrave':'\xEC','Igrave':'\xCC','ii':'\u2148','iiiint':'\u2A0C','iiint':'\u222D','iinfin':'\u29DC','iiota':'\u2129','ijlig':'\u0133','IJlig':'\u0132','Im':'\u2111','imacr':'\u012B','Imacr':'\u012A','image':'\u2111','ImaginaryI':'\u2148','imagline':'\u2110','imagpart':'\u2111','imath':'\u0131','imof':'\u22B7','imped':'\u01B5','Implies':'\u21D2','in':'\u2208','incare':'\u2105','infin':'\u221E','infintie':'\u29DD','inodot':'\u0131','int':'\u222B','Int':'\u222C','intcal':'\u22BA','integers':'\u2124','Integral':'\u222B','intercal':'\u22BA','Intersection':'\u22C2','intlarhk':'\u2A17','intprod':'\u2A3C','InvisibleComma':'\u2063','InvisibleTimes':'\u2062','iocy':'\u0451','IOcy':'\u0401','iogon':'\u012F','Iogon':'\u012E','iopf':'\uD835\uDD5A','Iopf':'\uD835\uDD40','iota':'\u03B9','Iota':'\u0399','iprod':'\u2A3C','iquest':'\xBF','iscr':'\uD835\uDCBE','Iscr':'\u2110','isin':'\u2208','isindot':'\u22F5','isinE':'\u22F9','isins':'\u22F4','isinsv':'\u22F3','isinv':'\u2208','it':'\u2062','itilde':'\u0129','Itilde':'\u0128','iukcy':'\u0456','Iukcy':'\u0406','iuml':'\xEF','Iuml':'\xCF','jcirc':'\u0135','Jcirc':'\u0134','jcy':'\u0439','Jcy':'\u0419','jfr':'\uD835\uDD27','Jfr':'\uD835\uDD0D','jmath':'\u0237','jopf':'\uD835\uDD5B','Jopf':'\uD835\uDD41','jscr':'\uD835\uDCBF','Jscr':'\uD835\uDCA5','jsercy':'\u0458','Jsercy':'\u0408','jukcy':'\u0454','Jukcy':'\u0404','kappa':'\u03BA','Kappa':'\u039A','kappav':'\u03F0','kcedil':'\u0137','Kcedil':'\u0136','kcy':'\u043A','Kcy':'\u041A','kfr':'\uD835\uDD28','Kfr':'\uD835\uDD0E','kgreen':'\u0138','khcy':'\u0445','KHcy':'\u0425','kjcy':'\u045C','KJcy':'\u040C','kopf':'\uD835\uDD5C','Kopf':'\uD835\uDD42','kscr':'\uD835\uDCC0','Kscr':'\uD835\uDCA6','lAarr':'\u21DA','lacute':'\u013A','Lacute':'\u0139','laemptyv':'\u29B4','lagran':'\u2112','lambda':'\u03BB','Lambda':'\u039B','lang':'\u27E8','Lang':'\u27EA','langd':'\u2991','langle':'\u27E8','lap':'\u2A85','Laplacetrf':'\u2112','laquo':'\xAB','larr':'\u2190','lArr':'\u21D0','Larr':'\u219E','larrb':'\u21E4','larrbfs':'\u291F','larrfs':'\u291D','larrhk':'\u21A9','larrlp':'\u21AB','larrpl':'\u2939','larrsim':'\u2973','larrtl':'\u21A2','lat':'\u2AAB','latail':'\u2919','lAtail':'\u291B','late':'\u2AAD','lates':'\u2AAD\uFE00','lbarr':'\u290C','lBarr':'\u290E','lbbrk':'\u2772','lbrace':'{','lbrack':'[','lbrke':'\u298B','lbrksld':'\u298F','lbrkslu':'\u298D','lcaron':'\u013E','Lcaron':'\u013D','lcedil':'\u013C','Lcedil':'\u013B','lceil':'\u2308','lcub':'{','lcy':'\u043B','Lcy':'\u041B','ldca':'\u2936','ldquo':'\u201C','ldquor':'\u201E','ldrdhar':'\u2967','ldrushar':'\u294B','ldsh':'\u21B2','le':'\u2264','lE':'\u2266','LeftAngleBracket':'\u27E8','leftarrow':'\u2190','Leftarrow':'\u21D0','LeftArrow':'\u2190','LeftArrowBar':'\u21E4','LeftArrowRightArrow':'\u21C6','leftarrowtail':'\u21A2','LeftCeiling':'\u2308','LeftDoubleBracket':'\u27E6','LeftDownTeeVector':'\u2961','LeftDownVector':'\u21C3','LeftDownVectorBar':'\u2959','LeftFloor':'\u230A','leftharpoondown':'\u21BD','leftharpoonup':'\u21BC','leftleftarrows':'\u21C7','leftrightarrow':'\u2194','Leftrightarrow':'\u21D4','LeftRightArrow':'\u2194','leftrightarrows':'\u21C6','leftrightharpoons':'\u21CB','leftrightsquigarrow':'\u21AD','LeftRightVector':'\u294E','LeftTee':'\u22A3','LeftTeeArrow':'\u21A4','LeftTeeVector':'\u295A','leftthreetimes':'\u22CB','LeftTriangle':'\u22B2','LeftTriangleBar':'\u29CF','LeftTriangleEqual':'\u22B4','LeftUpDownVector':'\u2951','LeftUpTeeVector':'\u2960','LeftUpVector':'\u21BF','LeftUpVectorBar':'\u2958','LeftVector':'\u21BC','LeftVectorBar':'\u2952','leg':'\u22DA','lEg':'\u2A8B','leq':'\u2264','leqq':'\u2266','leqslant':'\u2A7D','les':'\u2A7D','lescc':'\u2AA8','lesdot':'\u2A7F','lesdoto':'\u2A81','lesdotor':'\u2A83','lesg':'\u22DA\uFE00','lesges':'\u2A93','lessapprox':'\u2A85','lessdot':'\u22D6','lesseqgtr':'\u22DA','lesseqqgtr':'\u2A8B','LessEqualGreater':'\u22DA','LessFullEqual':'\u2266','LessGreater':'\u2276','lessgtr':'\u2276','LessLess':'\u2AA1','lesssim':'\u2272','LessSlantEqual':'\u2A7D','LessTilde':'\u2272','lfisht':'\u297C','lfloor':'\u230A','lfr':'\uD835\uDD29','Lfr':'\uD835\uDD0F','lg':'\u2276','lgE':'\u2A91','lHar':'\u2962','lhard':'\u21BD','lharu':'\u21BC','lharul':'\u296A','lhblk':'\u2584','ljcy':'\u0459','LJcy':'\u0409','ll':'\u226A','Ll':'\u22D8','llarr':'\u21C7','llcorner':'\u231E','Lleftarrow':'\u21DA','llhard':'\u296B','lltri':'\u25FA','lmidot':'\u0140','Lmidot':'\u013F','lmoust':'\u23B0','lmoustache':'\u23B0','lnap':'\u2A89','lnapprox':'\u2A89','lne':'\u2A87','lnE':'\u2268','lneq':'\u2A87','lneqq':'\u2268','lnsim':'\u22E6','loang':'\u27EC','loarr':'\u21FD','lobrk':'\u27E6','longleftarrow':'\u27F5','Longleftarrow':'\u27F8','LongLeftArrow':'\u27F5','longleftrightarrow':'\u27F7','Longleftrightarrow':'\u27FA','LongLeftRightArrow':'\u27F7','longmapsto':'\u27FC','longrightarrow':'\u27F6','Longrightarrow':'\u27F9','LongRightArrow':'\u27F6','looparrowleft':'\u21AB','looparrowright':'\u21AC','lopar':'\u2985','lopf':'\uD835\uDD5D','Lopf':'\uD835\uDD43','loplus':'\u2A2D','lotimes':'\u2A34','lowast':'\u2217','lowbar':'_','LowerLeftArrow':'\u2199','LowerRightArrow':'\u2198','loz':'\u25CA','lozenge':'\u25CA','lozf':'\u29EB','lpar':'(','lparlt':'\u2993','lrarr':'\u21C6','lrcorner':'\u231F','lrhar':'\u21CB','lrhard':'\u296D','lrm':'\u200E','lrtri':'\u22BF','lsaquo':'\u2039','lscr':'\uD835\uDCC1','Lscr':'\u2112','lsh':'\u21B0','Lsh':'\u21B0','lsim':'\u2272','lsime':'\u2A8D','lsimg':'\u2A8F','lsqb':'[','lsquo':'\u2018','lsquor':'\u201A','lstrok':'\u0142','Lstrok':'\u0141','lt':'<','Lt':'\u226A','LT':'<','ltcc':'\u2AA6','ltcir':'\u2A79','ltdot':'\u22D6','lthree':'\u22CB','ltimes':'\u22C9','ltlarr':'\u2976','ltquest':'\u2A7B','ltri':'\u25C3','ltrie':'\u22B4','ltrif':'\u25C2','ltrPar':'\u2996','lurdshar':'\u294A','luruhar':'\u2966','lvertneqq':'\u2268\uFE00','lvnE':'\u2268\uFE00','macr':'\xAF','male':'\u2642','malt':'\u2720','maltese':'\u2720','map':'\u21A6','Map':'\u2905','mapsto':'\u21A6','mapstodown':'\u21A7','mapstoleft':'\u21A4','mapstoup':'\u21A5','marker':'\u25AE','mcomma':'\u2A29','mcy':'\u043C','Mcy':'\u041C','mdash':'\u2014','mDDot':'\u223A','measuredangle':'\u2221','MediumSpace':'\u205F','Mellintrf':'\u2133','mfr':'\uD835\uDD2A','Mfr':'\uD835\uDD10','mho':'\u2127','micro':'\xB5','mid':'\u2223','midast':'*','midcir':'\u2AF0','middot':'\xB7','minus':'\u2212','minusb':'\u229F','minusd':'\u2238','minusdu':'\u2A2A','MinusPlus':'\u2213','mlcp':'\u2ADB','mldr':'\u2026','mnplus':'\u2213','models':'\u22A7','mopf':'\uD835\uDD5E','Mopf':'\uD835\uDD44','mp':'\u2213','mscr':'\uD835\uDCC2','Mscr':'\u2133','mstpos':'\u223E','mu':'\u03BC','Mu':'\u039C','multimap':'\u22B8','mumap':'\u22B8','nabla':'\u2207','nacute':'\u0144','Nacute':'\u0143','nang':'\u2220\u20D2','nap':'\u2249','napE':'\u2A70\u0338','napid':'\u224B\u0338','napos':'\u0149','napprox':'\u2249','natur':'\u266E','natural':'\u266E','naturals':'\u2115','nbsp':'\xA0','nbump':'\u224E\u0338','nbumpe':'\u224F\u0338','ncap':'\u2A43','ncaron':'\u0148','Ncaron':'\u0147','ncedil':'\u0146','Ncedil':'\u0145','ncong':'\u2247','ncongdot':'\u2A6D\u0338','ncup':'\u2A42','ncy':'\u043D','Ncy':'\u041D','ndash':'\u2013','ne':'\u2260','nearhk':'\u2924','nearr':'\u2197','neArr':'\u21D7','nearrow':'\u2197','nedot':'\u2250\u0338','NegativeMediumSpace':'\u200B','NegativeThickSpace':'\u200B','NegativeThinSpace':'\u200B','NegativeVeryThinSpace':'\u200B','nequiv':'\u2262','nesear':'\u2928','nesim':'\u2242\u0338','NestedGreaterGreater':'\u226B','NestedLessLess':'\u226A','NewLine':'\n','nexist':'\u2204','nexists':'\u2204','nfr':'\uD835\uDD2B','Nfr':'\uD835\uDD11','nge':'\u2271','ngE':'\u2267\u0338','ngeq':'\u2271','ngeqq':'\u2267\u0338','ngeqslant':'\u2A7E\u0338','nges':'\u2A7E\u0338','nGg':'\u22D9\u0338','ngsim':'\u2275','ngt':'\u226F','nGt':'\u226B\u20D2','ngtr':'\u226F','nGtv':'\u226B\u0338','nharr':'\u21AE','nhArr':'\u21CE','nhpar':'\u2AF2','ni':'\u220B','nis':'\u22FC','nisd':'\u22FA','niv':'\u220B','njcy':'\u045A','NJcy':'\u040A','nlarr':'\u219A','nlArr':'\u21CD','nldr':'\u2025','nle':'\u2270','nlE':'\u2266\u0338','nleftarrow':'\u219A','nLeftarrow':'\u21CD','nleftrightarrow':'\u21AE','nLeftrightarrow':'\u21CE','nleq':'\u2270','nleqq':'\u2266\u0338','nleqslant':'\u2A7D\u0338','nles':'\u2A7D\u0338','nless':'\u226E','nLl':'\u22D8\u0338','nlsim':'\u2274','nlt':'\u226E','nLt':'\u226A\u20D2','nltri':'\u22EA','nltrie':'\u22EC','nLtv':'\u226A\u0338','nmid':'\u2224','NoBreak':'\u2060','NonBreakingSpace':'\xA0','nopf':'\uD835\uDD5F','Nopf':'\u2115','not':'\xAC','Not':'\u2AEC','NotCongruent':'\u2262','NotCupCap':'\u226D','NotDoubleVerticalBar':'\u2226','NotElement':'\u2209','NotEqual':'\u2260','NotEqualTilde':'\u2242\u0338','NotExists':'\u2204','NotGreater':'\u226F','NotGreaterEqual':'\u2271','NotGreaterFullEqual':'\u2267\u0338','NotGreaterGreater':'\u226B\u0338','NotGreaterLess':'\u2279','NotGreaterSlantEqual':'\u2A7E\u0338','NotGreaterTilde':'\u2275','NotHumpDownHump':'\u224E\u0338','NotHumpEqual':'\u224F\u0338','notin':'\u2209','notindot':'\u22F5\u0338','notinE':'\u22F9\u0338','notinva':'\u2209','notinvb':'\u22F7','notinvc':'\u22F6','NotLeftTriangle':'\u22EA','NotLeftTriangleBar':'\u29CF\u0338','NotLeftTriangleEqual':'\u22EC','NotLess':'\u226E','NotLessEqual':'\u2270','NotLessGreater':'\u2278','NotLessLess':'\u226A\u0338','NotLessSlantEqual':'\u2A7D\u0338','NotLessTilde':'\u2274','NotNestedGreaterGreater':'\u2AA2\u0338','NotNestedLessLess':'\u2AA1\u0338','notni':'\u220C','notniva':'\u220C','notnivb':'\u22FE','notnivc':'\u22FD','NotPrecedes':'\u2280','NotPrecedesEqual':'\u2AAF\u0338','NotPrecedesSlantEqual':'\u22E0','NotReverseElement':'\u220C','NotRightTriangle':'\u22EB','NotRightTriangleBar':'\u29D0\u0338','NotRightTriangleEqual':'\u22ED','NotSquareSubset':'\u228F\u0338','NotSquareSubsetEqual':'\u22E2','NotSquareSuperset':'\u2290\u0338','NotSquareSupersetEqual':'\u22E3','NotSubset':'\u2282\u20D2','NotSubsetEqual':'\u2288','NotSucceeds':'\u2281','NotSucceedsEqual':'\u2AB0\u0338','NotSucceedsSlantEqual':'\u22E1','NotSucceedsTilde':'\u227F\u0338','NotSuperset':'\u2283\u20D2','NotSupersetEqual':'\u2289','NotTilde':'\u2241','NotTildeEqual':'\u2244','NotTildeFullEqual':'\u2247','NotTildeTilde':'\u2249','NotVerticalBar':'\u2224','npar':'\u2226','nparallel':'\u2226','nparsl':'\u2AFD\u20E5','npart':'\u2202\u0338','npolint':'\u2A14','npr':'\u2280','nprcue':'\u22E0','npre':'\u2AAF\u0338','nprec':'\u2280','npreceq':'\u2AAF\u0338','nrarr':'\u219B','nrArr':'\u21CF','nrarrc':'\u2933\u0338','nrarrw':'\u219D\u0338','nrightarrow':'\u219B','nRightarrow':'\u21CF','nrtri':'\u22EB','nrtrie':'\u22ED','nsc':'\u2281','nsccue':'\u22E1','nsce':'\u2AB0\u0338','nscr':'\uD835\uDCC3','Nscr':'\uD835\uDCA9','nshortmid':'\u2224','nshortparallel':'\u2226','nsim':'\u2241','nsime':'\u2244','nsimeq':'\u2244','nsmid':'\u2224','nspar':'\u2226','nsqsube':'\u22E2','nsqsupe':'\u22E3','nsub':'\u2284','nsube':'\u2288','nsubE':'\u2AC5\u0338','nsubset':'\u2282\u20D2','nsubseteq':'\u2288','nsubseteqq':'\u2AC5\u0338','nsucc':'\u2281','nsucceq':'\u2AB0\u0338','nsup':'\u2285','nsupe':'\u2289','nsupE':'\u2AC6\u0338','nsupset':'\u2283\u20D2','nsupseteq':'\u2289','nsupseteqq':'\u2AC6\u0338','ntgl':'\u2279','ntilde':'\xF1','Ntilde':'\xD1','ntlg':'\u2278','ntriangleleft':'\u22EA','ntrianglelefteq':'\u22EC','ntriangleright':'\u22EB','ntrianglerighteq':'\u22ED','nu':'\u03BD','Nu':'\u039D','num':'#','numero':'\u2116','numsp':'\u2007','nvap':'\u224D\u20D2','nvdash':'\u22AC','nvDash':'\u22AD','nVdash':'\u22AE','nVDash':'\u22AF','nvge':'\u2265\u20D2','nvgt':'>\u20D2','nvHarr':'\u2904','nvinfin':'\u29DE','nvlArr':'\u2902','nvle':'\u2264\u20D2','nvlt':'<\u20D2','nvltrie':'\u22B4\u20D2','nvrArr':'\u2903','nvrtrie':'\u22B5\u20D2','nvsim':'\u223C\u20D2','nwarhk':'\u2923','nwarr':'\u2196','nwArr':'\u21D6','nwarrow':'\u2196','nwnear':'\u2927','oacute':'\xF3','Oacute':'\xD3','oast':'\u229B','ocir':'\u229A','ocirc':'\xF4','Ocirc':'\xD4','ocy':'\u043E','Ocy':'\u041E','odash':'\u229D','odblac':'\u0151','Odblac':'\u0150','odiv':'\u2A38','odot':'\u2299','odsold':'\u29BC','oelig':'\u0153','OElig':'\u0152','ofcir':'\u29BF','ofr':'\uD835\uDD2C','Ofr':'\uD835\uDD12','ogon':'\u02DB','ograve':'\xF2','Ograve':'\xD2','ogt':'\u29C1','ohbar':'\u29B5','ohm':'\u03A9','oint':'\u222E','olarr':'\u21BA','olcir':'\u29BE','olcross':'\u29BB','oline':'\u203E','olt':'\u29C0','omacr':'\u014D','Omacr':'\u014C','omega':'\u03C9','Omega':'\u03A9','omicron':'\u03BF','Omicron':'\u039F','omid':'\u29B6','ominus':'\u2296','oopf':'\uD835\uDD60','Oopf':'\uD835\uDD46','opar':'\u29B7','OpenCurlyDoubleQuote':'\u201C','OpenCurlyQuote':'\u2018','operp':'\u29B9','oplus':'\u2295','or':'\u2228','Or':'\u2A54','orarr':'\u21BB','ord':'\u2A5D','order':'\u2134','orderof':'\u2134','ordf':'\xAA','ordm':'\xBA','origof':'\u22B6','oror':'\u2A56','orslope':'\u2A57','orv':'\u2A5B','oS':'\u24C8','oscr':'\u2134','Oscr':'\uD835\uDCAA','oslash':'\xF8','Oslash':'\xD8','osol':'\u2298','otilde':'\xF5','Otilde':'\xD5','otimes':'\u2297','Otimes':'\u2A37','otimesas':'\u2A36','ouml':'\xF6','Ouml':'\xD6','ovbar':'\u233D','OverBar':'\u203E','OverBrace':'\u23DE','OverBracket':'\u23B4','OverParenthesis':'\u23DC','par':'\u2225','para':'\xB6','parallel':'\u2225','parsim':'\u2AF3','parsl':'\u2AFD','part':'\u2202','PartialD':'\u2202','pcy':'\u043F','Pcy':'\u041F','percnt':'%','period':'.','permil':'\u2030','perp':'\u22A5','pertenk':'\u2031','pfr':'\uD835\uDD2D','Pfr':'\uD835\uDD13','phi':'\u03C6','Phi':'\u03A6','phiv':'\u03D5','phmmat':'\u2133','phone':'\u260E','pi':'\u03C0','Pi':'\u03A0','pitchfork':'\u22D4','piv':'\u03D6','planck':'\u210F','planckh':'\u210E','plankv':'\u210F','plus':'+','plusacir':'\u2A23','plusb':'\u229E','pluscir':'\u2A22','plusdo':'\u2214','plusdu':'\u2A25','pluse':'\u2A72','PlusMinus':'\xB1','plusmn':'\xB1','plussim':'\u2A26','plustwo':'\u2A27','pm':'\xB1','Poincareplane':'\u210C','pointint':'\u2A15','popf':'\uD835\uDD61','Popf':'\u2119','pound':'\xA3','pr':'\u227A','Pr':'\u2ABB','prap':'\u2AB7','prcue':'\u227C','pre':'\u2AAF','prE':'\u2AB3','prec':'\u227A','precapprox':'\u2AB7','preccurlyeq':'\u227C','Precedes':'\u227A','PrecedesEqual':'\u2AAF','PrecedesSlantEqual':'\u227C','PrecedesTilde':'\u227E','preceq':'\u2AAF','precnapprox':'\u2AB9','precneqq':'\u2AB5','precnsim':'\u22E8','precsim':'\u227E','prime':'\u2032','Prime':'\u2033','primes':'\u2119','prnap':'\u2AB9','prnE':'\u2AB5','prnsim':'\u22E8','prod':'\u220F','Product':'\u220F','profalar':'\u232E','profline':'\u2312','profsurf':'\u2313','prop':'\u221D','Proportion':'\u2237','Proportional':'\u221D','propto':'\u221D','prsim':'\u227E','prurel':'\u22B0','pscr':'\uD835\uDCC5','Pscr':'\uD835\uDCAB','psi':'\u03C8','Psi':'\u03A8','puncsp':'\u2008','qfr':'\uD835\uDD2E','Qfr':'\uD835\uDD14','qint':'\u2A0C','qopf':'\uD835\uDD62','Qopf':'\u211A','qprime':'\u2057','qscr':'\uD835\uDCC6','Qscr':'\uD835\uDCAC','quaternions':'\u210D','quatint':'\u2A16','quest':'?','questeq':'\u225F','quot':'"','QUOT':'"','rAarr':'\u21DB','race':'\u223D\u0331','racute':'\u0155','Racute':'\u0154','radic':'\u221A','raemptyv':'\u29B3','rang':'\u27E9','Rang':'\u27EB','rangd':'\u2992','range':'\u29A5','rangle':'\u27E9','raquo':'\xBB','rarr':'\u2192','rArr':'\u21D2','Rarr':'\u21A0','rarrap':'\u2975','rarrb':'\u21E5','rarrbfs':'\u2920','rarrc':'\u2933','rarrfs':'\u291E','rarrhk':'\u21AA','rarrlp':'\u21AC','rarrpl':'\u2945','rarrsim':'\u2974','rarrtl':'\u21A3','Rarrtl':'\u2916','rarrw':'\u219D','ratail':'\u291A','rAtail':'\u291C','ratio':'\u2236','rationals':'\u211A','rbarr':'\u290D','rBarr':'\u290F','RBarr':'\u2910','rbbrk':'\u2773','rbrace':'}','rbrack':']','rbrke':'\u298C','rbrksld':'\u298E','rbrkslu':'\u2990','rcaron':'\u0159','Rcaron':'\u0158','rcedil':'\u0157','Rcedil':'\u0156','rceil':'\u2309','rcub':'}','rcy':'\u0440','Rcy':'\u0420','rdca':'\u2937','rdldhar':'\u2969','rdquo':'\u201D','rdquor':'\u201D','rdsh':'\u21B3','Re':'\u211C','real':'\u211C','realine':'\u211B','realpart':'\u211C','reals':'\u211D','rect':'\u25AD','reg':'\xAE','REG':'\xAE','ReverseElement':'\u220B','ReverseEquilibrium':'\u21CB','ReverseUpEquilibrium':'\u296F','rfisht':'\u297D','rfloor':'\u230B','rfr':'\uD835\uDD2F','Rfr':'\u211C','rHar':'\u2964','rhard':'\u21C1','rharu':'\u21C0','rharul':'\u296C','rho':'\u03C1','Rho':'\u03A1','rhov':'\u03F1','RightAngleBracket':'\u27E9','rightarrow':'\u2192','Rightarrow':'\u21D2','RightArrow':'\u2192','RightArrowBar':'\u21E5','RightArrowLeftArrow':'\u21C4','rightarrowtail':'\u21A3','RightCeiling':'\u2309','RightDoubleBracket':'\u27E7','RightDownTeeVector':'\u295D','RightDownVector':'\u21C2','RightDownVectorBar':'\u2955','RightFloor':'\u230B','rightharpoondown':'\u21C1','rightharpoonup':'\u21C0','rightleftarrows':'\u21C4','rightleftharpoons':'\u21CC','rightrightarrows':'\u21C9','rightsquigarrow':'\u219D','RightTee':'\u22A2','RightTeeArrow':'\u21A6','RightTeeVector':'\u295B','rightthreetimes':'\u22CC','RightTriangle':'\u22B3','RightTriangleBar':'\u29D0','RightTriangleEqual':'\u22B5','RightUpDownVector':'\u294F','RightUpTeeVector':'\u295C','RightUpVector':'\u21BE','RightUpVectorBar':'\u2954','RightVector':'\u21C0','RightVectorBar':'\u2953','ring':'\u02DA','risingdotseq':'\u2253','rlarr':'\u21C4','rlhar':'\u21CC','rlm':'\u200F','rmoust':'\u23B1','rmoustache':'\u23B1','rnmid':'\u2AEE','roang':'\u27ED','roarr':'\u21FE','robrk':'\u27E7','ropar':'\u2986','ropf':'\uD835\uDD63','Ropf':'\u211D','roplus':'\u2A2E','rotimes':'\u2A35','RoundImplies':'\u2970','rpar':')','rpargt':'\u2994','rppolint':'\u2A12','rrarr':'\u21C9','Rrightarrow':'\u21DB','rsaquo':'\u203A','rscr':'\uD835\uDCC7','Rscr':'\u211B','rsh':'\u21B1','Rsh':'\u21B1','rsqb':']','rsquo':'\u2019','rsquor':'\u2019','rthree':'\u22CC','rtimes':'\u22CA','rtri':'\u25B9','rtrie':'\u22B5','rtrif':'\u25B8','rtriltri':'\u29CE','RuleDelayed':'\u29F4','ruluhar':'\u2968','rx':'\u211E','sacute':'\u015B','Sacute':'\u015A','sbquo':'\u201A','sc':'\u227B','Sc':'\u2ABC','scap':'\u2AB8','scaron':'\u0161','Scaron':'\u0160','sccue':'\u227D','sce':'\u2AB0','scE':'\u2AB4','scedil':'\u015F','Scedil':'\u015E','scirc':'\u015D','Scirc':'\u015C','scnap':'\u2ABA','scnE':'\u2AB6','scnsim':'\u22E9','scpolint':'\u2A13','scsim':'\u227F','scy':'\u0441','Scy':'\u0421','sdot':'\u22C5','sdotb':'\u22A1','sdote':'\u2A66','searhk':'\u2925','searr':'\u2198','seArr':'\u21D8','searrow':'\u2198','sect':'\xA7','semi':';','seswar':'\u2929','setminus':'\u2216','setmn':'\u2216','sext':'\u2736','sfr':'\uD835\uDD30','Sfr':'\uD835\uDD16','sfrown':'\u2322','sharp':'\u266F','shchcy':'\u0449','SHCHcy':'\u0429','shcy':'\u0448','SHcy':'\u0428','ShortDownArrow':'\u2193','ShortLeftArrow':'\u2190','shortmid':'\u2223','shortparallel':'\u2225','ShortRightArrow':'\u2192','ShortUpArrow':'\u2191','shy':'\xAD','sigma':'\u03C3','Sigma':'\u03A3','sigmaf':'\u03C2','sigmav':'\u03C2','sim':'\u223C','simdot':'\u2A6A','sime':'\u2243','simeq':'\u2243','simg':'\u2A9E','simgE':'\u2AA0','siml':'\u2A9D','simlE':'\u2A9F','simne':'\u2246','simplus':'\u2A24','simrarr':'\u2972','slarr':'\u2190','SmallCircle':'\u2218','smallsetminus':'\u2216','smashp':'\u2A33','smeparsl':'\u29E4','smid':'\u2223','smile':'\u2323','smt':'\u2AAA','smte':'\u2AAC','smtes':'\u2AAC\uFE00','softcy':'\u044C','SOFTcy':'\u042C','sol':'/','solb':'\u29C4','solbar':'\u233F','sopf':'\uD835\uDD64','Sopf':'\uD835\uDD4A','spades':'\u2660','spadesuit':'\u2660','spar':'\u2225','sqcap':'\u2293','sqcaps':'\u2293\uFE00','sqcup':'\u2294','sqcups':'\u2294\uFE00','Sqrt':'\u221A','sqsub':'\u228F','sqsube':'\u2291','sqsubset':'\u228F','sqsubseteq':'\u2291','sqsup':'\u2290','sqsupe':'\u2292','sqsupset':'\u2290','sqsupseteq':'\u2292','squ':'\u25A1','square':'\u25A1','Square':'\u25A1','SquareIntersection':'\u2293','SquareSubset':'\u228F','SquareSubsetEqual':'\u2291','SquareSuperset':'\u2290','SquareSupersetEqual':'\u2292','SquareUnion':'\u2294','squarf':'\u25AA','squf':'\u25AA','srarr':'\u2192','sscr':'\uD835\uDCC8','Sscr':'\uD835\uDCAE','ssetmn':'\u2216','ssmile':'\u2323','sstarf':'\u22C6','star':'\u2606','Star':'\u22C6','starf':'\u2605','straightepsilon':'\u03F5','straightphi':'\u03D5','strns':'\xAF','sub':'\u2282','Sub':'\u22D0','subdot':'\u2ABD','sube':'\u2286','subE':'\u2AC5','subedot':'\u2AC3','submult':'\u2AC1','subne':'\u228A','subnE':'\u2ACB','subplus':'\u2ABF','subrarr':'\u2979','subset':'\u2282','Subset':'\u22D0','subseteq':'\u2286','subseteqq':'\u2AC5','SubsetEqual':'\u2286','subsetneq':'\u228A','subsetneqq':'\u2ACB','subsim':'\u2AC7','subsub':'\u2AD5','subsup':'\u2AD3','succ':'\u227B','succapprox':'\u2AB8','succcurlyeq':'\u227D','Succeeds':'\u227B','SucceedsEqual':'\u2AB0','SucceedsSlantEqual':'\u227D','SucceedsTilde':'\u227F','succeq':'\u2AB0','succnapprox':'\u2ABA','succneqq':'\u2AB6','succnsim':'\u22E9','succsim':'\u227F','SuchThat':'\u220B','sum':'\u2211','Sum':'\u2211','sung':'\u266A','sup':'\u2283','Sup':'\u22D1','sup1':'\xB9','sup2':'\xB2','sup3':'\xB3','supdot':'\u2ABE','supdsub':'\u2AD8','supe':'\u2287','supE':'\u2AC6','supedot':'\u2AC4','Superset':'\u2283','SupersetEqual':'\u2287','suphsol':'\u27C9','suphsub':'\u2AD7','suplarr':'\u297B','supmult':'\u2AC2','supne':'\u228B','supnE':'\u2ACC','supplus':'\u2AC0','supset':'\u2283','Supset':'\u22D1','supseteq':'\u2287','supseteqq':'\u2AC6','supsetneq':'\u228B','supsetneqq':'\u2ACC','supsim':'\u2AC8','supsub':'\u2AD4','supsup':'\u2AD6','swarhk':'\u2926','swarr':'\u2199','swArr':'\u21D9','swarrow':'\u2199','swnwar':'\u292A','szlig':'\xDF','Tab':'\t','target':'\u2316','tau':'\u03C4','Tau':'\u03A4','tbrk':'\u23B4','tcaron':'\u0165','Tcaron':'\u0164','tcedil':'\u0163','Tcedil':'\u0162','tcy':'\u0442','Tcy':'\u0422','tdot':'\u20DB','telrec':'\u2315','tfr':'\uD835\uDD31','Tfr':'\uD835\uDD17','there4':'\u2234','therefore':'\u2234','Therefore':'\u2234','theta':'\u03B8','Theta':'\u0398','thetasym':'\u03D1','thetav':'\u03D1','thickapprox':'\u2248','thicksim':'\u223C','ThickSpace':'\u205F\u200A','thinsp':'\u2009','ThinSpace':'\u2009','thkap':'\u2248','thksim':'\u223C','thorn':'\xFE','THORN':'\xDE','tilde':'\u02DC','Tilde':'\u223C','TildeEqual':'\u2243','TildeFullEqual':'\u2245','TildeTilde':'\u2248','times':'\xD7','timesb':'\u22A0','timesbar':'\u2A31','timesd':'\u2A30','tint':'\u222D','toea':'\u2928','top':'\u22A4','topbot':'\u2336','topcir':'\u2AF1','topf':'\uD835\uDD65','Topf':'\uD835\uDD4B','topfork':'\u2ADA','tosa':'\u2929','tprime':'\u2034','trade':'\u2122','TRADE':'\u2122','triangle':'\u25B5','triangledown':'\u25BF','triangleleft':'\u25C3','trianglelefteq':'\u22B4','triangleq':'\u225C','triangleright':'\u25B9','trianglerighteq':'\u22B5','tridot':'\u25EC','trie':'\u225C','triminus':'\u2A3A','TripleDot':'\u20DB','triplus':'\u2A39','trisb':'\u29CD','tritime':'\u2A3B','trpezium':'\u23E2','tscr':'\uD835\uDCC9','Tscr':'\uD835\uDCAF','tscy':'\u0446','TScy':'\u0426','tshcy':'\u045B','TSHcy':'\u040B','tstrok':'\u0167','Tstrok':'\u0166','twixt':'\u226C','twoheadleftarrow':'\u219E','twoheadrightarrow':'\u21A0','uacute':'\xFA','Uacute':'\xDA','uarr':'\u2191','uArr':'\u21D1','Uarr':'\u219F','Uarrocir':'\u2949','ubrcy':'\u045E','Ubrcy':'\u040E','ubreve':'\u016D','Ubreve':'\u016C','ucirc':'\xFB','Ucirc':'\xDB','ucy':'\u0443','Ucy':'\u0423','udarr':'\u21C5','udblac':'\u0171','Udblac':'\u0170','udhar':'\u296E','ufisht':'\u297E','ufr':'\uD835\uDD32','Ufr':'\uD835\uDD18','ugrave':'\xF9','Ugrave':'\xD9','uHar':'\u2963','uharl':'\u21BF','uharr':'\u21BE','uhblk':'\u2580','ulcorn':'\u231C','ulcorner':'\u231C','ulcrop':'\u230F','ultri':'\u25F8','umacr':'\u016B','Umacr':'\u016A','uml':'\xA8','UnderBar':'_','UnderBrace':'\u23DF','UnderBracket':'\u23B5','UnderParenthesis':'\u23DD','Union':'\u22C3','UnionPlus':'\u228E','uogon':'\u0173','Uogon':'\u0172','uopf':'\uD835\uDD66','Uopf':'\uD835\uDD4C','uparrow':'\u2191','Uparrow':'\u21D1','UpArrow':'\u2191','UpArrowBar':'\u2912','UpArrowDownArrow':'\u21C5','updownarrow':'\u2195','Updownarrow':'\u21D5','UpDownArrow':'\u2195','UpEquilibrium':'\u296E','upharpoonleft':'\u21BF','upharpoonright':'\u21BE','uplus':'\u228E','UpperLeftArrow':'\u2196','UpperRightArrow':'\u2197','upsi':'\u03C5','Upsi':'\u03D2','upsih':'\u03D2','upsilon':'\u03C5','Upsilon':'\u03A5','UpTee':'\u22A5','UpTeeArrow':'\u21A5','upuparrows':'\u21C8','urcorn':'\u231D','urcorner':'\u231D','urcrop':'\u230E','uring':'\u016F','Uring':'\u016E','urtri':'\u25F9','uscr':'\uD835\uDCCA','Uscr':'\uD835\uDCB0','utdot':'\u22F0','utilde':'\u0169','Utilde':'\u0168','utri':'\u25B5','utrif':'\u25B4','uuarr':'\u21C8','uuml':'\xFC','Uuml':'\xDC','uwangle':'\u29A7','vangrt':'\u299C','varepsilon':'\u03F5','varkappa':'\u03F0','varnothing':'\u2205','varphi':'\u03D5','varpi':'\u03D6','varpropto':'\u221D','varr':'\u2195','vArr':'\u21D5','varrho':'\u03F1','varsigma':'\u03C2','varsubsetneq':'\u228A\uFE00','varsubsetneqq':'\u2ACB\uFE00','varsupsetneq':'\u228B\uFE00','varsupsetneqq':'\u2ACC\uFE00','vartheta':'\u03D1','vartriangleleft':'\u22B2','vartriangleright':'\u22B3','vBar':'\u2AE8','Vbar':'\u2AEB','vBarv':'\u2AE9','vcy':'\u0432','Vcy':'\u0412','vdash':'\u22A2','vDash':'\u22A8','Vdash':'\u22A9','VDash':'\u22AB','Vdashl':'\u2AE6','vee':'\u2228','Vee':'\u22C1','veebar':'\u22BB','veeeq':'\u225A','vellip':'\u22EE','verbar':'|','Verbar':'\u2016','vert':'|','Vert':'\u2016','VerticalBar':'\u2223','VerticalLine':'|','VerticalSeparator':'\u2758','VerticalTilde':'\u2240','VeryThinSpace':'\u200A','vfr':'\uD835\uDD33','Vfr':'\uD835\uDD19','vltri':'\u22B2','vnsub':'\u2282\u20D2','vnsup':'\u2283\u20D2','vopf':'\uD835\uDD67','Vopf':'\uD835\uDD4D','vprop':'\u221D','vrtri':'\u22B3','vscr':'\uD835\uDCCB','Vscr':'\uD835\uDCB1','vsubne':'\u228A\uFE00','vsubnE':'\u2ACB\uFE00','vsupne':'\u228B\uFE00','vsupnE':'\u2ACC\uFE00','Vvdash':'\u22AA','vzigzag':'\u299A','wcirc':'\u0175','Wcirc':'\u0174','wedbar':'\u2A5F','wedge':'\u2227','Wedge':'\u22C0','wedgeq':'\u2259','weierp':'\u2118','wfr':'\uD835\uDD34','Wfr':'\uD835\uDD1A','wopf':'\uD835\uDD68','Wopf':'\uD835\uDD4E','wp':'\u2118','wr':'\u2240','wreath':'\u2240','wscr':'\uD835\uDCCC','Wscr':'\uD835\uDCB2','xcap':'\u22C2','xcirc':'\u25EF','xcup':'\u22C3','xdtri':'\u25BD','xfr':'\uD835\uDD35','Xfr':'\uD835\uDD1B','xharr':'\u27F7','xhArr':'\u27FA','xi':'\u03BE','Xi':'\u039E','xlarr':'\u27F5','xlArr':'\u27F8','xmap':'\u27FC','xnis':'\u22FB','xodot':'\u2A00','xopf':'\uD835\uDD69','Xopf':'\uD835\uDD4F','xoplus':'\u2A01','xotime':'\u2A02','xrarr':'\u27F6','xrArr':'\u27F9','xscr':'\uD835\uDCCD','Xscr':'\uD835\uDCB3','xsqcup':'\u2A06','xuplus':'\u2A04','xutri':'\u25B3','xvee':'\u22C1','xwedge':'\u22C0','yacute':'\xFD','Yacute':'\xDD','yacy':'\u044F','YAcy':'\u042F','ycirc':'\u0177','Ycirc':'\u0176','ycy':'\u044B','Ycy':'\u042B','yen':'\xA5','yfr':'\uD835\uDD36','Yfr':'\uD835\uDD1C','yicy':'\u0457','YIcy':'\u0407','yopf':'\uD835\uDD6A','Yopf':'\uD835\uDD50','yscr':'\uD835\uDCCE','Yscr':'\uD835\uDCB4','yucy':'\u044E','YUcy':'\u042E','yuml':'\xFF','Yuml':'\u0178','zacute':'\u017A','Zacute':'\u0179','zcaron':'\u017E','Zcaron':'\u017D','zcy':'\u0437','Zcy':'\u0417','zdot':'\u017C','Zdot':'\u017B','zeetrf':'\u2128','ZeroWidthSpace':'\u200B','zeta':'\u03B6','Zeta':'\u0396','zfr':'\uD835\uDD37','Zfr':'\u2128','zhcy':'\u0436','ZHcy':'\u0416','zigrarr':'\u21DD','zopf':'\uD835\uDD6B','Zopf':'\u2124','zscr':'\uD835\uDCCF','Zscr':'\uD835\uDCB5','zwj':'\u200D','zwnj':'\u200C'};
+ var decodeMapLegacy = {'aacute':'\xE1','Aacute':'\xC1','acirc':'\xE2','Acirc':'\xC2','acute':'\xB4','aelig':'\xE6','AElig':'\xC6','agrave':'\xE0','Agrave':'\xC0','amp':'&','AMP':'&','aring':'\xE5','Aring':'\xC5','atilde':'\xE3','Atilde':'\xC3','auml':'\xE4','Auml':'\xC4','brvbar':'\xA6','ccedil':'\xE7','Ccedil':'\xC7','cedil':'\xB8','cent':'\xA2','copy':'\xA9','COPY':'\xA9','curren':'\xA4','deg':'\xB0','divide':'\xF7','eacute':'\xE9','Eacute':'\xC9','ecirc':'\xEA','Ecirc':'\xCA','egrave':'\xE8','Egrave':'\xC8','eth':'\xF0','ETH':'\xD0','euml':'\xEB','Euml':'\xCB','frac12':'\xBD','frac14':'\xBC','frac34':'\xBE','gt':'>','GT':'>','iacute':'\xED','Iacute':'\xCD','icirc':'\xEE','Icirc':'\xCE','iexcl':'\xA1','igrave':'\xEC','Igrave':'\xCC','iquest':'\xBF','iuml':'\xEF','Iuml':'\xCF','laquo':'\xAB','lt':'<','LT':'<','macr':'\xAF','micro':'\xB5','middot':'\xB7','nbsp':'\xA0','not':'\xAC','ntilde':'\xF1','Ntilde':'\xD1','oacute':'\xF3','Oacute':'\xD3','ocirc':'\xF4','Ocirc':'\xD4','ograve':'\xF2','Ograve':'\xD2','ordf':'\xAA','ordm':'\xBA','oslash':'\xF8','Oslash':'\xD8','otilde':'\xF5','Otilde':'\xD5','ouml':'\xF6','Ouml':'\xD6','para':'\xB6','plusmn':'\xB1','pound':'\xA3','quot':'"','QUOT':'"','raquo':'\xBB','reg':'\xAE','REG':'\xAE','sect':'\xA7','shy':'\xAD','sup1':'\xB9','sup2':'\xB2','sup3':'\xB3','szlig':'\xDF','thorn':'\xFE','THORN':'\xDE','times':'\xD7','uacute':'\xFA','Uacute':'\xDA','ucirc':'\xFB','Ucirc':'\xDB','ugrave':'\xF9','Ugrave':'\xD9','uml':'\xA8','uuml':'\xFC','Uuml':'\xDC','yacute':'\xFD','Yacute':'\xDD','yen':'\xA5','yuml':'\xFF'};
+ var decodeMapNumeric = {'0':'\uFFFD','128':'\u20AC','130':'\u201A','131':'\u0192','132':'\u201E','133':'\u2026','134':'\u2020','135':'\u2021','136':'\u02C6','137':'\u2030','138':'\u0160','139':'\u2039','140':'\u0152','142':'\u017D','145':'\u2018','146':'\u2019','147':'\u201C','148':'\u201D','149':'\u2022','150':'\u2013','151':'\u2014','152':'\u02DC','153':'\u2122','154':'\u0161','155':'\u203A','156':'\u0153','158':'\u017E','159':'\u0178'};
+ var invalidReferenceCodePoints = [1,2,3,4,5,6,7,8,11,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,64976,64977,64978,64979,64980,64981,64982,64983,64984,64985,64986,64987,64988,64989,64990,64991,64992,64993,64994,64995,64996,64997,64998,64999,65000,65001,65002,65003,65004,65005,65006,65007,65534,65535,131070,131071,196606,196607,262142,262143,327678,327679,393214,393215,458750,458751,524286,524287,589822,589823,655358,655359,720894,720895,786430,786431,851966,851967,917502,917503,983038,983039,1048574,1048575,1114110,1114111];
+
+ /*--------------------------------------------------------------------------*/
+
+ var stringFromCharCode = String.fromCharCode;
+
+ var object = {};
+ var hasOwnProperty = object.hasOwnProperty;
+ var has = function(object, propertyName) {
+ return hasOwnProperty.call(object, propertyName);
+ };
+
+ var contains = function(array, value) {
+ var index = -1;
+ var length = array.length;
+ while (++index < length) {
+ if (array[index] == value) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ var merge = function(options, defaults) {
+ if (!options) {
+ return defaults;
+ }
+ var result = {};
+ var key;
+ for (key in defaults) {
+ // A `hasOwnProperty` check is not needed here, since only recognized
+ // option names are used anyway. Any others are ignored.
+ result[key] = has(options, key) ? options[key] : defaults[key];
+ }
+ return result;
+ };
+
+ // Modified version of `ucs2encode`; see https://mths.be/punycode.
+ var codePointToSymbol = function(codePoint, strict) {
+ var output = '';
+ if ((codePoint >= 0xD800 && codePoint <= 0xDFFF) || codePoint > 0x10FFFF) {
+ // See issue #4:
+ // “Otherwise, if the number is in the range 0xD800 to 0xDFFF or is
+ // greater than 0x10FFFF, then this is a parse error. Return a U+FFFD
+ // REPLACEMENT CHARACTER.”
+ if (strict) {
+ parseError('character reference outside the permissible Unicode range');
+ }
+ return '\uFFFD';
+ }
+ if (has(decodeMapNumeric, codePoint)) {
+ if (strict) {
+ parseError('disallowed character reference');
+ }
+ return decodeMapNumeric[codePoint];
+ }
+ if (strict && contains(invalidReferenceCodePoints, codePoint)) {
+ parseError('disallowed character reference');
+ }
+ if (codePoint > 0xFFFF) {
+ codePoint -= 0x10000;
+ output += stringFromCharCode(codePoint >>> 10 & 0x3FF | 0xD800);
+ codePoint = 0xDC00 | codePoint & 0x3FF;
+ }
+ output += stringFromCharCode(codePoint);
+ return output;
+ };
+
+ var hexEscape = function(codePoint) {
+ return '&#x' + codePoint.toString(16).toUpperCase() + ';';
+ };
+
+ var decEscape = function(codePoint) {
+ return '&#' + codePoint + ';';
+ };
+
+ var parseError = function(message) {
+ throw Error('Parse error: ' + message);
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ var encode = function(string, options) {
+ options = merge(options, encode.options);
+ var strict = options.strict;
+ if (strict && regexInvalidRawCodePoint.test(string)) {
+ parseError('forbidden code point');
+ }
+ var encodeEverything = options.encodeEverything;
+ var useNamedReferences = options.useNamedReferences;
+ var allowUnsafeSymbols = options.allowUnsafeSymbols;
+ var escapeCodePoint = options.decimal ? decEscape : hexEscape;
+
+ var escapeBmpSymbol = function(symbol) {
+ return escapeCodePoint(symbol.charCodeAt(0));
+ };
+
+ if (encodeEverything) {
+ // Encode ASCII symbols.
+ string = string.replace(regexAsciiWhitelist, function(symbol) {
+ // Use named references if requested & possible.
+ if (useNamedReferences && has(encodeMap, symbol)) {
+ return '&' + encodeMap[symbol] + ';';
+ }
+ return escapeBmpSymbol(symbol);
+ });
+ // Shorten a few escapes that represent two symbols, of which at least one
+ // is within the ASCII range.
+ if (useNamedReferences) {
+ string = string
+ .replace(/&gt;\u20D2/g, '&nvgt;')
+ .replace(/&lt;\u20D2/g, '&nvlt;')
+ .replace(/&#x66;&#x6A;/g, '&fjlig;');
+ }
+ // Encode non-ASCII symbols.
+ if (useNamedReferences) {
+ // Encode non-ASCII symbols that can be replaced with a named reference.
+ string = string.replace(regexEncodeNonAscii, function(string) {
+ // Note: there is no need to check `has(encodeMap, string)` here.
+ return '&' + encodeMap[string] + ';';
+ });
+ }
+ // Note: any remaining non-ASCII symbols are handled outside of the `if`.
+ } else if (useNamedReferences) {
+ // Apply named character references.
+ // Encode `<>"'&` using named character references.
+ if (!allowUnsafeSymbols) {
+ string = string.replace(regexEscape, function(string) {
+ return '&' + encodeMap[string] + ';'; // no need to check `has()` here
+ });
+ }
+ // Shorten escapes that represent two symbols, of which at least one is
+ // `<>"'&`.
+ string = string
+ .replace(/&gt;\u20D2/g, '&nvgt;')
+ .replace(/&lt;\u20D2/g, '&nvlt;');
+ // Encode non-ASCII symbols that can be replaced with a named reference.
+ string = string.replace(regexEncodeNonAscii, function(string) {
+ // Note: there is no need to check `has(encodeMap, string)` here.
+ return '&' + encodeMap[string] + ';';
+ });
+ } else if (!allowUnsafeSymbols) {
+ // Encode `<>"'&` using hexadecimal escapes, now that they’re not handled
+ // using named character references.
+ string = string.replace(regexEscape, escapeBmpSymbol);
+ }
+ return string
+ // Encode astral symbols.
+ .replace(regexAstralSymbols, function($0) {
+ // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
+ var high = $0.charCodeAt(0);
+ var low = $0.charCodeAt(1);
+ var codePoint = (high - 0xD800) * 0x400 + low - 0xDC00 + 0x10000;
+ return escapeCodePoint(codePoint);
+ })
+ // Encode any remaining BMP symbols that are not printable ASCII symbols
+ // using a hexadecimal escape.
+ .replace(regexBmpWhitelist, escapeBmpSymbol);
+ };
+ // Expose default options (so they can be overridden globally).
+ encode.options = {
+ 'allowUnsafeSymbols': false,
+ 'encodeEverything': false,
+ 'strict': false,
+ 'useNamedReferences': false,
+ 'decimal' : false
+ };
+
+ var decode = function(html, options) {
+ options = merge(options, decode.options);
+ var strict = options.strict;
+ if (strict && regexInvalidEntity.test(html)) {
+ parseError('malformed character reference');
+ }
+ return html.replace(regexDecode, function($0, $1, $2, $3, $4, $5, $6, $7, $8) {
+ var codePoint;
+ var semicolon;
+ var decDigits;
+ var hexDigits;
+ var reference;
+ var next;
+
+ if ($1) {
+ reference = $1;
+ // Note: there is no need to check `has(decodeMap, reference)`.
+ return decodeMap[reference];
+ }
+
+ if ($2) {
+ // Decode named character references without trailing `;`, e.g. `&amp`.
+ // This is only a parse error if it gets converted to `&`, or if it is
+ // followed by `=` in an attribute context.
+ reference = $2;
+ next = $3;
+ if (next && options.isAttributeValue) {
+ if (strict && next == '=') {
+ parseError('`&` did not start a character reference');
+ }
+ return $0;
+ } else {
+ if (strict) {
+ parseError(
+ 'named character reference was not terminated by a semicolon'
+ );
+ }
+ // Note: there is no need to check `has(decodeMapLegacy, reference)`.
+ return decodeMapLegacy[reference] + (next || '');
+ }
+ }
+
+ if ($4) {
+ // Decode decimal escapes, e.g. `&#119558;`.
+ decDigits = $4;
+ semicolon = $5;
+ if (strict && !semicolon) {
+ parseError('character reference was not terminated by a semicolon');
+ }
+ codePoint = parseInt(decDigits, 10);
+ return codePointToSymbol(codePoint, strict);
+ }
+
+ if ($6) {
+ // Decode hexadecimal escapes, e.g. `&#x1D306;`.
+ hexDigits = $6;
+ semicolon = $7;
+ if (strict && !semicolon) {
+ parseError('character reference was not terminated by a semicolon');
+ }
+ codePoint = parseInt(hexDigits, 16);
+ return codePointToSymbol(codePoint, strict);
+ }
+
+ // If we’re still here, `if ($7)` is implied; it’s an ambiguous
+ // ampersand for sure. https://mths.be/notes/ambiguous-ampersands
+ if (strict) {
+ parseError(
+ 'named character reference was not terminated by a semicolon'
+ );
+ }
+ return $0;
+ });
+ };
+ // Expose default options (so they can be overridden globally).
+ decode.options = {
+ 'isAttributeValue': false,
+ 'strict': false
+ };
+
+ var escape = function(string) {
+ return string.replace(regexEscape, function($0) {
+ // Note: there is no need to check `has(escapeMap, $0)` here.
+ return escapeMap[$0];
+ });
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ var he = {
+ 'version': '1.2.0',
+ 'encode': encode,
+ 'decode': decode,
+ 'escape': escape,
+ 'unescape': decode
+ };
+
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ typeof define == 'function' &&
+ typeof define.amd == 'object' &&
+ define.amd
+ ) {
+ define(function() {
+ return he;
+ });
+ } else if (freeExports && !freeExports.nodeType) {
+ if (freeModule) { // in Node.js, io.js, or RingoJS v0.8.0+
+ freeModule.exports = he;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ for (var key in he) {
+ has(he, key) && (freeExports[key] = he[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.he = he;
+ }
+
+}(this));
diff --git a/js/lib/jquery.min.js b/assets/js/lib/jquery.min.js
index a1c07fd..a1c07fd 100644
--- a/js/lib/jquery.min.js
+++ b/assets/js/lib/jquery.min.js
diff --git a/js/lib/localforage.min.js b/assets/js/lib/localforage.min.js
index eb87534..eb87534 100644
--- a/js/lib/localforage.min.js
+++ b/assets/js/lib/localforage.min.js
diff --git a/js/lib/materialize.min.js b/assets/js/lib/materialize.min.js
index 4ff077d..4ff077d 100644
--- a/js/lib/materialize.min.js
+++ b/assets/js/lib/materialize.min.js
diff --git a/assets/js/lib/mergedeep.js b/assets/js/lib/mergedeep.js
new file mode 100644
index 0000000..a56aa1d
--- /dev/null
+++ b/assets/js/lib/mergedeep.js
@@ -0,0 +1,31 @@
+// https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge
+/**
+ * Simple object check.
+ * @param item
+ * @returns {boolean}
+ */
+function isObject(item) {
+ return (item && typeof item === 'object' && !Array.isArray(item));
+}
+
+/**
+ * Deep merge two objects.
+ * @param target
+ * @param ...sources
+ */
+function mergeDeep(target, ...sources) {
+ if (!sources.length) return target;
+ const source = sources.shift();
+
+ if (isObject(target) && isObject(source)) {
+ for (const key in source) {
+ if (isObject(source[key])) {
+ if (!target[key]) Object.assign(target, { [key]: {} });
+ mergeDeep(target[key], source[key]);
+ } else {
+ Object.assign(target, { [key]: source[key] });
+ }
+ }
+ }
+ return mergeDeep(target, ...sources);
+}
diff --git a/assets/js/lib/sjcl.js b/assets/js/lib/sjcl.js
new file mode 100644
index 0000000..c257544
--- /dev/null
+++ b/assets/js/lib/sjcl.js
@@ -0,0 +1,61 @@
+"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}};
+sjcl.cipher.aes=function(a){this.s[0][0][0]||this.O();var b,c,d,e,f=this.s[0][4],g=this.s[1];b=a.length;var h=1;if(4!==b&&6!==b&&8!==b)throw new sjcl.exception.invalid("invalid aes key size");this.b=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(0===a%b||8===b&&4===a%b)c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255],0===a%b&&(c=c<<8^c>>>24^h<<24,h=h<<1^283*(h>>7));d[a]=d[a-b]^c}for(b=0;a;b++,a--)c=d[b&3?a:a-4],e[b]=4>=a||4>b?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^g[3][f[c&
+255]]};
+sjcl.cipher.aes.prototype={encrypt:function(a){return t(this,a,0)},decrypt:function(a){return t(this,a,1)},s:[[[],[],[],[],[]],[[],[],[],[],[]]],O:function(){var a=this.s[0],b=this.s[1],c=a[4],d=b[4],e,f,g,h=[],k=[],l,n,m,p;for(e=0;0x100>e;e++)k[(h[e]=e<<1^283*(e>>7))^e]=e;for(f=g=0;!c[f];f^=l||1,g=k[g]||1)for(m=g^g<<1^g<<2^g<<3^g<<4,m=m>>8^m&255^99,c[f]=m,d[m]=f,n=h[e=h[l=h[f]]],p=0x1010101*n^0x10001*e^0x101*l^0x1010100*f,n=0x101*h[m]^0x1010100*m,e=0;4>e;e++)a[e][f]=n=n<<24^n>>>8,b[e][m]=p=p<<24^p>>>8;for(e=
+0;5>e;e++)a[e]=a[e].slice(0),b[e]=b[e].slice(0)}};
+function t(a,b,c){if(4!==b.length)throw new sjcl.exception.invalid("invalid aes block size");var d=a.b[c],e=b[0]^d[0],f=b[c?3:1]^d[1],g=b[2]^d[2];b=b[c?1:3]^d[3];var h,k,l,n=d.length/4-2,m,p=4,r=[0,0,0,0];h=a.s[c];a=h[0];var q=h[1],v=h[2],w=h[3],x=h[4];for(m=0;m<n;m++)h=a[e>>>24]^q[f>>16&255]^v[g>>8&255]^w[b&255]^d[p],k=a[f>>>24]^q[g>>16&255]^v[b>>8&255]^w[e&255]^d[p+1],l=a[g>>>24]^q[b>>16&255]^v[e>>8&255]^w[f&255]^d[p+2],b=a[b>>>24]^q[e>>16&255]^v[f>>8&255]^w[g&255]^d[p+3],p+=4,e=h,f=k,g=l;for(m=
+0;4>m;m++)r[c?3&-m:m]=x[e>>>24]<<24^x[f>>16&255]<<16^x[g>>8&255]<<8^x[b&255]^d[p++],h=e,e=f,f=g,g=b,b=h;return r}
+sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.$(a.slice(b/32),32-(b&31)).slice(1);return void 0===c?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<<c)-1},concat:function(a,b){if(0===a.length||0===b.length)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return 32===d?a.concat(b):sjcl.bitArray.$(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;return 0===
+b?0:32*(b-1)+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(32*a.length<b)return a;a=a.slice(0,Math.ceil(b/32));var c=a.length;b=b&31;0<c&&b&&(a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1));return a},partial:function(a,b,c){return 32===a?b:(c?b|0:b<<32-a)+0x10000000000*a},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return!1;var c=0,d;for(d=0;d<a.length;d++)c|=a[d]^b[d];return 0===
+c},$:function(a,b,c,d){var e;e=0;for(void 0===d&&(d=[]);32<=b;b-=32)d.push(c),c=0;if(0===b)return d.concat(a);for(e=0;e<a.length;e++)d.push(c|a[e]>>>b),c=a[e]<<32-b;e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,32<b+a?c:d.pop(),1));return d},i:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]},byteswapM:function(a){var b,c;for(b=0;b<a.length;++b)c=a[b],a[b]=c>>>24|c>>>8&0xff00|(c&0xff00)<<8|c<<24;return a}};
+sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d<c/8;d++)0===(d&3)&&(e=a[d/4]),b+=String.fromCharCode(e>>>8>>>8>>>8),e<<=8;return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c<a.length;c++)d=d<<8|a.charCodeAt(c),3===(c&3)&&(b.push(d),d=0);c&3&&b.push(sjcl.bitArray.partial(8*(c&3),d));return b}};
+sjcl.codec.hex={fromBits:function(a){var b="",c;for(c=0;c<a.length;c++)b+=((a[c]|0)+0xf00000000000).toString(16).substr(4);return b.substr(0,sjcl.bitArray.bitLength(a)/4)},toBits:function(a){var b,c=[],d;a=a.replace(/\s|0x/g,"");d=a.length;a=a+"00000000";for(b=0;b<a.length;b+=8)c.push(parseInt(a.substr(b,8),16)^0);return sjcl.bitArray.clamp(c,4*d)}};
+sjcl.codec.base32={B:"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",X:"0123456789ABCDEFGHIJKLMNOPQRSTUV",BITS:32,BASE:5,REMAINING:27,fromBits:function(a,b,c){var d=sjcl.codec.base32.BASE,e=sjcl.codec.base32.REMAINING,f="",g=0,h=sjcl.codec.base32.B,k=0,l=sjcl.bitArray.bitLength(a);c&&(h=sjcl.codec.base32.X);for(c=0;f.length*d<l;)f+=h.charAt((k^a[c]>>>g)>>>e),g<d?(k=a[c]<<d-g,g+=e,c++):(k<<=d,g-=d);for(;f.length&7&&!b;)f+="=";return f},toBits:function(a,b){a=a.replace(/\s|=/g,"").toUpperCase();var c=sjcl.codec.base32.BITS,
+d=sjcl.codec.base32.BASE,e=sjcl.codec.base32.REMAINING,f=[],g,h=0,k=sjcl.codec.base32.B,l=0,n,m="base32";b&&(k=sjcl.codec.base32.X,m="base32hex");for(g=0;g<a.length;g++){n=k.indexOf(a.charAt(g));if(0>n){if(!b)try{return sjcl.codec.base32hex.toBits(a)}catch(p){}throw new sjcl.exception.invalid("this isn't "+m+"!");}h>e?(h-=e,f.push(l^n>>>h),l=n<<c-h):(h+=d,l^=n<<c-h)}h&56&&f.push(sjcl.bitArray.partial(h&56,l,1));return f}};
+sjcl.codec.base32hex={fromBits:function(a,b){return sjcl.codec.base32.fromBits(a,b,1)},toBits:function(a){return sjcl.codec.base32.toBits(a,1)}};
+sjcl.codec.base64={B:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(a,b,c){var d="",e=0,f=sjcl.codec.base64.B,g=0,h=sjcl.bitArray.bitLength(a);c&&(f=f.substr(0,62)+"-_");for(c=0;6*d.length<h;)d+=f.charAt((g^a[c]>>>e)>>>26),6>e?(g=a[c]<<6-e,e+=26,c++):(g<<=6,e-=6);for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d,e=0,f=sjcl.codec.base64.B,g=0,h;b&&(f=f.substr(0,62)+"-_");for(d=0;d<a.length;d++){h=f.indexOf(a.charAt(d));
+if(0>h)throw new sjcl.exception.invalid("this isn't base64!");26<e?(e-=26,c.push(g^h>>>e),g=h<<32-e):(e+=6,g^=h<<32-e)}e&56&&c.push(sjcl.bitArray.partial(e&56,g,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.b[0]||this.O();a?(this.F=a.F.slice(0),this.A=a.A.slice(0),this.l=a.l):this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()};
+sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.F=this.Y.slice(0);this.A=[];this.l=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.A=sjcl.bitArray.concat(this.A,a);b=this.l;a=this.l=b+sjcl.bitArray.bitLength(a);if(0x1fffffffffffff<a)throw new sjcl.exception.invalid("Cannot hash more than 2^53 - 1 bits");if("undefined"!==typeof Uint32Array){var d=new Uint32Array(c),e=0;for(b=512+b-(512+b&0x1ff);b<=a;b+=512)u(this,d.subarray(16*e,
+16*(e+1))),e+=1;c.splice(0,16*e)}else for(b=512+b-(512+b&0x1ff);b<=a;b+=512)u(this,c.splice(0,16));return this},finalize:function(){var a,b=this.A,c=this.F,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.l/0x100000000));for(b.push(this.l|0);b.length;)u(this,b.splice(0,16));this.reset();return c},Y:[],b:[],O:function(){function a(a){return 0x100000000*(a-Math.floor(a))|0}for(var b=0,c=2,d,e;64>b;c++){e=!0;for(d=2;d*d<=c;d++)if(0===c%d){e=
+!1;break}e&&(8>b&&(this.Y[b]=a(Math.pow(c,.5))),this.b[b]=a(Math.pow(c,1/3)),b++)}}};
+function u(a,b){var c,d,e,f=a.F,g=a.b,h=f[0],k=f[1],l=f[2],n=f[3],m=f[4],p=f[5],r=f[6],q=f[7];for(c=0;64>c;c++)16>c?d=b[c]:(d=b[c+1&15],e=b[c+14&15],d=b[c&15]=(d>>>7^d>>>18^d>>>3^d<<25^d<<14)+(e>>>17^e>>>19^e>>>10^e<<15^e<<13)+b[c&15]+b[c+9&15]|0),d=d+q+(m>>>6^m>>>11^m>>>25^m<<26^m<<21^m<<7)+(r^m&(p^r))+g[c],q=r,r=p,p=m,m=n+d|0,n=l,l=k,k=h,h=d+(k&l^n&(k^l))+(k>>>2^k>>>13^k>>>22^k<<30^k<<19^k<<10)|0;f[0]=f[0]+h|0;f[1]=f[1]+k|0;f[2]=f[2]+l|0;f[3]=f[3]+n|0;f[4]=f[4]+m|0;f[5]=f[5]+p|0;f[6]=f[6]+r|0;f[7]=
+f[7]+q|0}
+sjcl.mode.ccm={name:"ccm",G:[],listenProgress:function(a){sjcl.mode.ccm.G.push(a)},unListenProgress:function(a){a=sjcl.mode.ccm.G.indexOf(a);-1<a&&sjcl.mode.ccm.G.splice(a,1)},fa:function(a){var b=sjcl.mode.ccm.G.slice(),c;for(c=0;c<b.length;c+=1)b[c](a)},encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,k=h.bitLength(c)/8,l=h.bitLength(g)/8;e=e||64;d=d||[];if(7>k)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(f=2;4>f&&l>>>8*f;f++);f<15-k&&(f=15-k);c=h.clamp(c,
+8*(15-f));b=sjcl.mode.ccm.V(a,b,c,d,e,f);g=sjcl.mode.ccm.C(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),k=f.clamp(b,h-e),l=f.bitSlice(b,h-e),h=(h-e)/8;if(7>g)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(b=2;4>b&&h>>>8*b;b++);b<15-g&&(b=15-g);c=f.clamp(c,8*(15-b));k=sjcl.mode.ccm.C(a,k,c,l,e,b);a=sjcl.mode.ccm.V(a,k.data,c,d,e,b);if(!f.equal(k.tag,a))throw new sjcl.exception.corrupt("ccm: tag doesn't match");
+return k.data},na:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,k=h.i;d=[h.partial(8,(b.length?64:0)|d-2<<2|f-1)];d=h.concat(d,c);d[3]|=e;d=a.encrypt(d);if(b.length)for(c=h.bitLength(b)/8,65279>=c?g=[h.partial(16,c)]:0xffffffff>=c&&(g=h.concat([h.partial(16,65534)],[c])),g=h.concat(g,b),b=0;b<g.length;b+=4)d=a.encrypt(k(d,g.slice(b,b+4).concat([0,0,0])));return d},V:function(a,b,c,d,e,f){var g=sjcl.bitArray,h=g.i;e/=8;if(e%2||4>e||16<e)throw new sjcl.exception.invalid("ccm: invalid tag length");
+if(0xffffffff<d.length||0xffffffff<b.length)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");c=sjcl.mode.ccm.na(a,d,c,e,g.bitLength(b)/8,f);for(d=0;d<b.length;d+=4)c=a.encrypt(h(c,b.slice(d,d+4).concat([0,0,0])));return g.clamp(c,8*e)},C:function(a,b,c,d,e,f){var g,h=sjcl.bitArray;g=h.i;var k=b.length,l=h.bitLength(b),n=k/50,m=n;c=h.concat([h.partial(8,f-1)],c).concat([0,0,0]).slice(0,4);d=h.bitSlice(g(d,a.encrypt(c)),0,e);if(!k)return{tag:d,data:[]};for(g=0;g<k;g+=4)g>n&&(sjcl.mode.ccm.fa(g/
+k),n+=m),c[3]++,e=a.encrypt(c),b[g]^=e[0],b[g+1]^=e[1],b[g+2]^=e[2],b[g+3]^=e[3];return{tag:d,data:h.clamp(b,l)}}};
+sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,f){if(128!==sjcl.bitArray.bitLength(c))throw new sjcl.exception.invalid("ocb iv must be 128 bits");var g,h=sjcl.mode.ocb2.S,k=sjcl.bitArray,l=k.i,n=[0,0,0,0];c=h(a.encrypt(c));var m,p=[];d=d||[];e=e||64;for(g=0;g+4<b.length;g+=4)m=b.slice(g,g+4),n=l(n,m),p=p.concat(l(c,a.encrypt(l(c,m)))),c=h(c);m=b.slice(g);b=k.bitLength(m);g=a.encrypt(l(c,[0,0,0,b]));m=k.clamp(l(m.concat([0,0,0]),g),b);n=l(n,l(m.concat([0,0,0]),g));n=a.encrypt(l(n,l(c,h(c))));
+d.length&&(n=l(n,f?d:sjcl.mode.ocb2.pmac(a,d)));return p.concat(k.concat(m,k.clamp(n,e)))},decrypt:function(a,b,c,d,e,f){if(128!==sjcl.bitArray.bitLength(c))throw new sjcl.exception.invalid("ocb iv must be 128 bits");e=e||64;var g=sjcl.mode.ocb2.S,h=sjcl.bitArray,k=h.i,l=[0,0,0,0],n=g(a.encrypt(c)),m,p,r=sjcl.bitArray.bitLength(b)-e,q=[];d=d||[];for(c=0;c+4<r/32;c+=4)m=k(n,a.decrypt(k(n,b.slice(c,c+4)))),l=k(l,m),q=q.concat(m),n=g(n);p=r-32*c;m=a.encrypt(k(n,[0,0,0,p]));m=k(m,h.clamp(b.slice(c),p).concat([0,
+0,0]));l=k(l,m);l=a.encrypt(k(l,k(n,g(n))));d.length&&(l=k(l,f?d:sjcl.mode.ocb2.pmac(a,d)));if(!h.equal(h.clamp(l,e),h.bitSlice(b,r)))throw new sjcl.exception.corrupt("ocb: tag doesn't match");return q.concat(h.clamp(m,p))},pmac:function(a,b){var c,d=sjcl.mode.ocb2.S,e=sjcl.bitArray,f=e.i,g=[0,0,0,0],h=a.encrypt([0,0,0,0]),h=f(h,d(d(h)));for(c=0;c+4<b.length;c+=4)h=d(h),g=f(g,a.encrypt(f(h,b.slice(c,c+4))));c=b.slice(c);128>e.bitLength(c)&&(h=f(h,d(h)),c=e.concat(c,[-2147483648,0,0,0]));g=f(g,c);
+return a.encrypt(f(d(f(h,d(h))),g))},S:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^135*(a[0]>>>31)]}};
+sjcl.mode.gcm={name:"gcm",encrypt:function(a,b,c,d,e){var f=b.slice(0);b=sjcl.bitArray;d=d||[];a=sjcl.mode.gcm.C(!0,a,f,d,c,e||128);return b.concat(a.data,a.tag)},decrypt:function(a,b,c,d,e){var f=b.slice(0),g=sjcl.bitArray,h=g.bitLength(f);e=e||128;d=d||[];e<=h?(b=g.bitSlice(f,h-e),f=g.bitSlice(f,0,h-e)):(b=f,f=[]);a=sjcl.mode.gcm.C(!1,a,f,d,c,e);if(!g.equal(a.tag,b))throw new sjcl.exception.corrupt("gcm: tag doesn't match");return a.data},ka:function(a,b){var c,d,e,f,g,h=sjcl.bitArray.i;e=[0,0,
+0,0];f=b.slice(0);for(c=0;128>c;c++){(d=0!==(a[Math.floor(c/32)]&1<<31-c%32))&&(e=h(e,f));g=0!==(f[3]&1);for(d=3;0<d;d--)f[d]=f[d]>>>1|(f[d-1]&1)<<31;f[0]>>>=1;g&&(f[0]^=-0x1f000000)}return e},j:function(a,b,c){var d,e=c.length;b=b.slice(0);for(d=0;d<e;d+=4)b[0]^=0xffffffff&c[d],b[1]^=0xffffffff&c[d+1],b[2]^=0xffffffff&c[d+2],b[3]^=0xffffffff&c[d+3],b=sjcl.mode.gcm.ka(b,a);return b},C:function(a,b,c,d,e,f){var g,h,k,l,n,m,p,r,q=sjcl.bitArray;m=c.length;p=q.bitLength(c);r=q.bitLength(d);h=q.bitLength(e);
+g=b.encrypt([0,0,0,0]);96===h?(e=e.slice(0),e=q.concat(e,[1])):(e=sjcl.mode.gcm.j(g,[0,0,0,0],e),e=sjcl.mode.gcm.j(g,e,[0,0,Math.floor(h/0x100000000),h&0xffffffff]));h=sjcl.mode.gcm.j(g,[0,0,0,0],d);n=e.slice(0);d=h.slice(0);a||(d=sjcl.mode.gcm.j(g,h,c));for(l=0;l<m;l+=4)n[3]++,k=b.encrypt(n),c[l]^=k[0],c[l+1]^=k[1],c[l+2]^=k[2],c[l+3]^=k[3];c=q.clamp(c,p);a&&(d=sjcl.mode.gcm.j(g,h,c));a=[Math.floor(r/0x100000000),r&0xffffffff,Math.floor(p/0x100000000),p&0xffffffff];d=sjcl.mode.gcm.j(g,d,a);k=b.encrypt(e);
+d[0]^=k[0];d[1]^=k[1];d[2]^=k[2];d[3]^=k[3];return{tag:q.bitSlice(d,0,f),data:c}}};sjcl.misc.hmac=function(a,b){this.W=b=b||sjcl.hash.sha256;var c=[[],[]],d,e=b.prototype.blockSize/32;this.w=[new b,new b];a.length>e&&(a=b.hash(a));for(d=0;d<e;d++)c[0][d]=a[d]^909522486,c[1][d]=a[d]^1549556828;this.w[0].update(c[0]);this.w[1].update(c[1]);this.R=new b(this.w[0])};
+sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(a){if(this.aa)throw new sjcl.exception.invalid("encrypt on already updated hmac called!");this.update(a);return this.digest(a)};sjcl.misc.hmac.prototype.reset=function(){this.R=new this.W(this.w[0]);this.aa=!1};sjcl.misc.hmac.prototype.update=function(a){this.aa=!0;this.R.update(a)};sjcl.misc.hmac.prototype.digest=function(){var a=this.R.finalize(),a=(new this.W(this.w[1])).update(a).finalize();this.reset();return a};
+sjcl.misc.pbkdf2=function(a,b,c,d,e){c=c||1E4;if(0>d||0>c)throw new sjcl.exception.invalid("invalid params to pbkdf2");"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,k,l=[],n=sjcl.bitArray;for(k=1;32*l.length<(d||1);k++){e=f=a.encrypt(n.concat(b,[k]));for(g=1;g<c;g++)for(f=a.encrypt(f),h=0;h<f.length;h++)e[h]^=f[h];l=l.concat(e)}d&&(l=n.clamp(l,d));return l};
+sjcl.prng=function(a){this.c=[new sjcl.hash.sha256];this.m=[0];this.P=0;this.H={};this.N=0;this.U={};this.Z=this.f=this.o=this.ha=0;this.b=[0,0,0,0,0,0,0,0];this.h=[0,0,0,0];this.L=void 0;this.M=a;this.D=!1;this.K={progress:{},seeded:{}};this.u=this.ga=0;this.I=1;this.J=2;this.ca=0x10000;this.T=[0,48,64,96,128,192,0x100,384,512,768,1024];this.da=3E4;this.ba=80};
+sjcl.prng.prototype={randomWords:function(a,b){var c=[],d;d=this.isReady(b);var e;if(d===this.u)throw new sjcl.exception.notReady("generator isn't seeded");if(d&this.J){d=!(d&this.I);e=[];var f=0,g;this.Z=e[0]=(new Date).valueOf()+this.da;for(g=0;16>g;g++)e.push(0x100000000*Math.random()|0);for(g=0;g<this.c.length&&(e=e.concat(this.c[g].finalize()),f+=this.m[g],this.m[g]=0,d||!(this.P&1<<g));g++);this.P>=1<<this.c.length&&(this.c.push(new sjcl.hash.sha256),this.m.push(0));this.f-=f;f>this.o&&(this.o=
+f);this.P++;this.b=sjcl.hash.sha256.hash(this.b.concat(e));this.L=new sjcl.cipher.aes(this.b);for(d=0;4>d&&(this.h[d]=this.h[d]+1|0,!this.h[d]);d++);}for(d=0;d<a;d+=4)0===(d+1)%this.ca&&y(this),e=z(this),c.push(e[0],e[1],e[2],e[3]);y(this);return c.slice(0,a)},setDefaultParanoia:function(a,b){if(0===a&&"Setting paranoia=0 will ruin your security; use it only for testing"!==b)throw new sjcl.exception.invalid("Setting paranoia=0 will ruin your security; use it only for testing");this.M=a},addEntropy:function(a,
+b,c){c=c||"user";var d,e,f=(new Date).valueOf(),g=this.H[c],h=this.isReady(),k=0;d=this.U[c];void 0===d&&(d=this.U[c]=this.ha++);void 0===g&&(g=this.H[c]=0);this.H[c]=(this.H[c]+1)%this.c.length;switch(typeof a){case "number":void 0===b&&(b=1);this.c[g].update([d,this.N++,1,b,f,1,a|0]);break;case "object":c=Object.prototype.toString.call(a);if("[object Uint32Array]"===c){e=[];for(c=0;c<a.length;c++)e.push(a[c]);a=e}else for("[object Array]"!==c&&(k=1),c=0;c<a.length&&!k;c++)"number"!==typeof a[c]&&
+(k=1);if(!k){if(void 0===b)for(c=b=0;c<a.length;c++)for(e=a[c];0<e;)b++,e=e>>>1;this.c[g].update([d,this.N++,2,b,f,a.length].concat(a))}break;case "string":void 0===b&&(b=a.length);this.c[g].update([d,this.N++,3,b,f,a.length]);this.c[g].update(a);break;default:k=1}if(k)throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");this.m[g]+=b;this.f+=b;h===this.u&&(this.isReady()!==this.u&&A("seeded",Math.max(this.o,this.f)),A("progress",this.getProgress()))},
+isReady:function(a){a=this.T[void 0!==a?a:this.M];return this.o&&this.o>=a?this.m[0]>this.ba&&(new Date).valueOf()>this.Z?this.J|this.I:this.I:this.f>=a?this.J|this.u:this.u},getProgress:function(a){a=this.T[a?a:this.M];return this.o>=a?1:this.f>a?1:this.f/a},startCollectors:function(){if(!this.D){this.a={loadTimeCollector:B(this,this.ma),mouseCollector:B(this,this.oa),keyboardCollector:B(this,this.la),accelerometerCollector:B(this,this.ea),touchCollector:B(this,this.qa)};if(window.addEventListener)window.addEventListener("load",
+this.a.loadTimeCollector,!1),window.addEventListener("mousemove",this.a.mouseCollector,!1),window.addEventListener("keypress",this.a.keyboardCollector,!1),window.addEventListener("devicemotion",this.a.accelerometerCollector,!1),window.addEventListener("touchmove",this.a.touchCollector,!1);else if(document.attachEvent)document.attachEvent("onload",this.a.loadTimeCollector),document.attachEvent("onmousemove",this.a.mouseCollector),document.attachEvent("keypress",this.a.keyboardCollector);else throw new sjcl.exception.bug("can't attach event");
+this.D=!0}},stopCollectors:function(){this.D&&(window.removeEventListener?(window.removeEventListener("load",this.a.loadTimeCollector,!1),window.removeEventListener("mousemove",this.a.mouseCollector,!1),window.removeEventListener("keypress",this.a.keyboardCollector,!1),window.removeEventListener("devicemotion",this.a.accelerometerCollector,!1),window.removeEventListener("touchmove",this.a.touchCollector,!1)):document.detachEvent&&(document.detachEvent("onload",this.a.loadTimeCollector),document.detachEvent("onmousemove",
+this.a.mouseCollector),document.detachEvent("keypress",this.a.keyboardCollector)),this.D=!1)},addEventListener:function(a,b){this.K[a][this.ga++]=b},removeEventListener:function(a,b){var c,d,e=this.K[a],f=[];for(d in e)e.hasOwnProperty(d)&&e[d]===b&&f.push(d);for(c=0;c<f.length;c++)d=f[c],delete e[d]},la:function(){C(this,1)},oa:function(a){var b,c;try{b=a.x||a.clientX||a.offsetX||0,c=a.y||a.clientY||a.offsetY||0}catch(d){c=b=0}0!=b&&0!=c&&this.addEntropy([b,c],2,"mouse");C(this,0)},qa:function(a){a=
+a.touches[0]||a.changedTouches[0];this.addEntropy([a.pageX||a.clientX,a.pageY||a.clientY],1,"touch");C(this,0)},ma:function(){C(this,2)},ea:function(a){a=a.accelerationIncludingGravity.x||a.accelerationIncludingGravity.y||a.accelerationIncludingGravity.z;if(window.orientation){var b=window.orientation;"number"===typeof b&&this.addEntropy(b,1,"accelerometer")}a&&this.addEntropy(a,2,"accelerometer");C(this,0)}};
+function A(a,b){var c,d=sjcl.random.K[a],e=[];for(c in d)d.hasOwnProperty(c)&&e.push(d[c]);for(c=0;c<e.length;c++)e[c](b)}function C(a,b){"undefined"!==typeof window&&window.performance&&"function"===typeof window.performance.now?a.addEntropy(window.performance.now(),b,"loadtime"):a.addEntropy((new Date).valueOf(),b,"loadtime")}function y(a){a.b=z(a).concat(z(a));a.L=new sjcl.cipher.aes(a.b)}function z(a){for(var b=0;4>b&&(a.h[b]=a.h[b]+1|0,!a.h[b]);b++);return a.L.encrypt(a.h)}
+function B(a,b){return function(){b.apply(a,arguments)}}sjcl.random=new sjcl.prng(6);
+a:try{var D,E,F,G;if(G="undefined"!==typeof module&&module.exports){var H;try{H=require("crypto")}catch(a){H=null}G=E=H}if(G&&E.randomBytes)D=E.randomBytes(128),D=new Uint32Array((new Uint8Array(D)).buffer),sjcl.random.addEntropy(D,1024,"crypto['randomBytes']");else if("undefined"!==typeof window&&"undefined"!==typeof Uint32Array){F=new Uint32Array(32);if(window.crypto&&window.crypto.getRandomValues)window.crypto.getRandomValues(F);else if(window.msCrypto&&window.msCrypto.getRandomValues)window.msCrypto.getRandomValues(F);
+else break a;sjcl.random.addEntropy(F,1024,"crypto['getRandomValues']")}}catch(a){"undefined"!==typeof window&&window.console&&(console.log("There was an error collecting entropy from the browser:"),console.log(a))}
+sjcl.json={defaults:{v:1,iter:1E4,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},ja:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.g({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.g(f,c);c=f.adata;"string"===typeof f.salt&&(f.salt=sjcl.codec.base64.toBits(f.salt));"string"===typeof f.iv&&(f.iv=sjcl.codec.base64.toBits(f.iv));if(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||"string"===typeof a&&100>=f.iter||64!==f.ts&&96!==f.ts&&128!==f.ts||128!==f.ks&&192!==f.ks&&0x100!==f.ks||2>f.iv.length||
+4<f.iv.length)throw new sjcl.exception.invalid("json encrypt: invalid parameters");"string"===typeof a?(g=sjcl.misc.cachedPbkdf2(a,f),a=g.key.slice(0,f.ks/32),f.salt=g.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.publicKey&&(g=a.kem(),f.kemtag=g.tag,a=g.key.slice(0,f.ks/32));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));"string"===typeof c&&(f.adata=c=sjcl.codec.utf8String.toBits(c));g=new sjcl.cipher[f.cipher](a);e.g(d,f);d.key=a;f.ct="ccm"===f.mode&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&
+b instanceof ArrayBuffer?sjcl.arrayBuffer.ccm.encrypt(g,b,f.iv,c,f.ts):sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return f},encrypt:function(a,b,c,d){var e=sjcl.json,f=e.ja.apply(e,arguments);return e.encode(f)},ia:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.g(e.g(e.g({},e.defaults),b),c,!0);var f,g;f=b.adata;"string"===typeof b.salt&&(b.salt=sjcl.codec.base64.toBits(b.salt));"string"===typeof b.iv&&(b.iv=sjcl.codec.base64.toBits(b.iv));if(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||"string"===
+typeof a&&100>=b.iter||64!==b.ts&&96!==b.ts&&128!==b.ts||128!==b.ks&&192!==b.ks&&0x100!==b.ks||!b.iv||2>b.iv.length||4<b.iv.length)throw new sjcl.exception.invalid("json decrypt: invalid parameters");"string"===typeof a?(g=sjcl.misc.cachedPbkdf2(a,b),a=g.key.slice(0,b.ks/32),b.salt=g.salt):sjcl.ecc&&a instanceof sjcl.ecc.elGamal.secretKey&&(a=a.unkem(sjcl.codec.base64.toBits(b.kemtag)).slice(0,b.ks/32));"string"===typeof f&&(f=sjcl.codec.utf8String.toBits(f));g=new sjcl.cipher[b.cipher](a);f="ccm"===
+b.mode&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&b.ct instanceof ArrayBuffer?sjcl.arrayBuffer.ccm.decrypt(g,b.ct,b.iv,b.tag,f,b.ts):sjcl.mode[b.mode].decrypt(g,b.ct,b.iv,f,b.ts);e.g(d,b);d.key=a;return 1===c.raw?f:sjcl.codec.utf8String.fromBits(f)},decrypt:function(a,b,c,d){var e=sjcl.json;return e.ia(a,e.decode(b),c,d)},encode:function(a){var b,c="{",d="";for(b in a)if(a.hasOwnProperty(b)){if(!b.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");c+=d+'"'+
+b+'":';d=",";switch(typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';break;case "object":c+='"'+sjcl.codec.base64.fromBits(a[b],0)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type");}}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");if(!a.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");a=a.replace(/^\{|\}$/g,"").split(/,/);var b={},c,d;for(c=0;c<a.length;c++){if(!(d=a[c].match(/^\s*(?:(["']?)([a-z][a-z0-9]*)\1)\s*:\s*(?:(-?\d+)|"([a-z0-9+\/%*_.@=\-]*)"|(true|false))$/i)))throw new sjcl.exception.invalid("json decode: this isn't json!");
+null!=d[3]?b[d[2]]=parseInt(d[3],10):null!=d[4]?b[d[2]]=d[2].match(/^(ct|adata|salt|iv)$/)?sjcl.codec.base64.toBits(d[4]):unescape(d[4]):null!=d[5]&&(b[d[2]]="true"===d[5])}return b},g:function(a,b,c){void 0===a&&(a={});if(void 0===b)return a;for(var d in b)if(b.hasOwnProperty(d)){if(c&&void 0!==a[d]&&a[d]!==b[d])throw new sjcl.exception.invalid("required parameter overridden");a[d]=b[d]}return a},sa:function(a,b){var c={},d;for(d in a)a.hasOwnProperty(d)&&a[d]!==b[d]&&(c[d]=a[d]);return c},ra:function(a,
+b){var c={},d;for(d=0;d<b.length;d++)void 0!==a[b[d]]&&(c[b[d]]=a[b[d]]);return c}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc.pa={};sjcl.misc.cachedPbkdf2=function(a,b){var c=sjcl.misc.pa,d;b=b||{};d=b.iter||1E3;c=c[a]=c[a]||{};d=c[d]=c[d]||{firstSalt:b.salt&&b.salt.length?b.salt.slice(0):sjcl.random.randomWords(2,0)};c=void 0===b.salt?d.firstSalt:b.salt;d[c]=d[c]||sjcl.misc.pbkdf2(a,c,b.iter);return{key:d[c].slice(0),salt:c.slice(0)}};
+"undefined"!==typeof module&&module.exports&&(module.exports=sjcl);"function"===typeof define&&define([],function(){return sjcl});
+
diff --git a/assets/js/lib/themes.js b/assets/js/lib/themes.js
new file mode 100644
index 0000000..0415fd2
--- /dev/null
+++ b/assets/js/lib/themes.js
@@ -0,0 +1,188 @@
+const THEME_COLOR_SCHEMES = {
+ light: {
+ "color-primary": "rgba(0, 128, 83, 1)",
+ "color-secondary": "rgba(0, 77, 50, 1)",
+ "color-accent": "rgba(0, 156, 101, 1)",
+ "color-primary-light": "rgba(230, 250, 231, 1)",
+ "color-invalid": "rgba(192, 0, 0, 1)",
+ "color-primary-transparent": "rgba(0, 128, 83, 0.3)",
+
+ "background-color": "rgba(255, 255, 255, 1)",
+ "background-accent": "rgba(0, 156, 101, 0.2)",
+ "background-accent-1": "rgb(192, 219, 207, 1)",
+
+ "color-header-bold": "rgba(255, 255, 255, 1)",
+ "color-header-span": "rgba(255, 255, 255, 1)",
+
+ "color-text": "rgba(0, 0, 0, 1)",
+ "color-text-light": "rgba(97, 97, 97, 1)",
+ "color-label-text": "rgba(158, 158, 158, 1)",
+ "color-table-hover": "rgba(242, 242, 242, 0.5)",
+
+ "color-sidenav-background": "rgba(255, 255, 255, 1)",
+ "color-sidenav-text": "rgba(0, 0, 0, 0.87)",
+ "color-sidenav-header": "rgba(0, 0, 0, 0.54)",
+ "color-divider": "rgba(224, 224, 224, 1)",
+
+ "color-btn-flat": "rgba(52, 52, 52, 1)",
+ "color-btn-flat-disabled": "rgba(178, 178, 178, 1)"
+
+ },
+ dark: {
+ "color-primary": "rgba(0, 128, 83, 1)",
+ "color-secondary": "rgba(0, 94, 61, 1)",
+ "color-accent": "rgba(20, 117, 83, 1)",
+ "color-primary-light": "rgba(6, 137, 9, 1)", // it's not really light anymore, is it?
+ "color-invalid": "rgba(192, 0, 0, 1)",
+ "color-primary-transparent": "rgba(0, 128, 83, 0.3)",
+
+ "background-color": "rgba(31, 31, 31, 1)",
+ "background-accent": "rgba(0, 92, 44, 0.2)",
+ "background-accent-1": "rgb(26, 43, 32, 1)",
+
+ "color-header-bold": "rgba(255, 255, 255, 1)",
+ "color-header-span": "rgba(255, 255, 255, 1)",
+
+ "color-text": "rgba(189, 189, 189, 1)",
+ "color-text-light": "rgba(160, 160, 160, 1)",
+ "color-label-text": "rgba(158, 158, 158, 1)",
+ "color-table-hover": "rgba(70, 70, 70, 0.5)",
+
+ "color-sidenav-background": "rgba(31, 31, 31, 1)",
+ "color-sidenav-text": "rgba(189, 189, 189, 1)",
+ "color-sidenav-header": "rgba(189, 189, 189, 0.54)",
+ "color-divider": "rgba(80, 80, 80, 1)",
+
+ "color-btn-flat": "rgba(178, 178, 178, 1)",
+ "color-btn-flat-disabled": "rgba(52, 52, 52, 1)"
+ },
+ night: {
+ "color-primary": "rgba(0, 79, 51, 1)",
+ "color-secondary": "rgba(0, 94, 61, 1)",
+ "color-accent": "rgba(28, 107, 79, 1)",
+ "color-primary-light": "rgba(0, 77, 2, 1)", // it's not really light anymore, is it?
+ "color-invalid": "rgba(138, 0, 0, 1)",
+ "color-primary-transparent": "rgba(0, 79, 51, 0.3)",
+
+ "background-color": "rgba(0, 0, 0, 1)",
+ "background-accent": "rgba(0, 38, 18, 0.2)",
+ "background-accent-1": "rgb(20, 33, 25, 1)",
+
+ "color-header-bold": "rgba(255, 255, 255, 1)",
+ "color-header-span": "rgba(255, 255, 255, 1)",
+
+ "color-text": "rgba(255, 255, 255, 1)",
+ "color-text-light": "rgba(200, 200, 200, 1)",
+ "color-label-text": "rgba(158, 158, 158, 1)",
+ "color-table-hover": "rgba(43, 43, 43, 0.5)",
+
+ "color-sidenav-background": "rgba(0, 0, 0, 1)",
+ "color-sidenav-text": "rgba(255, 255, 255, 1)",
+ "color-sidenav-header": "rgba(255, 255, 255, 0.54)",
+ "color-divider": "rgba(50, 50, 50, 1)",
+
+ "color-btn-flat": "rgba(178, 178, 178, 1)",
+ "color-btn-flat-disabled": "rgba(52, 52, 52, 1)"
+ },
+ left: {
+ "color-primary": "rgba(173, 0, 0, 1)",
+ "color-secondary": "rgba(148, 15, 15, 1)",
+ "color-accent": "rgba(181, 42, 42, 1)",
+ "color-primary-light": "rgba(250, 230, 230, 1)",
+ "color-invalid": "rgba(120, 0, 108, 1)",
+ "color-primary-transparent": "rgba(173, 0, 0, 0.3)",
+
+ "background-color": "rgba(255, 255, 255, 1)",
+ "background-accent": "rgba(199, 38, 38, 0.2)",
+ "background-accent-1": "rgb(219, 192, 192, 1)",
+
+ "color-header-bold": "rgba(255, 255, 255, 1)",
+ "color-header-span": "rgba(255, 255, 255, 1)",
+
+ "color-text": "rgba(0, 0, 0, 1)",
+ "color-text-light": "rgba(97, 97, 97, 1)",
+ "color-label-text": "rgba(158, 158, 158, 1)",
+ "color-table-hover": "rgba(242, 242, 242, 0.5)",
+
+ "color-sidenav-background": "rgba(255, 255, 255, 1)",
+ "color-sidenav-text": "rgba(0, 0, 0, 0.87)",
+ "color-sidenav-header": "rgba(0, 0, 0, 0.54)",
+ "color-divider": "rgba(224, 224, 224, 1)",
+
+ "color-btn-flat": "rgba(52, 52, 52, 1)",
+ "color-btn-flat-disabled": "rgba(178, 178, 178, 1)"
+ },
+ right: {
+ "color-primary": "rgba(7, 0, 105, 1)",
+ "color-secondary": "rgba(0, 94, 156, 1)",
+ "color-accent": "rgba(217, 214, 80, 1)",
+ "color-primary-light": "rgba(195, 191, 255, 1)",
+ "color-invalid": "rgba(192, 0, 0, 1)",
+ "color-primary-transparent": "rgba(7, 0, 105, 0.3)",
+
+ "background-color": "rgba(255, 255, 255, 1)",
+ "background-accent": "rgba(0, 60, 156, 0.2)",
+ "background-accent-1": "rgb(215, 219, 192, 1)",
+
+ "color-header-bold": "rgba(255, 255, 255, 1)",
+ "color-header-span": "rgba(255, 255, 255, 1)",
+
+ "color-text": "rgba(0, 0, 0, 1)",
+ "color-text-light": "rgba(97, 97, 97, 1)",
+ "color-label-text": "rgba(158, 158, 158, 1)",
+ "color-table-hover": "rgba(242, 242, 242, 0.5)",
+
+ "color-sidenav-background": "rgba(255, 255, 255, 1)",
+ "color-sidenav-text": "rgba(0, 0, 0, 0.87)",
+ "color-sidenav-header": "rgba(0, 0, 0, 0.54)",
+ "color-divider": "rgba(224, 224, 224, 1)",
+
+ "color-btn-flat": "rgba(52, 52, 52, 1)",
+ "color-btn-flat-disabled": "rgba(178, 178, 178, 1)"
+ },
+ piratska: {
+ "color-primary": "rgba(246, 143, 53, 1)",
+ "color-secondary": "rgba(250, 165, 61, 1)",
+ "color-accent": "rgba(0, 0, 0, 1)",
+ "color-primary-light": "rgba(195, 191, 255, 1)",
+ "color-invalid": "rgba(192, 0, 0, 1)",
+ "color-primary-transparent": "rgba(252, 195, 126, 0.3)",
+
+ "background-color": "rgba(255, 255, 255, 1)",
+ "background-accent": "rgba(158, 119, 0, 0.2)",
+ "background-accent-1": "rgb(215, 219, 192, 1)",
+
+ "color-header-bold": "rgba(255, 255, 255, 1)",
+ "color-header-span": "rgba(255, 255, 255, 1)",
+
+ "color-text": "rgba(0, 0, 0, 1)",
+ "color-text-light": "rgba(97, 97, 97, 1)",
+ "color-label-text": "rgba(158, 158, 158, 1)",
+ "color-table-hover": "rgba(242, 242, 242, 0.5)",
+
+ "color-sidenav-background": "rgba(255, 255, 255, 1)",
+ "color-sidenav-text": "rgba(0, 0, 0, 0.87)",
+ "color-sidenav-header": "rgba(0, 0, 0, 0.54)",
+ "color-divider": "rgba(224, 224, 224, 1)",
+
+ "color-btn-flat": "rgba(52, 52, 52, 1)",
+ "color-btn-flat-disabled": "rgba(178, 178, 178, 1)"
+ }
+}
+
+function applyTheme(themeName) {
+ for (const [property, value] of Object.entries(THEME_COLOR_SCHEMES[themeName])) {
+ document.documentElement.style.setProperty(`--${property}`, value);
+ }
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+ localforage.getItem("theme").then((selectedTheme) => {
+ if (selectedTheme == null) {
+ let isOsDarkTheme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+ applyTheme(isOsDarkTheme ? "dark" : "light");
+ } else {
+ applyTheme(selectedTheme);
+ }
+ });
+});
diff --git a/assets/js/lib/xss.js b/assets/js/lib/xss.js
new file mode 100644
index 0000000..e0a6ba0
--- /dev/null
+++ b/assets/js/lib/xss.js
@@ -0,0 +1,1613 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/**
+ * default settings
+ *
+ * @author Zongmin Lei<leizongmin@gmail.com>
+ */
+
+var FilterCSS = require("cssfilter").FilterCSS;
+var getDefaultCSSWhiteList = require("cssfilter").getDefaultWhiteList;
+var _ = require("./util");
+
+function getDefaultWhiteList() {
+ return {
+ a: ["target", "href", "title"],
+ abbr: ["title"],
+ address: [],
+ area: ["shape", "coords", "href", "alt"],
+ article: [],
+ aside: [],
+ audio: ["autoplay", "controls", "loop", "preload", "src"],
+ b: [],
+ bdi: ["dir"],
+ bdo: ["dir"],
+ big: [],
+ blockquote: ["cite"],
+ br: [],
+ caption: [],
+ center: [],
+ cite: [],
+ code: [],
+ col: ["align", "valign", "span", "width"],
+ colgroup: ["align", "valign", "span", "width"],
+ dd: [],
+ del: ["datetime"],
+ details: ["open"],
+ div: [],
+ dl: [],
+ dt: [],
+ em: [],
+ font: ["color", "size", "face"],
+ footer: [],
+ h1: [],
+ h2: [],
+ h3: [],
+ h4: [],
+ h5: [],
+ h6: [],
+ header: [],
+ hr: [],
+ i: [],
+ img: ["src", "alt", "title", "width", "height"],
+ ins: ["datetime"],
+ li: [],
+ mark: [],
+ nav: [],
+ ol: [],
+ p: [],
+ pre: [],
+ s: [],
+ section: [],
+ small: [],
+ span: [],
+ sub: [],
+ sup: [],
+ strong: [],
+ table: ["width", "border", "align", "valign"],
+ tbody: ["align", "valign"],
+ td: ["width", "rowspan", "colspan", "align", "valign"],
+ tfoot: ["align", "valign"],
+ th: ["width", "rowspan", "colspan", "align", "valign"],
+ thead: ["align", "valign"],
+ tr: ["rowspan", "align", "valign"],
+ tt: [],
+ u: [],
+ ul: [],
+ video: ["autoplay", "controls", "loop", "preload", "src", "height", "width"]
+ };
+}
+
+var defaultCSSFilter = new FilterCSS();
+
+/**
+ * default onTag function
+ *
+ * @param {String} tag
+ * @param {String} html
+ * @param {Object} options
+ * @return {String}
+ */
+function onTag(tag, html, options) {
+ // do nothing
+}
+
+/**
+ * default onIgnoreTag function
+ *
+ * @param {String} tag
+ * @param {String} html
+ * @param {Object} options
+ * @return {String}
+ */
+function onIgnoreTag(tag, html, options) {
+ // do nothing
+}
+
+/**
+ * default onTagAttr function
+ *
+ * @param {String} tag
+ * @param {String} name
+ * @param {String} value
+ * @return {String}
+ */
+function onTagAttr(tag, name, value) {
+ // do nothing
+}
+
+/**
+ * default onIgnoreTagAttr function
+ *
+ * @param {String} tag
+ * @param {String} name
+ * @param {String} value
+ * @return {String}
+ */
+function onIgnoreTagAttr(tag, name, value) {
+ // do nothing
+}
+
+/**
+ * default escapeHtml function
+ *
+ * @param {String} html
+ */
+function escapeHtml(html) {
+ return html.replace(REGEXP_LT, "&lt;").replace(REGEXP_GT, "&gt;");
+}
+
+/**
+ * default safeAttrValue function
+ *
+ * @param {String} tag
+ * @param {String} name
+ * @param {String} value
+ * @param {Object} cssFilter
+ * @return {String}
+ */
+function safeAttrValue(tag, name, value, cssFilter) {
+ // unescape attribute value firstly
+ value = friendlyAttrValue(value);
+
+ if (name === "href" || name === "src") {
+ // filter `href` and `src` attribute
+ // only allow the value that starts with `http://` | `https://` | `mailto:` | `/` | `#` | and others
+ value = _.trim(value);
+ if (value === "#") return "#";
+ if (
+ !(
+ value.substr(0, 7) === "http://" ||
+ value.substr(0, 8) === "https://" ||
+ value.substr(0, 6) === "ftp://" ||
+ value.substr(0, 7) === "mailto:" ||
+ value.substr(0, 4) === "tel:" ||
+ value.substr(0, 11) === "data:image/" ||
+ value.substr(0, 2) === "./" ||
+ value.substr(0, 3) === "../" ||
+ value[0] === "#" ||
+ value[0] === "/"
+ )
+ ) {
+ return "";
+ }
+ } else if (name === "background") {
+ // filter `background` attribute (maybe no use)
+ // `javascript:`
+ REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
+ if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
+ return "";
+ }
+ } else if (name === "style") {
+ // `expression()`
+ REGEXP_DEFAULT_ON_TAG_ATTR_7.lastIndex = 0;
+ if (REGEXP_DEFAULT_ON_TAG_ATTR_7.test(value)) {
+ return "";
+ }
+ // `url()`
+ REGEXP_DEFAULT_ON_TAG_ATTR_8.lastIndex = 0;
+ if (REGEXP_DEFAULT_ON_TAG_ATTR_8.test(value)) {
+ REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
+ if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
+ return "";
+ }
+ }
+ if (cssFilter !== false) {
+ cssFilter = cssFilter || defaultCSSFilter;
+ value = cssFilter.process(value);
+ }
+ }
+
+ // escape `<>"` before returns
+ value = escapeAttrValue(value);
+ return value;
+}
+
+// RegExp list
+var REGEXP_LT = /</g;
+var REGEXP_GT = />/g;
+var REGEXP_QUOTE = /"/g;
+var REGEXP_QUOTE_2 = /&quot;/g;
+var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/gim;
+var REGEXP_ATTR_VALUE_COLON = /&colon;?/gim;
+var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/gim;
+var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//gm;
+var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/gi;
+var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/gi;
+var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//gi;
+var REGEXP_DEFAULT_ON_TAG_ATTR_7 = /e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/gi;
+var REGEXP_DEFAULT_ON_TAG_ATTR_8 = /u\s*r\s*l\s*\(.*/gi;
+
+/**
+ * escape doube quote
+ *
+ * @param {String} str
+ * @return {String} str
+ */
+function escapeQuote(str) {
+ return str.replace(REGEXP_QUOTE, "&quot;");
+}
+
+/**
+ * unescape double quote
+ *
+ * @param {String} str
+ * @return {String} str
+ */
+function unescapeQuote(str) {
+ return str.replace(REGEXP_QUOTE_2, '"');
+}
+
+/**
+ * escape html entities
+ *
+ * @param {String} str
+ * @return {String}
+ */
+function escapeHtmlEntities(str) {
+ return str.replace(REGEXP_ATTR_VALUE_1, function replaceUnicode(str, code) {
+ return code[0] === "x" || code[0] === "X"
+ ? String.fromCharCode(parseInt(code.substr(1), 16))
+ : String.fromCharCode(parseInt(code, 10));
+ });
+}
+
+/**
+ * escape html5 new danger entities
+ *
+ * @param {String} str
+ * @return {String}
+ */
+function escapeDangerHtml5Entities(str) {
+ return str
+ .replace(REGEXP_ATTR_VALUE_COLON, ":")
+ .replace(REGEXP_ATTR_VALUE_NEWLINE, " ");
+}
+
+/**
+ * clear nonprintable characters
+ *
+ * @param {String} str
+ * @return {String}
+ */
+function clearNonPrintableCharacter(str) {
+ var str2 = "";
+ for (var i = 0, len = str.length; i < len; i++) {
+ str2 += str.charCodeAt(i) < 32 ? " " : str.charAt(i);
+ }
+ return _.trim(str2);
+}
+
+/**
+ * get friendly attribute value
+ *
+ * @param {String} str
+ * @return {String}
+ */
+function friendlyAttrValue(str) {
+ str = unescapeQuote(str);
+ str = escapeHtmlEntities(str);
+ str = escapeDangerHtml5Entities(str);
+ str = clearNonPrintableCharacter(str);
+ return str;
+}
+
+/**
+ * unescape attribute value
+ *
+ * @param {String} str
+ * @return {String}
+ */
+function escapeAttrValue(str) {
+ str = escapeQuote(str);
+ str = escapeHtml(str);
+ return str;
+}
+
+/**
+ * `onIgnoreTag` function for removing all the tags that are not in whitelist
+ */
+function onIgnoreTagStripAll() {
+ return "";
+}
+
+/**
+ * remove tag body
+ * specify a `tags` list, if the tag is not in the `tags` list then process by the specify function (optional)
+ *
+ * @param {array} tags
+ * @param {function} next
+ */
+function StripTagBody(tags, next) {
+ if (typeof next !== "function") {
+ next = function() {};
+ }
+
+ var isRemoveAllTag = !Array.isArray(tags);
+ function isRemoveTag(tag) {
+ if (isRemoveAllTag) return true;
+ return _.indexOf(tags, tag) !== -1;
+ }
+
+ var removeList = [];
+ var posStart = false;
+
+ return {
+ onIgnoreTag: function(tag, html, options) {
+ if (isRemoveTag(tag)) {
+ if (options.isClosing) {
+ var ret = "[/removed]";
+ var end = options.position + ret.length;
+ removeList.push([
+ posStart !== false ? posStart : options.position,
+ end
+ ]);
+ posStart = false;
+ return ret;
+ } else {
+ if (!posStart) {
+ posStart = options.position;
+ }
+ return "[removed]";
+ }
+ } else {
+ return next(tag, html, options);
+ }
+ },
+ remove: function(html) {
+ var rethtml = "";
+ var lastPos = 0;
+ _.forEach(removeList, function(pos) {
+ rethtml += html.slice(lastPos, pos[0]);
+ lastPos = pos[1];
+ });
+ rethtml += html.slice(lastPos);
+ return rethtml;
+ }
+ };
+}
+
+/**
+ * remove html comments
+ *
+ * @param {String} html
+ * @return {String}
+ */
+function stripCommentTag(html) {
+ return html.replace(STRIP_COMMENT_TAG_REGEXP, "");
+}
+var STRIP_COMMENT_TAG_REGEXP = /<!--[\s\S]*?-->/g;
+
+/**
+ * remove invisible characters
+ *
+ * @param {String} html
+ * @return {String}
+ */
+function stripBlankChar(html) {
+ var chars = html.split("");
+ chars = chars.filter(function(char) {
+ var c = char.charCodeAt(0);
+ if (c === 127) return false;
+ if (c <= 31) {
+ if (c === 10 || c === 13) return true;
+ return false;
+ }
+ return true;
+ });
+ return chars.join("");
+}
+
+exports.whiteList = getDefaultWhiteList();
+exports.getDefaultWhiteList = getDefaultWhiteList;
+exports.onTag = onTag;
+exports.onIgnoreTag = onIgnoreTag;
+exports.onTagAttr = onTagAttr;
+exports.onIgnoreTagAttr = onIgnoreTagAttr;
+exports.safeAttrValue = safeAttrValue;
+exports.escapeHtml = escapeHtml;
+exports.escapeQuote = escapeQuote;
+exports.unescapeQuote = unescapeQuote;
+exports.escapeHtmlEntities = escapeHtmlEntities;
+exports.escapeDangerHtml5Entities = escapeDangerHtml5Entities;
+exports.clearNonPrintableCharacter = clearNonPrintableCharacter;
+exports.friendlyAttrValue = friendlyAttrValue;
+exports.escapeAttrValue = escapeAttrValue;
+exports.onIgnoreTagStripAll = onIgnoreTagStripAll;
+exports.StripTagBody = StripTagBody;
+exports.stripCommentTag = stripCommentTag;
+exports.stripBlankChar = stripBlankChar;
+exports.cssFilter = defaultCSSFilter;
+exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList;
+
+},{"./util":4,"cssfilter":8}],2:[function(require,module,exports){
+/**
+ * xss
+ *
+ * @author Zongmin Lei<leizongmin@gmail.com>
+ */
+
+var DEFAULT = require("./default");
+var parser = require("./parser");
+var FilterXSS = require("./xss");
+
+/**
+ * filter xss function
+ *
+ * @param {String} html
+ * @param {Object} options { whiteList, onTag, onTagAttr, onIgnoreTag, onIgnoreTagAttr, safeAttrValue, escapeHtml }
+ * @return {String}
+ */
+function filterXSS(html, options) {
+ var xss = new FilterXSS(options);
+ return xss.process(html);
+}
+
+exports = module.exports = filterXSS;
+exports.filterXSS = filterXSS;
+exports.FilterXSS = FilterXSS;
+for (var i in DEFAULT) exports[i] = DEFAULT[i];
+for (var i in parser) exports[i] = parser[i];
+
+// using `xss` on the browser, output `filterXSS` to the globals
+if (typeof window !== "undefined") {
+ window.filterXSS = module.exports;
+}
+
+// using `xss` on the WebWorker, output `filterXSS` to the globals
+function isWorkerEnv() {
+ return typeof self !== 'undefined' && typeof DedicatedWorkerGlobalScope !== 'undefined' && self instanceof DedicatedWorkerGlobalScope;
+}
+if (isWorkerEnv()) {
+ self.filterXSS = module.exports;
+}
+
+},{"./default":1,"./parser":3,"./xss":5}],3:[function(require,module,exports){
+/**
+ * Simple HTML Parser
+ *
+ * @author Zongmin Lei<leizongmin@gmail.com>
+ */
+
+var _ = require("./util");
+
+/**
+ * get tag name
+ *
+ * @param {String} html e.g. '<a hef="#">'
+ * @return {String}
+ */
+function getTagName(html) {
+ var i = _.spaceIndex(html);
+ if (i === -1) {
+ var tagName = html.slice(1, -1);
+ } else {
+ var tagName = html.slice(1, i + 1);
+ }
+ tagName = _.trim(tagName).toLowerCase();
+ if (tagName.slice(0, 1) === "/") tagName = tagName.slice(1);
+ if (tagName.slice(-1) === "/") tagName = tagName.slice(0, -1);
+ return tagName;
+}
+
+/**
+ * is close tag?
+ *
+ * @param {String} html 如:'<a hef="#">'
+ * @return {Boolean}
+ */
+function isClosing(html) {
+ return html.slice(0, 2) === "</";
+}
+
+/**
+ * parse input html and returns processed html
+ *
+ * @param {String} html
+ * @param {Function} onTag e.g. function (sourcePosition, position, tag, html, isClosing)
+ * @param {Function} escapeHtml
+ * @return {String}
+ */
+function parseTag(html, onTag, escapeHtml) {
+ "use strict";
+
+ var rethtml = "";
+ var lastPos = 0;
+ var tagStart = false;
+ var quoteStart = false;
+ var currentPos = 0;
+ var len = html.length;
+ var currentTagName = "";
+ var currentHtml = "";
+
+ for (currentPos = 0; currentPos < len; currentPos++) {
+ var c = html.charAt(currentPos);
+ if (tagStart === false) {
+ if (c === "<") {
+ tagStart = currentPos;
+ continue;
+ }
+ } else {
+ if (quoteStart === false) {
+ if (c === "<") {
+ rethtml += escapeHtml(html.slice(lastPos, currentPos));
+ tagStart = currentPos;
+ lastPos = currentPos;
+ continue;
+ }
+ if (c === ">") {
+ rethtml += escapeHtml(html.slice(lastPos, tagStart));
+ currentHtml = html.slice(tagStart, currentPos + 1);
+ currentTagName = getTagName(currentHtml);
+ rethtml += onTag(
+ tagStart,
+ rethtml.length,
+ currentTagName,
+ currentHtml,
+ isClosing(currentHtml)
+ );
+ lastPos = currentPos + 1;
+ tagStart = false;
+ continue;
+ }
+ if ((c === '"' || c === "'") && html.charAt(currentPos - 1) === "=") {
+ quoteStart = c;
+ continue;
+ }
+ } else {
+ if (c === quoteStart) {
+ quoteStart = false;
+ continue;
+ }
+ }
+ }
+ }
+ if (lastPos < html.length) {
+ rethtml += escapeHtml(html.substr(lastPos));
+ }
+
+ return rethtml;
+}
+
+var REGEXP_ILLEGAL_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/gim;
+
+/**
+ * parse input attributes and returns processed attributes
+ *
+ * @param {String} html e.g. `href="#" target="_blank"`
+ * @param {Function} onAttr e.g. `function (name, value)`
+ * @return {String}
+ */
+function parseAttr(html, onAttr) {
+ "use strict";
+
+ var lastPos = 0;
+ var retAttrs = [];
+ var tmpName = false;
+ var len = html.length;
+
+ function addAttr(name, value) {
+ name = _.trim(name);
+ name = name.replace(REGEXP_ILLEGAL_ATTR_NAME, "").toLowerCase();
+ if (name.length < 1) return;
+ var ret = onAttr(name, value || "");
+ if (ret) retAttrs.push(ret);
+ }
+
+ // 逐个分析字符
+ for (var i = 0; i < len; i++) {
+ var c = html.charAt(i);
+ var v, j;
+ if (tmpName === false && c === "=") {
+ tmpName = html.slice(lastPos, i);
+ lastPos = i + 1;
+ continue;
+ }
+ if (tmpName !== false) {
+ if (
+ i === lastPos &&
+ (c === '"' || c === "'") &&
+ html.charAt(i - 1) === "="
+ ) {
+ j = html.indexOf(c, i + 1);
+ if (j === -1) {
+ break;
+ } else {
+ v = _.trim(html.slice(lastPos + 1, j));
+ addAttr(tmpName, v);
+ tmpName = false;
+ i = j;
+ lastPos = i + 1;
+ continue;
+ }
+ }
+ }
+ if (/\s|\n|\t/.test(c)) {
+ html = html.replace(/\s|\n|\t/g, " ");
+ if (tmpName === false) {
+ j = findNextEqual(html, i);
+ if (j === -1) {
+ v = _.trim(html.slice(lastPos, i));
+ addAttr(v);
+ tmpName = false;
+ lastPos = i + 1;
+ continue;
+ } else {
+ i = j - 1;
+ continue;
+ }
+ } else {
+ j = findBeforeEqual(html, i - 1);
+ if (j === -1) {
+ v = _.trim(html.slice(lastPos, i));
+ v = stripQuoteWrap(v);
+ addAttr(tmpName, v);
+ tmpName = false;
+ lastPos = i + 1;
+ continue;
+ } else {
+ continue;
+ }
+ }
+ }
+ }
+
+ if (lastPos < html.length) {
+ if (tmpName === false) {
+ addAttr(html.slice(lastPos));
+ } else {
+ addAttr(tmpName, stripQuoteWrap(_.trim(html.slice(lastPos))));
+ }
+ }
+
+ return _.trim(retAttrs.join(" "));
+}
+
+function findNextEqual(str, i) {
+ for (; i < str.length; i++) {
+ var c = str[i];
+ if (c === " ") continue;
+ if (c === "=") return i;
+ return -1;
+ }
+}
+
+function findBeforeEqual(str, i) {
+ for (; i > 0; i--) {
+ var c = str[i];
+ if (c === " ") continue;
+ if (c === "=") return i;
+ return -1;
+ }
+}
+
+function isQuoteWrapString(text) {
+ if (
+ (text[0] === '"' && text[text.length - 1] === '"') ||
+ (text[0] === "'" && text[text.length - 1] === "'")
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+function stripQuoteWrap(text) {
+ if (isQuoteWrapString(text)) {
+ return text.substr(1, text.length - 2);
+ } else {
+ return text;
+ }
+}
+
+exports.parseTag = parseTag;
+exports.parseAttr = parseAttr;
+
+},{"./util":4}],4:[function(require,module,exports){
+module.exports = {
+ indexOf: function(arr, item) {
+ var i, j;
+ if (Array.prototype.indexOf) {
+ return arr.indexOf(item);
+ }
+ for (i = 0, j = arr.length; i < j; i++) {
+ if (arr[i] === item) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ forEach: function(arr, fn, scope) {
+ var i, j;
+ if (Array.prototype.forEach) {
+ return arr.forEach(fn, scope);
+ }
+ for (i = 0, j = arr.length; i < j; i++) {
+ fn.call(scope, arr[i], i, arr);
+ }
+ },
+ trim: function(str) {
+ if (String.prototype.trim) {
+ return str.trim();
+ }
+ return str.replace(/(^\s*)|(\s*$)/g, "");
+ },
+ spaceIndex: function(str) {
+ var reg = /\s|\n|\t/;
+ var match = reg.exec(str);
+ return match ? match.index : -1;
+ }
+};
+
+},{}],5:[function(require,module,exports){
+/**
+ * filter xss
+ *
+ * @author Zongmin Lei<leizongmin@gmail.com>
+ */
+
+var FilterCSS = require("cssfilter").FilterCSS;
+var DEFAULT = require("./default");
+var parser = require("./parser");
+var parseTag = parser.parseTag;
+var parseAttr = parser.parseAttr;
+var _ = require("./util");
+
+/**
+ * returns `true` if the input value is `undefined` or `null`
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+function isNull(obj) {
+ return obj === undefined || obj === null;
+}
+
+/**
+ * get attributes for a tag
+ *
+ * @param {String} html
+ * @return {Object}
+ * - {String} html
+ * - {Boolean} closing
+ */
+function getAttrs(html) {
+ var i = _.spaceIndex(html);
+ if (i === -1) {
+ return {
+ html: "",
+ closing: html[html.length - 2] === "/"
+ };
+ }
+ html = _.trim(html.slice(i + 1, -1));
+ var isClosing = html[html.length - 1] === "/";
+ if (isClosing) html = _.trim(html.slice(0, -1));
+ return {
+ html: html,
+ closing: isClosing
+ };
+}
+
+/**
+ * shallow copy
+ *
+ * @param {Object} obj
+ * @return {Object}
+ */
+function shallowCopyObject(obj) {
+ var ret = {};
+ for (var i in obj) {
+ ret[i] = obj[i];
+ }
+ return ret;
+}
+
+/**
+ * FilterXSS class
+ *
+ * @param {Object} options
+ * whiteList, onTag, onTagAttr, onIgnoreTag,
+ * onIgnoreTagAttr, safeAttrValue, escapeHtml
+ * stripIgnoreTagBody, allowCommentTag, stripBlankChar
+ * css{whiteList, onAttr, onIgnoreAttr} `css=false` means don't use `cssfilter`
+ */
+function FilterXSS(options) {
+ options = shallowCopyObject(options || {});
+
+ if (options.stripIgnoreTag) {
+ if (options.onIgnoreTag) {
+ console.error(
+ 'Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'
+ );
+ }
+ options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;
+ }
+
+ options.whiteList = options.whiteList || DEFAULT.whiteList;
+ options.onTag = options.onTag || DEFAULT.onTag;
+ options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr;
+ options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag;
+ options.onIgnoreTagAttr = options.onIgnoreTagAttr || DEFAULT.onIgnoreTagAttr;
+ options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
+ options.escapeHtml = options.escapeHtml || DEFAULT.escapeHtml;
+ this.options = options;
+
+ if (options.css === false) {
+ this.cssFilter = false;
+ } else {
+ options.css = options.css || {};
+ this.cssFilter = new FilterCSS(options.css);
+ }
+}
+
+/**
+ * start process and returns result
+ *
+ * @param {String} html
+ * @return {String}
+ */
+FilterXSS.prototype.process = function(html) {
+ // compatible with the input
+ html = html || "";
+ html = html.toString();
+ if (!html) return "";
+
+ var me = this;
+ var options = me.options;
+ var whiteList = options.whiteList;
+ var onTag = options.onTag;
+ var onIgnoreTag = options.onIgnoreTag;
+ var onTagAttr = options.onTagAttr;
+ var onIgnoreTagAttr = options.onIgnoreTagAttr;
+ var safeAttrValue = options.safeAttrValue;
+ var escapeHtml = options.escapeHtml;
+ var cssFilter = me.cssFilter;
+
+ // remove invisible characters
+ if (options.stripBlankChar) {
+ html = DEFAULT.stripBlankChar(html);
+ }
+
+ // remove html comments
+ if (!options.allowCommentTag) {
+ html = DEFAULT.stripCommentTag(html);
+ }
+
+ // if enable stripIgnoreTagBody
+ var stripIgnoreTagBody = false;
+ if (options.stripIgnoreTagBody) {
+ var stripIgnoreTagBody = DEFAULT.StripTagBody(
+ options.stripIgnoreTagBody,
+ onIgnoreTag
+ );
+ onIgnoreTag = stripIgnoreTagBody.onIgnoreTag;
+ }
+
+ var retHtml = parseTag(
+ html,
+ function(sourcePosition, position, tag, html, isClosing) {
+ var info = {
+ sourcePosition: sourcePosition,
+ position: position,
+ isClosing: isClosing,
+ isWhite: whiteList.hasOwnProperty(tag)
+ };
+
+ // call `onTag()`
+ var ret = onTag(tag, html, info);
+ if (!isNull(ret)) return ret;
+
+ if (info.isWhite) {
+ if (info.isClosing) {
+ return "</" + tag + ">";
+ }
+
+ var attrs = getAttrs(html);
+ var whiteAttrList = whiteList[tag];
+ var attrsHtml = parseAttr(attrs.html, function(name, value) {
+ // call `onTagAttr()`
+ var isWhiteAttr = _.indexOf(whiteAttrList, name) !== -1;
+ var ret = onTagAttr(tag, name, value, isWhiteAttr);
+ if (!isNull(ret)) return ret;
+
+ if (isWhiteAttr) {
+ // call `safeAttrValue()`
+ value = safeAttrValue(tag, name, value, cssFilter);
+ if (value) {
+ return name + '="' + value + '"';
+ } else {
+ return name;
+ }
+ } else {
+ // call `onIgnoreTagAttr()`
+ var ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);
+ if (!isNull(ret)) return ret;
+ return;
+ }
+ });
+
+ // build new tag html
+ var html = "<" + tag;
+ if (attrsHtml) html += " " + attrsHtml;
+ if (attrs.closing) html += " /";
+ html += ">";
+ return html;
+ } else {
+ // call `onIgnoreTag()`
+ var ret = onIgnoreTag(tag, html, info);
+ if (!isNull(ret)) return ret;
+ return escapeHtml(html);
+ }
+ },
+ escapeHtml
+ );
+
+ // if enable stripIgnoreTagBody
+ if (stripIgnoreTagBody) {
+ retHtml = stripIgnoreTagBody.remove(retHtml);
+ }
+
+ return retHtml;
+};
+
+module.exports = FilterXSS;
+
+},{"./default":1,"./parser":3,"./util":4,"cssfilter":8}],6:[function(require,module,exports){
+/**
+ * cssfilter
+ *
+ * @author 老雷<leizongmin@gmail.com>
+ */
+
+var DEFAULT = require('./default');
+var parseStyle = require('./parser');
+var _ = require('./util');
+
+
+/**
+ * 返回值是否为空
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+function isNull (obj) {
+ return (obj === undefined || obj === null);
+}
+
+/**
+ * 浅拷贝对象
+ *
+ * @param {Object} obj
+ * @return {Object}
+ */
+function shallowCopyObject (obj) {
+ var ret = {};
+ for (var i in obj) {
+ ret[i] = obj[i];
+ }
+ return ret;
+}
+
+/**
+ * 创建CSS过滤器
+ *
+ * @param {Object} options
+ * - {Object} whiteList
+ * - {Function} onAttr
+ * - {Function} onIgnoreAttr
+ * - {Function} safeAttrValue
+ */
+function FilterCSS (options) {
+ options = shallowCopyObject(options || {});
+ options.whiteList = options.whiteList || DEFAULT.whiteList;
+ options.onAttr = options.onAttr || DEFAULT.onAttr;
+ options.onIgnoreAttr = options.onIgnoreAttr || DEFAULT.onIgnoreAttr;
+ options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
+ this.options = options;
+}
+
+FilterCSS.prototype.process = function (css) {
+ // 兼容各种奇葩输入
+ css = css || '';
+ css = css.toString();
+ if (!css) return '';
+
+ var me = this;
+ var options = me.options;
+ var whiteList = options.whiteList;
+ var onAttr = options.onAttr;
+ var onIgnoreAttr = options.onIgnoreAttr;
+ var safeAttrValue = options.safeAttrValue;
+
+ var retCSS = parseStyle(css, function (sourcePosition, position, name, value, source) {
+
+ var check = whiteList[name];
+ var isWhite = false;
+ if (check === true) isWhite = check;
+ else if (typeof check === 'function') isWhite = check(value);
+ else if (check instanceof RegExp) isWhite = check.test(value);
+ if (isWhite !== true) isWhite = false;
+
+ // 如果过滤后 value 为空则直接忽略
+ value = safeAttrValue(name, value);
+ if (!value) return;
+
+ var opts = {
+ position: position,
+ sourcePosition: sourcePosition,
+ source: source,
+ isWhite: isWhite
+ };
+
+ if (isWhite) {
+
+ var ret = onAttr(name, value, opts);
+ if (isNull(ret)) {
+ return name + ':' + value;
+ } else {
+ return ret;
+ }
+
+ } else {
+
+ var ret = onIgnoreAttr(name, value, opts);
+ if (!isNull(ret)) {
+ return ret;
+ }
+
+ }
+ });
+
+ return retCSS;
+};
+
+
+module.exports = FilterCSS;
+
+},{"./default":7,"./parser":9,"./util":10}],7:[function(require,module,exports){
+/**
+ * cssfilter
+ *
+ * @author 老雷<leizongmin@gmail.com>
+ */
+
+function getDefaultWhiteList () {
+ // 白名单值说明:
+ // true: 允许该属性
+ // Function: function (val) { } 返回true表示允许该属性,其他值均表示不允许
+ // RegExp: regexp.test(val) 返回true表示允许该属性,其他值均表示不允许
+ // 除上面列出的值外均表示不允许
+ var whiteList = {};
+
+ whiteList['align-content'] = false; // default: auto
+ whiteList['align-items'] = false; // default: auto
+ whiteList['align-self'] = false; // default: auto
+ whiteList['alignment-adjust'] = false; // default: auto
+ whiteList['alignment-baseline'] = false; // default: baseline
+ whiteList['all'] = false; // default: depending on individual properties
+ whiteList['anchor-point'] = false; // default: none
+ whiteList['animation'] = false; // default: depending on individual properties
+ whiteList['animation-delay'] = false; // default: 0
+ whiteList['animation-direction'] = false; // default: normal
+ whiteList['animation-duration'] = false; // default: 0
+ whiteList['animation-fill-mode'] = false; // default: none
+ whiteList['animation-iteration-count'] = false; // default: 1
+ whiteList['animation-name'] = false; // default: none
+ whiteList['animation-play-state'] = false; // default: running
+ whiteList['animation-timing-function'] = false; // default: ease
+ whiteList['azimuth'] = false; // default: center
+ whiteList['backface-visibility'] = false; // default: visible
+ whiteList['background'] = true; // default: depending on individual properties
+ whiteList['background-attachment'] = true; // default: scroll
+ whiteList['background-clip'] = true; // default: border-box
+ whiteList['background-color'] = true; // default: transparent
+ whiteList['background-image'] = true; // default: none
+ whiteList['background-origin'] = true; // default: padding-box
+ whiteList['background-position'] = true; // default: 0% 0%
+ whiteList['background-repeat'] = true; // default: repeat
+ whiteList['background-size'] = true; // default: auto
+ whiteList['baseline-shift'] = false; // default: baseline
+ whiteList['binding'] = false; // default: none
+ whiteList['bleed'] = false; // default: 6pt
+ whiteList['bookmark-label'] = false; // default: content()
+ whiteList['bookmark-level'] = false; // default: none
+ whiteList['bookmark-state'] = false; // default: open
+ whiteList['border'] = true; // default: depending on individual properties
+ whiteList['border-bottom'] = true; // default: depending on individual properties
+ whiteList['border-bottom-color'] = true; // default: current color
+ whiteList['border-bottom-left-radius'] = true; // default: 0
+ whiteList['border-bottom-right-radius'] = true; // default: 0
+ whiteList['border-bottom-style'] = true; // default: none
+ whiteList['border-bottom-width'] = true; // default: medium
+ whiteList['border-collapse'] = true; // default: separate
+ whiteList['border-color'] = true; // default: depending on individual properties
+ whiteList['border-image'] = true; // default: none
+ whiteList['border-image-outset'] = true; // default: 0
+ whiteList['border-image-repeat'] = true; // default: stretch
+ whiteList['border-image-slice'] = true; // default: 100%
+ whiteList['border-image-source'] = true; // default: none
+ whiteList['border-image-width'] = true; // default: 1
+ whiteList['border-left'] = true; // default: depending on individual properties
+ whiteList['border-left-color'] = true; // default: current color
+ whiteList['border-left-style'] = true; // default: none
+ whiteList['border-left-width'] = true; // default: medium
+ whiteList['border-radius'] = true; // default: 0
+ whiteList['border-right'] = true; // default: depending on individual properties
+ whiteList['border-right-color'] = true; // default: current color
+ whiteList['border-right-style'] = true; // default: none
+ whiteList['border-right-width'] = true; // default: medium
+ whiteList['border-spacing'] = true; // default: 0
+ whiteList['border-style'] = true; // default: depending on individual properties
+ whiteList['border-top'] = true; // default: depending on individual properties
+ whiteList['border-top-color'] = true; // default: current color
+ whiteList['border-top-left-radius'] = true; // default: 0
+ whiteList['border-top-right-radius'] = true; // default: 0
+ whiteList['border-top-style'] = true; // default: none
+ whiteList['border-top-width'] = true; // default: medium
+ whiteList['border-width'] = true; // default: depending on individual properties
+ whiteList['bottom'] = false; // default: auto
+ whiteList['box-decoration-break'] = true; // default: slice
+ whiteList['box-shadow'] = true; // default: none
+ whiteList['box-sizing'] = true; // default: content-box
+ whiteList['box-snap'] = true; // default: none
+ whiteList['box-suppress'] = true; // default: show
+ whiteList['break-after'] = true; // default: auto
+ whiteList['break-before'] = true; // default: auto
+ whiteList['break-inside'] = true; // default: auto
+ whiteList['caption-side'] = false; // default: top
+ whiteList['chains'] = false; // default: none
+ whiteList['clear'] = true; // default: none
+ whiteList['clip'] = false; // default: auto
+ whiteList['clip-path'] = false; // default: none
+ whiteList['clip-rule'] = false; // default: nonzero
+ whiteList['color'] = true; // default: implementation dependent
+ whiteList['color-interpolation-filters'] = true; // default: auto
+ whiteList['column-count'] = false; // default: auto
+ whiteList['column-fill'] = false; // default: balance
+ whiteList['column-gap'] = false; // default: normal
+ whiteList['column-rule'] = false; // default: depending on individual properties
+ whiteList['column-rule-color'] = false; // default: current color
+ whiteList['column-rule-style'] = false; // default: medium
+ whiteList['column-rule-width'] = false; // default: medium
+ whiteList['column-span'] = false; // default: none
+ whiteList['column-width'] = false; // default: auto
+ whiteList['columns'] = false; // default: depending on individual properties
+ whiteList['contain'] = false; // default: none
+ whiteList['content'] = false; // default: normal
+ whiteList['counter-increment'] = false; // default: none
+ whiteList['counter-reset'] = false; // default: none
+ whiteList['counter-set'] = false; // default: none
+ whiteList['crop'] = false; // default: auto
+ whiteList['cue'] = false; // default: depending on individual properties
+ whiteList['cue-after'] = false; // default: none
+ whiteList['cue-before'] = false; // default: none
+ whiteList['cursor'] = false; // default: auto
+ whiteList['direction'] = false; // default: ltr
+ whiteList['display'] = true; // default: depending on individual properties
+ whiteList['display-inside'] = true; // default: auto
+ whiteList['display-list'] = true; // default: none
+ whiteList['display-outside'] = true; // default: inline-level
+ whiteList['dominant-baseline'] = false; // default: auto
+ whiteList['elevation'] = false; // default: level
+ whiteList['empty-cells'] = false; // default: show
+ whiteList['filter'] = false; // default: none
+ whiteList['flex'] = false; // default: depending on individual properties
+ whiteList['flex-basis'] = false; // default: auto
+ whiteList['flex-direction'] = false; // default: row
+ whiteList['flex-flow'] = false; // default: depending on individual properties
+ whiteList['flex-grow'] = false; // default: 0
+ whiteList['flex-shrink'] = false; // default: 1
+ whiteList['flex-wrap'] = false; // default: nowrap
+ whiteList['float'] = false; // default: none
+ whiteList['float-offset'] = false; // default: 0 0
+ whiteList['flood-color'] = false; // default: black
+ whiteList['flood-opacity'] = false; // default: 1
+ whiteList['flow-from'] = false; // default: none
+ whiteList['flow-into'] = false; // default: none
+ whiteList['font'] = true; // default: depending on individual properties
+ whiteList['font-family'] = true; // default: implementation dependent
+ whiteList['font-feature-settings'] = true; // default: normal
+ whiteList['font-kerning'] = true; // default: auto
+ whiteList['font-language-override'] = true; // default: normal
+ whiteList['font-size'] = true; // default: medium
+ whiteList['font-size-adjust'] = true; // default: none
+ whiteList['font-stretch'] = true; // default: normal
+ whiteList['font-style'] = true; // default: normal
+ whiteList['font-synthesis'] = true; // default: weight style
+ whiteList['font-variant'] = true; // default: normal
+ whiteList['font-variant-alternates'] = true; // default: normal
+ whiteList['font-variant-caps'] = true; // default: normal
+ whiteList['font-variant-east-asian'] = true; // default: normal
+ whiteList['font-variant-ligatures'] = true; // default: normal
+ whiteList['font-variant-numeric'] = true; // default: normal
+ whiteList['font-variant-position'] = true; // default: normal
+ whiteList['font-weight'] = true; // default: normal
+ whiteList['grid'] = false; // default: depending on individual properties
+ whiteList['grid-area'] = false; // default: depending on individual properties
+ whiteList['grid-auto-columns'] = false; // default: auto
+ whiteList['grid-auto-flow'] = false; // default: none
+ whiteList['grid-auto-rows'] = false; // default: auto
+ whiteList['grid-column'] = false; // default: depending on individual properties
+ whiteList['grid-column-end'] = false; // default: auto
+ whiteList['grid-column-start'] = false; // default: auto
+ whiteList['grid-row'] = false; // default: depending on individual properties
+ whiteList['grid-row-end'] = false; // default: auto
+ whiteList['grid-row-start'] = false; // default: auto
+ whiteList['grid-template'] = false; // default: depending on individual properties
+ whiteList['grid-template-areas'] = false; // default: none
+ whiteList['grid-template-columns'] = false; // default: none
+ whiteList['grid-template-rows'] = false; // default: none
+ whiteList['hanging-punctuation'] = false; // default: none
+ whiteList['height'] = true; // default: auto
+ whiteList['hyphens'] = false; // default: manual
+ whiteList['icon'] = false; // default: auto
+ whiteList['image-orientation'] = false; // default: auto
+ whiteList['image-resolution'] = false; // default: normal
+ whiteList['ime-mode'] = false; // default: auto
+ whiteList['initial-letters'] = false; // default: normal
+ whiteList['inline-box-align'] = false; // default: last
+ whiteList['justify-content'] = false; // default: auto
+ whiteList['justify-items'] = false; // default: auto
+ whiteList['justify-self'] = false; // default: auto
+ whiteList['left'] = false; // default: auto
+ whiteList['letter-spacing'] = true; // default: normal
+ whiteList['lighting-color'] = true; // default: white
+ whiteList['line-box-contain'] = false; // default: block inline replaced
+ whiteList['line-break'] = false; // default: auto
+ whiteList['line-grid'] = false; // default: match-parent
+ whiteList['line-height'] = false; // default: normal
+ whiteList['line-snap'] = false; // default: none
+ whiteList['line-stacking'] = false; // default: depending on individual properties
+ whiteList['line-stacking-ruby'] = false; // default: exclude-ruby
+ whiteList['line-stacking-shift'] = false; // default: consider-shifts
+ whiteList['line-stacking-strategy'] = false; // default: inline-line-height
+ whiteList['list-style'] = true; // default: depending on individual properties
+ whiteList['list-style-image'] = true; // default: none
+ whiteList['list-style-position'] = true; // default: outside
+ whiteList['list-style-type'] = true; // default: disc
+ whiteList['margin'] = true; // default: depending on individual properties
+ whiteList['margin-bottom'] = true; // default: 0
+ whiteList['margin-left'] = true; // default: 0
+ whiteList['margin-right'] = true; // default: 0
+ whiteList['margin-top'] = true; // default: 0
+ whiteList['marker-offset'] = false; // default: auto
+ whiteList['marker-side'] = false; // default: list-item
+ whiteList['marks'] = false; // default: none
+ whiteList['mask'] = false; // default: border-box
+ whiteList['mask-box'] = false; // default: see individual properties
+ whiteList['mask-box-outset'] = false; // default: 0
+ whiteList['mask-box-repeat'] = false; // default: stretch
+ whiteList['mask-box-slice'] = false; // default: 0 fill
+ whiteList['mask-box-source'] = false; // default: none
+ whiteList['mask-box-width'] = false; // default: auto
+ whiteList['mask-clip'] = false; // default: border-box
+ whiteList['mask-image'] = false; // default: none
+ whiteList['mask-origin'] = false; // default: border-box
+ whiteList['mask-position'] = false; // default: center
+ whiteList['mask-repeat'] = false; // default: no-repeat
+ whiteList['mask-size'] = false; // default: border-box
+ whiteList['mask-source-type'] = false; // default: auto
+ whiteList['mask-type'] = false; // default: luminance
+ whiteList['max-height'] = true; // default: none
+ whiteList['max-lines'] = false; // default: none
+ whiteList['max-width'] = true; // default: none
+ whiteList['min-height'] = true; // default: 0
+ whiteList['min-width'] = true; // default: 0
+ whiteList['move-to'] = false; // default: normal
+ whiteList['nav-down'] = false; // default: auto
+ whiteList['nav-index'] = false; // default: auto
+ whiteList['nav-left'] = false; // default: auto
+ whiteList['nav-right'] = false; // default: auto
+ whiteList['nav-up'] = false; // default: auto
+ whiteList['object-fit'] = false; // default: fill
+ whiteList['object-position'] = false; // default: 50% 50%
+ whiteList['opacity'] = false; // default: 1
+ whiteList['order'] = false; // default: 0
+ whiteList['orphans'] = false; // default: 2
+ whiteList['outline'] = false; // default: depending on individual properties
+ whiteList['outline-color'] = false; // default: invert
+ whiteList['outline-offset'] = false; // default: 0
+ whiteList['outline-style'] = false; // default: none
+ whiteList['outline-width'] = false; // default: medium
+ whiteList['overflow'] = false; // default: depending on individual properties
+ whiteList['overflow-wrap'] = false; // default: normal
+ whiteList['overflow-x'] = false; // default: visible
+ whiteList['overflow-y'] = false; // default: visible
+ whiteList['padding'] = true; // default: depending on individual properties
+ whiteList['padding-bottom'] = true; // default: 0
+ whiteList['padding-left'] = true; // default: 0
+ whiteList['padding-right'] = true; // default: 0
+ whiteList['padding-top'] = true; // default: 0
+ whiteList['page'] = false; // default: auto
+ whiteList['page-break-after'] = false; // default: auto
+ whiteList['page-break-before'] = false; // default: auto
+ whiteList['page-break-inside'] = false; // default: auto
+ whiteList['page-policy'] = false; // default: start
+ whiteList['pause'] = false; // default: implementation dependent
+ whiteList['pause-after'] = false; // default: implementation dependent
+ whiteList['pause-before'] = false; // default: implementation dependent
+ whiteList['perspective'] = false; // default: none
+ whiteList['perspective-origin'] = false; // default: 50% 50%
+ whiteList['pitch'] = false; // default: medium
+ whiteList['pitch-range'] = false; // default: 50
+ whiteList['play-during'] = false; // default: auto
+ whiteList['position'] = false; // default: static
+ whiteList['presentation-level'] = false; // default: 0
+ whiteList['quotes'] = false; // default: text
+ whiteList['region-fragment'] = false; // default: auto
+ whiteList['resize'] = false; // default: none
+ whiteList['rest'] = false; // default: depending on individual properties
+ whiteList['rest-after'] = false; // default: none
+ whiteList['rest-before'] = false; // default: none
+ whiteList['richness'] = false; // default: 50
+ whiteList['right'] = false; // default: auto
+ whiteList['rotation'] = false; // default: 0
+ whiteList['rotation-point'] = false; // default: 50% 50%
+ whiteList['ruby-align'] = false; // default: auto
+ whiteList['ruby-merge'] = false; // default: separate
+ whiteList['ruby-position'] = false; // default: before
+ whiteList['shape-image-threshold'] = false; // default: 0.0
+ whiteList['shape-outside'] = false; // default: none
+ whiteList['shape-margin'] = false; // default: 0
+ whiteList['size'] = false; // default: auto
+ whiteList['speak'] = false; // default: auto
+ whiteList['speak-as'] = false; // default: normal
+ whiteList['speak-header'] = false; // default: once
+ whiteList['speak-numeral'] = false; // default: continuous
+ whiteList['speak-punctuation'] = false; // default: none
+ whiteList['speech-rate'] = false; // default: medium
+ whiteList['stress'] = false; // default: 50
+ whiteList['string-set'] = false; // default: none
+ whiteList['tab-size'] = false; // default: 8
+ whiteList['table-layout'] = false; // default: auto
+ whiteList['text-align'] = true; // default: start
+ whiteList['text-align-last'] = true; // default: auto
+ whiteList['text-combine-upright'] = true; // default: none
+ whiteList['text-decoration'] = true; // default: none
+ whiteList['text-decoration-color'] = true; // default: currentColor
+ whiteList['text-decoration-line'] = true; // default: none
+ whiteList['text-decoration-skip'] = true; // default: objects
+ whiteList['text-decoration-style'] = true; // default: solid
+ whiteList['text-emphasis'] = true; // default: depending on individual properties
+ whiteList['text-emphasis-color'] = true; // default: currentColor
+ whiteList['text-emphasis-position'] = true; // default: over right
+ whiteList['text-emphasis-style'] = true; // default: none
+ whiteList['text-height'] = true; // default: auto
+ whiteList['text-indent'] = true; // default: 0
+ whiteList['text-justify'] = true; // default: auto
+ whiteList['text-orientation'] = true; // default: mixed
+ whiteList['text-overflow'] = true; // default: clip
+ whiteList['text-shadow'] = true; // default: none
+ whiteList['text-space-collapse'] = true; // default: collapse
+ whiteList['text-transform'] = true; // default: none
+ whiteList['text-underline-position'] = true; // default: auto
+ whiteList['text-wrap'] = true; // default: normal
+ whiteList['top'] = false; // default: auto
+ whiteList['transform'] = false; // default: none
+ whiteList['transform-origin'] = false; // default: 50% 50% 0
+ whiteList['transform-style'] = false; // default: flat
+ whiteList['transition'] = false; // default: depending on individual properties
+ whiteList['transition-delay'] = false; // default: 0s
+ whiteList['transition-duration'] = false; // default: 0s
+ whiteList['transition-property'] = false; // default: all
+ whiteList['transition-timing-function'] = false; // default: ease
+ whiteList['unicode-bidi'] = false; // default: normal
+ whiteList['vertical-align'] = false; // default: baseline
+ whiteList['visibility'] = false; // default: visible
+ whiteList['voice-balance'] = false; // default: center
+ whiteList['voice-duration'] = false; // default: auto
+ whiteList['voice-family'] = false; // default: implementation dependent
+ whiteList['voice-pitch'] = false; // default: medium
+ whiteList['voice-range'] = false; // default: medium
+ whiteList['voice-rate'] = false; // default: normal
+ whiteList['voice-stress'] = false; // default: normal
+ whiteList['voice-volume'] = false; // default: medium
+ whiteList['volume'] = false; // default: medium
+ whiteList['white-space'] = false; // default: normal
+ whiteList['widows'] = false; // default: 2
+ whiteList['width'] = true; // default: auto
+ whiteList['will-change'] = false; // default: auto
+ whiteList['word-break'] = true; // default: normal
+ whiteList['word-spacing'] = true; // default: normal
+ whiteList['word-wrap'] = true; // default: normal
+ whiteList['wrap-flow'] = false; // default: auto
+ whiteList['wrap-through'] = false; // default: wrap
+ whiteList['writing-mode'] = false; // default: horizontal-tb
+ whiteList['z-index'] = false; // default: auto
+
+ return whiteList;
+}
+
+
+/**
+ * 匹配到白名单上的一个属性时
+ *
+ * @param {String} name
+ * @param {String} value
+ * @param {Object} options
+ * @return {String}
+ */
+function onAttr (name, value, options) {
+ // do nothing
+}
+
+/**
+ * 匹配到不在白名单上的一个属性时
+ *
+ * @param {String} name
+ * @param {String} value
+ * @param {Object} options
+ * @return {String}
+ */
+function onIgnoreAttr (name, value, options) {
+ // do nothing
+}
+
+var REGEXP_URL_JAVASCRIPT = /javascript\s*\:/img;
+
+/**
+ * 过滤属性值
+ *
+ * @param {String} name
+ * @param {String} value
+ * @return {String}
+ */
+function safeAttrValue(name, value) {
+ if (REGEXP_URL_JAVASCRIPT.test(value)) return '';
+ return value;
+}
+
+
+exports.whiteList = getDefaultWhiteList();
+exports.getDefaultWhiteList = getDefaultWhiteList;
+exports.onAttr = onAttr;
+exports.onIgnoreAttr = onIgnoreAttr;
+exports.safeAttrValue = safeAttrValue;
+
+},{}],8:[function(require,module,exports){
+/**
+ * cssfilter
+ *
+ * @author 老雷<leizongmin@gmail.com>
+ */
+
+var DEFAULT = require('./default');
+var FilterCSS = require('./css');
+
+
+/**
+ * XSS过滤
+ *
+ * @param {String} css 要过滤的CSS代码
+ * @param {Object} options 选项:whiteList, onAttr, onIgnoreAttr
+ * @return {String}
+ */
+function filterCSS (html, options) {
+ var xss = new FilterCSS(options);
+ return xss.process(html);
+}
+
+
+// 输出
+exports = module.exports = filterCSS;
+exports.FilterCSS = FilterCSS;
+for (var i in DEFAULT) exports[i] = DEFAULT[i];
+
+// 在浏览器端使用
+if (typeof window !== 'undefined') {
+ window.filterCSS = module.exports;
+}
+
+},{"./css":6,"./default":7}],9:[function(require,module,exports){
+/**
+ * cssfilter
+ *
+ * @author 老雷<leizongmin@gmail.com>
+ */
+
+var _ = require('./util');
+
+
+/**
+ * 解析style
+ *
+ * @param {String} css
+ * @param {Function} onAttr 处理属性的函数
+ * 参数格式: function (sourcePosition, position, name, value, source)
+ * @return {String}
+ */
+function parseStyle (css, onAttr) {
+ css = _.trimRight(css);
+ if (css[css.length - 1] !== ';') css += ';';
+ var cssLength = css.length;
+ var isParenthesisOpen = false;
+ var lastPos = 0;
+ var i = 0;
+ var retCSS = '';
+
+ function addNewAttr () {
+ // 如果没有正常的闭合圆括号,则直接忽略当前属性
+ if (!isParenthesisOpen) {
+ var source = _.trim(css.slice(lastPos, i));
+ var j = source.indexOf(':');
+ if (j !== -1) {
+ var name = _.trim(source.slice(0, j));
+ var value = _.trim(source.slice(j + 1));
+ // 必须有属性名称
+ if (name) {
+ var ret = onAttr(lastPos, retCSS.length, name, value, source);
+ if (ret) retCSS += ret + '; ';
+ }
+ }
+ }
+ lastPos = i + 1;
+ }
+
+ for (; i < cssLength; i++) {
+ var c = css[i];
+ if (c === '/' && css[i + 1] === '*') {
+ // 备注开始
+ var j = css.indexOf('*/', i + 2);
+ // 如果没有正常的备注结束,则后面的部分全部跳过
+ if (j === -1) break;
+ // 直接将当前位置调到备注结尾,并且初始化状态
+ i = j + 1;
+ lastPos = i + 1;
+ isParenthesisOpen = false;
+ } else if (c === '(') {
+ isParenthesisOpen = true;
+ } else if (c === ')') {
+ isParenthesisOpen = false;
+ } else if (c === ';') {
+ if (isParenthesisOpen) {
+ // 在圆括号里面,忽略
+ } else {
+ addNewAttr();
+ }
+ } else if (c === '\n') {
+ addNewAttr();
+ }
+ }
+
+ return _.trim(retCSS);
+}
+
+module.exports = parseStyle;
+
+},{"./util":10}],10:[function(require,module,exports){
+module.exports = {
+ indexOf: function (arr, item) {
+ var i, j;
+ if (Array.prototype.indexOf) {
+ return arr.indexOf(item);
+ }
+ for (i = 0, j = arr.length; i < j; i++) {
+ if (arr[i] === item) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ forEach: function (arr, fn, scope) {
+ var i, j;
+ if (Array.prototype.forEach) {
+ return arr.forEach(fn, scope);
+ }
+ for (i = 0, j = arr.length; i < j; i++) {
+ fn.call(scope, arr[i], i, arr);
+ }
+ },
+ trim: function (str) {
+ if (String.prototype.trim) {
+ return str.trim();
+ }
+ return str.replace(/(^\s*)|(\s*$)/g, '');
+ },
+ trimRight: function (str) {
+ if (String.prototype.trimRight) {
+ return str.trimRight();
+ }
+ return str.replace(/(\s*$)/g, '');
+ }
+};
+
+},{}]},{},[2]);
diff --git a/assets/js/login.js b/assets/js/login.js
new file mode 100644
index 0000000..ab59058
--- /dev/null
+++ b/assets/js/login.js
@@ -0,0 +1,59 @@
+// const API_ENDPOINT = "https://gimb.tk/test.php"; // deprecated
+document.addEventListener("DOMContentLoaded", () => {
+ setupEventListeners();
+})
+
+function setupEventListeners() {
+ // Setup login button listener
+ $("#login-button").click(() => {
+ login();
+ });
+
+ window.addEventListener("keyup", (event) => {
+ // Number 13 is the "Enter" key on the keyboard
+ if (event.keyCode === 13) {
+ // Cancel the default action, if needed
+ event.preventDefault();
+ login();
+ }
+ });
+}
+
+// Handle login button click
+function login() {
+ let username = $("#username").val();
+ let password = $("#password").val();
+ var gsecInstance;
+ try {
+ gsecInstance = new gsec();
+ } catch (error) {
+ $.ajax({
+ url: 'js/gsec.js?ajaxload',
+ async: false,
+ dataType: "script",
+ });
+ try {
+ gsecInstance = new gsec();
+ } catch (error) {
+ alert(D("browserNotSupported"));
+ }
+ }
+ gsecInstance.login(username, password).then( (value) => {
+ if (typeof value == "string") {
+ let promises_to_run = [
+ localforage.setItem("logged_in", true),
+ localforage.setItem("username", username),
+ localforage.setItem("password", password)
+ ];
+ Promise.all(promises_to_run).then(function () {
+ window.location.replace("/pages/timetable.html");
+ });
+ } else {
+ UIAlert("loginFailed");
+ $("#password").val("");
+ }
+ }).catch((err) => {
+ gsecErrorHandlerUI(err);
+ $("#password").val("");
+ });
+}
diff --git a/assets/js/logout.js b/assets/js/logout.js
new file mode 100644
index 0000000..f9c2543
--- /dev/null
+++ b/assets/js/logout.js
@@ -0,0 +1,5 @@
+// navigator.serviceWorker.controller.postMessage(JSON.stringify({action: "deletecaches"})); // cache only sets on initialization, so it should not be deleted.
+// since sw.js is not cached, updates work.
+localforage.clear().then(() => { // deletes localforage
+ window.location.replace("/index.html");
+});
diff --git a/assets/js/meals.js b/assets/js/meals.js
new file mode 100644
index 0000000..78de032
--- /dev/null
+++ b/assets/js/meals.js
@@ -0,0 +1,393 @@
+const API_ENDPOINT = "https://lopolis-api.gimb.tk/";
+
+async function checkLogin() {
+ localforage.getItem("logged_in_lopolis").then((value) => {
+ if (value != true) {
+ $("#meals-container").hide();
+ $("#meals-login-container").show();
+ } else {
+ $("#meals-container").show();
+ $("#meals-login-container").hide();
+ loadMeals();
+ }
+ }).catch((err) => {
+ console.log(err);
+ });
+}
+
+function setLoading(state) {
+ if (state) {
+ $("#loading-bar").removeClass("hidden");
+ } else {
+ $("#loading-bar").addClass("hidden");
+ }
+}
+
+async function getToken(callback, callbackparams = []) {
+ setLoading(true);
+ let promises_to_run = [
+ localforage.getItem("lopolis_username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("lopolis_password").then((value) => {
+ password = value;
+ })
+ ];
+ await Promise.all(promises_to_run);
+
+ $.ajax({
+ url: API_ENDPOINT + "gettoken",
+ crossDomain: true,
+ contentType: "application/json",
+ data: JSON.stringify({
+ "username": username,
+ "password": password
+ }),
+
+ dataType: "json",
+ cache: false,
+ type: "POST",
+
+ success: (dataauth) => {
+ if(dataauth == null || dataauth.error == true) {
+ UIAlert(D("authenticationError"), "getToken(): response error or null");
+ localforage.setItem("logged_in_lopolis", false).then( function(){
+ checkLogin();
+ });
+ } else if (dataauth.error == false) {
+ let empty = {};
+ empty.token = dataauth.data;
+ let argumentsToCallback = [empty].concat(callbackparams);
+ callback(...argumentsToCallback); // poslje token v {token: xxx}
+ } else {
+ UIAlert( D("authenticationError"), "getToken(): invalid response, no condition met");
+ }
+ setLoading(false);
+ },
+ error: () => {
+ UIAlert( D("lopolisAPIConnectionError"), "getToken(): AJAX error");
+ setLoading(false);
+ }
+ });
+}
+
+async function getMenus(dataauth, callback, callbackparams = []) {
+ setLoading(true);
+ let current_date = new Date();
+ // naloži za dva meseca vnaprej (če so zadnji dnevi v mesecu)
+ let mealsgathered = {};
+ let promises_to_wait_for = [];
+ for (let iteration = 1; iteration <= 2; iteration++) {
+
+ promises_to_wait_for[iteration] = $.ajax({
+ url: API_ENDPOINT+"getmenus",
+ crossDomain: true,
+ contentType: "application/json",
+ data: JSON.stringify({
+ "month": current_date.getMonth() + iteration,
+ "year": current_date.getFullYear()
+ }),
+
+ headers: {
+ "Authorization": `Bearer ${dataauth.token}`
+ },
+
+ dataType: "json",
+ cache: false,
+ type: "POST",
+
+ success: (meals) => {
+ if(meals == null || meals.error == true) {
+ UIAlert( D("errorGettingMenus"), "getMenus(): response error or null");
+ setLoading(false);
+ localforage.setItem("logged_in_lopolis", false).then( () => {
+ checkLogin();
+ });
+ } else if (meals.error == false) {
+ setLoading(false);
+ mealsgathered[iteration] = meals;
+ } else {
+ setLoading(false);
+ UIAlert( D("errorUnexpectedResponse") , "getMenus(): invalid response, no condition met");
+ }
+ },
+
+ error: () => {
+ setLoading(false);
+ UIAlert( D("lopolisAPIConnectionError"), "getMenus(): AJAX error");
+ }
+ });
+ }
+
+ await Promise.all(promises_to_wait_for); // javascript is ducking amazing
+
+ let allmeals = {};
+ let passtocallback = {};
+
+ for (const [index, monthmeals] of Object.entries(mealsgathered)) { // although this is not very javascripty
+ allmeals = mergeDeep(allmeals, monthmeals.data);
+ }
+
+ passtocallback.data = allmeals;
+ passtocallback.token = dataauth.token;
+ let toBePassed = [passtocallback].concat(callbackparams);
+ callback(...toBePassed);
+
+}
+
+async function loadMeals() {
+ getToken(getMenus, [displayMeals, []]);
+}
+
+function displayMeals(meals) {
+ // console.log(JSON.stringify(meals)); // debug // dela!
+
+ let root_element = document.getElementById("meals-collapsible");
+ for (const [date, mealzz] of Object.entries(meals.data)) {
+ let unabletochoosequestionmark = "";
+ let readonly = mealzz.readonly;
+ var datum = new Date(date);
+
+ // Create root element for a date entry
+ let subject_entry = document.createElement("li");
+
+ // Create subject collapsible header
+ let subject_header = document.createElement("div");
+ subject_header.classList.add("collapsible-header");
+ subject_header.classList.add("collapsible-header-root");
+
+ // Create header text element
+ let subject_header_text = document.createElement("span");
+
+ if(mealzz.readonly) {
+ unabletochoosequestionmark = `*${S("readOnly")}*`;
+ }
+
+ // Use ES6 templates
+ subject_header_text = `${dateString.day(datum.getDay())}, ${datum.getDate()}. ${dateString.month(datum.getMonth())} ${datum.getFullYear()} (${mealzz.meal} @ ${mealzz.location}) ${unabletochoosequestionmark}`;
+
+ // Create collection for displaying individuals meals
+ let subject_body = document.createElement("div");
+ subject_body.className = "collapsible-body";
+ let subject_body_root = document.createElement("ul");
+ subject_body_root.className = "collection";
+
+ for(const [dindex, dmil] of Object.entries(mealzz.menu_options)) {
+ // Create element for individual meal
+ let meal_node = document.createElement("li");
+ meal_node.className = "collection-item";
+ meal_node.classList.add("collection-item")
+ meal_node.classList.add("meal-node");
+ meal_node.dataset["index"] = dindex;
+
+ if (!readonly) {
+ meal_node.onclick = () => {
+ setMenu(date, dmil.value);
+ }
+ }
+
+ let meal_node_div = document.createElement("div");
+ // Node for left text
+ let meal_lefttext = document.createElement("span");
+ // Node for the right text
+ let meal_righttext = document.createElement("div");
+ meal_righttext.className = "secondary-content";
+ // Apply different style, if the meal is selected
+ if (dmil.selected) {
+ // Text
+ meal_lefttext.innerHTML = `<i>${dmil.text}</i>`;
+ // Number
+ meal_righttext.innerText = S("selected");
+ } else {
+ // Text
+ meal_lefttext.innerText = dmil.text;
+ // Number
+ meal_righttext.innerText = "";
+ }
+ meal_node_div.appendChild(meal_lefttext);
+ meal_node_div.appendChild(meal_righttext);
+ meal_node.appendChild(meal_node_div);
+ subject_body_root.appendChild(meal_node);
+ }
+
+ subject_header.appendChild(subject_header_text);
+ subject_body.append(subject_body_root);
+ subject_entry.append(subject_header);
+ subject_entry.append(subject_body);
+ root_element.append(subject_entry);
+ }
+ $("#meals-collapsible").append(root_element);
+ // refreshClickHandlers();
+}
+
+function clearMeals() {
+ const table = document.getElementById("meals-collapsible");
+ while (table.firstChild) {
+ table.removeChild(table.firstChild);
+ }
+}
+
+function refreshMeals() {
+ clearMeals();
+ loadMeals();
+}
+
+function lopolisLogout() {
+ localforage.setItem("logged_in_lopolis", false);
+ $("#meals-collapsible").html("");
+ checkLogin();
+}
+
+async function lopolisLogin() {
+ setLoading(true);
+ var usernameEl = $("#meals-username");
+ var passwordEl = $("#meals-password");
+ $.ajax({
+ url: API_ENDPOINT+"gettoken",
+ crossDomain: true,
+ contentType: "application/json",
+ data: JSON.stringify({
+ "username": usernameEl.val(),
+ "password": passwordEl.val()
+ }),
+
+ dataType: "json",
+ cache: false,
+ type: "POST",
+
+ success: async function(data) {
+ if(data == null) {
+ UIAlert( S("requestForAuthenticationFailed"), "lopolisLogin(): date is is null");
+ setLoading(false);
+ usernameEl.val("");
+ passwordEl.val("");
+ } else if(data.error == true) {
+ UIAlert( S("loginFailed"), "lopolisLogin(): login failed. data.error is true");
+ usernameEl.val("");
+ passwordEl.val("");
+ setLoading(false);
+ } else {
+ let promises_to_run = [
+ localforage.setItem("logged_in_lopolis", true),
+ localforage.setItem("lopolis_username", usernameEl.val()),
+ localforage.setItem("lopolis_password", passwordEl.val())
+ ];
+ await Promise.all(promises_to_run);
+ checkLogin();
+ UIAlert("Credential match!");
+ }
+ },
+
+ error: () => {
+ UIAlert( D("loginError"), "lopolisLogin(): ajax.error");
+ setLoading(false);
+ }
+ });
+}
+
+async function setMenus(currentmeals = 69, toBeSentChoices) { // currentmeals je getMenus response in vsebuje tudi token.
+
+ if (currentmeals === 69) {
+ getToken(getMenus, [setMenus, toBeSentChoices]);
+ return;
+ }
+
+ for(const [mealzzdate, mealzz] of Object.entries(currentmeals.data)) {
+ if (mealzzdate in toBeSentChoices === false) {
+ for (const [mealid, mealdata] of Object.entries(mealzz.menu_options)) {
+ console.log(mealdata);
+ if(mealdata.selected == true || mealzz.readonly == true) {
+ toBeSentChoices[mealzzdate] = mealdata.value;
+ break;
+ }
+ }
+ }
+ }
+
+ setLoading(true);
+
+ $.ajax({
+ url: API_ENDPOINT + "setmenus",
+ crossDomain: true,
+ contentType: "application/json",
+ data: JSON.stringify( { "choices": toBeSentChoices } ),
+ headers: {
+ "Authorization": "Bearer " + currentmeals.token
+ },
+ dataType: "json",
+ cache: false,
+ type: "POST",
+
+ success: (response) => {
+ if(response === null || response.error == true) {
+ UIAlert( D("errorSettingMeals"), "setMenus(): response error or null");
+ } else if (response.error == false) {
+ UIAlert( D("mealSet"), "setMenus(): meni nastavljen");
+ } else {
+ UIAlert( D("errorUnexpectedResponse"), "setMenus(): invalid response, no condition met");
+ }
+ setLoading(false);
+ },
+
+ error: () => {
+ setLoading(false);
+ UIAlert( D("lopolisAPIConnectionError"), "setMenus(): AJAX error");
+ }
+ });
+}
+async function setMenu(date, menu) {
+ let choice = {};
+ choice[date] = menu;
+ getToken(getMenus, [setMenus, choice]);
+}
+
+
+function setupEventListeners() {
+ $("#meals-login").click(() => {
+ lopolisLogin();
+ });
+
+ $("#meals-logout").click(() => {
+ lopolisLogout();
+ });
+}
+
+// Initialization code
+document.addEventListener("DOMContentLoaded", async () => {
+ checkLogin();
+
+ setupEventListeners();
+
+ let coll_elem = document.querySelectorAll('.collapsible');
+ M.Collapsible.init(coll_elem, {});
+
+ // Setup refresh handler
+ $("#refresh-icon").click(function () {
+ refreshMeals();
+ });
+
+ let elems = document.querySelectorAll('.modal');
+ M.Modal.init(elems, {});
+ // Setup side menu
+ const menus = document.querySelectorAll('.side-menu');
+ M.Sidenav.init(menus, { edge: 'right', draggable: true });
+
+ // Setup side modal
+ const modals = document.querySelectorAll('.side-modal');
+ M.Sidenav.init(modals, { edge: 'left', draggable: false });
+
+ var elemsx = document.querySelectorAll('select');
+ M.FormSelect.init(elemsx);
+
+ var datepickerelems = document.querySelectorAll('.datepicker');
+ var today = new Date();
+ M.Datepicker.init(datepickerelems, {
+ firstDay: 1,
+ minDate: today,
+ showDaysInNextAndPreviousMonths: true,
+ showClearBtn: true,
+ format: "dddd, dd. mmmm yyyy"
+ });
+
+ refreshMeals();
+});
diff --git a/assets/js/messaging.js b/assets/js/messaging.js
new file mode 100644
index 0000000..890da18
--- /dev/null
+++ b/assets/js/messaging.js
@@ -0,0 +1,650 @@
+const API_ENDPOINT = "https://gimb.tk/test.php";
+const DIRECTORY_URL = "/directory.json";
+
+const ENCRYPTED_MESSAGE_REGEX = /<!-- ba-e2eemsg-(\d{4}) -->(\S+?)<!-- end-msg -->/;
+
+// "Global" object for name directory and messages
+var directory = null;
+var messages = {
+ "0": [],
+ "1": [],
+ "2": []
+}
+var current_tab = 0;
+
+async function checkLogin() {
+ localforage.getItem("logged_in").then(function (value) {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch(function (err) {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+// -----------HTML HELPERS-----------
+function htmlEncode(value) {
+ // Create a in-memory element, set its inner text (which is automatically encoded)
+ // Then grab the encoded contents back out. The element never exists on the DOM.
+ return $("<textarea/>").text(value).html();
+}
+
+function htmlDecode(value) {
+ return $("<textarea/>").html(value).text();
+}
+// ---------------------------------
+
+// Try to fetch name:id directory
+async function loadDirectory() {
+ $.ajax({
+ url: DIRECTORY_URL,
+ crossDomain: true,
+ dataType: "json",
+ cache: false,
+ type: "GET",
+ success: async function (data) {
+ // If we were able to retrieve it, update the saved directory
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ localforage.getItem("grades").then((value) => {
+ grades = value;
+ })
+ ];
+ await Promise.all(promises_to_run);
+ var gsecdata;
+ try {
+ let gsecInstance = new gsec();
+ await gsecInstance.login(username, password);
+ gsecdata = await gsecInstance.fetchTeachersDirectory().catch( (err) => {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ });
+ } catch (err) {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ }
+ directory = {...gsecdata, ...data };
+ console.log(directory);
+ localforage.setItem("directory", directory);
+ // Populate autocomplete
+ populateAutocomplete();
+ },
+ error: () => {
+ // Otherwise, try to retrieve stored directory
+ localforage.getItem("directory").then((stored_directory) => {
+ if (stored_directory === null) {
+ // If unable, set directory to null (so other functions know that we don't have it)
+ UIAlert( D("nameDirectoryNotSet"), "loadDirectory(): stored_directory === null" );
+ directory = null;
+ // Disable send button
+ document.getElementById("msg-send").disabled = true;
+ } else {
+ directory = stored_directory;
+ // Populate autocomplete
+ populateAutocomplete();
+ }
+ });
+ }
+ });
+}
+
+function populateAutocomplete() {
+ let elems = document.querySelectorAll(".autocomplete-fullname");
+ // če se uporablja globalna var directory, ki je shranjena kot objekt (vedno shranjen kot reference), bo pri let x=y x le pointer na object y
+ // in se bo spremenil z spremembo "originala". spodnja stvar itak ni preveč efficent, loop čez vseh 7000 ljudi bi lahko delal težave...
+ // kakšen Object.keys bi bila boljša varianta ampak raje napišem tale komentar... idk, to se mi je zdelo uporabno ampak sedaj obžalujem
+ // samo guglal sem "copying an object js"
+ let autocomplete_entries = Object.assign({}, directory);
+ for (let variableKey in autocomplete_entries) {
+ autocomplete_entries[variableKey] = null;
+ }
+
+ M.Autocomplete.init(elems, {
+ data: autocomplete_entries,
+ onAutocomplete: validateName,
+ minLength: 0
+ });
+
+ $(document).ready(function () {
+ if (window.location.hash.length > 1 && !window.location.hash.substring(1).startsWith("beziapp")) {
+ var hashValue = decodeURIComponent(window.location.hash.substring(1));
+ $("#full-name").val(hashValue);
+ if (hashValue in directory) {
+ $("#msg-send").removeAttr("disabled");
+ }
+ $("#beziapp-new-message").modal();
+ $("#beziapp-new-message").modal("open");
+ }
+ });
+
+ M.updateTextFields();
+ validateName();
+}
+
+// Function to toggle loading bar
+function setLoading(state) {
+ if (state) {
+ $("#loading-bar").removeClass("hidden");
+ } else {
+ $("#loading-bar").addClass("hidden");
+ }
+}
+
+// Function, responsible for fetching and displaying data
+async function loadMessages(force_refresh = true, messageType = 0) {
+ setLoading(true);
+ // Load required data
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ localforage.getItem("messages").then((value) => {
+ messages = value;
+ })
+ ];
+
+ Promise.all(promises_to_run).then(() => {
+
+ if (messages[messageType] == null || messages[messageType].length === 0 || force_refresh) {
+ $.ajax({
+ url: API_ENDPOINT,
+ crossDomain: true,
+ data: {
+ "u": username,
+ "p": password,
+ "m": "fetchsporocilaseznam",
+ "a": messageType // Message type, see API doc for details
+ },
+ dataType: "json",
+ cache: false,
+ type: "GET",
+
+ success: (data) => {
+ // If data is null, the request failed
+ if (data === null) {
+ UIAlert( D("requestFailed") );
+ setLoading(false);
+ } else {
+ // Save messages & populate view
+ // console.log(data); // debug
+ messages[messageType.toString()] = data;
+ localforage.setItem("messages", messages).then((value) => {
+ displayData(messageType);
+ setLoading(false);
+ });
+ }
+ },
+
+ error: () => {
+ UIAlert( D("errorFetchingMessages") );
+ setLoading(false);
+ }
+ })
+ } else {
+ displayData(messageType);
+ setLoading(false);
+ }
+ });
+}
+
+async function loadMsg(id) {
+
+ setLoading(true);
+ // Load required data
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ ];
+
+ Promise.all(promises_to_run).then(() => {
+ $.ajax({
+ url: API_ENDPOINT,
+ crossDomain: true,
+ data: {
+ "u": username,
+ "p": password,
+ "m": "fetchsporocilo",
+ "a": id.replace(/_/g, "|")
+ },
+ dataType: "json",
+ cache: false,
+ type: "GET",
+ success: (data) => {
+ // If data is null, the request failed
+ if (data === null) {
+ UIAlert( `${D("unableToReceiveTheMessage")} ${D("requestFailed")}` );
+ } else {
+ displayMessage(id, data);
+ }
+ setLoading(false);
+ },
+
+ error: () => {
+ UIAlert( `${D("unableToReceiveTheMessage")} ${D("noInternetConnection")}` );
+ setLoading(false);
+ }
+
+ })
+ });
+}
+
+
+async function deleteMsg(id) {
+ setLoading(true);
+ // Load required data
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }), localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ ];
+ Promise.all(promises_to_run).then(() => {
+ $.ajax({
+ url: API_ENDPOINT,
+ crossDomain: true,
+ data: {
+ "u": username,
+ "p": password,
+ "m": "izbrisisporocilo",
+ "a": id
+ },
+ dataType: "json",
+ cache: false,
+ type: "GET",
+ success: (data) => {
+ // If data is null, the request failed
+ if (data === null) {
+ UIAlert( `${D("unableToDeleteTheMessage")} ${D("requestFailed")}` );
+ } else {
+ document.getElementById("msg_box-" + id).remove();
+ }
+ setLoading(false);
+ },
+
+ error: () => {
+ UIAlert( `${D("unableToDeleteTheMessage")} ${D("noInternetConnection")}` );
+ setLoading(false);
+ }
+
+ })
+ });
+}
+
+function displayMessage(id, data) {
+ let regex_results = ENCRYPTED_MESSAGE_REGEX.exec(data["telo"]);
+ if (regex_results != null) {
+ var datatodecrypt = regex_results[2];
+ var randomencdivid = Math.floor(Math.random() * 9999).toString().padStart(4, "0");
+
+ var msgcontent = `
+ <div id='ba-msg-e2ee-form-${randomencdivid}'>
+ ${D("thisMessageWasEncrypted")}
+ <input type="password" autocomplete="new-password" id="ba-msg-e2ee-password-${randomencdivid}" placeholder="${S("password")} ...">
+ <button
+ type="button"
+ value="Decrypt"
+ class="btn waves-effect waves-light"
+ onclick="
+ try {
+ $('#ba-msg-e2ee-content-${randomencdivid}').html(
+ filterXSS(
+ sjcl.decrypt(
+ $('#ba-msg-e2ee-password-${randomencdivid}').val(),
+ $('#ba-msg-e2ee-content-${randomencdivid}').html()
+ )
+ )
+ );
+ $('#ba-msg-e2ee-content-${randomencdivid}').show();
+ $('#ba-msg-e2ee-form-${randomencdivid}').hide();
+ } catch (err) {
+ alert('${D("incorrectPassword")}');
+ }
+ "
+ >
+ ${S("decrypt")}
+ </button>
+ </div>
+ <div id="ba-msg-e2ee-content-${randomencdivid}" hidden>
+ ${datatodecrypt}
+ </div>
+ `
+ $(`#msg_body-${id.replace(/\|/g, "_")}`).html(msgcontent);
+ } else {
+ $(`#msg_body-${id.replace(/\|/g, "_")}`).html(filterXSS(data["telo"]));
+ }
+}
+
+// Function for displaying data
+function displayData(messageType) {
+ let div_selector = "";
+ switch (messageType) {
+ case 0:
+ div_selector = "#beziapp-received";
+ break;
+ case 1:
+ div_selector = "#beziapp-sent";
+ break;
+ case 2:
+ div_selector = "#beziapp-deleted";
+ break;
+ }
+
+ // $("#storage-bar").show();
+ $("#storage-progressbar").width(Number(Number(getNumMessages(messageType) / 120) * 100).toFixed(2) + "%");
+ $("#storage-stats").text(`${getNumMessages(messageType)}/120`);
+
+ let msg_list = $(div_selector);
+ msg_list.html("");
+ messages[messageType.toString()].forEach(element => {
+ if (!element["zadeva"].startsWith("ba-ctlmsg")) {
+
+ msg_list.append(`
+ <div class="col s12 m12" id="msg_box-${filterXSS(element["id"])}">
+ <div class="card color-card">
+ <div class="card-content general-text">
+ <span class="card-title">
+ ${filterXSS(element["zadeva"])}
+ </span>
+ <p id="msg_body-${filterXSS(element["id"]).replace(/\|/g, "_")}">
+ <button
+ class="btn waves-effect waves-light"
+ onclick="loadMsg('${filterXSS(element["id"])}')"
+ type="submit"
+ >
+ Load message
+ <i class="material-icons right">move_to_inbox</i>
+ </button>
+ <p>
+ </div>
+ <div class="card-action general-text">
+ <a onclick="deleteMsg('${filterXSS(element["id"])}')">
+ <i class="material-icons">delete</i>
+ </a>
+ <a onclick="
+ $('#full-name').val('${filterXSS(element["posiljatelj"])}');
+ $('#msg-subject').val('Re: ${filterXSS(element["zadeva"])}');
+ M.updateTextFields();
+ "
+ data-target="beziapp-new-message"
+ class="modal-trigger"
+ >
+ <i class="material-icons">reply</i>
+ </a>
+ ${filterXSS(element["posiljatelj"])} &raquo; ${filterXSS(element["datum"]["dan"])}. ${filterXSS(element["datum"]["mesec"])}. ${filterXSS(element["datum"]["leto"])} at ${filterXSS(element["cas"]["ura"]).padStart(2, "0")}:${filterXSS(element["cas"]["minuta"]).padStart(2, "0")}
+ </div>
+ </div>
+ </div>
+ `);
+ }
+ });
+}
+
+// -1 = cumulative
+function getNumMessages(messageType = -1) {
+ if (messageType === -1) {
+ let sum = 0;
+ for (const [messageType, messageList] of Object.entries(messages)) {
+ sum += messageList.length;
+ }
+ return sum;
+ } else {
+ var what_to_return = messages[messageType].length;
+ if(what_to_return == null)
+ return 0
+ return what_to_return;
+ }
+}
+
+async function sendMessage(recipient_number, subject, body) {
+ setLoading(true);
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ ];
+ Promise.all(promises_to_run).then(() => {
+ $.ajax({
+ url: API_ENDPOINT,
+ crossDomain: true,
+ data: {
+ "u": username,
+ "p": password,
+ "m": "posljisporocilo",
+ "a": recipient_number,
+ "b": subject,
+ "c": body
+ },
+
+ dataType: "json",
+ cache: false,
+
+ type: "POST",
+ success: () => {
+ // we CAN'T know wether the message was delievered
+ UIAlert(D("messageWasProbablySent"));
+ setLoading(false);
+ },
+ error: () => {
+ UIAlert(D("errorSendingMessage"), D("noInternetConnection"));
+ setLoading(false);
+ }
+ })
+ });
+}
+
+async function validateName() {
+ if (directory !== null) {
+ if ($("#full-name").val() in directory) {
+ $("#full-name").addClass("valid");
+ $("#full-name").removeClass("invalid");
+ $("#msg-send").removeAttr("disabled");
+ } else {
+ $("#full-name").addClass("invalid");
+ $("#full-name").removeClass("valid");
+ $("#msg-send").attr("disabled", "disabled");
+ }
+ }
+}
+
+// Setup event listeners for buttons
+function setupEventListeners() {
+ // Button to add a photo
+ $("#msg-add-photo").click(() => {
+ let input = document.createElement("input");
+ input.type = "file";
+ input.onchange = (e) => {
+ // getting a hold of the file reference
+ let file = e.target.files[0];
+ // setting up the reader
+ let reader = new FileReader();
+ reader.readAsDataURL(file); // this is reading as data url
+
+ // here we tell the reader what to do when it's done reading...
+ reader.onload = readerEvent => {
+ additionalstufftoaddtomessage += `<br><img src="${readerEvent.target.result}" />`; // this is the content!
+ if ($("#msg-added-image").html().length > 1) {
+ $("#msg-added-image").append(`<img style="width: 20mm" src="${readerEvent.target.result}" />`);
+ } else {
+ $("#msg-added-image").html(`
+ <input
+ type="button"
+ value="${S("removeImages")}"
+ class="btn waves-effect waves-light"
+ onclick="
+ additionalstufftoaddtomessage = '';
+ $('#msg-added-image').html('');
+ "
+ />
+ <br>
+ ${D("largeImagesNote")}
+ <br>
+ ${S("attachedImages")}:
+ <br>
+ <img style="width:20mm" src="${readerEvent.target.result}" />
+ `);
+ // ravno obratni narekovaji
+ }
+ UIAlert(D("imageAddedAsAnAttachment"));
+ }
+ }
+ input.click();
+ });
+
+ // Verify recipient when input loses focus
+ $("#full-name").on("blur", validateName);
+
+ // Setup refresh icon
+ $("#refresh-icon").click(() => {
+ loadMessages(true, current_tab);
+ });
+
+ // Setup checkbox handler
+ $("#encrypt-checkbox").change(function() {
+ if (this.checked) {
+ $("#encryption-key-input").prop("hidden", false);
+ } else {
+ $("#encryption-key-input").prop("hidden", true);
+ }
+ });
+
+ // Button to send message
+ $("#msg-send").click(() => {
+ localforage.getItem("directory").then(function (value) {
+ var msgcontent = $("#msg-body").val() + additionalstufftoaddtomessage;
+ var msgsubject = $("#msg-subject").val();
+ if ($("#encryption-key-input").prop("hidden") !== true) {
+ var randomencdivid = Math.floor(Math.random() * 9999).toString().padStart(4, "0");
+ var addrparts = window.location.href.split("/"); // engleski
+
+ var encrypted_message = sjcl.encrypt($("#msg-e2ee-pass-input").val(), msgcontent);
+ msgcontent = `
+ <script src="${addrparts[0]}//${addrparts[2]}/js/lib/sjcl.js"></script>
+ <div id="ba-msg-e2ee-form-${randomencdivid}">
+ This message was encrypted by BežiApp.
+ <input type="password" autocomplete="new-password" id="ba-msg-e2ee-password-${randomencdivid}" placeholder="Enter password ...">
+ <input type="button" value="Decrypt" onclick="
+ try {
+ console.log($('ba-msg-e2ee-content-${randomencdivid}').text());
+ $('#ba-msg-e2ee-content-${randomencdivid}').html(
+ sjcl.decrypt(
+ $('#ba-msg-e2ee-password-${randomencdivid}').val(),
+ $('#ba-msg-e2ee-content-${randomencdivid}').text()
+ )
+ );
+ $('#ba-msg-e2ee-content-${randomencdivid}').show();
+ $('#ba-msg-e2ee-form-${randomencdivid}').hide();
+ } catch(err) {
+ alert('${D("incorrectPassword")}');
+ }
+ ">
+ </div>
+ <div id="ba-msg-e2ee-content-${randomencdivid}" hidden>
+ <!-- ba-e2eemsg-${msgcontent.length.toString().padStart(4, "0")} -->${encrypted_message}<!-- end-msg -->
+ </div>
+ `
+ }
+
+ console.log(msgcontent);
+ console.log(encrypted_message);
+
+ sendMessage(value[$("#full-name").val()], msgsubject, htmlEncode(msgcontent));
+ $("#msg-body").val("");
+ $("#full-name").val("");
+ $("#msg-subject").val("");
+ // $("#msg-send").prop("disabled", true);
+ $("#msg-send").attr("disabled", "disabled");
+ additionalstufftoaddtomessage = "";
+
+ $("#msg-added-image").html("");
+ $("#msg-e2ee-pass").hide();
+
+ }).catch(function (err) {
+ UIAlert( `${D("unableToReadDirectory")} ${D("messageCouldNotBeSend")}`, "45245" );
+ console.log(err);
+ });
+ });
+}
+
+function getUrlParameter(sParam) {
+ const url_params = new URLSearchParams(window.location.search);
+ const found_param = url_params.get(sParam);
+ return found_param
+}
+
+var additionalstufftoaddtomessage = "";
+document.addEventListener("DOMContentLoaded", () => {
+
+ checkLogin();
+
+ // Setup modals
+ const modal_elems = document.querySelectorAll('.modal');
+ const modal_options = {
+ onOpenStart: () => { $("#fab-new").hide() },
+ onCloseEnd: () => { $("#fab-new").show() },
+ dismissible: false
+ };
+ M.Modal.init(modal_elems, modal_options);
+
+ loadDirectory();
+ setupEventListeners();
+
+ // Setup tabs
+ const tabs = document.querySelectorAll(".tabs");
+ const tab_options = {
+ // swipeable: true, // TODO: figure out how to fix height when it's enabled (it's good for UX to have it enabled)
+ onShow: (tab) => {
+ if ($(tab).hasClass("active")) {
+ switch (tab.id) {
+ case "beziapp-received":
+ current_tab = 0;
+ loadMessages(false, 0);
+ break;
+ case "beziapp-sent":
+ current_tab = 1;
+ loadMessages(false, 1);
+ break;
+ case "beziapp-deleted":
+ current_tab = 2;
+ loadMessages(false, 2);
+ break;
+ }
+ }
+ }
+ };
+ M.Tabs.init(tabs, tab_options);
+
+ // Setup floating action button
+ const fab_options = {
+ hoverEnabled: false,
+ toolbarEnabled: false
+ }
+ const fab_elem = document.querySelectorAll(".fixed-action-btn");
+ M.FloatingActionButton.init(fab_elem, fab_options);
+
+ var receivedmessages = null;
+ loadMessages(true, 0);
+ M.updateTextFields();
+
+ // Setup side menu
+ const menus = document.querySelectorAll(".side-menu");
+ M.Sidenav.init(menus, { edge: "right", draggable: true });
+});
diff --git a/js/privacypolicy.js b/assets/js/privacypolicy.js
index 9955abf..9955abf 100644
--- a/js/privacypolicy.js
+++ b/assets/js/privacypolicy.js
diff --git a/assets/js/settings.js b/assets/js/settings.js
new file mode 100644
index 0000000..3106f14
--- /dev/null
+++ b/assets/js/settings.js
@@ -0,0 +1,130 @@
+// settings.js -- TODO
+
+async function setLanguage(langCode) {
+ localforage.setItem("chosenLang", langCode).then((value) => {
+ console.log("Language set: " + value);
+ UIAlert(D("languageSet"), "setLanguage(): languageSet");
+ });
+}
+
+async function setTheme(targetTheme) {
+ localforage.setItem("theme", targetTheme).then((value) => {
+ console.log("Theme set: " + value);
+ UIAlert(D("themeSet"), "setTheme(): themeSet");
+ });
+}
+
+async function setErrorReporting(targetE) {
+ localforage.setItem("errorReporting", targetE).then((value) => {
+ console.log("ErrorReporing set: " + value);
+ UIAlert(D("errorReportingSet"), "setErrorReporting(): errorReportingSet");
+ });
+}
+
+function shuffleArray(array) {
+ for (var i = array.length - 1; i > 0; i--) {
+ var j = Math.floor(Math.random() * (i + 1));
+ var temp = array[i];
+ array[i] = array[j];
+ array[j] = temp;
+ }
+}
+
+function setUIAdditionalOptions(state) {
+ var SENSITIVE_THEMES = {
+ "left": "Left / leva",
+ "right": "Right / desna",
+ "piratska": "Pirate / piratska"
+ };
+ if (state === true) {
+ var theme_keys = Object.keys(SENSITIVE_THEMES);
+ shuffleArray(theme_keys);
+ theme_keys.forEach((item) => {
+ var option_element = $(`<option value="${item}" id="option-${item}" data-theme="${item}">${SENSITIVE_THEMES[item]}</option>`);
+ $("#select-theme").append(option_element);
+ });
+ } else {
+ Object.keys(SENSITIVE_THEMES).forEach((item) => {
+ $("#option-" + item).remove();
+ });
+ }
+ var elems = document.querySelectorAll('#select-theme');
+ M.FormSelect.init(elems, {});
+}
+
+async function setAdditionalOptions(state) {
+ localforage.setItem("triggerWarningAccepted", state).then((value) => {
+ console.log("TriggerWarning set: " + value);
+ UIAlert(D("triggerWarningSet"), "setAdditionalOptions(): triggerWarningSet");
+ setUIAdditionalOptions(value);
+ });
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+
+ // Setup checkbox handler
+ $("#triggered-checkbox").change(function() {
+ if (this.checked) {
+ setAdditionalOptions(true);
+ } else {
+ setAdditionalOptions(false);
+ }
+ });
+
+ $("#select-language").on("change", function() {
+ setLanguage($(this).find(":selected").val());
+ });
+
+ $("#select-theme").on("change", function() {
+ setTheme($(this).find(":selected").val());
+ });
+
+ $("#select-errorreporting").on("change", function() {
+ setErrorReporting($(this).find(":selected").val());
+ });
+
+ localforage.getItem("chosenLang").then((value) => {
+ let selectedLanguage = value;
+ if(value == null || value.length < 1) {
+ selectedLanguage = "sl";
+ }
+ $(`#option-${selectedLanguage}`).attr("selected", true);
+ }).catch(() => {});
+
+ localforage.getItem("theme").then((value) => {
+ let selectedTheme = value;
+ if(value == null || value.length < 1) {
+ selectedTheme = "themeLight";
+ }
+ $(`#option-${selectedTheme}`).attr("selected", true);
+ }).catch(() => {});
+
+ localforage.getItem("errorReporting").then((value) => {
+ let selectedE = value;
+ if(value == null || value.length < 1) {
+ selectedE = "on";
+ }
+ $(`#option-${selectedE}`).attr("selected", true);
+ }).catch(() => {});
+
+ localforage.getItem("triggerWarningAccepted").then((value) => {
+ if (value === true) {
+ $("#triggered-checkbox").attr("checked", true);
+ setUIAdditionalOptions(true);
+ }
+ }).catch(() => {});
+
+ // Setup side menu
+ const menus = document.querySelectorAll(".side-menu");
+ M.Sidenav.init(menus, { edge: "right", draggable: true });
+
+ var elems = document.querySelectorAll(".theme-select");
+ M.FormSelect.init(elems, {});
+
+ var elems = document.querySelectorAll(".errorreporting-select");
+ M.FormSelect.init(elems, {});
+
+ // Setup language select dropdown
+ var elems = document.querySelectorAll(".lang-select");
+ M.FormSelect.init(elems, {});
+});
diff --git a/assets/js/setup-storage.js b/assets/js/setup-storage.js
new file mode 100644
index 0000000..0d2552a
--- /dev/null
+++ b/assets/js/setup-storage.js
@@ -0,0 +1,44 @@
+async function setupStorage(force = false) {
+ let logged_in;
+ promises_check_if_already_installed = [
+ localforage.getItem("logged_in").then( function(val) {
+ console.log("[setupStorage] logged in status: " + val);
+ logged_in = val;
+ })
+ ];
+ await Promise.all(promises_check_if_already_installed);
+
+ let promises_update = [
+ localforage.setItem("profile", {}),
+ localforage.setItem("timetable", []),
+ localforage.setItem("teachers", []),
+ localforage.setItem("gradings", []),
+ localforage.setItem("grades", []),
+ localforage.setItem("absences", {}),
+ localforage.setItem("messages", { "0": [], "1": [], "2": []}), // see messages.js:129, commit 8eb9ca9caca30fbbe023243657535ab4088be377
+ localforage.setItem("directory", {}), //\\ well I could remember my own code but I didn't.
+ localforage.setItem("meals", {}),
+ localforage.setItem("chosenLang", "en"),
+ localforage.setItem("theme", "light"),
+ localforage.setItem("errorReporting", "on"),
+ localforage.setItem("triggerWarningAccepted", false)
+ ];
+
+ if (logged_in && force == false) { // torej, če je že bila prijava narejena, ne posodobi backwards-compatible vrednosti (username, password,...)
+ await Promise.all(promises_update);
+ console.log("[setupStorage] user logged in: only updated");
+ } else {
+
+ let promises_first_install = [
+ localforage.setItem("logged_in", false),
+ localforage.setItem("username", ""),
+ localforage.setItem("password", ""),
+ localforage.setItem("chosenLang", "en"),
+ localforage.setItem("theme", "light"),
+ localforage.setItem("triggerWarningAccepted", false)
+ ];
+ await localforage.clear();
+ await Promise.all(promises_first_install);
+ console.log("[setupStorage] user not logged in: set up whole database");
+ }
+}
diff --git a/assets/js/teachers.js b/assets/js/teachers.js
new file mode 100644
index 0000000..da83c06
--- /dev/null
+++ b/assets/js/teachers.js
@@ -0,0 +1,150 @@
+// const API_ENDPOINT = "https://gimb.tk/test.php"; // deprecated
+// const API_ENDPOINT = "http://localhost:5000/test.php";
+
+var teachers = null;
+
+// Set loading bar visibility
+function setLoading(state) {
+ if (state) {
+ $("#loading-bar").removeClass("hidden");
+ } else {
+ $("#loading-bar").addClass("hidden");
+ }
+}
+
+// Function, responsible for fetching and displaying data
+async function loadTeachers(force_refresh = false) {
+ setLoading(true);
+ // Load required data
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ localforage.getItem("teachers").then((value) => {
+ teachers = value;
+ })
+ ];
+ await Promise.all(promises_to_run);
+ // If we don't have a list of teachers, query it
+ if (teachers == null || teachers == [] || force_refresh) {
+ try {
+ let gsecInstance = new gsec();
+ await gsecInstance.login(username, password);
+ gsecInstance.fetchTeachers().then( (value) => {
+ teachers = value;
+ localforage.setItem("teachers", value).then((saved_teachers) => {
+ displayData(saved_teachers);
+ setLoading(false);
+ });
+ setLoading(false);
+ }).catch( (err) => {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ });
+ } catch (err) {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ }
+ } else {
+ displayData(teachers);
+ setLoading(false);
+ }
+}
+
+// Function for displaying data
+function displayData(teachers) {
+ for(const teacher of Object.keys(teachers)) {
+ // Create row
+ let row = document.createElement("tr");
+ // Create cell 1
+ let cell_name = document.createElement("td");
+ let cell_name_text = document.createTextNode(teacher);
+ // Create cell 2
+ let cell_subject = document.createElement("td");
+ // Array ([0]) is useless, since every teacher is duplicated (for each subject) // <-- ne velja za gsec.js, velja pa za gimsisextclient, PHP varianta
+ var subjectsString = "";
+ for(const subject of Object.keys(teachers[teacher]["subjects"])) {
+ subjectsString += subject;
+ subjectsString += ", ";
+ }
+ let cell_subject_text = document.createTextNode(subjectsString.slice(0, -2)); // slajsnemo zadnji ", "
+ cell_name.appendChild(cell_name_text);
+ row.appendChild(cell_name);
+ cell_subject.appendChild(cell_subject_text);
+ row.appendChild(cell_subject);
+ $("#teachers-body").append(row);
+ };
+ // Refresh handlers
+ refreshTableClickHandlers();
+}
+
+async function checkLogin() {
+ localforage.getItem("logged_in").then((value) => {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch((err) => {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+function clearTable() {
+ const table = document.getElementById("teachers-body");
+ while (table.firstChild) {
+ table.removeChild(table.firstChild);
+ }
+}
+
+function refreshTableClickHandlers() {
+ // Arrow function doesn't work apparently?
+ $('#teachers-body').find("tr").click(function () {
+ teacherInfo($(this).index());
+ });
+}
+
+function teacherInfo(teacher_id) {
+ let name = Object.keys(teachers)[teacher_id];
+ let teacher_object = teachers[name];
+ var subjectsString = "";
+ for(const subject of Object.keys(teacher_object["subjects"])) {
+ subjectsString += subject;
+ subjectsString += ", ";
+ }
+ let subject = subjectsString.slice(0, -2);
+ let office_day = dateString.day(teacher_object["tpMeetings"]["day"]);
+ let office_lesson = teacher_object["tpMeetings"]["period"];
+ $("#teacher-name").text(name);
+ $("#teacher-subject").text(`${S("schoolSubject")}: ${subject}`);
+ $("#teacher-office").text(`${office_day}, ${S("lesson")} ${office_lesson}`);
+
+ const modal = document.querySelectorAll(".side-modal")[0];
+ M.Sidenav.getInstance(modal).open();
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+ checkLogin();
+ loadTeachers();
+
+ // Setup refresh handler
+ $("#refresh-icon").click(() => {
+ clearTable();
+ loadTeachers(true);
+ });
+
+ // Set row onClick functions
+ refreshTableClickHandlers();
+
+ // Setup side menu
+ const menus = document.querySelectorAll('.side-menu');
+ M.Sidenav.init(menus, { edge: 'right', draggable: true });
+
+ // Setup side modal
+ const modals = document.querySelectorAll('.side-modal');
+ M.Sidenav.init(modals, { edge: 'left', draggable: false });
+});
diff --git a/assets/js/timetable.js b/assets/js/timetable.js
new file mode 100644
index 0000000..290b748
--- /dev/null
+++ b/assets/js/timetable.js
@@ -0,0 +1,302 @@
+// const API_ENDPOINT = "https://gimb.tk/test.php"; // deprecated
+// const API_ENDPOINT = "http://localhost:5000/test.php";
+
+var calendar_obj = null;
+
+function checkLogin() {
+ localforage.getItem("logged_in").then((value) => {
+ // This code runs once the value has been loaded
+ // from the offline store.
+ if (value !== true) {
+ window.location.replace("/index.html");
+ }
+ }).catch((err) => {
+ // This code runs if there were any errors
+ console.log(err);
+ });
+}
+
+// Set loading bar visibility
+function setLoading(state) {
+ if (state) {
+ $("#loading-bar").removeClass("hidden");
+ } else {
+ $("#loading-bar").addClass("hidden");
+ }
+}
+
+// ----GET COLOR FROM STRING--------
+
+/**
+ *
+ * Calculate hash code from a string
+ * @param {string} input_string String to convert to hash
+ * @returns {string} calculated hash code
+ *
+ */
+function hashCode(str) { // java String#hashCode
+ var hash = 0;
+ for (var i = 0; i < str.length; i++) {
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ return hash;
+}
+
+/**
+ *
+ * Convert last 3 bytes of an integer to RGB color
+ * @param {integer} input_integer Integer that will be converted to RGB color
+ * @returns {string} Hex color code
+ *
+ */
+function intToRGB(i) {
+ var c = (i & 0x00FFFFFF)
+ .toString(16)
+ .toUpperCase();
+
+ return "00000".substring(0, 6 - c.length) + c;
+}
+
+// http://www.w3.org/TR/AERT#color-contrast
+/**
+ *
+ * Calculate the matching foreground color for a given background (improves UX)
+ * @param {string} background_color Background color of the object
+ * @returns {string} Forground color that will match background color
+ */
+function getForegroundFromBackground(background_color) {
+ let color_hex = background_color.replace("#", "");
+ let rgb = [
+ parseInt(color_hex.substring(0, 2), 16),
+ parseInt(color_hex.substring(2, 4), 16),
+ parseInt(color_hex.substring(4, 6), 16)
+ ];
+ let o = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000);
+ if (o > 180) {
+ return "#000000";
+ } else {
+ return "#ffffff";
+ }
+}
+
+/**
+ *
+ * Convert a given string to hex color
+ * @param {string} input_string Input string
+ * @returns {string} Hex RGB color
+ */
+function getHexColorFromString(str) {
+ return "#" + intToRGB(hashCode(str));
+}
+// --------------------------------------------------
+
+// ---------DATE FUNCTION-------------
+function getDateString(date) {
+ let year_str = date.getFullYear();
+ let month_str = date.getMonth() + 1
+ month_str = month_str.toString().padStart(2, "0");
+ let day_str = date.getDate();
+ day_str = day_str.toString().padStart(2, "0");
+
+ let date_string = year_str + "-" + month_str + "-" + day_str;
+ return date_string;
+}
+
+function getLastMonday(date_object) {
+ if (date_object.getDay() === 0) {
+ date_object.setDate(date_object.getDate() - 6);
+ } else {
+ date_object.setDate(date_object.getDate() - date_object.getDay() + 1);
+ }
+ return date_object;
+}
+// ----------------------------------
+
+async function loadTimetable(date_object, force_refresh = false) {
+ setLoading(true);
+ var timetable, username, password;
+ let date_monday = getLastMonday(date_object);
+ let date_string = getDateString(date_monday);
+ let promises_to_run = [
+ localforage.getItem("username").then((value) => {
+ username = value;
+ }),
+ localforage.getItem("password").then((value) => {
+ password = value;
+ }),
+ localforage.getItem("timetable").then((value) => {
+ timetable = value;
+ })
+ ];
+ await Promise.all(promises_to_run);
+ if (force_refresh || timetable == null || !(date_string in timetable)) {
+ try {
+ let gsecInstance = new gsec();
+ await gsecInstance.login(username, password);
+ gsecInstance.fetchTimetable(date_object).then( (value) => {
+ containsPeriods = false;
+ for(var iteration = 0; iteration <= 6; iteration++) {
+ if(Object.keys(value[iteration]).length > 0) {
+ containsPeriods = true;
+ // break;
+ }
+ }
+ if(!containsPeriods) {
+ UIAlert( D("noPeriods") );
+ setLoading(false);
+ } else {
+ if (timetable === null) {
+ timetable = {};
+ }
+ timetable[date_string] = value;
+ localforage.setItem("timetable", timetable).then(() => {
+ displayTimetable(value, date_monday);
+ setLoading(false);
+ });
+ }
+ }).catch( (err) => {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ });
+ } catch (err) {
+ gsecErrorHandlerUI(err);
+ setLoading(false);
+ }
+ } else {
+ displayTimetable(timetable[date_string], date_monday);
+ setLoading(false);
+ }
+}
+
+function getLessonTimes(lesson_number) {
+ const lessonTimes = [
+ ["07:10:00", "07:55:00"],
+ ["08:00:00", "08:45:00"],
+ ["08:50:00", "09:35:00"],
+ ["09:40:00", "10:25:00"],
+ ["10:55:00", "11:40:00"],
+ ["11:45:00", "12:30:00"],
+ ["12:35:00", "13:20:00"],
+ ["13:25:00", "14:10:00"],
+ ["14:15:00", "15:00:00"],
+ ["15:05:00", "15:50:00"],
+ ["15:55:00", "16:40:00"],
+ ["16:45:00", "17:30:00"],
+ ["17:35:00", "18:20:00"],
+ ["18:25:00", "19:10:00"]
+ ];
+ return lessonTimes[lesson_number];
+}
+
+function displayTimetable(weekly_timetable, date_object) {
+ let transformed_timetable = [];
+
+ let num_days = Object.keys(weekly_timetable).length;
+
+ for (let i = 0; i < num_days; i++) {
+
+ // Get date string (required by callendar)
+ let date_string = getDateString(date_object);
+ // Go to next day (for next loop iteration)
+ date_object.setDate(date_object.getDate() + 1);
+
+ let daily_timetable = weekly_timetable[i.toString()];
+
+ Object.keys(daily_timetable).forEach((lesson_number) => {
+ let lesson = daily_timetable[lesson_number];
+
+ let lesson_times = getLessonTimes(parseInt(lesson_number));
+ let bg_color = getHexColorFromString(lesson["acronym"]);
+ let fg_color = getForegroundFromBackground(bg_color);
+
+ let lesson_metadata = {
+ subject: lesson["subject"],
+ class: lesson["class"],
+ teacher: lesson["teacher"],
+ classroom: lesson["place"],
+ start: lesson_times[0].substring(0, 5),
+ end: lesson_times[1].substring(0, 5)
+ }
+
+ let lesson_object = {
+ id: JSON.stringify(lesson_metadata),
+ title: lesson["acronym"],
+ start: date_string + " " + lesson_times[0],
+ end: date_string + " " + lesson_times[1],
+ backgroundColor: bg_color,
+ textColor: fg_color
+ };
+
+ transformed_timetable.push(lesson_object);
+
+ });
+
+ }
+ // Update calendar
+ calendar_obj.removeAllEvents();
+ calendar_obj.addEventSource(transformed_timetable);
+}
+
+function eventClickHandler(eventClickInfo) {
+ let lesson_metadata = JSON.parse(eventClickInfo.event.id);
+
+ let lesson_subject = lesson_metadata["subject"];
+ let lesson_teacher = lesson_metadata["teacher"];
+ let lesson_classroom = lesson_metadata["classroom"];
+ let lesson_class = lesson_metadata["class"];
+ let lesson_duration = lesson_metadata["start"] + " - " + lesson_metadata["end"];
+
+ $("#lesson-subject").text(lesson_subject);
+ $("#lesson-teacher").text(lesson_teacher);
+ $("#lesson-class").text(lesson_class);
+ $("#lesson-classroom").text(lesson_classroom);
+ $("#lesson-duration").text(lesson_duration);
+
+ const modal = document.querySelectorAll(".side-modal")[0];
+ M.Sidenav.getInstance(modal).open();
+}
+
+
+document.addEventListener("DOMContentLoaded", () => {
+ checkLogin();
+
+ let calendarEl = document.getElementById("calendar");
+ calendar_obj = new FullCalendar.Calendar(calendarEl, {
+ plugins: ["timeGrid"],
+ eventClick: eventClickHandler,
+
+ defaultView: "timeGridWeek",
+ contentHeight: "auto",
+ height: "auto",
+ width: "auto",
+ timeGridEventMinHeight: 35,
+
+ nowIndicator: true,
+ firstDay: 1,
+ weekends: false,
+
+ minTime: "07:10:00",
+ maxTime: "19:10:00"
+ });
+ calendar_obj.render();
+
+ loadTimetable(new Date());
+
+ // Handlers for next/prev/today buttons
+ $(".fc-today-button, .fc-prev-button, .fc-next-button").click(() => {
+ loadTimetable(calendar_obj.getDate());
+ });
+
+ // Setup refresh handler
+ $("#refresh-icon").click(() => {
+ loadTimetable(calendar_obj.getDate(), true);
+ });
+
+ // Setup side menu
+ const menus = document.querySelectorAll(".side-menu");
+ M.Sidenav.init(menus, { edge: "right", draggable: true });
+
+ // Setup side modal
+ const modals = document.querySelectorAll('.side-modal');
+ M.Sidenav.init(modals, { edge: 'left', draggable: false });
+});
diff --git a/js/tos.js b/assets/js/tos.js
index 9955abf..9955abf 100644
--- a/js/tos.js
+++ b/assets/js/tos.js