Skip to content

Instantly share code, notes, and snippets.

@D4koon
Last active April 14, 2020 07:01
Show Gist options
  • Select an option

  • Save D4koon/72e1da0946be54156bcd573b3b99fa62 to your computer and use it in GitHub Desktop.

Select an option

Save D4koon/72e1da0946be54156bcd573b3b99fa62 to your computer and use it in GitHub Desktop.
Median Documentation Enhancer
// ==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