// ==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
});
});
}
})();