diff --git a/src/wikitabs.css b/src/wikitabs.css new file mode 100644 index 0000000000000000000000000000000000000000..48fca1e3ceec6c7e0675b9952164a44b2eeadd70 --- /dev/null +++ b/src/wikitabs.css @@ -0,0 +1,41 @@ +/** + * Wikitabs CSS Stylings + */ + + +.wikitabs-tab-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin-top: -10px; + margin-bottom: 10px; + border-top: #bbb solid 1px; + border-bottom: #bbb solid 1px; +} + + +.wikitabs-tab-container div { + display: flex; + justify-content: center; + padding-top: 10px; + padding-bottom: 10px; + + background-color: #f5fcff; + + transition: background-color 0.2s ease; +} + + +.wikitabs-tab-container div:hover { + background-color: white; +} + + +.wikitabs-tab-element { + flex-grow: 1; + padding-right: 10px; + padding-left: 10px; + + cursor: pointer; +} + diff --git a/src/wikitabs.js b/src/wikitabs.js new file mode 100644 index 0000000000000000000000000000000000000000..76a35f964eab10d6cec0c19b3fd724947bc1dac6 --- /dev/null +++ b/src/wikitabs.js @@ -0,0 +1,282 @@ +/** + * Javascript logic for "WikiTabs". + * This is what creates the clickable tabbed filters at the top of wiki pages. + * + * Creates tabs based on H2 headers. Note that at least two matching H2 headers must be found on page. + * If no header values are provided as a "wikitabs-template-variables" element, then no tabbing will be generated. + * Note that the values are also case-sensitive. + * + * As an example, somewhere on the wiki page, there needs a HMTL element of the format: + * <div id="wikitabs-template-variables">["H2 Header Value #1", "H2 Header Value #2"]</div> + * Where each "H2 Header Value" is replaced by the title of an actual H2 on the page, that is desired to show up as a + * tab. There needs to be at minimum two elements. And there can be as many as you want (but more than three to five + * elements tends to crowd the tabs). + */ + + +// Global Variables. +var wiki_content_container = document.getElementById('mw-content-text').getElementsByClassName("mw-parser-output")[0]; +var wikitabs_header_dict = { + "All": [], + "Other": [], +}; +var wikitabs_header_ordering = []; +var generate_wiki_tab_bool = true; + +// Get "tabbable topics" if provided. +var wiki_tabbable_topics = [] +if (document.getElementById("wikitabs-template-variables")) { + // Topics were provided by template. + wiki_tabbable_topics = document.getElementById("wikitabs-template-variables").innerHTML; + + // Topics acquired. Element no longer servers a purpose. Remove. + document.getElementById("wikitabs-template-variables").remove(); +} else { + // Topics were not provided by template. Do not tab. + wiki_tabbable_topics =[]; +} +console.log("wiki_tabbable_topics:"); +console.log(wiki_tabbable_topics); + + +function wiki_tab_generation_main() { + // console.log("Starting \"Wiki Tab Generation\" logic."); + + // Populate variables. + populate_wiki_tab_variables(); + generate_wiki_tabs(); +} + + +/** + * Loops through all page content elements. All elements are automatically stored into header_dict["all"]. + * + * If element is a H2 header, then a new key is created in header_dict, using the H2 text value. + * + * If at least one H2 header has been found, then the element is stored under both header_dict["all"] and + * header_dict[<header_name>], where <header_name> is the most recently found H2 element. + */ +function populate_wiki_tab_variables() { + var current_header = "Other"; + + // Loop through all content elements. Aka, elements found in within the 'mw-context-tree' id. + var page_length = wiki_content_container.childNodes.length; + for (var index = 0; index < page_length; index++) { + // Save current element for easy reference. + var current_element = wiki_content_container.childNodes[index]; + + // Check if element is H2 header. If so, create a new key in header_dict, using element's text value. + if (current_element.tagName == 'H2') { + current_header = current_element.firstChild.innerHTML; + + // Check if item is in "tabbable topics" array. + if (wiki_tabbable_topics.includes(current_header)) { + // Item is in "tabbable topics" array. We can create a new page tab for this content. + // Create new key with header value, and also push to ordering dict to preserve page content ordering. + wikitabs_header_dict[current_header] = []; + wikitabs_header_ordering.push(current_header); + } else { + // Item is not in "tabbable topics" array. Put content into "Other" tab. + current_header = "Other"; + } + } + + // Add element into "all" key. + wikitabs_header_dict["All"].push(wiki_content_container.childNodes[index].cloneNode(true)); + // Also add element to dictionary, under current found header. + wikitabs_header_dict[current_header].push(wiki_content_container.childNodes[index]); + } + + // Check if any elements were added to "Other" section. If so, add "Other" to list of dict values. + if (wikitabs_header_dict["Other"] != []) { + wikitabs_header_ordering.push("Other"); + } + + // Workaround for weird bug with extra, unecessary <p><br></p> elements at top of page. + // Fix for "All" tab. + while (wikitabs_header_dict["All"].length > 0 && wikitabs_header_dict["All"][0].outerHTML == "<p><br>\n</p>") { + wikitabs_header_dict["All"].shift(); + } + // Also fix for "Other" tab. + while (wikitabs_header_dict["Other"].length > 0 && wikitabs_header_dict["Other"][0].outerHTML == "<p><br>\n</p>") { + wikitabs_header_dict["Other"].shift(); + } +} + + +/** + * Generates actual tab elements on page. + */ +function generate_wiki_tabs() { + + // Check if we have at least two unique headers to create tabs for. + // If so, generate wikitabs html. Otherwise, leave page as is. + header_array_clone = wikitabs_header_ordering.slice(); + + // We don't want to count the "Other" tab for counting purposes. It may or may not be present. + if (header_array_clone.includes("Other")) { + var index = header_array_clone.indexOf("Other"); + header_array_clone.splice(index, 1); + } + + // Now that we have an array of only "unique" tab values, check length. + if (header_array_clone.length >= 2) { + generate_wiki_tab_bool = true; + } else { + generate_wiki_tab_bool = false; + } + + // Only proceed to manipulate page if wiki tabs should be generated. + if (generate_wiki_tab_bool) { + var page_tabs = document.createElement("div"); + page_tabs.className = "wikitabs-tab-container"; + var page_divs = document.createElement("div"); + page_divs.className = "wikitabs-content-container"; + + // Default "All" tab. Will always be present in a wiki tabs page. + // Create tab for "All". + var all_tab = document.createElement("div"); + all_tab.id = "wikitabs-all-tab"; + all_tab.className = "wikitabs-tab-element"; + all_tab.innerHTML = "All"; + page_tabs.append(all_tab); + + // Create page content div for "All". + var all_div = document.createElement("div"); + all_div.id = "wikitabs-all-div"; + all_div.className = "wikitabs-content-element"; + var all_arr_length = wikitabs_header_dict["All"].length; + for (index = 0; index < all_arr_length; index++) { + all_div.appendChild(wikitabs_header_dict["All"][index]); + } + page_divs.append(all_div); + + // In most cases, we will have an automatically generated "Other" tab, too. + // Create tab for "Other". + var other_tab = document.createElement("div"); + other_tab.id = "wikitabs-other-tab"; + other_tab.className = "wikitabs-tab-element"; + other_tab.innerHTML = "Other"; + + // Create page content for "Other". + var other_div = document.createElement("div"); + other_div.id = "wikitabs-other-div"; + other_div.className = "wikitabs-content-element"; + + // Loop through all values present in ordering array to dynamically add all other headers. + // Looping through this way ensures we preserve page content ordering. + for (var index = 0; index < wikitabs_header_ordering.length; index++) { + + // Check if header item is in "tabbable topics" array. + if (wiki_tabbable_topics.includes(wikitabs_header_ordering[index])) { + // Item is in "tabbable topics" array. We can create a new page tab for this content. + var curr_header = wikitabs_header_ordering[index]; + var header_slug = slugify_text(curr_header); + + // Create tab for current header. + var curr_tab = document.createElement("div"); + curr_tab.id = "wikitabs-" + header_slug + "-tab"; + curr_tab.className = "wikitabs-tab-element"; + curr_tab.innerHTML = curr_header; + page_tabs.append(curr_tab); + + // Create page content div for current header. + var curr_div = document.createElement("div"); + curr_div.id = "wikitabs-" + header_slug + "-div"; + curr_div.className = "wikitabs-content-element"; + var array_length = wikitabs_header_dict[curr_header].length; + for (var inner_index = 0; inner_index < array_length; inner_index++) { + curr_div.appendChild(wikitabs_header_dict[curr_header][inner_index]); + } + page_divs.append(curr_div); + + } else { + // Item is not in "tabbable topics" array. Put content into "Other" tab. + var array_length = wikitabs_header_dict["Other"].length; + for (var inner_index = 0; inner_index < array_length; inner_index++) { + other_div.appendChild(wikitabs_header_dict["Other"][inner_index]); + } + } + } + + // Check if "other" section was populated at all. If so, append to main wikitab containers. + if (other_div.childElementCount > 0) { + // Other tab has elements and should exist. + page_tabs.append(other_tab); + page_divs.append(other_div); + } else { + // Other tab does not have elements and should not exist. + wikitabs_header_ordering.splice("Other", 1); + } + + // Make sure nothing is in parent element. Everything should be processed by now. + parent_element = wiki_content_container; + while (parent_element.firstChild) { + parent_element.removeChild(parent_element.firstChild); + } + + // Finally, append elements to page itself. + wiki_content_container.appendChild(page_tabs); + wiki_content_container.appendChild(page_divs); + + // Add "All" value to start of array. + //If we have tabs at all, then this is always present and first. + wikitabs_header_ordering.unshift("All"); + + // Add click event listeners for wikitabs. + for (var index = 0; index < wikitabs_header_ordering.length; index++) { + // Get tab elements. + var current_tab = document.getElementsByClassName('wikitabs-tab-container')[0].childNodes[index]; + + // Add click handling to tab elements. + current_tab.addEventListener("click", function() { + // Get index of clicked tab element. + // Do this by cycling back through parent container until we get to first child, and counting each. + var element_index = 0; + var current_tab = this; + while (current_tab.previousSibling != null) { + current_tab = current_tab.previousSibling; + element_index++; + } + + // Hide all div tab-sections, except the one clicked. + for (var tab_index = 0; tab_index < wikitabs_header_ordering.length; tab_index++) { + // Get parent of content elements to hide/show. + content_container = document.getElementsByClassName('wikitabs-content-container')[0]; + + // Check if same index as clicked. If so, show instead. + if (element_index == tab_index) { + // On clicked element. Set to "block" to display. + content_container.childNodes[tab_index].style.display = "block"; + } else { + // On a sibling element. Set to display none to hide. + content_container.childNodes[tab_index].style.display = "none"; + } + } + }); + + // Also hide all content, initially. Otherwise we'll have duplicates on page. + var tab_content = document.getElementsByClassName('wikitabs-content-container')[0].childNodes[index]; + tab_content.style.display = "none"; + } + + // Set "All" content to be visible. Everything else should be hidden by now. + document.getElementsByClassName('wikitabs-content-container')[0].childNodes[0].style.display = "block"; + } +} + + +/** + * Takes given text string and returns equivalent "slug" version. + * + * Slugs are safe for urls and html id's. + * Essentially, all letters are converted to lowercase, and whitespace is converted to dashes. + */ +function slugify_text(orig_text) { + return orig_text.toLowerCase().replace(/ /g, "-"); +} + + +// Run "Wiki Tab Generation" logic. +wiki_tab_generation_main(); +