// ==UserScript== // @name DeviantArt Search Galleries and Favorites // @namespace http://tampermonkey.net/ // @version 0.2 // @description Creates a search function that works on artist galleries and favorites collections. Search by deviation title and artist name. Optional alphabetical sorting. // @author corepower // @match https://www.deviantart.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=deviantart.com // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; /////////////////////////////////////// ////// User Preference Variables ////// // Maximum search return: Maximum number of results that will tile after a search. This can be set as large as you like, but the page may slow down. // Possibly even consider lowering it if things are laggy while searching. const maximumsearchreturn = 1000; // Sort results alphabetically: if set to true, search results will be alphabetized. Used in conject with sortresultsbyartistortitle variable below. // If set to false, the default DeviantArt order is used which is based on date added. const sortresultsalphabetically = false; //Sort results by artist or title: if set to "artist" sorting will be based on artist. If set to "title", sorting will be based on title. const sortresultsbyartistortitle = "title"; // Pagination rate limit: the amount of time, in seconds to delay downloading gallery/collection page results during search indexing. // Can be set to 0 but if lots of large galleries are indexed, DeviantArt may automatically temp ban your account/IP. // Note: Multiple searches on a single page will not rerun the index, therefore unlimited searches may be run on a single page without consequence as long as the page is not navigated or refreshed. const paginationratelimit = 1.0; // Rate limit warning: If true, puts a warning message in the search output text while indexing about rate limits if the rate limit is under two seconds on a large search. // Set to false to disable warning. const ratelimitwarning = true; // Pivotal row height: height, in pixels, that the dynamic deviation tiles will be based around. Tiles will be this tall or larger. const pivotalrowheight = 280; // Tile margin: margin between deviation tiles, in pixels. DeviantArt uses 4 pixels, don't know why you would change this but you can. const tilemargin = 4; // Preferred size: this is a named identifier for a size of DeviantArt generated thumbnails. Larger numbers are generally higher quality. // Set this variable based on your internet speed or personal preference. // Choose a value from amongst the following list: ["92S", "150", "200H", "300W", "375W", "414W", "250T", "350T","400T", "preview", "social_preview"] const preferredsize = "350T"; // Debug: debug flag turns on various console outputs for debugging. Outputs may or may not make sense, they were added as needed. const debug = false; //// End User Preference Variables //// /////////////////////////////////////// ///////////////////////////////////////////////////////// ////// Desired Features List - NOT YET IMPLEMENTED ////// // These features may come in future versions. // * Navigate within DeviantArt and reset search capability based on new page (partially implemented) // * Dynamic tile visibility so that any number of search results can be efficiently displayed without slowing down the page. // * React framework tie-ins for things like element creation, event listeners, and deviation favoriting. This may not be feasible. I don't know React. ////// End Desired Features List //////////////////////// ///////////////////////////////////////////////////////// //Special event listener for React-style internal navigation //Credit to Raja Osama: https://rajaosama.me/blogs/detect-react-route-change-in-vanilla-js //Re-run entire script after this type of navigation let monitored_url = location.href; document.body.addEventListener('click', ()=>{ requestAnimationFrame(()=>{ if(monitored_url!==location.href){ let old_url = monitored_url; monitored_url = location.href //DEBUG if(debug && false) console.log('url changed to: ', monitored_url); //Check for specific case where navigation is between Galleries and Favorites on a single artist let old_url_parts = old_url.split("/"); let monitored_url_parts = monitored_url.split("/"); //Disregard these base path type of /artist if(old_url_parts.length < 5 || monitored_url_parts.length < 5) return; //Check for if navigations from gallery -> favourite or favourite -> gallery let is_old_url_favourite = old_url_parts[4] == "favourites"; let is_new_url_favourite = monitored_url_parts[4] == "favourites"; if( is_old_url_favourite ? !is_new_url_favourite : is_new_url_favourite ) { return; //TODO modify main script execution for this type of internal navigation then enable observer by uncommenting code below //set observer on this element to load // waitForElm('._3h7d3').then((elm) => { // if(debug && true) console.log('Element ._3h7d3 is ready'); // //executeScript(); // }); } else { //not a problem navigation, run script regularly executeScript(); } } }); }, true); //DEBUG if(debug && false) console.log("Entrypoint ready state: ", document.readyState); if(debug && false) document.addEventListener('readystatechange', () => console.log("Ready state change: ", document.readyState)); //Page needs to be fully loaded for script to work if(document.readyState == "loading" || document.readyState == "interactive") window.addEventListener('load',executeScript); else executeScript(); //Function wrapper for the entire script to enable document readiness check above, as well as for re-running after React-style navigation events function executeScript() { //DEBUG if(debug && false) console.log("Entered script execution."); if(debug && false) console.log("Internal entrypoint ready state: ", document.readyState); //Only execute script on gallery pages, otherwise return immediately let pathparts = window.location.pathname.split("/"); if(pathparts.length < 3) { return; } if(pathparts[2] != "gallery" && pathparts[2] != "favourites") { return; } //Creating and adding this search element structure to the DOM //
// // // // // // // // //
let searchparentparent = document.getElementsByClassName("_3h7d3")[0]; let searchparent = document.createElement("div"); searchparent.className = "_1hkGk DRK5r _1bp4v"; searchparent.style.width = "350px"; let searchiconparentparent = document.createElement("span"); searchiconparentparent.className = "DLN_H"; let searchiconparent = document.createElement("span"); searchiconparent.className = "z8jNZ _1yoxj _2F1i2"; var searchicon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); searchicon.setAttribute("viewBox", "0 0 24 24"); var searchiconpath = document.createElementNS("http://www.w3.org/2000/svg", "path"); searchiconpath.setAttribute("d", "M19 11a8 8 0 10-3.095 6.32l3.63 3.63.095.083a1 1 0 001.32-1.498l-3.63-3.63A7.965 7.965 0 0019 11zM5 11a6 6 0 1112 0 6 6 0 01-12 0z"); let searchelement = document.createElement('input'); searchelement.type = 'text'; searchelement.className = 'aFKMF _3kAA3 _2KZ9p'; searchelement.setAttribute('aria-invalid', 'false'); searchelement.id = 'search-gallery'; searchelement.autocomplete = 'off'; searchelement.placeholder = 'Search Gallery'; searchelement.value = ''; searchicon.appendChild(searchiconpath); searchiconparent.appendChild(searchicon); searchiconparentparent.appendChild(searchiconparent); searchparent.appendChild(searchiconparentparent); searchparent.appendChild(searchelement); //Create search results text output and put it next to the search box let searchoutputtext = document.createElement("span"); searchoutputtext.style.margin = "8px"; searchoutputtext.style.maxWidth = "1000px"; searchparentparent.appendChild(searchoutputtext); searchparentparent.appendChild(searchparent); //Add event listeners searchelement.addEventListener("focus", buildIndex, {once : true}); searchelement.addEventListener("keyup", search); //define persistent globals for search indexing const csrftoken = window.__CSRF_TOKEN__; const artist_friendly_id = pathparts[1];//from the early URL path deconstruction let deviations;//this will be objects to index search with let matchingdeviations;//this will be objects that match the search let initialstatescripttext = document.getElementsByTagName("script")[11].innerHTML; let initialstatematch = initialstatescripttext.match(/\.__INITIAL_STATE__\s*=\s*JSON\.parse\(("[^\n]+")/); if(initialstatematch.length < 2) { console.error("DeviantArt Gallery Search Userscript Error: Could not parse script tag for __INITIAL_STATE__") searchelement.placeholder = 'Script Error'; return; } let initialstateJSON = JSON.parse(eval(initialstatematch[1])); //console.log(initialstateJSON); let isgallery = pathparts[2] == "gallery"; let collectionorgalleryfoldername = isgallery ? "galleryFolder" : "collectionFolder"; let foldersJSON = initialstateJSON["@@entities"][collectionorgalleryfoldername]; //console.log(foldersJSON); let isallfolder = false; if(pathparts.length >= 4 && pathparts[3] == "all") { isallfolder = true; } let folderid = "-1";// folder id of "-1" corresponds to "All" gallery let gallerysize = 0; if(!isallfolder) { //Get folder id from current url as most up-to-date source, else default assigned folder id from initial state //TODO this is imperfect. Fails if internal React navigation between galleries and favorites is initiated. if(pathparts.length >= 4) { folderid = pathparts[3]; } else {//Featured or imperfect navigation, for now just handle featured for(let folder in foldersJSON) { if(foldersJSON[folder].name == "Featured"){ folderid = folder; break; } } //folderid = initialstateJSON["gallectionSection"]["currentlyViewedFolderId"]; } } gallerysize = foldersJSON[folderid]["totalItemCount"]; //construct reuseable gallery pagination api string pieces let paginationstring_base = "https://www.deviantart.com/_napi/da-user-profile/api/" + (isgallery ? "gallery" : "collection") + "/contents?username=" + artist_friendly_id + "&csrf_token=" + csrftoken + (isallfolder ? "&all_folder=true" : "&folderid=" + folderid) + "&limit=60&offset="; //Get remaining UI elements needed for reference let itemscontainer = document.getElementsByClassName("RMUi2")[0]; let searchitemscontainer = itemscontainer.cloneNode(); itemscontainer.parentNode.appendChild(searchitemscontainer); //This function indexes the gallery to be searched on textbox focus async function buildIndex() { searchelement.placeholder = 'Building Search Index...'; searchelement.blur(); searchelement.setAttribute('readonly', 'readonly'); //DEBUG indexing performance let starttime; if(debug && true) { starttime = performance.now(); } let currentoffset = 0; let paginated_urls = []; while(currentoffset < gallerysize) { let currentpaginationurl = paginationstring_base + currentoffset; paginated_urls.push(currentpaginationurl); currentoffset += 60;//60 is maximum pagination } //DEBUG if(debug && false) console.log(paginated_urls); await concatenateJson(paginated_urls) .then(concatenatedJson => { //prepare indexable items by making them lowercase let lowercasejson = concatenatedJson; for(let i=0; i< lowercasejson.length; i++) { let deviation = lowercasejson[i].deviation; deviation.gs_username = deviation.author.username.toLowerCase(); deviation.gs_title = deviation.title.toLowerCase(); //Collect static thumbnail meta info. easier to do once let thumbpathstring = ""; for(let i=0; i 30 && paginationratelimit < 2.0 && ratelimitwarning) { searchoutputtext.style.color = "orange"; searchoutputtext.innerHTML = "This is a large gallery to search. If you run searches like this frequently, " + "consider raising the rate limit to 2 seconds or longer to avoid DeviantArt temp bans. " + "Rate limits can be changed and this warning can be turned off in the user preference variables inside the userscript."; } // Loop through the URLs for (let i=0; i 0 && waitmilliseconds != 0) { //DEBUG if(debug && false) console.log("Waiting " + waitmilliseconds + " milliseconds."); await new Promise(resolve => setTimeout(resolve, waitmilliseconds)); } //DEBUG if(debug && false) console.log("Download round " + i); // Download the current URL and concatenate the JSON result let response = await fetchJson(urls[i]); let json = response.results; results = results.concat(json); } //Reset output text after possible rate limit warning. searchoutputtext.style.color = "rgb(242, 242, 242)"; searchoutputtext.innerHTML = ""; return results; } //Searches through deviations array on keyup event function search() { let currentsearchtext = searchelement.value.toLowerCase(); //DEBUG if(debug && true) console.log("Searching: " + currentsearchtext); //Restore gallery visibility and abort if search goes inactive if(currentsearchtext == "") { itemscontainer.style.display = ""; searchitemscontainer.style.display = "none"; searchoutputtext.innerHTML = ""; return; } //hide original gallery, show custom search gallery itemscontainer.style.display = "none"; searchitemscontainer.style.display = ""; //search let resultsarray = []; for(let i=0; i< deviations.length; i++) { let deviation = deviations[i].deviation; if(deviation.gs_username.includes(currentsearchtext)) { resultsarray.push(deviation); continue; } if(deviation.gs_title.includes(currentsearchtext)) { resultsarray.push(deviation); continue; } } matchingdeviations = resultsarray; //Now display based on results if(resultsarray.length == 0) { searchoutputtext.innerHTML = "No results."; return; } //DEBUG if(debug && true) console.log("Results:", matchingdeviations); if(resultsarray.length > maximumsearchreturn) { searchoutputtext.innerHTML = resultsarray.length + " results. Too many for display. (Maximum is set to " + maximumsearchreturn + " results)"; return; } searchoutputtext.innerHTML = resultsarray.length + " result" + (resultsarray.length == 1 ? "." : "s."); assignTileDimensions(); tileElements(); } //This function matches row widths, row height preference, and image aspect ratios to tile items evenly in every row. function assignTileDimensions() { let viewportwidth = parseFloat(window.getComputedStyle(itemscontainer).width); //DEBUG if(debug && false) console.log("Viewport width: ", viewportwidth); //begin tiling rows let useditemscount = 0; while(useditemscount < matchingdeviations.length) { let currentindex = useditemscount; //discover row item count based on pivotal row height let rowitemcount = 0; let currenttotalaspectedwidth = 0; let isfinalrow = false; while(currentindex < matchingdeviations.length) { let croppedwidth = matchingdeviations[currentindex].gs_thumb_width; let croppedheight = matchingdeviations[currentindex].gs_thumb_height; let aspectedwidth = getAspectedWidth(croppedwidth, croppedheight, pivotalrowheight); currenttotalaspectedwidth += aspectedwidth + (tilemargin*2); if(currenttotalaspectedwidth >= viewportwidth) { break; } else { rowitemcount++; currentindex++; } //last row case detector if(currentindex == matchingdeviations.length) { isfinalrow = true; } } //now we have row item count, size to fill usable viewport width and then aspect for final row height //console.log("Row item count: ", rowitemcount); var useableviewportwidth = viewportwidth - (rowitemcount*tilemargin*2); //console.log("Useable viewport width: ", useableviewportwidth); currentindex = useditemscount;//reset to beginning of row index if(!isfinalrow)//perform sizing calculations on every row but the last { //get width ratio denominator var sumwidthratios = 0; for(let i=0; i", deviation.media.prettyName); let tokenstring = deviation.media.token == null ? "" : "?token=" + deviation.media.token[0]; let thumburl = deviation.media.baseUri + thumbpathstring + tokenstring; //Create Image DONE let imgelement = document.createElement("img"); imgelement.alt = deviation.title; imgelement.src = thumburl; imgelement.style.width = "100%"; imgelement.style.height = "100%"; imgelement.style.objectFit = "cover"; imgelement.style.objectPosition = "50% 100%"; //Image container Done let imgcontainer = document.createElement("div") imgcontainer.className = "_24Wda"; imgcontainer.style.width = "100%"; imgcontainer.style.height = "100%"; //Create link to deviation page Done let artlinkelement = document.createElement("a") artlinkelement.href = deviation.url; //Draggable div container, also contains mouseover event assigned later let draggablecontainer = document.createElement("div") draggablecontainer.style.width = "100%"; draggablecontainer.style.height = "100%"; draggablecontainer.className = "_1xcj5 _1QdgI"; draggablecontainer.draggable = "true"; imgcontainer.appendChild(imgelement); artlinkelement.appendChild(imgcontainer); draggablecontainer.appendChild(artlinkelement); outermostdiv.appendChild(draggablecontainer); ////////////////////////////////// //Mouseover elements section ////////////////////////////////// //Outer mouseover element let outermousediv = document.createElement("div"); outermousediv.style.visibility = "hidden"; outermousediv.style.width = "100%"; outermousediv.style.height = "100%"; outermousediv.className = "_2jPGh _3Cax3"; //event attaches to its parent draggablecontainer.addEventListener("mouseover", (event) => { outermousediv.style.visibility = ""; }); draggablecontainer.addEventListener("mouseout", (event) => { outermousediv.style.visibility = "hidden"; }); //Divs for slight black fade on hover let blackfadeouterdiv = document.createElement("div"); blackfadeouterdiv.className = "_1mmGw"; let blackfadeinnerdiv = document.createElement("div"); blackfadeinnerdiv.className = "cjZ9o _2QZ8F _3b-i8"; blackfadeouterdiv.appendChild(blackfadeinnerdiv); outermousediv.appendChild(blackfadeouterdiv); //Next inward div let innermousediv = document.createElement("div"); innermousediv.style.width = "100%"; innermousediv.style.height = "100%"; innermousediv.className = "_2ehf4 YpNhf"; //Div for all all meta elements container (title, artist, icon, comments) let metaelementscontainer = document.createElement("div"); metaelementscontainer.className = "_5Xty_"; //Div to contain title and artist elements (title, artist, icon) let titleartistcontainer = document.createElement("div"); titleartistcontainer.className = "_1mmGw _31MCr"; //Link with Title let arttitlelinkelement = document.createElement("a") arttitlelinkelement.href = deviation.url; arttitlelinkelement.className = "KoW6A"; //Title let arttitleelement = document.createElement("h2") arttitleelement.className = "_1lmpZ"; arttitleelement.innerHTML = deviation.title; arttitlelinkelement.appendChild(arttitleelement); titleartistcontainer.appendChild(arttitlelinkelement); metaelementscontainer.appendChild(titleartistcontainer); innermousediv.appendChild(metaelementscontainer); outermousediv.appendChild(innermousediv); draggablecontainer.appendChild(outermousediv); //Subsection for artist icon and name //Divs to contain title and artist elements (title, artist, icon) let iconartistcontainer = document.createElement("div"); iconartistcontainer.className = "_13y-9"; let iconartistcontainer2 = document.createElement("div"); iconartistcontainer2.className = "_2o1Q1"; //Div to contain artist icon let iconcontainer = document.createElement("div"); iconcontainer.className = "_3CR67 _1I9Ar"; //Link for artist that surrounds icon let artisticonlinkelement = document.createElement("a") artisticonlinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artisticonlinkelement.className = "user-link _2f0dA _23x0l"; //Artist icon element let artisticonelement = document.createElement("img"); artisticonelement.alt = deviation.author.username + "'s avatar"; artisticonelement.src = deviation.author.usericon; artisticonelement.style.width = "24px"; artisticonelement.style.height = "24px"; artisticonelement.loading = "lazy"; artisticonelement.className = "_1IDJa"; artisticonlinkelement.appendChild(artisticonelement); iconcontainer.appendChild(artisticonlinkelement); //Div to contain artist name let artistnamecontainer = document.createElement("div"); artistnamecontainer.className = "_3CR67 k4CiA"; //Link for artist that surrounds icon let artistnamelinkelement = document.createElement("a") artistnamelinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artistnamelinkelement.className = "user-link _2f0dA"; //Artist name text span let artistnametextelement = document.createElement("span") artistnametextelement.className = "_2UI2c"; artistnametextelement.innerHTML = deviation.author.username; //Artist cursor span let artistcursorelement = document.createElement("span") artistcursorelement.className = "_3LUMH _1NhtS G0rcN"; artistcursorelement.style.cursor = "pointer"; artistcursorelement.role = "img"; artistnamelinkelement.appendChild(artistnametextelement); artistnamelinkelement.appendChild(artistcursorelement); artistnamecontainer.appendChild(artistnamelinkelement); iconartistcontainer2.appendChild(iconcontainer); iconartistcontainer2.appendChild(artistnamecontainer); iconartistcontainer.appendChild(iconartistcontainer2); titleartistcontainer.appendChild(iconartistcontainer); //Subsection for comments icon and link elements //Div to contain comment icon and link elements let commenticonlinkcontainer = document.createElement("div"); commenticonlinkcontainer.className = "_1mmGw _2WpJA _6oiPd"; //Link for comment section let commentlinkelement = document.createElement("a") commentlinkelement.href = deviation.url + "#comments"; commentlinkelement.className = "_1-Wh7 x48yz"; //Comment icon span let commenticonspan = document.createElement("span") commenticonspan.className = "z8jNZ _1yoxj _38kc5"; //Comment icon (SVG) let commenticonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); commenticonSVG.setAttribute("viewBox", "0 0 24 24"); commenticonSVG.setAttribute("version", "1.1"); commenticonSVG.setAttribute("xlmns:xlink", "http://www.w3.org/1999/xlink"); //Comment icon (SVG PATH) let commenticonpath = document.createElementNS("http://www.w3.org/2000/svg", "path"); commenticonpath.setAttribute("fill-rule", "evenodd"); commenticonpath.setAttribute("d", "M20 3a1 1 0 01.993.883L21 4v9.586a1 1 0 01-.206.608l-.087.099-2.414 2.414a1 1 0 01-.576.284l-.131.009H13l-2.7 3.6a1 1 0" + " 01-.683.393L9.5 21H8a1 1 0 01-.993-.883L7 20v-3H4a1 1 0 01-.993-.883L3 16V6.414a1 1 0 01.206-.608l.087-.099 2.414-2.414a1 1 0 01.576-.284L6.414" + " 3H20zm-1 2H6.828L5 6.828V15h4v4l3-4h5.17L19 13.17V5z"); //Comment count span let commentcountspan = document.createElement("span") commentcountspan.innerHTML = deviation.stats.comments; commenticonSVG.appendChild(commenticonpath); commenticonspan.appendChild(commenticonSVG); commentlinkelement.appendChild(commenticonspan); commentlinkelement.appendChild(commentcountspan); commenticonlinkcontainer.appendChild(commentlinkelement); metaelementscontainer.appendChild(commenticonlinkcontainer); return outermostdiv; } function createLiteratureDeviationElement(deviation) { let outermostdiv = document.createElement("div"); outermostdiv.style.width = deviation.gs_tile_width; outermostdiv.style.height = deviation.gs_tile_height; outermostdiv.style.display = "inline-block"; outermostdiv.style.float = "left"; outermostdiv.style.position = "relative"; outermostdiv.style.margin = tilemargin + "px"; //Draggable div container, also contains mouseover event assigned later let draggablecontainer = document.createElement("div") draggablecontainer.style.width = "100%"; draggablecontainer.style.height = "100%"; draggablecontainer.className = "_1xcj5 _1QdgI"; draggablecontainer.draggable = "true"; //Section element that contains literature display elements let sectionelement = document.createElement("section"); sectionelement.className = "_33VtO _3rqZq"; sectionelement.style.width = "100%"; sectionelement.style.height = "100%"; //Fancy literature deviation background element container let literaturebgcontainer = document.createElement("div") literaturebgcontainer.className = "xPxyA LXVwg"; //Fancy literature deviation background (SVG) let litbgSVG = document.createElement("svg"); litbgSVG.setAttribute("viewBox", "0 0 15 12"); litbgSVG.setAttribute("height", "100%"); litbgSVG.setAttribute("preserveAspectRatio", "xMidYMin slice"); litbgSVG.setAttribute("fill-rule", "evenodd"); //Fancy literature deviation background (lineargradient) let litbglineargradient = document.createElement("linearGradient"); litbglineargradient.setAttribute("x1", "87.8481761%"); litbglineargradient.setAttribute("y1", "16.3690766%"); litbglineargradient.setAttribute("x2", "45.4107524%"); litbglineargradient.setAttribute("y2", "71.4898596%"); //Fancy literature deviation background (stop color)s let litbgstop1 = document.createElement("stop"); litbgstop1.setAttribute("stop-color", "#00FF62"); litbgstop1.setAttribute("offset", "0%"); let litbgstop2 = document.createElement("stop"); litbgstop2.setAttribute("stop-color", "#3197EF"); litbgstop2.setAttribute("offset", "100%"); litbgstop2.setAttribute("stop-opacity", "0"); litbglineargradient.appendChild(litbgstop1); litbglineargradient.appendChild(litbgstop2); litbgSVG.appendChild(litbglineargradient); literaturebgcontainer.appendChild(litbgSVG); sectionelement.appendChild(literaturebgcontainer); draggablecontainer.appendChild(sectionelement); outermostdiv.appendChild(draggablecontainer); //Literature Preview Subsection //Fancy literature deviation background element container let literaturecategorydiv = document.createElement("div") literaturecategorydiv.className = "_3hLq8"; literaturecategorydiv.innerHTML = "Literature"; //Literature Preview Title let literaturepreviewtitle = document.createElement("h2") literaturepreviewtitle.className = "_2mwJN"; literaturepreviewtitle.innerHTML = deviation.title; //Literature Preview Text let literaturepreviewtext = document.createElement("h2") literaturepreviewtext.className = "heXvc"; literaturepreviewtext.innerHTML = deviation.textContent.excerpt; sectionelement.appendChild(literaturecategorydiv); sectionelement.appendChild(literaturepreviewtitle); sectionelement.appendChild(literaturepreviewtext); ////////////////////////////////// //Mouseover elements section ////////////////////////////////// //Outer mouseover element let outermousediv = document.createElement("div"); outermousediv.style.visibility = "hidden"; outermousediv.style.width = "100%"; outermousediv.style.height = "100%"; outermousediv.className = "_2jPGh _3Cax3"; //event attaches to its parent draggablecontainer.addEventListener("mouseover", (event) => { outermousediv.style.visibility = ""; }); draggablecontainer.addEventListener("mouseout", (event) => { outermousediv.style.visibility = "hidden"; }); //Divs for slight black fade on hover let blackfadeouterdiv = document.createElement("div"); blackfadeouterdiv.className = "_1mmGw"; let blackfadeinnerdiv = document.createElement("div"); blackfadeinnerdiv.className = "cjZ9o _2QZ8F _3b-i8"; blackfadeouterdiv.appendChild(blackfadeinnerdiv); outermousediv.appendChild(blackfadeouterdiv); //Next inward div let innermousediv = document.createElement("div"); innermousediv.style.width = "100%"; innermousediv.style.height = "100%"; innermousediv.className = "_2ehf4 YpNhf"; //Div for all all meta elements container (title, artist, icon, comments) let metaelementscontainer = document.createElement("div"); metaelementscontainer.className = "_5Xty_"; //Div to contain title and artist elements (title, artist, icon) let titleartistcontainer = document.createElement("div"); titleartistcontainer.className = "_1mmGw _31MCr"; // //Link with Title // let arttitlelinkelement = document.createElement("a") // arttitlelinkelement.href = deviation.url; // arttitlelinkelement.className = "KoW6A"; // //Title // let arttitleelement = document.createElement("h2") // arttitleelement.className = "_1lmpZ"; // arttitleelement.innerHTML = deviation.title; // arttitlelinkelement.appendChild(arttitleelement); //titleartistcontainer.appendChild(arttitlelinkelement); metaelementscontainer.appendChild(titleartistcontainer); innermousediv.appendChild(metaelementscontainer); outermousediv.appendChild(innermousediv); draggablecontainer.appendChild(outermousediv); //Subsection for artist icon and name //Divs to contain title and artist elements (title, artist, icon) let iconartistcontainer = document.createElement("div"); iconartistcontainer.className = "_13y-9"; let iconartistcontainer2 = document.createElement("div"); iconartistcontainer2.className = "_2o1Q1"; //Div to contain artist icon let iconcontainer = document.createElement("div"); iconcontainer.className = "_3CR67 _1I9Ar"; //Link for artist that surrounds icon let artisticonlinkelement = document.createElement("a") artisticonlinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artisticonlinkelement.className = "user-link _2f0dA _23x0l"; //Artist icon element let artisticonelement = document.createElement("img"); artisticonelement.alt = deviation.author.username + "'s avatar"; artisticonelement.src = deviation.author.usericon; artisticonelement.style.width = "24px"; artisticonelement.style.height = "24px"; artisticonelement.loading = "lazy"; artisticonelement.className = "_1IDJa"; artisticonlinkelement.appendChild(artisticonelement); iconcontainer.appendChild(artisticonlinkelement); //Div to contain artist name let artistnamecontainer = document.createElement("div"); artistnamecontainer.className = "_3CR67 k4CiA"; //Link for artist that surrounds icon let artistnamelinkelement = document.createElement("a") artistnamelinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artistnamelinkelement.className = "user-link _2f0dA"; //Artist name text span let artistnametextelement = document.createElement("span") artistnametextelement.className = "_2UI2c"; artistnametextelement.innerHTML = deviation.author.username; //Artist cursor span let artistcursorelement = document.createElement("span") artistcursorelement.className = "_3LUMH _1NhtS G0rcN"; artistcursorelement.style.cursor = "pointer"; artistcursorelement.role = "img"; artistnamelinkelement.appendChild(artistnametextelement); artistnamelinkelement.appendChild(artistcursorelement); artistnamecontainer.appendChild(artistnamelinkelement); iconartistcontainer2.appendChild(iconcontainer); iconartistcontainer2.appendChild(artistnamecontainer); iconartistcontainer.appendChild(iconartistcontainer2); titleartistcontainer.appendChild(iconartistcontainer); //Subsection for comments icon and link elements //Div to contain comment icon and link elements let commenticonlinkcontainer = document.createElement("div"); commenticonlinkcontainer.className = "_1mmGw _2WpJA _6oiPd"; //Link for comment section let commentlinkelement = document.createElement("a") commentlinkelement.href = deviation.url + "#comments"; commentlinkelement.className = "_1-Wh7 x48yz"; //Comment icon span let commenticonspan = document.createElement("span") commenticonspan.className = "z8jNZ _1yoxj _38kc5"; //Comment icon (SVG) let commenticonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); commenticonSVG.setAttribute("viewBox", "0 0 24 24"); commenticonSVG.setAttribute("version", "1.1"); commenticonSVG.setAttribute("xlmns:xlink", "http://www.w3.org/1999/xlink"); //Comment icon (SVG PATH) let commenticonpath = document.createElementNS("http://www.w3.org/2000/svg", "path"); commenticonpath.setAttribute("fill-rule", "evenodd"); commenticonpath.setAttribute("d", "M20 3a1 1 0 01.993.883L21 4v9.586a1 1 0 01-.206.608l-.087.099-2.414 2.414a1 1 0 01-.576.284l-.131.009H13l-2.7 3.6a1 1 0" + " 01-.683.393L9.5 21H8a1 1 0 01-.993-.883L7 20v-3H4a1 1 0 01-.993-.883L3 16V6.414a1 1 0 01.206-.608l.087-.099 2.414-2.414a1 1 0 01.576-.284L6.414" + " 3H20zm-1 2H6.828L5 6.828V15h4v4l3-4h5.17L19 13.17V5z"); //Comment count span let commentcountspan = document.createElement("span") commentcountspan.innerHTML = deviation.stats.comments; commenticonSVG.appendChild(commenticonpath); commenticonspan.appendChild(commenticonSVG); commentlinkelement.appendChild(commenticonspan); commentlinkelement.appendChild(commentcountspan); commenticonlinkcontainer.appendChild(commentlinkelement); metaelementscontainer.appendChild(commenticonlinkcontainer); return outermostdiv; } function createJournalDeviationElement(deviation) { let outermostdiv = document.createElement("div"); outermostdiv.style.width = deviation.gs_tile_width; outermostdiv.style.height = deviation.gs_tile_height; outermostdiv.style.display = "inline-block"; outermostdiv.style.float = "left"; outermostdiv.style.position = "relative"; outermostdiv.style.margin = tilemargin + "px"; //Draggable div container, also contains mouseover event assigned later let draggablecontainer = document.createElement("div") draggablecontainer.style.width = "100%"; draggablecontainer.style.height = "100%"; draggablecontainer.className = "_1xcj5 _1QdgI"; draggablecontainer.draggable = "true"; //Section element that contains journal display elements let sectionelement = document.createElement("section"); sectionelement.className = "_1C7DQ _1L6MH"; sectionelement.style.width = "100%"; sectionelement.style.height = "100%"; //Journal title container let journaltitlecontainer = document.createElement("div") journaltitlecontainer.className = "_1i4Yb"; //Journal Title let journaltitle = document.createElement("h2") journaltitle.className = "mhmhR"; journaltitle.innerHTML = deviation.title; journaltitlecontainer.appendChild(journaltitle); sectionelement.appendChild(journaltitlecontainer); draggablecontainer.appendChild(sectionelement); outermostdiv.appendChild(draggablecontainer); //Journal Excerpt subsection //Journal preview container let journalpreviewcontainer = document.createElement("div") journalpreviewcontainer.className = "_2Hfrr"; //Journal datetime container let journaldtcontainer = document.createElement("div") journaldtcontainer.className = "uBAbQ"; //Journal datetime, also get the correct date format. Example: Dec 14, 2015 let journaldt = document.createElement("time") journaldt.dateTime = deviation.publishedTime; let date = new Date(deviation.publishedTime); let formattedDate = date.toLocaleDateString('en-US', {month: 'short', day: 'numeric', year: 'numeric'}); journaldt.innerHTML = formattedDate; //Journal excerpt div let journalexcerptdiv = document.createElement("div") journalexcerptdiv.className = "legacy-journal _2HUtS"; journalexcerptdiv.innerHTML = deviation.textContent.excerpt; journaldtcontainer.appendChild(journaldt); journalpreviewcontainer.appendChild(journaldtcontainer); journalpreviewcontainer.appendChild(journalexcerptdiv); sectionelement.appendChild(journalpreviewcontainer); ////////////////////////////////// //Mouseover elements section ////////////////////////////////// //Outer mouseover element let outermousediv = document.createElement("div"); outermousediv.style.visibility = "hidden"; outermousediv.style.width = "100%"; outermousediv.style.height = "100%"; outermousediv.className = "_2jPGh _3Cax3"; //event attaches to its parent draggablecontainer.addEventListener("mouseover", (event) => { outermousediv.style.visibility = ""; }); draggablecontainer.addEventListener("mouseout", (event) => { outermousediv.style.visibility = "hidden"; }); //Divs for slight black fade on hover let blackfadeouterdiv = document.createElement("div"); blackfadeouterdiv.className = "_1mmGw"; let blackfadeinnerdiv = document.createElement("div"); blackfadeinnerdiv.className = "cjZ9o _2QZ8F _3b-i8"; blackfadeouterdiv.appendChild(blackfadeinnerdiv); outermousediv.appendChild(blackfadeouterdiv); //Next inward div let innermousediv = document.createElement("div"); innermousediv.style.width = "100%"; innermousediv.style.height = "100%"; innermousediv.className = "_2ehf4 YpNhf"; //Div for all all meta elements container (title, artist, icon, comments) let metaelementscontainer = document.createElement("div"); metaelementscontainer.className = "_5Xty_"; //Div to contain title and artist elements (title, artist, icon) let titleartistcontainer = document.createElement("div"); titleartistcontainer.className = "_1mmGw _31MCr"; // arttitlelinkelement.appendChild(arttitleelement); //titleartistcontainer.appendChild(arttitlelinkelement); metaelementscontainer.appendChild(titleartistcontainer); innermousediv.appendChild(metaelementscontainer); outermousediv.appendChild(innermousediv); draggablecontainer.appendChild(outermousediv); //Subsection for artist icon and name //Divs to contain title and artist elements (title, artist, icon) let iconartistcontainer = document.createElement("div"); iconartistcontainer.className = "_13y-9"; let iconartistcontainer2 = document.createElement("div"); iconartistcontainer2.className = "_2o1Q1"; //Div to contain artist icon let iconcontainer = document.createElement("div"); iconcontainer.className = "_3CR67 _1I9Ar"; //Link for artist that surrounds icon let artisticonlinkelement = document.createElement("a") artisticonlinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artisticonlinkelement.className = "user-link _2f0dA _23x0l"; //Artist icon element let artisticonelement = document.createElement("img"); artisticonelement.alt = deviation.author.username + "'s avatar"; artisticonelement.src = deviation.author.usericon; artisticonelement.style.width = "24px"; artisticonelement.style.height = "24px"; artisticonelement.loading = "lazy"; artisticonelement.className = "_1IDJa"; artisticonlinkelement.appendChild(artisticonelement); iconcontainer.appendChild(artisticonlinkelement); //Div to contain artist name let artistnamecontainer = document.createElement("div"); artistnamecontainer.className = "_3CR67 k4CiA"; //Link for artist that surrounds icon let artistnamelinkelement = document.createElement("a") artistnamelinkelement.href = "https://www.deviantart.com/" + deviation.gs_username; artistnamelinkelement.className = "user-link _2f0dA"; //Artist name text span let artistnametextelement = document.createElement("span") artistnametextelement.className = "_2UI2c"; artistnametextelement.innerHTML = deviation.author.username; //Artist cursor span let artistcursorelement = document.createElement("span") artistcursorelement.className = "_3LUMH _1NhtS G0rcN"; artistcursorelement.style.cursor = "pointer"; artistcursorelement.role = "img"; artistnamelinkelement.appendChild(artistnametextelement); artistnamelinkelement.appendChild(artistcursorelement); artistnamecontainer.appendChild(artistnamelinkelement); iconartistcontainer2.appendChild(iconcontainer); iconartistcontainer2.appendChild(artistnamecontainer); iconartistcontainer.appendChild(iconartistcontainer2); titleartistcontainer.appendChild(iconartistcontainer); //Subsection for comments icon and link elements //Div to contain comment icon and link elements let commenticonlinkcontainer = document.createElement("div"); commenticonlinkcontainer.className = "_1mmGw _2WpJA _6oiPd"; //Link for comment section let commentlinkelement = document.createElement("a") commentlinkelement.href = deviation.url + "#comments"; commentlinkelement.className = "_1-Wh7 x48yz"; //Comment icon span let commenticonspan = document.createElement("span") commenticonspan.className = "z8jNZ _1yoxj _38kc5"; //Comment icon (SVG) let commenticonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); commenticonSVG.setAttribute("viewBox", "0 0 24 24"); commenticonSVG.setAttribute("version", "1.1"); commenticonSVG.setAttribute("xlmns:xlink", "http://www.w3.org/1999/xlink"); //Comment icon (SVG PATH) let commenticonpath = document.createElementNS("http://www.w3.org/2000/svg", "path"); commenticonpath.setAttribute("fill-rule", "evenodd"); commenticonpath.setAttribute("d", "M20 3a1 1 0 01.993.883L21 4v9.586a1 1 0 01-.206.608l-.087.099-2.414 2.414a1 1 0 01-.576.284l-.131.009H13l-2.7 3.6a1 1 0" + " 01-.683.393L9.5 21H8a1 1 0 01-.993-.883L7 20v-3H4a1 1 0 01-.993-.883L3 16V6.414a1 1 0 01.206-.608l.087-.099 2.414-2.414a1 1 0 01.576-.284L6.414" + " 3H20zm-1 2H6.828L5 6.828V15h4v4l3-4h5.17L19 13.17V5z"); //Comment count span let commentcountspan = document.createElement("span") commentcountspan.innerHTML = deviation.stats.comments; commenticonSVG.appendChild(commenticonpath); commenticonspan.appendChild(commenticonSVG); commentlinkelement.appendChild(commenticonspan); commentlinkelement.appendChild(commentcountspan); commenticonlinkcontainer.appendChild(commentlinkelement); metaelementscontainer.appendChild(commenticonlinkcontainer); return outermostdiv; } ///////////////////////////////////////////////////////////////////////////////////// ///// Other Helper Functions ///// ///////////////////////////////////////////////////////////////////////////////////// function getAspectedWidth(origwidth, origheight, sizedheight) { return (origwidth * sizedheight) / origheight; } function getAspectedHeight(origwidth, origheight, sizedwidth) { return (origheight * sizedwidth) / origwidth; } //Function to sort deviationJSON based on attribute provided function sortDeviationByKey(array, key) { return array.sort(function(a, b) { var x = a.deviation[key]; var y = b.deviation[key]; return x.localeCompare(y); }); } } //DOM Element Mutation Observer //Credit to Yong Wang on StackOverflow: https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } })();