dA_DragnFav

drag thumbs and deviations to yur favourites/collections

当前为 2020-12-30 提交的版本,查看 最新版本

// ==UserScript==
// @name         dA_DragnFav
// @namespace    phi.pf-control.de/userscripts/dA_DragnFav/dA_DragnFav.user.js
// @version      1.1
// @description  drag thumbs and deviations to yur favourites/collections
// @author       Dediggefedde
// @match        https://www.deviantart.com/*
// @require    	 http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js
// @require      http://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
// @grant        GM_addStyle
// @grant        GM.xmlHttpRequest
// @grant        GM.setValue
// @grant        GM.getValue
// ==/UserScript==

/* globals $*/
/* jshint esnext:true */

// faving informations:
//	POST https://www.deviantart.com/_napi/shared_api/collections/collect
//	{"itemid":864977862,"folderids":[1468881,48232885],"csrf_token":"Jt3u9HHt4hGtMxG7.qluh26.7-qAIhS-bmbIhMbgks-rKcuE_j1y6EsqMJyonDrrjsE"}

(function() {
    'use strict';

    //# temporary variables
    let grIDs = [];
    let settingmode=false;
    let groupOrder=[];
    let hiddengroups=[];
    let headerfilled=false;

    //# resources
    //hook copied from deviantart svg
    let imgHook='<svg width="50" height="50" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg"><path d="M1.237 6.187L0 4.95l1.237-1.238L2.475 4.95l3.712-3.713 1.238 1.238-4.95 4.95-1.238-1.238z" fill-rule="evenodd"></path></svg>';
    //imgGear copied from inkscape "render gear", slightly adjusted
    let imgGear='<svg  xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 20.444057 20.232336" > <g transform="translate(-15.480352,-5.6695418)">  <g transform="matrix(0.26458333,0,0,0.26458333,25.702381,15.78571)"  style="fill:#000000">  <path  style="fill:#000000;stroke:#000000;stroke-width:1"  d="m 28.46196,-3.25861 4.23919,-0.48535 0.51123,0.00182 4.92206,1.5536 v 4.37708 l -4.92206,1.5536 -0.51123,0.00182 -4.23919,-0.48535 -1.40476,6.15466 4.02996,1.40204 0.45982,0.22345 3.76053,3.53535 -1.89914,3.94361 -5.1087,-0.73586 -0.4614,-0.22017 -3.60879,-2.2766 -3.93605,4.93565 3.02255,3.01173 0.31732,0.40083 1.8542,4.81687 -3.42214,2.72907 -4.2835,-2.87957 -0.32017,-0.39856 -2.26364,-3.61694 -5.68776,2.73908 1.41649,4.0249 0.11198,0.49883 -0.41938,5.14435 -4.26734,0.97399 -2.6099,-4.45294 -0.11554,-0.49801 -0.47013,-4.2409 h -6.31294 l -0.47013,4.2409 -0.11554,0.49801 -2.6099,4.45294 -4.26734,-0.97399 -0.41938,-5.14435 0.11198,-0.49883 1.41649,-4.0249 -5.68776,-2.73908 -2.26364,3.61694 -0.32017,0.39856 -4.2835,2.87957 -3.42214,-2.72907 1.8542,-4.81687 0.31732,-0.40083 3.02255,-3.01173 -3.93605,-4.93565 -3.60879,2.2766 -0.4614,0.22017 -5.1087,0.73586 -1.89914,-3.94361 3.76053,-3.53535 0.45982,-0.22345 4.02996,-1.40204 -1.40476,-6.15466 -4.23919,0.48535 -0.51123,-0.00182 -4.92206,-1.5536 v -4.37708 l 4.92206,-1.5536 0.51123,-0.00182 4.23919,0.48535 1.40476,-6.15466 -4.02996,-1.40204 -0.45982,-0.22345 -3.76053,-3.53535 1.89914,-3.94361 5.1087,0.73586 0.4614,0.22017 3.60879,2.2766 3.93605,-4.93565 -3.02255,-3.01173 -0.31732,-0.40083 -1.8542,-4.81687 3.42214,-2.72907 4.2835,2.87957 0.32017,0.39856 2.26364,3.61694 5.68776,-2.73908 -1.41649,-4.0249 -0.11198,-0.49883 0.41938,-5.14435 4.26734,-0.97399 2.6099,4.45294 0.11554,0.49801 0.47013,4.2409 h 6.31294 l 0.47013,-4.2409 0.11554,-0.49801 2.6099,-4.45294 4.26734,0.97399 0.41938,5.14435 -0.11198,0.49883 -1.41649,4.0249 5.68776,2.73908 2.26364,-3.61694 0.32017,-0.39856 4.2835,-2.87957 3.42214,2.72907 -1.8542,4.81687 -0.31732,0.40083 -3.02255,3.01173 3.93605,4.93565 3.60879,-2.2766 0.4614,-0.22017 5.1087,-0.73586 1.89914,3.94361 -3.76053,3.53535 -0.45982,0.22345 -4.02996,1.40204 z"  />  <circle  style="fill:#ffffff;stroke:#000000;stroke-width:1"  cx="0"  cy="0"  r="15" />  </g>  </g> </svg>';

    //CSS styling
    GM_addStyle("" +
                "#dA_DragnFav_header {background-color: #8fac85cc;z-index: 99;position: fixed;width: 100%;border: 2px ridge white;display: none;padding: 20px;" +
                "  justify-content: center;align-items: center;flex-wrap: wrap;box-sizing: border-box;}" +
                "#dA_DragnFav_header div[folderId] {padding: 15px;border-radius: 5px;background-color: #ffffdd;border: 1px solid black;margin: 10px;" +
                "  display:flex;align-items: center;flex-direction: column;position: relative;overflow:clip;}" +
                //"#dA_DragnFav_header div[folderId] * {pointer-events: none;}" +
                "#dA_DragnFav_header div[folderId] img {height:50px;max-width: 100px; overflow: hidden;text-overflow: ellipsis;font-size: smaller;}" +
                "#dA_DragnFav_header div.dA_DragnFav_over{z-index:3;position: absolute;top: 0;width: 50%;height: 100%;background-color: #b9d9b1;display: flex;align-items: center;justify-content: center;opacity:0;}"+
                "#dA_DragnFav_header div.dA_DragnFav_right{right:0}"+
                "#dA_DragnFav_header div.dA_DragnFav_left{left:0}"+
                "#dA_DragnFav_header div.dA_DragnFav_over p{pointer-events: none;}"+
                "#dA_DragnFav_header div.dA_DragnFav_inside {position:absolute;z-index:2;left:0;width:100%;top:0;height:100%;display:flex;align-items: center;justify-content: center;visibility:hidden}"+
                "#dA_DragnFav_header div.dA_DragnFav_inside svg {fill:green;}"+
                "#dA_DragnFav_header div.dA_DragnFav_sets{position:absolute;top:10px;right:10px;cursor:pointer;}"+
                "#dA_DragnFav_header div.dA_DragnFav_markhide{background-color:#a22;}"+
                "");

    //# https requests
    //gets your currently available collections. returns promise with json response on success. response or parse error on error
    //assumes <21 collections
    function getCollections(offset=0) {
        return new Promise(function(resolve, reject) {
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/_napi/shared_api/gallection/folders?type=collection&offset="+offset+"&limit=20",
                headers: {
                    "accept": 'application/json, text/plain, */*',
                    "content-type": 'application/json;charset=UTF-8'
                },
                onerror: function(response) {
                    console.log("error:", response);
                    reject(response);
                },
                onload: async function(response) {
                    let dat;
                    try {
                        dat = JSON.parse(response.responseText);
                        if(dat.hasMore){
                            getCollections(dat.nextOffset).then(nret=>{
                                dat.results=dat.results.concat(nret.results);
                                resolve(dat);
                                return;
                            });
                        }else{
                            resolve(dat);
                        }
                    } catch (e) {
                        reject(e);
                    }
                }
            });
        });
    }

    //get list of collection ids, this deviation id is inside already
    //returns promise with json response on success. response or parse error on error
    function inCollections(id) {
        return new Promise(function(resolve, reject) {
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/_napi/shared_api/collections/collections_for_deviation?deviationid=" + id,
                headers: {
                    "accept": 'application/json, text/plain, */*',
                    "content-type": 'application/json;charset=UTF-8'
                },
                onerror: function(response) {
                    console.log("error:", response);
                    reject(response);
                },
                onload: async function(response) {
                    let dat;
                    try {
                        dat = JSON.parse(response.responseText);
                    } catch (e) {
                        reject(e);
                    }
                    resolve(dat);
                }
            });
        });
    }

    //sets the collection of the deviation with this id to the array cols (folder ids).
    //searches for validate_token on current page
    function setCollection(id, cols) {
        let token = $("input[name=validate_token]").val();
        let dat = {
            "itemid": id,
            "folderids": cols,
            "csrf_token": token
        };
        return new Promise(function(resolve, reject) {
            GM.xmlHttpRequest({
                method: "POST",
                url: "https://www.deviantart.com/_napi/shared_api/collections/collect",
                headers: {
                    "accept": 'application/json, text/plain, */*',
                    "content-type": 'application/json;charset=UTF-8'
                },
                dataType: 'json',
                data: JSON.stringify(dat),
                onerror: function(response) {
                    console.log("error:", response);
                    reject(response);
                },
                onload: async function(response) {
                    let dat;
                    try {
                        dat = JSON.parse(response.responseText); //returns {success:true}
                    } catch (e) {
                        reject(e);
                    }
                    resolve(dat);
                }
            });
        });
    }

    //# event listener
    //when items are dragged onto the bar
    function itemDroppedOnCol(event,add=true) {
        let id = event.originalEvent.dataTransfer.getData('id');

        let fEl = $(event.target).closest("[folderId]");
        let fId = parseInt(fEl.attr("folderId"));

        if(!add)grIDs=[];

        if (!grIDs.includes(fId)) {
            grIDs.push(fId);
            setCollection(id, grIDs).then(ret => {
                console.log("Setting collections:",ret);
            });
            fEl.find(".dA_DragnFav_inside").css("visibility", "visible");
        }
    }

    function leaveSettingMode(){
        settingmode=false;
        $("#dA_DragnFav_header div.dA_DragnFav_markhide").css("display","none");
        $("#dA_DragnFav_header div.dA_DragnFav_setDescr").remove();
        $("#dA_DragnFav_header").fadeOut();
    }

    function enterSettingMode(){
        settingmode=true;

        //show hidden
        $("#dA_DragnFav_header div.dA_DragnFav_markhide").css("display","block");

        //make sortable
        $("#dA_DragnFav_header").sortable({
             helper: 'clone',
            forceHelperSize :true,
            forcePlaceholderSize :true,
            placeholder: "ui-state-highlight",
            items: 'div.dA_DragnFav_group',
            cursor: 'move',
            update: function (event, ui) {
                let colOrder = $(this).sortable('toArray', {attribute: 'folderId'});
                GM.setValue("groupOrder", JSON.stringify(colOrder));
            }
        }).prepend("<div class='dA_DragnFav_setDescr' style='width:100%'>Drag items to rearange them. Click an item to hide/show it. Click the gear to leave the settings mode.</div>");
    }

    //# initialization, reruns periodically for dynamic site building
    //insert fav-header
    function fillHeader() {
        return new Promise(function(resolve, reject) {
            if(headerfilled){
                resolve("already");
                return;
            }
            getCollections(0).then((ret) => {
                headerfilled=true;
                let tex = "";
                ret.results.forEach((el) => {
                    let thumb="";
                    if (el.thumb && el.thumb.coverImage && el.thumb.coverImage.media && el.thumb.coverImage.media.types && el.thumb.coverImage.media.prettyName && el.thumb.coverImage.media.token) { //optional chaining breaks tampermonkey syntax highlight...
                        thumb +=`${el.thumb.coverImage.media.baseUri}/${el.thumb.coverImage.media.types[0].c.replace("<prettyName>", el.thumb.coverImage.media.prettyName)}?token=${el.thumb.coverImage.media.token[0]}`;
                    } else if(el.thumb && el.thumb.media && el.thumb.media.baseUri && el.thumb.media.types && el.thumb.media.prettyName && el.thumb.media.token) {
                        thumb +=`${el.thumb.media.baseUri}/${el.thumb.media.types[0].c.replace("<prettyName>", el.thumb.media.prettyName)}?token=${el.thumb.media.token[0]}`;
                    }
                    let thTitl="no thumb";
                    if(el.thumb){
                        thTitl=el.thumb.title;
                    }
                    tex += `
<div class='dA_DragnFav_group ${hiddengroups.includes(el.folderId)?"dA_DragnFav_markhide":""}' folderId='${el.folderId}' ${hiddengroups.includes(el.folderId)?"style='display:none;'":""}>
<img src='${thumb}' alt='${thTitl}' title='${thTitl}'/>
<div class='dA_DragnFav_groupTitle'>${el.name}</div>
<div class='dA_DragnFav_inside'>${imgHook}</div>
<div class='dA_DragnFav_over dA_DragnFav_left'><p>Add</p></div>
<div class='dA_DragnFav_over dA_DragnFav_right'><p>Move</p></div>
</div>`;
                });
                let header= $("#dA_DragnFav_header");
                header.html(tex).append(`<div class='dA_DragnFav_sets'>${imgGear}</div>`);

                if (groupOrder.length > 0) {
                    $.each(groupOrder, function (i, folderid) {
                        let $target = header.find(`[folderid='${folderid}']`);
                        $target.appendTo(header); // or prependTo for reverse
                    });
                }

                $("#dA_DragnFav_header div.dA_DragnFav_group").click(function(ev){
                    if(settingmode && !$(this).hasClass("noClick")){ //hide toggle click in setting-mode
                        let fid=parseInt($(this).attr("folderId"));
                        let fhidInd=hiddengroups.indexOf(fid);
                        if(fhidInd>-1){
                            hiddengroups.splice(fhidInd,1);
                            $(this).removeClass("dA_DragnFav_markhide");
                        }else{
                            hiddengroups.push(fid);
                            $(this).addClass("dA_DragnFav_markhide");
                        }
                        GM.setValue("hiddengroups",JSON.stringify(hiddengroups));
                    }
                });

                //fav-header drag receivable
                $("#dA_DragnFav_header div.dA_DragnFav_over").on('dragover', false).on("dragenter", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    $(ev.target).css("opacity", "1");
                }).on("dragleave", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    $(ev.target).css("opacity", "0");
                }).on("drop", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    $(ev.target).css("opacity", "0");
                    itemDroppedOnCol(ev,$(ev.target).hasClass("dA_DragnFav_left"));
                });

                //settings open on drag, close on click
                $("#dA_DragnFav_header div.dA_DragnFav_sets").on('dragover', false).on("dragenter", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                }).on("dragleave", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                }).on("drop", function(ev) {
                    ev.preventDefault();
                    ev.stopPropagation();
                    enterSettingMode();
                }).click(function(ev){
                    leaveSettingMode();
                });

                resolve("ok");
                return;
            });
        });
    }

    function dragEnd(){
        if(!settingmode){
            $("#dA_DragnFav_header").fadeOut();
        }
    }

    function dragStart(ev) {
        $("#dA_DragnFav_header").css("display", "flex");
        $("div.dA_DragnFav_over").css("opacity", "0");

        let drEl=$(this).closest("a[href]");
        let mode = $(this).attr("dA_DragnFav");
        let id;

        if (mode == 1 && drEl.attr("href")) {
            id = drEl.attr("href").match(/\d+$/i)[0];
        } else if (mode == 2){
            let idMat=location.href.match(/\/art\/[^\/]*?(\d+)/i);
            if(idMat.length>0){
                id = idMat[1]; //$("meta[property='og:url']").attr("content").match(/\d+$/i)[0];
            }
        }
        if(!id){
            console.log("error: ID can not be fetched",mode, drEl, $(this),$(ev.target),$(ev.target).attr("href"),location.href.match(/\/art\/[^\/]*?(\d+)/i));
        }
        ev.originalEvent.dataTransfer.setData('id', id);

        fillHeader().then(()=>{
            //highlight collections alread in
            $(".dA_DragnFav_inside").css("visibility", "");
            $(".dA_DragnFav_group").css("background-color", "");
            grIDs = [];
            inCollections(id).then(ret => {
                if(!ret.collectionIds){
                    console.log("error checking collections:",ret,id);
                }
                ret.collectionIds.forEach(el => {
                    $("#dA_DragnFav_header div[folderId=" + el + "]").find(".dA_DragnFav_inside").css("visibility", "visible");
                    $("#dA_DragnFav_header div[folderId=" + el + "]").closest(".dA_DragnFav_group").css("background-color", "rgb(180, 255, 200)");
                });
                grIDs = ret.collectionIds;
            });
        });
    }

    //make deviations draggable, passing link with ID
    function makeDraggable() {
        let els = $("[data-hook='deviation_link']").not("[dA_DragnFav]").attr("dA_DragnFav", 1).attr("draggable", "true");
        els.on("dragstart", dragStart).on("dragend", dragEnd);
        els = $("[data-hook='art_stage'] img").not("[dA_DragnFav]").attr("dA_DragnFav", 2).attr("draggable", "true");
        els.on("dragstart", dragStart).on("dragend", dragEnd);
    }

    //runs every interval
    function init() {
        if (document.getElementById("dA_DragnFav_header") == null) {
            $("header[role='banner']").after("<div id='dA_DragnFav_header'>");
            headerfilled=false;
        }
        makeDraggable();
        if (document.getElementById("dA_DragnFav_styles") == null) {
            $("head").append(
                '<link id="dA_DragnFav_styles"' +
                'href="//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/le-frog/jquery-ui.min.css" ' +
                'rel="stylesheet" type="text/css">'
            );
        }
    }

    if (window.top === window.self){
            GM.getValue("hiddengroups","").then(ret=>{
                 if(ret!=""){
                    hiddengroups=JSON.parse(ret);
                }
                return GM.getValue("groupOrder","");
            }).then(ret=>{
                if(ret!=""){
                    groupOrder=JSON.parse(ret);
                }
                setInterval(init, 1000); // check every second
            });
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址