Last active
April 14, 2020 07:01
-
-
Save D4koon/72e1da0946be54156bcd573b3b99fa62 to your computer and use it in GitHub Desktop.
Median Documentation Enhancer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name MedianDox | |
| // @version 5.1.0 | |
| // @description Median Documentation Enhancer | |
| // @author D4koon | |
| // @include http*://docs.median-xl.com/doc/* | |
| // @grant GM_addStyle | |
| // @grant GM.setValue | |
| // @grant GM.getValue | |
| // @namespace https://gist.github.com/D4koon/ | |
| // @updateURL https://gist.githubusercontent.com/D4koon/72e1da0946be54156bcd573b3b99fa62/raw/MedianDox.user.js | |
| // @downloadURL https://gist.githubusercontent.com/D4koon/72e1da0946be54156bcd573b3b99fa62/raw/MedianDox.user.js | |
| // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js | |
| // ==/UserScript== | |
| function initMedianDox() { | |
| let MedianDoxVersion = GM_info.script.version | |
| if ($("#idx0").text().indexOf("TIERED UNIQUES") === -1 && | |
| $("#idx0").text().indexOf("SACRED UNIQUES") === -1 && | |
| $("#idx0").text().indexOf("BASE ITEMS") === -1) { | |
| console.log(MedianDoxVersion + ' NOT loaded for this site!'); | |
| return; | |
| } | |
| console.log('Loading ' + MedianDoxVersion); | |
| let startTimestamp = performance.now(); | |
| setupDescriptionToggle(); | |
| // Building the Interface for the data | |
| const dataInterface = new DataInterface(); | |
| let $itemFilters = $("<div id='itemClassFilter'></div>"); | |
| $itemFilters.css({'margin-left': '30px'}); | |
| $itemFilters.insertBefore("#idx1"); | |
| // == Add search-box == | |
| // WARNING: I put in a delay but basically disabled it again since the delay is now only 10 ms. | |
| // Originally it was ~250ms but since ther are no performance-problems, as far as i can see, i just leave it like that. | |
| $itemSearch = $("<input name='itemSearch' id='itemSearch' type='search' maxlength='128' title='Search for keywords' class='inputbox search tiny' size='20' value='' placeholder='Search…'>"); | |
| // Timeout variable for the search-delay | |
| let timeout = null; | |
| // Listen for inputs in the search-box | |
| $itemSearch.on('input', function (e) { | |
| let searchString = $(this).val(); | |
| // Clear the timeout if it has already been set. | |
| clearTimeout(timeout); | |
| // Make a new timeout set to go off. | |
| timeout = setTimeout(function () { | |
| console.log("Searching for: '" + searchString + "'"); | |
| showItemsWithName(dataInterface, searchString); | |
| }, 10); | |
| }); | |
| $itemFilters.append($itemSearch); | |
| // == Insert the class-filter right before the data for the items begins. == | |
| let $itemClassFilters = $("<div id='itemClassFilter'></div>"); | |
| $itemFilters.append($itemClassFilters); | |
| // == Create child-divs for the filter-categories == | |
| for (let i = 0; i < DataInterface.FilterCategoryNames.length; i++) { | |
| let itFilterCategoryName = DataInterface.FilterCategoryNames[i][0]; | |
| let $itemCategoryDiv = $("<div class='filterCategory " + itFilterCategoryName + "' id='itemClassFilter" + itFilterCategoryName + "'></div>") | |
| let $itemClassDescription = $("<div>" + itFilterCategoryName + "</div>"); | |
| $itemClassDescription.css({'text-align': 'center', 'font-size': 'medium', 'padding-bottom': '3px', 'font-weight': 'bold'}); | |
| $itemCategoryDiv.append($itemClassDescription); | |
| $itemClassFilters.append($itemCategoryDiv); | |
| } | |
| $itemClassFilters.append("<hr>"); | |
| // == Add filter-button for each item-class == | |
| for (let index = 0; index < dataInterface.itemClasses.length; index++) { | |
| const itemClass = dataInterface.itemClasses[index]; | |
| // = Create filter-button = | |
| let $itemClassFilterButton = $("<button class='medianDoxButton classFilterButton' type='button'>" + itemClass.SlotName + "</button>"); | |
| $itemClassFilterButton.data("itemClass", itemClass); | |
| $itemClassFilterButton.mousedown(function() { | |
| if ($(this).hasClass('selected')) { | |
| // This class is already selected => User wants to show all. | |
| dataInterface.showAll(); | |
| // Unselect current selection. | |
| $(this).toggleClass('selected'); | |
| } else { | |
| // Unselect current selection. | |
| unselectClassFilterButton(); | |
| // Hide all and then only display the selected. | |
| $(this).toggleClass('selected'); | |
| dataInterface.hideAll(); | |
| $(this).data("itemClass").show(); | |
| } | |
| }); | |
| // = Add filter-button to the right category = | |
| $("#itemClassFilter" + itemClass.Category).append($itemClassFilterButton); | |
| } | |
| console.log('Loading ' + MedianDoxVersion + ' finished in '+ (performance.now() - startTimestamp) + ' ms.'); | |
| } | |
| function showItemsWithName(dataInterface, searchString) { | |
| dataInterface.hideAll(); | |
| if(searchString.length == 0) { | |
| dataInterface.showAll(); | |
| return; | |
| } | |
| // = Remove selection from class-filter-button = | |
| unselectClassFilterButton(); | |
| let $result = dataInterface.findNameJquery(searchString); | |
| $result.show(); | |
| } | |
| function unselectClassFilterButton() { | |
| $('button.selected.classFilterButton').removeClass('selected'); | |
| } | |
| /** | |
| * Toggle the Description at the top of the page. | |
| */ | |
| function setupDescriptionToggle() { | |
| let $descriptionNodes = $('#idx0').nextUntil('.genbig'); | |
| (async () => { | |
| let hideDescription = await GM.getValue("hideDescription", false); | |
| console.log(hideDescription + ' hideDescription?'); | |
| $('#idx0').css('display', 'inline-block'); | |
| let toggleDescriptionButtonStyle = "style='margin: 0px 0px 10px 30px;'"; | |
| let $toggleDescriptionButton = $("<button type='button' class='medianDoxButton' " + toggleDescriptionButtonStyle + "></button>"); | |
| // = Handle button-press = | |
| $toggleDescriptionButton.mousedown(function() { | |
| hideDescription = !hideDescription; | |
| GM.setValue("hideDescription", hideDescription); | |
| updateDescriptionToggle($toggleDescriptionButton, hideDescription, $descriptionNodes); | |
| }); | |
| // = Init with the saved state = | |
| updateDescriptionToggle($toggleDescriptionButton, hideDescription, $descriptionNodes); | |
| // = Insert button into the DOM = | |
| $("#idx0").after($toggleDescriptionButton); | |
| })(); | |
| } | |
| function updateDescriptionToggle($toggleDescriptionButton, hideDescription, $descriptionNodes) { | |
| if (hideDescription) { | |
| $descriptionNodes.hide(); | |
| $toggleDescriptionButton.text("Show description"); | |
| $toggleDescriptionButton.addClass("selected"); | |
| } else { | |
| $descriptionNodes.show(); | |
| $toggleDescriptionButton.text("Hide description"); | |
| $toggleDescriptionButton.removeClass("selected"); | |
| } | |
| } | |
| class DataInterface { | |
| // == Filter categories == | |
| // = Inner-array-description = | |
| // The first element is always the name of the category | |
| // The following elements are "search-strings" that if when partly-match with a item-class will be pulled into that category | |
| // = Generic description = | |
| // The matching of the categories is from the last element backwards. | |
| // This means if a item-class matches with two "search-strings" it will end up in the category with the higher index. | |
| // If no match is found the item-class will be added to category with index 0 | |
| static get FilterCategoryNames() { | |
| return [["Generic-Weapons", "Weapons"], ["Generic-Armor-Jewlery", "Body Armors", "Body Armors", "Helms", "Circlets", "Shields", "Belts", "Gloves", "Boots", "Amulets", "Jewels", "Rings"], ["Amazon", "Amazon"], ["Assassin", "Assassin"], ["Barbarian", "Barbarian"], ["Druid", "Druid"], ["Necromancer", "Necromancer"], ["Paladin", "Paladin"], ["Sorceress", "Sorceress"]]; | |
| } | |
| constructor() { | |
| this.itemClasses = this.initItemClasses(); | |
| } | |
| initItemClasses() { | |
| let $htmlData = $(".genbig"); | |
| let itemClasses = []; | |
| for (let index = 1; index < $htmlData.length; index++) { | |
| let $iterNode = $htmlData.eq(index); | |
| let itemClassData = new ItemClassData($iterNode); | |
| itemClasses.push(itemClassData); | |
| } | |
| return itemClasses; | |
| } | |
| showAll() { | |
| let $nodesToShow = $(); | |
| for (let index = 0; index < this.itemClasses.length; index++) { | |
| const itItemClass = this.itemClasses[index]; | |
| $nodesToShow = $nodesToShow.add(itItemClass.$ItemClassNode); | |
| $nodesToShow = $nodesToShow.add(itItemClass.$ItemNodes); | |
| } | |
| $nodesToShow.show(); | |
| } | |
| hideAll() { | |
| for (let index = 0; index < this.itemClasses.length; index++) { | |
| const itItemClasses = this.itemClasses[index]; | |
| itItemClasses.hide(); | |
| } | |
| } | |
| findNameJquery(searchString) { | |
| let $result = $([]); | |
| for (let index = 0; index < this.itemClasses.length; index++) { | |
| const itItemClass = this.itemClasses[index]; | |
| let $tempResult = itItemClass.findNameJquery(searchString); | |
| $result = $result.add($tempResult); | |
| } | |
| return $result; | |
| } | |
| } | |
| class ItemClassData { | |
| static get CharacterTypes() { | |
| return ["Amazon", "Assassin", "Barbarian", "Druid", "Necromancer", "Paladin", "Sorceress"]; | |
| } | |
| constructor($itemClassNode) { | |
| // == Item-class-DOM == | |
| this.$ItemClassNode = $itemClassNode; | |
| this.$ItemNodes = this.init$ItemNodes(); | |
| // == Item-class-data == | |
| this.Items = this.initItems(); | |
| // = The node is still wrapped in an <b><b/> element so we use the text of the first child. = | |
| this.Name = $itemClassNode[0].children[0].innerText; | |
| this.SlotName = this.initSlotName(); | |
| this.Category = this.initCategory(); | |
| } | |
| // == Find the items in this class == | |
| // = Also replace the br-elements with a margin of the item-element so it is easier to work with = | |
| init$ItemNodes() { | |
| let $nodes = this.$ItemClassNode.nextUntil('.genbig'); | |
| let $tables = $nodes.filter('table'); | |
| let $brs = $nodes.filter('br'); | |
| // WARNING: This is a prety big performance-hit ~ 90-160ms. But i can live with it for now... | |
| $tables.addClass('itembox'); | |
| // NOTE: Hiding the br's is much faster then removing them. | |
| $brs.hide(); | |
| return $tables; | |
| } | |
| // = Find the items in this class = | |
| initItems() { | |
| let items = []; | |
| for (let index = 0; index < this.$ItemNodes.length; index++) { | |
| const $itemNode = this.$ItemNodes.eq(index); | |
| items.push(new ItemData($itemNode)); | |
| } | |
| return items; | |
| } | |
| // Item-Slot-Name | |
| // This removes the Character-Class from the name (like "Amazon Bow" => "Bow") | |
| initSlotName() { | |
| let slotName = this.Name; | |
| for (let i = 0; i < ItemClassData.CharacterTypes.length; i++) { | |
| let characterType = ItemClassData.CharacterTypes[i]; | |
| slotName = slotName.replace(characterType, ""); | |
| } | |
| // Remove whitespaces. | |
| slotName.trim(); | |
| return slotName; | |
| } | |
| // Find the right category for the item-class. | |
| initCategory() { | |
| // Look in categories with index >= 1 | |
| for (let iFilterCategory = DataInterface.FilterCategoryNames.length - 1; iFilterCategory >= 1; iFilterCategory--) { | |
| let itFilterCategory = DataInterface.FilterCategoryNames[iFilterCategory]; | |
| let itFilterCategoryName = itFilterCategory[0]; | |
| // Check for sub-categories/names | |
| for (let iFilterSubcategory = itFilterCategory.length - 1; iFilterSubcategory >= 1; iFilterSubcategory--) { | |
| let itFilterSubcategoryName = itFilterCategory[iFilterSubcategory]; | |
| // "indexOf() !== -1" => "true when match" | |
| if (this.Name.indexOf(itFilterSubcategoryName) !== -1) { | |
| return itFilterCategoryName; | |
| } | |
| } | |
| } | |
| // If no match found yet, add it to the Standard-category with index 0 | |
| return DataInterface.FilterCategoryNames[0][0]; | |
| } | |
| hide() { | |
| this.$ItemClassNode.hide(); | |
| this.$ItemNodes.hide(); | |
| } | |
| show() { | |
| this.$ItemClassNode.show(); | |
| this.$ItemNodes.show(); | |
| } | |
| findNameJquery(searchString) { | |
| // NOTE: cotainsIN is a custom extension, see code below. | |
| let $result = this.$ItemNodes.filter(":containsIN('" + searchString + "')"); | |
| return $result; | |
| } | |
| findName() { | |
| let matchingItems = []; | |
| for (let index = 0; index < this.ItemsData.length; index++) { | |
| const item = this.ItemsData[index]; | |
| // "indexOf() !== -1" => "true when match" | |
| if (item['name'].indexOf(itFilterSubcategoryName) !== -1) { | |
| matchingItems.push(item); | |
| } | |
| } | |
| } | |
| } | |
| class ItemData { | |
| constructor($itemNode) { | |
| // Item-class-DOM | |
| this.$ItemNode = $itemNode; | |
| // Item-data | |
| this._data = {}; | |
| // Travers until we find the th-element, in which the name is. | |
| this._data['name'] = this.initName(); | |
| } | |
| initName() { | |
| let thElement = this.$ItemNode.find('th'); | |
| if (thElement.length != 1) { | |
| console.log('ItemData::initName() - Could not find th-Element'); | |
| return 'Error' | |
| } | |
| return thElement[0].innerText; | |
| } | |
| } | |
| $(function() { | |
| // = Jquery-extension to support case-insensitiv text-search in elements = | |
| $.extend($.expr[":"], { | |
| "containsIN": function(elem, i, match, array) { | |
| return (elem.textContent || elem.innerText || "").toLowerCase().indexOf((match[3] || "").toLowerCase()) >= 0; | |
| } | |
| }); | |
| initMedianDox(); | |
| }); | |
| GM_addStyle(` | |
| /* NOTE: Somehow the colors come ot diffrent on the website when i make screenshot... i dont know why yet. Maybe somkind of global transperency? */ | |
| /* == Searchbox == */ | |
| #itemSearch { | |
| outline: none; | |
| border: 1px solid #333; | |
| color: #a97; | |
| padding: 3px 3px 3px 5px; | |
| font-weight: 400; | |
| font-family: Verdana,Helvetica,Arial,sans-serif; | |
| vertical-align: middle; | |
| background: #1A1A1A; | |
| margin-bottom: 8px; | |
| } | |
| #itemSearch:focus { border-color:#a09159; transition:border-color 250ms } | |
| /* == Filter-categories == | |
| inline-block to get them horizontal-stacked | |
| vertical-align to show them at the top | |
| padding for better visibility */ | |
| .filterCategory {display: inline-block; vertical-align: top; padding: 5px} | |
| .medianDoxButton { | |
| outline: none; | |
| background-color: #1A1A1A; | |
| color: #B7A88C; | |
| border: 2px solid #2D2B2B; | |
| border-radius: 4px; | |
| } | |
| .classFilterButton { | |
| width: 100%; | |
| display: block; | |
| padding-left: 3px; | |
| padding-right: 3px; | |
| } | |
| button.selected { background-color: #295916; } | |
| table.itembox { margin-bottom: 15px; } | |
| `); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment