editorLog = console.log; // editorLog = function(){}; debugLog = function(){}; // debugLog = console.log; //parseLog = function(){}; parseLog = console.log; errorLog = console.log; /* the structure of each object, and its realization as PreTeXt source or in HTML, is recorded in the objectStructure dictionary. {"xml:id": new_id, "sourcetag": new_tag, "parent": parent_description, "title": ""} In pretext, pieces are of the form ["piecename", "tag"], while in source, pieces are of the form ["piecename", "required_component"], while */ editing_mode = 0; current_page = location.host+location.pathname; debugLog("current_page", current_page); chosen_edit_option_key = "edit_option".concat(current_page); /* chosen_edit_option = readCookie(chosen_edit_option_key) || ""; editing_mode = chosen_edit_option; */ chosen_edit_option = "PLACEHOLDER"; debugLog("chosen_edit_option", chosen_edit_option, "chosen_edit_option", chosen_edit_option > 0); var font_families = { 'RS': "'Roboto Serif', serif;", 'OS': "'Open Sans', sans-serif;" } var font_vals = { // 'face': 'serif', 'size': [12, 8, 20], 'height': [135, 80, 200], 'wspace': [0, -10,20], 'lspace': [0, -20,20], 'wdth': [100, 50, 150], 'wght': [400, 100, 1000] } function fontcss(fvals) { console.log("in fontcss",fvals); var csskeys = Object.keys(fvals); var this_style = ""; for (var j=0; j < csskeys.length; ++j) { this_key = csskeys[j]; document.getElementById("the" + this_key).innerHTML = fvals[this_key][0]; console.log("in fontcss", this_key, "with value", fvals[this_key][0], "/10", fvals[this_key][0]/10.0); if (this_key == 'size') { this_style += "font-size: " + fvals[this_key][0].toString() + "pt; " } else if (this_key == 'height') { this_style += "line-height: " + (fvals[this_key][0]/100.0).toString() + "; " } else if (this_key == 'lspace') { this_style += "letter-spacing: " + (fvals[this_key][0]/200.0).toString() + "rem; " } else if (this_key == 'wspace') { this_style += "word-spacing: " + (fvals[this_key][0]/50.0).toString() + "rem; " } } this_style += "font-variation-settings: "; for (var j=0; j < csskeys.length; ++j) { this_key = csskeys[j]; if (this_key == 'wdth') { this_style += "'wdth' " + fvals[this_key][0].toString() + "," } else if (this_key == 'wght') { this_style += "'wght' " + fvals[this_key][0].toString() + "," } } this_style = this_style.slice(0, -1); this_style += ";"; console.log("returning this_style", this_style); return this_style } function choice_options(this_choice) { console.log("choice_options of", this_choice); val_dict = prefs_menu_vals[this_choice]; console.log("val_dict", val_dict); var these_keys = Object.keys(val_dict); these_keys.sort(); var these_choices = ""; for (var j=0; j < these_keys.length; ++j) { this_key = these_keys[j]; these_choices += '
  • '; these_choices += val_dict[this_key]; these_choices += '
  • '; } return these_choices } // we have to keep track of multiple consecutive carriage returns this_char = ""; prev_char = ""; prev_prev_char = ""; // sometimes we have to prevent Tab from changing focus this_focused_element = ""; prev_focused_element = ""; prev_prev_focused_element = ""; var menu_neutral_background = "#ddb"; var menu_active_background = "#fdd"; var recent_editing_actions = []; // we unshift to this, so most recent edit is first. // currently just a human-readable list var ongoing_editing_actions = []; var old_content = {}; // to hold old versions of changed materials // what will happen with internationalization? var keyletters = ["KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ"]; var movement_location_options = []; var movement_location = 0; var first_move = true; // used when starting to move, because object no longer occupies its original location Storage.prototype.setObject = function(key, value) { // this.setItem(key, JSON.stringify(value)); this.setItem(key, JSON.stringify(value, function(key, val) { // console.log("key", key, "value", value, "val", val); return val.toFixed ? Number(val.toFixed(3)) : val; })); } Storage.prototype.getObject = function(key) { var value = this.getItem(key); return value && JSON.parse(value); } function randomstring(len) { if (!len) { len = 10 } return "tMP" + (Math.random() + 1).toString(36).substring(2,len) } function removeItemFromList(lis, value) { var index = lis.indexOf(value); if (index > -1) { lis.splice(index, 1); } return lis; } function textNodesUnder(node){ var all = []; for (node=node.firstChild;node;node=node.nextSibling){ if (node.nodeType==3) { all.push([3, node]) } else if (node.nodeType==1) { all.push([1, node]) var thistag = node.cloneNode().outerHTML; console.log("thistag", thistag); [thisopen, thisclose] = thistag.split("><"); thisopen += ">"; thisclose += "<"; thisinsides = textNodesUnder(node.cloneNode().innerHTML); // all.push([0, thisopen]); for (var j=0; j < thisinsides.length; ++j) { // all.push(thisinsides[j]) } // all.push([0, thisclose]); } // probably we only want direct children // else all = all.concat(textNodesUnder(node)); } return all; } function wordsAllWrapped(node) { var these_text_nodes = textNodesUnder(node); console.log("node", node); console.log("these_text_nodes", these_text_nodes); for (var j=0; j < these_text_nodes.length; ++j) { var this_text_node = these_text_nodes[j]; var thistype = this_text_node[0]; var thisnode = this_text_node[1]; if (thistype == 3) { // var these_node_words_and_spaces = these_text_nodes[j].nodeValue.split(/(\s+)/); var these_node_words_and_spaces = thisnode.nodeValue.split(/(\s+)/); console.log("these_node_words_and_spaces", these_node_words_and_spaces); var spanned_words = ""; for (var k=0; k < these_node_words_and_spaces.length; ++k) { spanned_words += '' + these_node_words_and_spaces[k] + "" } var wass_text = document.createElement('div'); wass_text.setAttribute('class', 'wastext'); wass_text.innerHTML = spanned_words; // these_text_nodes[j].nodeValue = spanned_words thisnode.replaceWith(wass_text); wass_text.outerHTML = wass_text.innerHTML } else if (thistype == 1) { // // leave it there, but make sure we know about it // thisnode.classList.add("oneelement") if (thisnode.classList.contains("process-math") || thisnode.classList.contains("autopermalink") || thisnode.classList.contains("latex-logo") || thisnode.classList.contains("heading") || // should we instead check for tags that *can* contain line breaks? ["A", "CODE", "PRE"].includes(thisnode.tagName)) { //wrap it in a span.oneword var word_wrapper = document.createElement('span'); word_wrapper.setAttribute('class', 'oneword'); thisnode.parentNode.insertBefore(word_wrapper, thisnode); word_wrapper.appendChild(thisnode); } else { wordsAllWrapped(thisnode) } } } } function linesAllWrapped() { // var testID = "p-446"; // var testNode = document.getElementById(testID); // var all_para = document.querySelectorAll(".para"); // var all_para = document.querySelectorAll("SECTION P"); var all_para = document.querySelectorAll("SECTION .para:not(.logical)"); for (var pj = 0; pj < all_para.length; ++pj) { testNode = all_para[pj]; wordsAllWrapped(testNode); console.log(" wordsAllWrapped", pj, "of", testNode); var these_words = document.querySelectorAll(".oneword, .oneelement"); var this_line = []; var all_lines = ""; var current_height = these_words[0].getBoundingClientRect().bottom; var word_depth = 0; for (var j=0; j < these_words.length; ++j) { // next line is an error if the paragraphs starts with an element var this_word = these_words[j]; var this_parent = this_word.parentElement; if (this_word.classList.contains("oneword")) { this_height = this_word.getBoundingClientRect().bottom; if ( (Math.abs(this_height - current_height) < 10) && j > 0) { if (this_parent == testNode && word_depth == 0) { this_line.push(this_word.innerHTML) } else { console.log(word_depth, ":",this_parent == testNode, "this_parent", this_parent.tagName, "ccc", this_parent.ClassList, "xxx", this_word.innerHTML); if (word_depth == 0) { console.log("opening the tag", this_parent.tagName,"parent", this_parent,"of",this_word.innerHTML); var parent_classlist = this_parent.classList; console.log("parent_classlist", parent_classlist, "first", parent_classlist[0], "length", parent_classlist.length); // did not work. why? var parent_classes = parent_classlist.join(" "); this_line.push('<' + this_parent.tagName + ' class="' + parent_classlist[0] + '">'); this_line.push(this_word.innerHTML); word_depth += 1 } else { this_line.push(this_word.innerHTML); } if (this_word.nextElementSibling == null) { console.log("closing the tag", this_parent.tagName); // need to close the wrapping element this_line.push(""); word_depth -= 1; if (word_depth > 0 && this_word.parentElement.nextElementSibling == null) { this_line.push(""); word_depth -= 1; } } } } else { html_line_contents = ""; for (var k=0; k < this_line.length; ++k) { html_line_contents += this_line[k] } // hack which only handles up to depth 2 if (word_depth > 0) { html_line_contents += "" } if (word_depth > 1) { html_line_contents += "" } console.log("made", html_line_contents); all_lines += '
    ' + html_line_contents + '
    '; current_height = this_height; this_line = [this_word.innerHTML]; if (word_depth > 0) { this_line.unshift('<' + this_parent.tagName + ' class="' + this_parent.classList[0] + '">') } if (word_depth > 1) { this_line.unshift('<' + this_parent.tagName + ' class="' + this_parent.parentElement.classList[0] + '">') } } } else { // we have an html node this_line.push(this_word.outerHTML) } } // partial last line may be dangling // bug: need to fix closing tags at the end? maybe rely on automatic closure if (this_line.length > 0) { html_line_contents = ""; for (var k=0; k < this_line.length; ++k) { html_line_contents += this_line[k] } all_lines += '
    ' + html_line_contents + '
    ' } testNode.innerHTML = all_lines } } editorLog("adding tab listener"); document.addEventListener('keydown', logKeyDown); function nextsibligntabbable(startingplace, where) { var candidate = startingplace.nextElementSibling; if (where == "previous" ) { candidate = startingplace.previousElementSibling } while (candidate) { console.log("candidate A", candidate); if (candidate.hasAttribute("tabindex")) { return candidate } console.log("candidate B", candidate); if (where == "next" ) { candidate = candidate.nextElementSibling } else { candidate = candidate.previousElementSibling } } } function logKeyDown(e) { if (e.code == "ShiftLeft" || e.code == "ShiftRight" || e.code == "Shift") { return } prev_prev_char = prev_char; prev_char = this_char; this_char = e; debugLog("logKey",e,"XXX",e.code); var input_region = document.activeElement; debugLog("input_region", input_region); if (input_region.id == "user-preferences-button") { if (e.code == "Enter") { document.getElementById("preferences_menu_holder").classList.toggle("hidden") } else if (e.code == "Tab") { console.log("tabbing to main pref menu", document.getElementById("preferences_menu_holder").firstElementChild); console.log("tabbing to main pref menu", document.getElementById("preferences_menu_holder").firstElementChild.firstElementChild); e.preventDefault(); document.getElementById("preferences_menu_holder").firstElementChild.firstElementChild.focus() } } else if (input_region.hasAttribute("data-env")) { var this_submenu = input_region.getElementsByTagName("ol")[0]; console.log("this_submenu C", this_submenu); if ((e.code == "Enter") || (e.code == "ArrowRight")) { console.log("selecting",input_region, "which has", input_region.getElementsByTagName("ol"), "first",input_region.getElementsByTagName("ol")[0]); this_submenu.classList.toggle("hidden") input_region.classList.toggle("selected") input_region.parentElement.classList.toggle("active") } else if (e.code == "Tab" && e.shiftKey) { e.preventDefault(); if (input_region.classList.contains("selected")) { this_submenu.classList.loggle("hidden"); input_region.classList.loggle("selected"); } else { // cycle up through the elements // previous_sibling = input_region.previousElementSibling; previous_sibling = nextsibligntabbable(input_region, "previous"); if (previous_sibling) { previous_sibling.focus() } else { document.getElementById("preferences_menu_holder").classList.toggle("hidden"); document.getElementById("user-preferences-button").focus(); } } } else if (e.code == "Tab") { e.preventDefault(); if (input_region.classList.contains("selected")) { var firstchild = this_submenu.firstElementChild; if (firstchild.hasAttribute("tabindex")) { firstchild.focus() } else { nextsibligntabbable(firstchild, "next").focus() } } else { // cycle through the elements // next_sibling = input_region.nextElementSibling; next_sibling = nextsibligntabbable(input_region, "next"); if (next_sibling) { next_sibling.focus() } else { document.getElementById("preferences_menu_holder").classList.toggle("hidden"); document.getElementById("user-preferences-button").focus(); } } } } else if (input_region.hasAttribute("data-val")) { console.log("input_region", input_region); console.log("input_regionparentElement", input_region.parentElement); if ((e.code == "Enter") || (e.code == "ArrowRight")) { console.log("font_vals is", font_vals); var dataval = input_region.getAttribute("data-val"); var datachange = input_region.getAttribute("data-change"); console.log("dataval", dataval, "datachange",datachange); if (input_region.parentElement.classList.contains("fonts")) { font_vals[dataval][0] += parseFloat(datachange); if (font_vals[dataval][0] < font_vals[dataval][1]) { font_vals[dataval][0] = font_vals[dataval][1] } else if (font_vals[dataval][0] > font_vals[dataval][2]) { font_vals[dataval][0] = font_vals[dataval][2] } console.log("font_vals are", font_vals); console.log("css font_vals", fontcss(font_vals)); var new_style = fontcss(font_vals); var paras = document.getElementsByClassName('para'); for (i = 0; i < paras.length; i++) { paras[i].setAttribute('style', new_style); } } else if (input_region.parentElement.classList.contains("fontfamily")) { document.body.setAttribute("data-font", datachange); var checks = document.getElementsByClassName('ffcheck'); for (i = 0; i < checks.length; i++) { checks[i].innerHTML = ''; } document.getElementById("the" + datachange).innerHTML = "✔️"; } else if (input_region.parentElement.classList.contains("avatar")) { var checks = document.getElementsByClassName('avatarcheck'); for (i = 0; i < checks.length; i++) { checks[i].innerHTML = ''; } document.getElementById("theavatarbutton").innerHTML = dataval; document.getElementById("the" + dataval).innerHTML = "✔️"; } else if (input_region.parentElement.classList.contains("atmosphere")) { document.body.setAttribute("data-atmosphere", dataval); var checks = document.getElementsByClassName('atmospherecheck'); for (i = 0; i < checks.length; i++) { checks[i].innerHTML = ''; } document.getElementById("the" + dataval).innerHTML = "✔️"; } else if (input_region.parentElement.classList.contains("ruler")) { // could be motion or actual ruler if (["mouse", "arrow", "eye"].includes(dataval)) { document.body.setAttribute("data-motion", dataval); var checks = document.getElementsByClassName('motioncheck'); for (i = 0; i < checks.length; i++) { checks[i].innerHTML = ''; } document.getElementById("the" + dataval).innerHTML = "✔️"; } else { if (!document.body.hasAttribute("data-ruler")) { linesAllWrapped() } document.body.setAttribute("data-ruler", dataval); var checks = document.getElementsByClassName('rulercheck'); for (i = 0; i < checks.length; i++) { checks[i].innerHTML = ''; } document.getElementById("the" + dataval).innerHTML = "✔️"; } } } else if (e.code == "Tab" && e.shiftKey) { e.preventDefault(); // previous_sibling = input_region.previousElementSibling; previous_sibling = nextsibligntabbable(input_region, "previous"); console.log("previous_sibling",previous_sibling); if (previous_sibling) { previous_sibling.focus() } else { input_region.parentElement.parentElement.parentElement.classList.toggle("active"); input_region.parentElement.parentElement.classList.toggle("selected"); input_region.parentElement.parentElement.focus(); input_region.parentElement.classList.toggle("hidden"); } } else if (e.code == "Tab") { e.preventDefault(); // next_sibling = input_region.nextElementSibling; next_sibling = nextsibligntabbable(input_region, "next"); if (next_sibling) { next_sibling.focus() } else { input_region.parentElement.parentElement.parentElement.classList.toggle("active"); input_region.parentElement.parentElement.classList.toggle("selected"); input_region.parentElement.parentElement.focus(); input_region.parentElement.classList.toggle("hidden"); } } } return; editorLog("input_region", input_region); // if we are writing something, keystrokes usually are just text input if (document.getElementById('actively_editing')) { editorLog(" we are actively editing"); if (e.code == "Tab" && !document.getElementById('local_menu_holder')) { // disabled for now e.preventDefault(); return "" create_local_menu() } else if (document.getElementById('local_menu_holder')) { main_menu_navigator(e); } else { local_editing_action(e) } } else if (document.getElementById('phantomobject')) { var the_phantomobject = document.getElementById('phantomobject'); if (the_phantomobject.classList.contains('move')) { move_object(e) } else { alert("do not know what to do with that") } } else { console.log("calling main_menu_navigator"); main_menu_navigator(e); } }