dev_group_list2

Better Submit-to-group dialog

目前為 2021-11-26 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name dev_group_list2
  3. // @namespace http://www.deviantart.com/
  4. // @version 4.9
  5. // @description Better Submit-to-group dialog
  6. // @author Dediggefedde
  7. // @match https://www.deviantart.com/*
  8. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
  9. // @grant GM.xmlHttpRequest
  10. // @grant GM.setValue
  11. // @grant GM.getValue
  12.  
  13. // ==/UserScript==
  14. /* globals $*/
  15. // @require http://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
  16.  
  17. //dgl2=dgl2 identifiert/classname
  18. (function() {
  19. 'use strict';
  20. let userName = "";
  21. let moduleID = 0;
  22. let grPerReq = 24;
  23. let groups = []; //{userId,useridUuid,username,usericon,type,isNewDeviant}
  24. // isNewDeviant not needed.
  25. // type=oneof{group, super group}
  26. // usericon always starts with https://a.deviantart.net/avatars-big
  27. // useridUuid specific to the group, contains submission rights
  28. // userid of the group
  29. let groupN = 0;
  30. let listedGroups = [];
  31. let devID;
  32. let collections = [{
  33. id: 0,
  34. name: "all",
  35. groups: [],
  36. showing: 1
  37. }];
  38. let collectionOrder = [];
  39. let colMode = 0; //0 show, 1 add, 2 remove, 3 delete
  40. let colModeTarget = 0;
  41. let macros = []; //{id, name, data:[{folderName, folderID, groupID, type}]}
  42. let macroOrder = [];
  43. let macroMode = 0; //0 idle, 1 record, 2 play, 3 remove
  44. let macroModeTarget = 0; //for recording
  45. let lastFilter = 0;
  46. var dragSrcEl = null;
  47. let colListMode = 0; //0 collection, 1 macro;
  48. let svgTurnArrow = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1.507856 1.2692268" style="height:0.5em;"> <defs id="defs2"> <marker id="Arrow2Send" style="overflow:visible"> <path id="path8454" style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" transform="matrix(-0.3,0,0,-0.3,0.69,0)" /> </marker> </defs> <g id="layer1" transform="translate(-1.2565625,-0.79875001)"> <path style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Send)" d="m 2.38125,0.93125 c -1.3229167,0 -1.3229166,0.79375 0,0.79375" id="path8419"/> </g> </svg> ';
  49. let fetchingGroups = false; //prevent refresh button requesting multiple times at once
  50. let entityMap = {
  51. '&': '&amp;',
  52. '<': '&lt;',
  53. '>': '&gt;',
  54. '"': '&quot;',
  55. "'": '&#39;',
  56. '/': '&#x2F;',
  57. '`': '&#x60;',
  58. '=': '&#x3D;'
  59. };
  60. let loadedFolders = new Map();
  61. let lastGroupClickID;
  62. let notScanThisInstance = false;
  63. let targetName = ""; //target group or macro name
  64.  
  65. const errtyps = Object.freeze({
  66. Connection_Error: "Connection Error",
  67. No_User_ID: "No User ID",
  68. Unknown_Error: "Unkown Error",
  69. Parse_Error: "Parse Error",
  70. Wrong_Setting: "Wrong Profile setting"
  71. });
  72. //error protocol convention: ErrType of errtyps, ErrDescr as text, ErrDetail with exception object
  73. //alert of type/descr, console log with detail
  74.  
  75. function err(type, descr, detail) {
  76. return { ErrType: type, ErrDescr: descr, ErrDetail: detail };
  77. }
  78.  
  79. function errorHndl(err) {
  80. myMsgBox("<strong>Type</strong>: " + err.ErrType + "<br /><strong>Description</strong>: " + err.ErrDescr + "<br /><br />See the log (F12 in Chrome/Firefox) for more details.<br />If the error persist, feel free to write me a <a style=\"text-decoration: underline;\" href=\"https://www.deviantart.com/dediggefedde/art/dev-group-list2-817465905#comments\">comment</a>.", "Error");
  81. console.log("dev_group_list2 error:", err);
  82. }
  83.  
  84. //API calls getting data
  85. function fillGroups(offset) { //async+callback //load all groups
  86.  
  87. let murl = "https://www.deviantart.com/_napi/da-user-profile/api/module/groups/members?username=" + userName + "&moduleid=" + moduleID + "&offset=" + offset + "&limit=" + grPerReq;
  88. return new Promise(function(resolve, reject) {
  89. GM.xmlHttpRequest({
  90. method: "GET",
  91. url: murl,
  92. onerror: function(response) {
  93. reject(err(errtyps.Connection_Error, "Connection to " + murl + " failed", response));
  94. },
  95. onload: function(response) {
  96. if (response.status == 500) {
  97. resolve(oldFillGroups(offset));
  98. return
  99. }
  100. let resp = JSON.parse(response.responseText);
  101.  
  102. if (offset == 0) groups = [];
  103.  
  104. let newnams = resp.results.map(x => x.userId);
  105. if (groups.map(x => x.userId).filter(x => newnams.includes(x)).length > 0) {
  106. reject(err(errtyps.Wrong_Setting, "Double group entries detected. Probably wrong sorting at profile/about's group-member section. Please choose asc or desc, not random.", groups));
  107. $("#dgl2_refresh").css("cursor", "pointer");
  108. return;
  109. }
  110.  
  111. groups = groups.concat(resp.results);
  112. groupN = resp.total;
  113.  
  114. let frac = 0;
  115. if (groupN > 0) frac = groups.length / groupN * 100;
  116. frac = Math.round(frac);
  117.  
  118. $("#dgl2_refresh rect").css("fill", "url(#dgl2_grad1)");
  119. $("#dgl2_grad1_stop1").attr("offset", frac + "%");
  120. $("#dgl2_grad1_stop2").attr("offset", (frac + 1) + "%");
  121. $("#dgl2_refresh").attr("title", frac + " %");
  122. $("span.dgl2_descr").text("Loading List of Groups... " + frac + " %");
  123.  
  124. if (resp.hasMore) {
  125. resolve(fillGroups(resp.nextOffset));
  126. } else {
  127. GM.setValue("groups", JSON.stringify(groups));
  128. insertGroups();
  129. $("#dgl2_refresh rect").css("fill", "");
  130. $("#dgl2_refresh").css("cursor", "pointer");
  131.  
  132. resolve(groups);
  133. }
  134. }
  135. });
  136. });
  137. }
  138.  
  139. let ngroups = [];
  140.  
  141. function oldFillGroups(offset) { //only fills names and icons
  142. let murl = "https://www.deviantart.com/" + userName + "/modals/mygroups/?offset=" + offset + "&limit=100";
  143. return new Promise(function(resolve, reject) {
  144. GM.xmlHttpRequest({
  145. method: "GET",
  146. url: murl,
  147. onerror: function(response) {
  148. reject(err(errtyps.Connection_Error, "Connection to " + murl + " failed", response));
  149. },
  150. onload: function(response) {
  151.  
  152. let tgroups = response.responseText.split("<div class=\"mygroup")
  153. let rex = /<img.*?class="avatar".*?src="(.*?)".*?title="(.*?)"/i;
  154. let rex2 = /data-gruser-type="(.*?)"/i;
  155.  
  156. tgroups = tgroups.map(str => { //parse websites to group data
  157. let ret = {};
  158. let mat = str.match(rex);
  159. if (mat == null) return null;
  160. ret.usericon = mat[1];
  161. ret.username = mat[2];
  162. ret.type = str.match(rex2)[1];
  163. return ret;
  164. }).filter(el => el != null);
  165. ngroups = ngroups.concat(tgroups);
  166.  
  167. $("#dgl2_refresh rect").css("fill", "url(#dgl2_grad1)");
  168. $("span.dgl2_descr").text(`Loading List of Groups... ${ngroups.length}/?`);
  169.  
  170. if (response.responseText.indexOf("class=\"next disabled") == -1) { //next not disabled = new page available
  171. resolve(oldFillGroups(offset + 100));
  172. } else {
  173. ngroups.forEach(el => { //new groups not previously in list
  174. if (el == null) return;
  175. for (let gr of groups) {
  176. if (gr.username == el.username) {
  177. Object.assign(el, gr);
  178. //el=gr;
  179. //copy old information to prevent id/uuid loss
  180. }
  181. }
  182. });
  183. groups = ngroups;
  184. ngroups = [];
  185.  
  186. GM.setValue("groups", JSON.stringify(groups));
  187. insertGroups();
  188. $("#dgl2_refresh rect").css("fill", "");
  189. $("#dgl2_refresh").css("cursor", "pointer");
  190. resolve(groups);
  191. }
  192. }
  193. });
  194. });
  195. }
  196.  
  197. function grabIDfromPage(name) {
  198. return new Promise(function(resolve, reject) {
  199. GM.xmlHttpRequest({
  200. method: "GET",
  201. url: "https://www.deviantart.com/" + name,
  202. onerror: function(response) {
  203. reject(err(errtyps.Connection_Error, "Connection to https://www.deviantart.com/" + name + " failed", response));
  204. },
  205. onload: async function(response) {
  206.  
  207. $("#dgl2_refresh rect").css("fill", "url(#dgl2_grad1)");
  208. ngrpCnt += 1;
  209. $("span.dgl2_descr").text(`Loading List of Groups IDs... ${ngrpCnt}/${ngrpleft}`);
  210.  
  211. let rex = /itemid":(.*?),"friendid":"(.*?)"/i;
  212. let mat = response.responseText.match(rex);
  213. if (mat == null) {
  214. reject(err(errtyps.No_User_ID, "Request of " + name + "-id failed", response));
  215. return;
  216. }
  217.  
  218. groups.forEach(gr => {
  219. if (gr.username == name) {
  220. gr.userId = mat[1];
  221. gr.useridUuid = mat[2];
  222. }
  223. });
  224. GM.setValue("groups", JSON.stringify(groups));
  225. resolve(mat[1]);
  226. }
  227. });
  228. });
  229. }
  230.  
  231. function fillSubFolder(groupID, type, name) { //async+callback //type =[collection,gallery]
  232.  
  233. if (groupID == "undefined") {
  234. $(".dgl2_groupdialog ").css("cursor", "wait");
  235. $(".dgl2_groupButton").css("cursor", "wait");
  236. return grabIDfromPage(name).then(id => {
  237. groupID = id;
  238. lastGroupClickID = id;
  239. return fillSubFolder(groupID, type, name);
  240. }).catch(erg => {
  241. errorHndl(erg);
  242. $(".dgl2_groupdialog ").css("cursor", "pointer");
  243. return null;
  244. });
  245. }
  246.  
  247. return new Promise(function(resolve, reject) {
  248. let murl = "https://www.deviantart.com/_napi/shared_api/deviation/group_folders?groupid=" + groupID + "&type=" + type;
  249. GM.xmlHttpRequest({
  250. method: "GET",
  251. url: murl,
  252. onerror: function(response) {
  253. reject(err(errtyps.Connection_Error, "Connection to " + murl + " failed", response));
  254. },
  255. onload: async function(response) {
  256. let resp;
  257. let errg;
  258. try {
  259. resp = JSON.parse(response.responseText);
  260. } catch (ex) {
  261. errg = err(errtyps.Connection_Error, "Page problems. Please try again later!", ex)
  262. reject(errg)
  263. }
  264. if (resp.results == undefined) {
  265. errg = err(errtyps.Parse_Error, "Error parsing website response", response);
  266. errorHndl(errg);
  267. } else {
  268. insertSubFolders(resp.results);
  269. if (macroMode == 1) {
  270. for (let gr of macros[macroModeTarget].data) {
  271. if (gr.groupID == groupID) {
  272. $("button.dgl2_groupButton[folderID='" + gr.folderID + "']").addClass("folderInMacro");
  273. break;
  274. }
  275. }
  276. }
  277. }
  278. $(".dgl2_groupdialog").css("cursor", "");
  279. $(".dgl2_groupButton").css("cursor", "pointer");
  280. $("div.groupPopup").focus();
  281. resolve(resp.results);
  282.  
  283. }
  284. });
  285. });
  286. }
  287.  
  288. function fillModuleID() { //async+callback //get Module ID for group submission
  289. let murl = "https://www.deviantart.com/" + userName + "/about";
  290. return new Promise(function(resolve, reject) {
  291. GM.xmlHttpRequest({
  292. method: "GET",
  293. url: murl,
  294. onerror: function(response) {
  295. reject(err(errtyps.Connection_Error, "Connection to " + murl + " failed", response));
  296. },
  297. onload: async function(response) {
  298. try {
  299. let resp = (response.responseText);
  300. resp = resp.match(/{\\\"id\\\":(\d+),\\\"type\\\":\\\"group_list_members/i);
  301. if (resp != null && resp.length > 1) {
  302. moduleID = resp[1];
  303. resolve(moduleID);
  304. } else {
  305. reject(err(errtyps.Wrong_Setting, "No group-member section in /about page", resp));
  306. }
  307. } catch (ex) {
  308. reject(err(errtyps.Unknown_Error, "Something went wrong while accessing groups", ex));
  309. }
  310. }
  311. });
  312. });
  313. }
  314.  
  315. function fillListedGroups(devID) {
  316. let murl = "https://www.deviantart.com/deviation/" + devID + "/groups";
  317. return new Promise(function(resolve, reject) {
  318. GM.xmlHttpRequest({
  319. method: "GET",
  320. url: murl,
  321. onerror: function(response) {
  322. reject(err(errtyps.Connection_Error, "Connection to " + murl + " failed", response));
  323. },
  324. onload: async function(response) {
  325. let tex = response.responseText;
  326. let res = tex.matchAll(/gmi-groupid="(\d+)"/gi);
  327.  
  328. for (let entr of res) {
  329. listedGroups.push(parseInt(entr[1]));
  330. }
  331. resolve(listedGroups);
  332. }
  333. });
  334. });
  335.  
  336. }
  337.  
  338. function requestAddSubmission(token, devID, folderID, groupID, type) { //async+callback //type =[collection,gallery]
  339. let macroFchanged = false;
  340. if (macroMode == 1) {
  341. if (macros[macroModeTarget].data.some(e => e.groupID === groupID)) {
  342. macros[macroModeTarget].data.forEach(function(el) {
  343. if (el.groupID === groupID) {
  344. el.folderID = folderID;
  345. el.folderName = loadedFolders.get(folderID);
  346. }
  347. }); //change folder of present group
  348. macroFchanged = true;
  349. } else { //don't add if included already
  350. macros[macroModeTarget].data.push({
  351. folderName: loadedFolders.get(folderID),
  352. folderID: folderID,
  353. groupID: groupID,
  354. type: type
  355. });
  356. }
  357. GM.setValue("macros", JSON.stringify(macros));
  358. }
  359.  
  360. return new Promise(function(resolve, reject) {
  361. let dat = {
  362. "groupid": parseInt(groupID),
  363. "type": type.toString(),
  364. "folderid": parseInt(folderID),
  365. "deviationid": parseInt(devID),
  366. "csrf_token": token.toString()
  367. };
  368. if (macroMode == 1) { //don't submit while adding to macros
  369. resolve({ success: true, gname: groupNameById(groupID), fname: loadedFolders.get(folderID), fchanged: macroFchanged });
  370. } else {
  371. GM.xmlHttpRequest({
  372. method: "POST",
  373. url: "https://www.deviantart.com/_napi/shared_api/deviation/group_add",
  374. headers: {
  375. "accept": 'application/json, text/plain, */*',
  376. "content-type": 'application/json;charset=UTF-8'
  377. },
  378. dataType: 'json',
  379. data: JSON.stringify(dat),
  380. onerror: function(response) {
  381. response.gname = groupNameById(groupID);
  382. reject(err(errtyps.Connection_Error, "Connection to https://www.deviantart.com/_napi/shared_api/deviation/group_add failed", response));
  383. },
  384. onload: async function(response) {
  385. let resp = JSON.parse(response.responseText);
  386. resp.gname = groupNameById(groupID);
  387. resolve(resp);
  388. }
  389. });
  390. }
  391. });
  392. }
  393.  
  394. function playMacro(index) {
  395. macroMode = 2;
  396. let promises = [];
  397. let token = $("input[name=validate_token]").val();
  398. for (let d of macros[index].data) {
  399. promises.push(requestAddSubmission(token, devID, d.folderID, d.groupID, d.type));
  400. }
  401. Promise.all(promises).catch(err => {
  402. alert(macros[index].name + " Error!<br/>" + JSON.stringify(macros[index].data) + " " + JSON.stringify(err), "Error");
  403. }).then(res => {
  404. myMsgBox(
  405. res.map(obj => {
  406. let retval = "<strong>" + obj.gname + "</strong>: "
  407. if (obj.success) {
  408. retval += "Success! ";
  409. if (obj.needsVote == true) retval += " Vote pending";
  410. } else {
  411. retval += "Error! ";
  412. if (obj.errorDetails) retval += obj.errorDetails;
  413. }
  414. return retval;
  415. }).join("<br/>"), "Play Macro " + macros[macroModeTarget].name);
  416. })
  417. macroMode = 0;
  418. }
  419. //event handlers
  420. function Ev_groupClick(event) { //event propagation
  421. event.stopPropagation();
  422.  
  423. let targetBut = $(event.target).closest(".dgl2_groupButton");
  424. let groupID = targetBut.attr("groupID");
  425. let groupNam = targetBut.attr("groupname");
  426. //targetName="";
  427.  
  428. if (groupID == "undefined") {
  429. grabIDfromPage(groupNam).then(id => {
  430. $(event.target).closest(".dgl2_groupButton").attr("groupID", id);
  431. Ev_groupClick(event);
  432. });
  433. return;
  434. }
  435. let elInd;
  436. switch (colMode) {
  437. case 1: //add
  438. elInd = collections[colModeTarget].groups.indexOf(groupID);
  439. if (elInd == -1) {
  440. collections[colModeTarget].groups.push(groupID);
  441. GM.setValue("collections", JSON.stringify(collections));
  442. targetBut.addClass("dgl2_inCollection");
  443. targetName = targetBut.attr("groupName");
  444. }
  445. break;
  446. case 2: //remove
  447. targetName = collections[colModeTarget].name;
  448. switch (colListMode) {
  449. case 0: //collection
  450. elInd = collections[colModeTarget].groups.indexOf(groupID);
  451. if (elInd > -1) {
  452. collections[colModeTarget].groups.splice(elInd, 1);
  453. GM.setValue("collections", JSON.stringify(collections));
  454. }
  455. insertFilteredGroups(colModeTarget);
  456. break;
  457. case 1: //macro
  458. for (elInd = 0; elInd < macros[macroModeTarget].data.length; ++elInd) {
  459. if (macros[macroModeTarget].data[elInd].groupID == groupID) break;
  460. }
  461.  
  462. if (elInd < macros[macroModeTarget].data.length) {
  463. macros[macroModeTarget].data.splice(elInd, 1);
  464. GM.setValue("macros", JSON.stringify(macros));
  465. }
  466. insertMacroGroups(macroModeTarget);
  467. break;
  468. }
  469. break;
  470. case 0:
  471. default:
  472. if (targetBut.attr("type") == "group") {
  473. lastGroupClickID = targetBut.attr("groupID");
  474. fillSubFolder(groupID, "gallery", groupNam);
  475. if (macroMode != 1) {
  476. targetName = targetBut.attr("groupName");
  477. } else {
  478.  
  479. }
  480. displayModeText();
  481. //Add this deviation to a group folder
  482. } else if (targetBut.attr("type") == "folder") {
  483. let token = $("input[name=validate_token]").val();
  484. requestAddSubmission(token, devID, targetBut.attr("folderID"), targetBut.attr("groupID"), targetBut.attr("folderType")).then(function(arg) {
  485. if (arg.success == true) {
  486. if (macroMode == 1) {
  487. if (arg.fchanged) {
  488. myMsgBox(arg.gname + " target folder changed to " + arg.fname, "Info");
  489. } else {
  490. myMsgBox(arg.gname + "/" + arg.fname + " added to macro", "Info");
  491. }
  492. insertMacros(); //update titles
  493. insertGroups(); //go back to groups view
  494.  
  495. $("div.dgl2_groupWrapper").addClass("dgl2_addGroup");
  496. displayModeText();
  497. //$("span.dgl2_descr").text("macro " + macros[macroModeTarget].name + " is recording.");
  498. for (let el of macros[macroModeTarget].data) {
  499. $("button.dgl2_groupButton[groupID=" + el.groupID + "]").addClass("dgl2_inCollection");
  500. }
  501. macroMode = 1;
  502. let lastgrBut = $("button[groupid=" + lastGroupClickID + "]")
  503. lastgrBut[0].scrollIntoView();
  504. lastgrBut.addClass("shadow-pulse");
  505. } else {
  506. let retfun = function() {
  507. $("span.dgl2_titleText").click();
  508. let lastgrBut = $("button[groupid=" + lastGroupClickID + "]")
  509. lastgrBut[0].scrollIntoView();
  510. lastgrBut.addClass("shadow-pulse");
  511. };
  512. if (arg.needsVote) {
  513. myMsgBox("Success! Submission pending group's vote", "Info").then(retfun);
  514. } else {
  515. myMsgBox("Success! Submission added to group", "Info").then(retfun);
  516. }
  517. }
  518.  
  519. } else {
  520. throw arg;
  521. }
  522. /*
  523. deviationGroupCount: 1
  524. needsVote: true
  525. */
  526. }).catch(function(arg) {
  527. let tx = "deviation-ID: " + devID + "<br/>" +
  528. "Group-Name: " + (arg.gname ? arg.gname : "Unknown") + "<br/>" +
  529. (arg.errorDescription ? arg.errorDescription : "Unexpected error.") + "<br/>" +
  530. (arg.errorDetails ? arg.errorDetails : JSON.stringify(arg))
  531. let errg = err(errtyps.Unknown_Error, tx, arg);
  532. errorHndl(errg);
  533. });
  534. }
  535. }
  536. displayModeText();
  537. }
  538.  
  539. function Ev_ContextSubmit(event) {
  540. event.stopPropagation();
  541. event.preventDefault();
  542. event.target = $("#dgl2_grContext select option:selected").get(0);
  543. Ev_groupClick(event);
  544. $("#dgl2_grContext").hide().find("select").empty();
  545. }
  546.  
  547. function Ev_groupContext(event) {
  548. event.stopPropagation();
  549. event.preventDefault();
  550. let el = $("#dgl2_grContext");
  551. if (el.length == 0) {
  552. el = $("<div id='dgl2_grContext'><span class='desc'>Submit to a Folder</span><br /><select size=5></select><br/><button>Submit</button></div>").appendTo(document.body);
  553. el.find("button").click(Ev_ContextSubmit);
  554. }
  555. el.find("select").hide();
  556. el.finish().show().css({
  557. top: event.pageY + "px",
  558. left: event.pageX + "px"
  559. });
  560. let groupID = $(event.target).closest("button.dgl2_groupButton").attr("groupid")
  561. fillSubFolder(groupID, "gallery", $(event.target).closest("button.dgl2_groupButton").attr("groupname")).then(function() {
  562. el.find("select").show().focus().get(0).selectedIndex = 0;
  563. });
  564.  
  565. }
  566.  
  567. function switchColList() {
  568. switch (colListMode) {
  569. case 0:
  570. colListMode = 1;
  571. $("span.dgl2_CollTitle").html("Macros");
  572. insertMacros();
  573. break;
  574. case 1:
  575. $("span.dgl2_CollTitle").html("Collections");
  576. colListMode = 0;
  577. insertCollections();
  578. }
  579. }
  580.  
  581. function highlightLetter(which) {
  582.  
  583. $(".dgl2_letterfound").removeClass("dgl2_letterfound");
  584. $(".dgl2_groupdialog button.dgl2_groupButton[groupName^='" + which + "' i]").addClass("dgl2_letterfound").focus();
  585. $(".dgl2_groupdialog button.dgl2_groupButton[folderName^='" + which + "' i]").addClass("dgl2_letterfound").focus();
  586. }
  587.  
  588. function Ev_colListClick(event) {
  589. event.stopPropagation();
  590.  
  591. if ($(event.target).closest(".dgl2_CollTitleBut").length > 0) {
  592. switchColList();
  593. }
  594. let id = $(event.target).closest("li").first().attr("colID");
  595. if (id == undefined && $(event.target).closest("button").hasClass("dgl2_topBut")) id = 0;
  596. else if (id == undefined) return;
  597. let clasNam = $(event.target).closest("button").attr("class");
  598. let index;
  599. if (colListMode == 0) index = colIndexById(id);
  600. else if (colListMode == 1) index = makIndexById(id);
  601. $("div.dgl2_groupWrapper").removeClass("dgl2_addGroup").removeClass("dgl2_remGroup");
  602. $("button.dgl2_inCollection").removeClass("dgl2_inCollection");
  603. let el;
  604. let obj, dat;
  605. let d = new Date();
  606. let con;
  607. let nam;
  608. targetName = "";
  609.  
  610. if (clasNam) clasNam = clasNam.replace(" dgl2_topBut", "");
  611. switch (clasNam) {
  612. case "dgl2_export":
  613. obj = {
  614. collections: collections,
  615. collectionOrder: collectionOrder,
  616. macros: macros,
  617. macroOrder: macroOrder
  618. };
  619. dat = d.getFullYear() + ("0" + d.getMonth()).slice(-2) + ("0" + d.getDate()).slice(-2) + "-" + ("0" + d.getHours()).slice(-2) + ("0" + d.getMinutes()).slice(-2) + ("0" + d.getSeconds()).slice(-2);
  620. download(JSON.stringify(obj), "dev_group_list2_data_" + dat + ".txt");
  621. break;
  622. case "dgl2_import":
  623. upload().then(function(imp) {
  624. try {
  625. let i;
  626. let obj = JSON.parse(imp);
  627. if (obj.macros && obj.macroOrder) {
  628. macros = obj.macros;
  629. macroOrder = obj.macroOrder;
  630. }
  631. if (obj.collections && obj.collectionOrder) {
  632. collections = obj.collections;
  633. collectionOrder = obj.collectionOrder;
  634. } else if (obj[0] != undefined && (obj[0][0].indexOf("_collist") != -1 || obj[1][0].indexOf("_collist") != -1)) { //v1 compatibility mode
  635. collections = [{
  636. id: 0,
  637. name: "all",
  638. groups: [],
  639. showing: 1
  640. }];
  641. let ind = (obj[0][0].indexOf("_collist") != -1) ? 0 : ((obj[1][0].indexOf("_collist") != -1) ? 1 : -1)
  642. let oldList = obj[ind][1].split("\u0002");
  643. let coll = oldList.map((list, ind) => {
  644. let entries = list.split("\u0001");
  645. let nam = entries.shift();
  646. return {
  647. id: ind + 1,
  648. name: nam,
  649. groups: entries.map(el => {
  650. return $("button[groupname='" + el + "']").attr("groupid");
  651. }).filter(el => el != undefined),
  652. showing: 1
  653. }
  654. });
  655. collections = collections.concat(coll).sort((a, b) => a.id > b.id);
  656. collectionOrder = collections.map(col => "dgl2item-" + col.id);
  657. } else {
  658. throw "No collections found!";
  659. }
  660. //clean up old groups not beeing a member of anymore
  661. for (i in macros) {
  662. macros[i].data = macros[i].data.filter(el => { return groupNameById(el.groupID) != "" });
  663. }
  664. for (i in collections) {
  665. collections[i].groups = collections[i].groups.filter(el => { return groupNameById(el) != "" });
  666. }
  667. } catch (ex) {
  668. errorHndl(err(errtyps.Parse_Error, "Not a valid dev_group_list2 file", ex));
  669. return;
  670. }
  671.  
  672. GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
  673. GM.setValue("collections", JSON.stringify(collections));
  674. GM.setValue("macroOrder", JSON.stringify(macroOrder));
  675. GM.setValue("macros", JSON.stringify(macros));
  676. myMsgBox("Import successfull!", "Info");
  677. insertGroups();
  678. insertCollections();
  679. if (colListMode != 0) switchColList();
  680. });
  681. break;
  682. case "dgl2_add":
  683. insertGroups();
  684. switch (colListMode) {
  685. case 0: //collection
  686.  
  687. targetName = collections[index].name;
  688. //$("span.dgl2_descr").text("Add groups to the collection " + collections[index].name);
  689. colMode = 1;
  690. colModeTarget = index;
  691. $("div.dgl2_groupWrapper").addClass("dgl2_addGroup");
  692. for (el of collections[index].groups) {
  693. $("button.dgl2_groupButton[groupID=" + el + "]").addClass("dgl2_inCollection");
  694. }
  695. displayModeText();
  696. break;
  697. case 1:
  698. colMode = 0;
  699. insertGroups();
  700. $("div.dgl2_groupWrapper").addClass("dgl2_addGroup");
  701. targetName = macros[index].name;
  702. //$("span.dgl2_descr").text("macro " + macros[index].name + " is recording.");
  703. for (el of macros[index].data) {
  704. $("button.dgl2_groupButton[groupID=" + el.groupID + "]").addClass("dgl2_inCollection");
  705. }
  706. macroMode = 1;
  707. macroModeTarget = index;
  708. displayModeText();
  709. break;
  710. }
  711. break;
  712. case "dgl2_sub":
  713. switch (colListMode) {
  714. case 0: //collection
  715. insertFilteredGroups(index);
  716. targetName = collections[index].name;
  717. //$("span.dgl2_descr").text("Remove groups from the collection " + collections[index].name);
  718. colMode = 2;
  719. colModeTarget = index;
  720. $("div.dgl2_groupWrapper").addClass("dgl2_remGroup");
  721. displayModeText();
  722. break;
  723. case 1: //macro
  724. insertMacroGroups(index);
  725. targetName = macros[index].name;
  726. // $("span.dgl2_descr").text("Remove groups from the macro " + macros[index].name);
  727. colMode = 2;
  728. macroMode = 3;
  729. macroModeTarget = index;
  730. $("div.dgl2_groupWrapper").addClass("dgl2_remGroup");
  731. displayModeText();
  732. break;
  733. }
  734. break;
  735. case "dgl2_del":
  736. switch (colListMode) {
  737. case 0: //collection
  738. myMsgBox("Delete Collection " + collections[index].name + " ?", "Collection", 1).then(con => {
  739. if (!con) return;
  740. collections.splice(index, 1);
  741. collectionOrder.splice(collectionOrder.indexOf("dgl2item-" + id), 1);
  742.  
  743. GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
  744. GM.setValue("collections", JSON.stringify(collections));
  745.  
  746. insertGroups();
  747. insertCollections();
  748. });
  749. break;
  750. case 1: //macro
  751. myMsgBox("Delete Macro " + macros[index].name + " ?", "Macro", 1).then(con => {
  752. if (!con) return;
  753. macros.splice(index, 1);
  754. macroOrder.splice(macroOrder.indexOf("dgl2item-" + id), 1);
  755.  
  756. GM.setValue("macroOrder", JSON.stringify(macroOrder));
  757. GM.setValue("macros", JSON.stringify(macros));
  758.  
  759. insertGroups();
  760. insertMacros();
  761. });
  762. break;
  763. }
  764. break;
  765. case "dgl2_new":
  766. switch (colListMode) {
  767. case 0: //collection
  768. el = {
  769. name: "New Collection",
  770. groups: [],
  771. showing: 1
  772. };
  773. el.id = getLowestFree(collections);
  774. collections.push(el);
  775. collectionOrder.push("dgl2item-" + el.id);
  776.  
  777. GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
  778. GM.setValue("collections", JSON.stringify(collections));
  779.  
  780. insertGroups();
  781. insertCollections();
  782. break;
  783. case 1: //macros
  784. el = {
  785. name: "New Macro",
  786. data: []
  787. };
  788. el.id = getLowestFree(macros);
  789. macros.push(el);
  790. macroOrder.push("dgl2item-" + el.id);
  791.  
  792. GM.setValue("macroOrder", JSON.stringify(macroOrder));
  793. GM.setValue("macros", JSON.stringify(macros));
  794.  
  795. insertGroups();
  796. insertMacros();
  797. break;
  798. }
  799. break;
  800. case "dgl2_visible":
  801.  
  802. switch (colListMode) {
  803. case 0: //collection
  804. if (!collections[index].hasOwnProperty("showing")) collections[index].showing = 0;
  805. else collections[index].showing = 1 - collections[index].showing; //toggle 0 and 1
  806. GM.setValue("collections", JSON.stringify(collections));
  807.  
  808. $(event.target).closest("li").attr("active", collections[index].showing);
  809. insertGroups();
  810. break;
  811. case 1: //macro
  812. //donothing
  813. break;
  814. }
  815. break;
  816. case "dgl2_edit":
  817. switch (colListMode) {
  818. case 0: //collection
  819. myMsgBox("Please enter a new collection name!", "Change Collection Name", 2, collections[index].name).then(nam => {
  820. if (!nam) return;
  821. collections[index].name = nam;
  822. GM.setValue("collections", JSON.stringify(collections));
  823. insertCollections();
  824. });
  825. break;
  826. case 1: //macro
  827. myMsgBox("Please enter a new macro name!", "Change Macro Name", 2, macros[index].name).then(nam => {
  828. if (!nam) return;
  829. macros[index].name = nam;
  830. GM.setValue("macros", JSON.stringify(macros));
  831. insertMacros();
  832. });
  833. break;
  834. }
  835. break;
  836. case undefined:
  837. default:
  838. switch (colListMode) {
  839. case 0: //collection
  840. colMode = 0;
  841. insertFilteredGroups(index);
  842. break;
  843.  
  844. case 1: //macro
  845. //$("span.dgl2_descr").text("Add this deviation to a group folder");
  846. myMsgBox("Do you want to add this to the following groups?<br/>" + macros[index].data.map(obj => {
  847. return groupNameById(obj.groupID);
  848. }).join(", "), "Submit to Groups", 1).then(con => {
  849. if (!con) {} else {
  850. macroMode = 2;
  851. playMacro(index);
  852. }
  853. displayModeText();
  854. });
  855. break;
  856. }
  857.  
  858. }
  859. displayModeText();
  860. }
  861.  
  862. function Ev_getGroupClick() {
  863.  
  864. if (fetchingGroups) return;
  865. fetchingGroups = true;
  866. groups.forEach((el) => {
  867. if (el.userId == 0) {
  868. el.userId = "undefined"
  869. el.useridUuid = "undefined"
  870. }
  871. });
  872.  
  873. $("span.dgl2_descr").text("Loading Module ID...");
  874. $("#dgl2_refresh").css("cursor", "pointer");
  875. fillModuleID().then(function() {
  876. $("span.dgl2_descr").text("Loading List of Groups...");
  877. $("#dgl2_refresh").css("cursor", "wait");
  878. return fillGroups(0);
  879. }).then(function() {
  880. // $("span.dgl2_descr").text("Add this deviation to one of your groups");
  881. displayModeText();
  882. }).catch(function(e) {
  883. if (e.ErrType != null) errorHndl(e);
  884. else errorHndl(err(errtyps.Unknown_Error, "fillGroups error", e));
  885. }).finally(function() {
  886. fetchingGroups = false;
  887. });
  888. }
  889. //templates
  890. function getGroupTemplate(name, img, id) { //return HTML string
  891. //return "<button groupID="+id+" type='group' groupName="+name+" class='_3UhBt _3PBqc _3PBqc dgl2_groupButton'><div class='_1cFkE _11Rtb _3Lbo4 dgl2_groupButDiv'><div class='_3_Bqs _1lUlZ'><div class='pqF_I 3tZow'><span class='_1X7Yj _14i4i _23Ekg _3q2EJ'><img data-hook='user_avatar' alt='"+name+"'s avatar' class='_19ZLc dIDzJ' src='"+img+"'></span></div><div class='_3po1a _3_Nxk'><span class='_3g6BC _2PY8v _3YKkm _1sm3t _3HH04 _3y1P2 _3kEx1 _32s2-'><svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path d='M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z'></path></svg></span></div></div><div class='_3j_sQ _22Jp8'>"+name+"</div></div></button>";
  892. return "<button class='dgl2_groupButton' groupID=" + id + " type='group' groupName='" + escapeHtml(name) + "' >" +
  893. " <div class='dgl2_imgwrap'>" +
  894. " <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8' class='dgl2_hover'>" +
  895. " <path d='M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z'></path>" +
  896. " </svg>" +
  897. " <img class='dgl2_group_image' title='" + escapeHtml(name) + "' src='" + img + "'/>" +
  898. " </div>" +
  899. " <div class='dgl2_groupName'>" + escapeHtml(name) + "</div>" +
  900. "</button>";
  901. }
  902.  
  903. function getSubFolderOptionTemplate(name, devCnt, grID, foID, foType, img) {
  904. //text option only needs name,IDs and type
  905. return "<option class='dgl2_groupButton' groupID=" + grID + " folderName='" + escapeHtml(name) + "' folderID=" + foID + " folderType='" + foType + "' type='folder'>" + escapeHtml(name) + "</option>";
  906. }
  907.  
  908. function getSubFolderTemplate(name, devCnt, grID, foID, foType, img) { //return HTML string
  909. loadedFolders.set("" + foID, name);
  910. //return "<button class='_3UhBt _3PBqc _3PBqc dgl2_groupButton' groupID="+grID+" folderName="+name+" folderID="+foID+" folderType='"+foType+"' type='folder'><div class='_26MiR _2yz_F'><div class='_3Q4xp'><div class='_1PQmd'><div role='img' aria-label='Suggestions unwatch fav' class='_2i5F4 _2m25r' style='width: 112px; height: 48px; background-position: center center; background-size: cover; background-repeat: no-repeat; background-image: url(&quot;"+img+"&quot;);'><noscript></noscript></div></div><div class='_2ghXZ'><span class='_3g6BC _2PY8v _3YKkm _1AjcI'><svg width='0' height='0' 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></span></div></div><div class='_3a6pU'><div class='_1lEBY'>"+name+"</div><span class='_3_DY3'>"+devCnt+" deviations</span></div></div></button>";
  911. let imgstring;
  912. if (img.textContent) { //journal
  913. imgstring = "<p class='dgl2_journalSubF'>" + img.textContent.excerpt + "</p>";
  914. } else {
  915. let i;
  916. let cstr = "";
  917. img = img.media;
  918. for (i of img.types) {
  919. if (i.c != undefined) {
  920. cstr = i.c;
  921. break;
  922. }
  923. }
  924. if (cstr == "") {
  925. for (i of img.types) {
  926. if (i.s != undefined) {
  927. cstr = i.s;
  928. break;
  929. }
  930. }
  931. }
  932. if (img.baseUri) imgstring = img.baseUri + "/";
  933. if (img.prettyName) imgstring += cstr.replace("<prettyName>", img.prettyName);
  934. if (img.token) imgstring += "?token=" + img.token[0];
  935. imgstring = "<img class='dgl2_group_image' title='" + escapeHtml(name) + "' src='" + imgstring + "'/>";
  936. }
  937.  
  938. return "<button class='dgl2_groupButton' groupID=" + grID + " folderName='" + escapeHtml(name) + "' folderID=" + foID + " folderType='" + foType + "' type='folder'>" +
  939. " <div class='dgl2_imgwrap'>" +
  940. " <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8' class='dgl2_hover'>" +
  941. " <path d='M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z'></path>" +
  942. " </svg>" +
  943. imgstring +
  944. " </div>" +
  945. " <div class='dgl2_groupName'>" + escapeHtml(name) + "</div>" +
  946. " <div class='dgl2_devCnt'>" + devCnt + "</div>" +
  947. "</button>";
  948. }
  949.  
  950. function getSearchBarTemplate() {
  951. return "<input id='dgl2_searchbar' type='text' placeholder='Search'/>";
  952. }
  953.  
  954. function getCollectionColTemplate() {
  955. return "<div id='dgl2_CollTab'><div class='dgl2_CollTitleBut'>" + svgTurnArrow + "<span class='dgl2_CollTitle'>" +
  956. "Collections</span></div><div class='buttons'></div><ul class='sortableList'></ul></div>";
  957. }
  958.  
  959. function getAddButTemplate() {
  960. let sty = getComputedStyle(document.body);
  961. return "<button title='Add group to collection/macro' class='dgl2_add'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  962. " <g style='stroke:" + sty.color + ";stroke-width:15;'>" +
  963. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  964. " <line x1='86' y1='30' x2='86' y2='142' />" +
  965. " <line x1='30' y1='86' x2='142' y2='86' />" +
  966. " </g>" +
  967. "</svg></button>";
  968. }
  969.  
  970. function getRecButTemplate() {
  971. let sty = getComputedStyle(document.body);
  972. return "<button title='Add groups to macro' class='dgl2_add'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  973. " <g style='stroke:" + sty.color + ";stroke-width:15;'>" +
  974. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  975. " <ellipse cx='86' cy='86' rx='40' ry='40'></ellipse>" +
  976. " </g>" +
  977. "</svg></button>";
  978. }
  979.  
  980. function getNewColTemplate() {
  981. let sty = getComputedStyle(document.body);
  982. return "<button title='New collection/macro' class='dgl2_new dgl2_topBut'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  983. " <g style='stroke:" + sty.color + ";stroke-width:15;'>" +
  984. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  985. " <line x1='86' y1='30' x2='86' y2='142' />" +
  986. " <line x1='30' y1='86' x2='142' y2='86' />" +
  987. " </g>" +
  988. "</svg></button>";
  989. }
  990.  
  991. function getSubButTemplate() {
  992. let sty = getComputedStyle(document.body);
  993. return "<button title='Remove groups from collection/macro' class='dgl2_sub'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  994. " <g style='stroke:" + sty.color + ";stroke-width:15;'>" +
  995. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  996. " <line x1='30' y1='86' x2='142' y2='86' />" +
  997. " </g>" +
  998. "</svg></button>";
  999. }
  1000.  
  1001. function getRefreshButTemplate() {
  1002. let sty = getComputedStyle(document.body);
  1003. return "<button title='refresh list of groups' id='dgl2_refresh'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172' style=' fill:#000000;'><g fill='none' fill-rule='nonzero' stroke='none' stroke-width='1' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='10' stroke-dasharray='' stroke-dashoffset='0' font-family='none' font-weight='none' font-size='none' text-anchor='none' style='mix-blend-mode: normal'><path d='M0,172v-172h172v172z' fill='none'></path>" +
  1004. " <linearGradient id='dgl2_grad1' x1='0%' y1='100%' x2='0%' y2='0%'><stop id='dgl2_grad1_stop1' offset='0%' style='stop-color:rgb(0,255,0);stop-opacity:1' /><stop id='dgl2_grad1_stop2' offset='100%' style='stop-color:rgb(255,0,0);stop-opacity:1' /></linearGradient>" +
  1005. "<rect x='00' y='00' style='stroke:" + sty.color + ";stroke-width:5;opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  1006. "<g fill='" + sty.color + "'><path d='M62.00062,36.98l7.99979,10.89333h21.44625c18.10591,0 32.68,14.57409 32.68,32.68v21.78667h-16.34l21.78667,29.78646l21.78667,-29.78646h-16.34v-21.78667c0,-23.99937 -19.57396,-43.57333 -43.57333,-43.57333zM42.42667,39.87354l-21.78667,29.78646h16.34v21.78667c0,23.99938 19.57396,43.57333 43.57333,43.57333h29.44604l-7.99979,-10.89333h-21.44625c-18.10591,0 -32.68,-14.57409 -32.68,-32.68v-21.78667h16.34z'></path></g><path d='M43.86,172c-24.22321,0 -43.86,-19.63679 -43.86,-43.86v-84.28c0,-24.22321 19.63679,-43.86 43.86,-43.86h84.28c24.22321,0 43.86,19.63679 43.86,43.86v84.28c0,24.22321 -19.63679,43.86 -43.86,43.86z' fill='none'></path><path d='M47.3,168.56c-24.22321,0 -43.86,-19.63679 -43.86,-43.86v-77.4c0,-24.22321 19.63679,-43.86 43.86,-43.86h77.4c24.22321,0 43.86,19.63679 43.86,43.86v77.4c0,24.22321 -19.63679,43.86 -43.86,43.86z' fill='none'></path></g></svg></button";
  1007. }
  1008.  
  1009. function getDelButTemplate() {
  1010. let sty = getComputedStyle(document.body);
  1011. return "<button title='Delete collection/macro' class='dgl2_del'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
  1012. "<g style='stroke-width:5;stroke:" + sty.color + ";fill:none'>" +
  1013. " <rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
  1014. " <rect x='50' y='50' rx='5' ry='5' width='72' height='92'></rect>" +
  1015. " <rect x='65' y='35' rx='5' ry='5' width='42' height='15'></rect>" +
  1016. " <line x1='40' y1='50' x2='132' y2='50'/>" +
  1017. " <line x1='70' y1='132' x2='70' y2='60'/>" +
  1018. " <line x1='86' y1='132' x2='86' y2='60' />" +
  1019. " <line x1='104' y1='132' x2='104' y2='60' />" +
  1020. " </g>" +
  1021. "</svg></button>";
  1022. }
  1023.  
  1024. function getExportButTemplate() {
  1025. return '<button title="Export collection/macro list to file" class="dgl2_export dgl2_topBut"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 5.2916665 5.2916668">' +
  1026. ' <g transform="translate(0,-291.70832)">' +
  1027. ' <path style="fill:#008000;"' +
  1028. ' d="M 0.26458332,291.9729 H 5.0270831 v 4.7625 H 0.79345641 l -0.52887309,-0.51217 z" />' +
  1029. ' <rect style="fill:#ffffff;" width="3.7041667" height="1.8520833" x="0.79374999" y="292.23749" />' +
  1030. ' <rect style="fill:#ffffff;" width="2.6458333" height="1.3229259" x="1.3229166" y="295.41248" />' +
  1031. ' <rect style="fill:#008000;" width="0.52916676" height="0.79375702" x="2.9104166" y="295.67706" />' +
  1032. ' </g>' +
  1033. '</svg></button>';
  1034. }
  1035.  
  1036. function getImportButTemplate() {
  1037. return '<button class="dgl2_import dgl2_topBut" title="Import collection/macro list from file" ><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 5.2916665 5.2916668">' +
  1038. ' <g transform="translate(0,-291.70832)">' +
  1039. ' <rect style="fill:#806600;" width="3.96875" height="2.9104137" x="0.52916664" y="293.03125" />' +
  1040. ' <path style="fill:#ffcc00;" d="m 0.52916666,295.94165 0.79375004,-2.11666 h 3.96875 l -0.7937501,2.11666 z" />' +
  1041. ' <rect style="fill:#00DD00;" width="0.52916664" height="1.0583333" x="3.4395833" y="292.50208" />' +
  1042. ' <path style="fill:#00DD00;" d="m 3.175,292.50207 0.5291667,-0.52917 0.5291667,0.52917 z" />' +
  1043. ' </g>' +
  1044. '</svg></button>';
  1045. }
  1046.  
  1047. function getTitleBarTemplate() {
  1048. return '<span class="dgl2_titleText">Add to Group</span>' +
  1049. '<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill-rule="evenodd" d="M8.84210526,13 L8.84210526,21.1578947 L2,21.1578947 L2,9.57894737 L12,3 L22,9.57894737 L22,21.1578947 L15.1578947,21.1578947 L15.1578947,13 L8.84210526,13 Z"></path></svg>' +
  1050. '<span class="dgl2_descr">Add this deviation to one of your groups</span>'
  1051. }
  1052.  
  1053. function getEditButTemplate() {
  1054. return '<button title="Change collection/macro name" class="dgl2_edit"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.389 6.4503l-3-3-1.46-1.45-1.41 1.42-11.52 11.58-1 .97v6.03h5.987l1.013-1 11.41-11.76 1.39-1.41-1.41-1.38zm-4.45-1.62l3 3-.88.87-3-3 .88-.87zm.74 5.33l-8.21 8.2-2.801-3.0118 8.0028-8.099 3.0083 2.9108zm-12.68 9.84v-3.17l3.0433 3.17H3.9991z"></path></svg></button>';
  1055. }
  1056.  
  1057. function getVisibleButTemplate() {
  1058. return `<button title="Hide/show groups within collection" class="dgl2_visible">
  1059. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 250" stroke-width="20">
  1060. <defs>
  1061. <radialGradient id="c1" cx="0.5" cy="0.5" r="0.5">
  1062. <stop offset="0" stop-color="#ffffff" />
  1063. <stop offset=".5" stop-color="hsl(40, 60%, 60%)" />
  1064. <stop offset="1" stop-color="#3dff3d" />
  1065. </radialGradient>
  1066. </defs>
  1067. <ellipse role="sclera" cx="250" cy="125" rx="200" ry="100" fill="white"/>
  1068. <ellipse role="iris" cx="250" cy="125" rx="95" ry="95" stroke="black" fill="url(#c1)"/>
  1069. <ellipse role="pupil" cx="250" cy="125" rx="50" ry="50" stroke="none" fill="black"/>
  1070. <ellipse role="light" cx="200" cy="80" rx="50" ry="50" stroke="none" fill="#fffffaee"/>
  1071. <ellipse role="outline" cx="250" cy="125" rx="200" ry="100" stroke="black" fill="none"/>
  1072. </svg>
  1073. </button>`;
  1074. }
  1075.  
  1076. function addCss() {
  1077. if ($("#dgl2_style").length > 0) return;
  1078. let style = $("<style type='text/css' id='dgl2_style'></style>");
  1079.  
  1080. //searchbar
  1081. style.append("#dgl2_searchbar{background: var(--L3);box-shadow: inset 0 1px 4px 0 rgba(0,0,0,.25);padding: 5px;width: 50%;}");
  1082.  
  1083. //right collection column
  1084. style.append(`
  1085. #dgl2_CollTab{padding-left:15px; overflow-y: auto;font-family: CalibreSemiBold,sans-serif;font-weight: 600;font-size: 20px;font-display: swap;line-height: 24px;letter-spacing: .3px;margin-bottom: 28px;grid-row: 2;}
  1086. #dgl2_CollTab ul{overflow-wrap: anywhere;overflow: auto;list-style: none;padding-left: 10px;margin-top: 20px;}
  1087. #dgl2_CollTab ul li{cursor:pointer;padding:2px;display:grid;grid-template: auto/7px auto 16px 16px 16px 16px 16px;}
  1088. #dgl2_CollTab ul li:hover{background:linear-gradient(to right, rgba(255,0,0,0.1), rgba(255,0,0,0));}
  1089. #dgl2_CollTab button{cursor:pointer;border-width: 0;padding: 0;margin: 0;background-color: transparent;}
  1090. #dgl2_CollTab button:hover rect{fill:red;user-select: none; }
  1091. #dgl2_refresh{margin-left:auto;border-width:0px;background:transparent;cursor:pointer}
  1092. #dgl2_CollTab div.buttons{display: inline-block;vertical-align: middle;margin: 0 5px;}
  1093. div.dgl2_groupCol{overflow-y:auto;grid-row: 2;}
  1094. div.dgl2_groupdialog{display: grid;position: fixed;top: 50%;left: 50%;transform: translate(-50%,-50%);height: 80%;background-color:#afcaa9;grid-template-columns: auto 300px;grid-template-rows: 50px auto;width: 80%;border: 2px solid #2a5d00;border-radius: 10px;box-shadow: 1px 1px 2px ;}
  1095. div.dgl2_titlebar{cursor:move;display:flex;justify-content:space-between;grid-row: 1;grid-column: 1/3;background: linear-gradient(#619c32,#378201);color: white;align-items: center;}
  1096. div.dgl2_titlebar > * {margin: 7px;}
  1097. #dgl2_refresh:hover rect{fill:red;}
  1098. div.dgl2_closeDiag{border-radius: 50px;padding: 7px;cursor: pointer;}
  1099. #dgl2_refresh rect{fill:rgba(255,0,0,0.1);}
  1100. #dgl2_CollTab ul li span.handle{vertical-align:middle; display:inline-block; width:5px; height:100%; cursor:move; text-align:center;background-color:#363; background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABZJREFUeNpi2r9//38gYGAEESAAEGAAasgJOgzOKCoAAAAASUVORK5CYII=);}
  1101. button.dgl2_groupButton{border-radius:15px; background-color:rgba(255,255,255,0.5);margin:5px; padding:5px; width:120px; border-width:0px; overflow:hidden; position:relative; cursor:pointer; }
  1102. div.dgl2_imgwrap{ position: relative;}
  1103. svg.dgl2_hover{ position: absolute; left: 50%; width:50%; height:50%; transform: translate(-50%,50%); opacity:0; transition: ease 0.25s; }
  1104. img.dgl2_group_image{ opacity:1; width:100px; height:50px; transition: ease 0.25s; border-radius:2px; }
  1105. div.dgl2_groupName{ font-family: CalibreSemiBold; font-size: 15px; line-height: 15px; font-weight: 600; letter-spacing: 0.3px; word-break: break-word; }
  1106. button.dgl2_groupButton:hover{background-color:rgba(255,255,255,0.8);}
  1107. button.dgl2_groupButton:hover svg.dgl2_hover{opacity:1;}
  1108. button.dgl2_groupButton:hover img.dgl2_group_image{opacity:0.3;}
  1109. button.dgl2_groupButton:active{background-color:rgba(255,255,255,0.3);}
  1110. button.dgl2_groupButton.dgl2_inGroup{background-image:linear-gradient(red, transparent);}
  1111. span.dgl2_titleText{cursor:pointer;}
  1112. span.dgl2_descr{font-family: CalibreRegular,sans-serif; font-weight: 400; font-size: 13px; font-display: swap; letter-spacing: 1.3px; margin-left: 32px; text-transform: uppercase;}
  1113. button.dgl2_edit{height:0.5em;}
  1114. button.dgl2_edit:hover path{fill:red;}
  1115. #dgl2_CollTab button svg{width: 90%;}
  1116. #dgl2_CollTab button.dgl2_visible:hover ellipse{stroke: red;}
  1117. div.dgl2_addGroup{background-color: rgba(0, 255, 0, 0.3);}
  1118. div.dgl2_remGroup{background-color: rgba(255, 0, 0, 0.3);}
  1119. button.dgl2_inCollection{background-color: rgba(15, 104, 5, 0.7);}
  1120. button.dgl2_export:hover ,button.dgl2_import:hover {opacity:0.8}
  1121. button.dgl2_export:active ,button.dgl2_import:active {opacity:1}
  1122. div.dgl2_CollTitleBut{cursor:pointer;display:inline-block;}
  1123. div.dgl2_CollTitleBut:hover span{color:red;}
  1124. .dgl2_journalSubF { overflow: hidden; height: 50px; font-size: xx-small; text-align: left; margin-bottom: 5px;}
  1125. .groupPopup .ui-widget-content{background-color:#afcaa9 !important;color:black;}
  1126. button.dgl2_letterfound {background-color: rgba(105, 14, 5, 0.7);}
  1127. .folderInMacro {background-color:rgba(205, 24, 25, 0.6)!important;}
  1128. @keyframes shadowPulse {0% {box-shadow: 0px 0px 50px 20px #f00;} 100% {box-shadow: 0px 0px 50px 20px #ff000000;}}
  1129. .shadow-pulse {animation-name: shadowPulse;animation-duration: 0.5s;animation-iteration-count: infinite;animation-timing-function: linear; animation-direction:alternate;}
  1130. #dgl2_grContext{display: none;z-index: 1000;position: absolute;overflow: hidden;white-space: nowrap;padding: 5px;background-color: #afcaa9;border-radius: 5px;border: 2px solid green;}
  1131. #dgl2_grContext select{background: none; border: none;width:100%;margin:5px 0;}
  1132. #dgl2_grContext select option{background-color: #ddffd8;}
  1133. #dgl2_grContext select option:nth-child(even) {background-color: #6fd061;}
  1134. #dgl2_grContext select option::selection {color: red;background: yellow;}
  1135. #dgl2_grContext button {cursor:pointer; width: 100%;background-color: #408706;color: white;border: 1px outset black;border-radius: 5px;}
  1136. #dgl2_grContext button:hover { background-color: #608706;}
  1137. #dgl2_CollTab li[active='0'] button.dgl2_visible ellipse[role='iris'] { fill: lightgray;stroke:lightgray}
  1138. #dgl2_CollTab li[active='0'] button.dgl2_visible ellipse { fill: lightgray;}
  1139. #dgl2_CollTab li button{display: flex; height: 100%;align-items: center;}
  1140. #dgl2_CollTab li.dgl2_drgover{border-top: 2px solid blue;}
  1141. #dgl2_alertBox {z-index:7777;box-shadow: 1px 1px 2px black;position: fixed;top: 50%;left: 50%;background-color: #afcaa9;border-radius: 5px;border: 2px solid #285c00;transform: translate(-50%, -50%);}
  1142. #dgl2_alertBox .dgl2_alertTitle{cursor:move;background-color:#5d982d;color:white;font-weight: bold;}
  1143. #dgl2_alertBox div.dgl2_alertButtons div{padding: 5px;display: inline-block;border-radius: 5px;border: 1px solid;cursor: pointer;margin:5px;}
  1144. #dgl2_alertBox .dgl2_alertOKBut{background-color: #d0e8cb;}
  1145. #dgl2_alertBox .dgl2_alertCancelBut{background-color: #e8e3cb;}
  1146. #dgl2_alertBox>div{padding:5px;}
  1147. #dgl2_alertBox>div{padding:5px;}
  1148. #dgl2_alertBox div.dgl2_alertButtons{display: flex;flex-direction: row-reverse;}
  1149. #dgl2_promptVal{display: block;margin: 5px;border-radius: 5px;width: 90%;}
  1150. `);
  1151.  
  1152. // style.append(".noTitleDialog .ui-dialog-titlebar {display:none}");
  1153.  
  1154. $("head").append(style);
  1155.  
  1156. // $("head").append(
  1157. // '<link ' +
  1158. // 'href="//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/le-frog/jquery-ui.min.css" ' +
  1159. // 'rel="stylesheet" type="text/css">'
  1160. // );
  1161.  
  1162. }
  1163.  
  1164. //function from https://www.w3schools.com/howto/howto_js_draggable.asp
  1165. //makes elements draggable
  1166. function dragElement(elmnt) {
  1167. let pos1 = 0,
  1168. pos2 = 0,
  1169. pos3 = 0,
  1170. pos4 = 0;
  1171. if (elmnt.querySelector(".dgl2_alertTitle")) {
  1172. // if present, the header is where you move the DIV from:
  1173. elmnt.querySelector(".dgl2_alertTitle").onmousedown = dragMouseDown;
  1174. }
  1175. if (elmnt.querySelector(".dgl2_titlebar")) {
  1176. // if present, the header is where you move the DIV from:
  1177. elmnt.querySelector(".dgl2_titlebar").onmousedown = dragMouseDown;
  1178. } else {
  1179. // otherwise, move the DIV from anywhere inside the DIV:
  1180. elmnt.onmousedown = dragMouseDown;
  1181. }
  1182.  
  1183. function dragMouseDown(e) {
  1184. e = e || window.event;
  1185. if (e.target.tagName == "INPUT") return;
  1186.  
  1187. e.preventDefault();
  1188. // get the mouse cursor position at startup:
  1189. pos3 = e.clientX;
  1190. pos4 = e.clientY;
  1191. document.onmouseup = closeDragElement;
  1192. // call a function whenever the cursor moves:
  1193. document.onmousemove = elementDrag;
  1194. }
  1195.  
  1196. function elementDrag(e) {
  1197. e = e || window.event;
  1198. e.preventDefault();
  1199. // calculate the new cursor position:
  1200. pos1 = pos3 - e.clientX;
  1201. pos2 = pos4 - e.clientY;
  1202. pos3 = e.clientX;
  1203. pos4 = e.clientY;
  1204. // set the element's new position:
  1205. elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
  1206. elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  1207. }
  1208.  
  1209. function closeDragElement() {
  1210. // stop moving when mouse button is released:
  1211. document.onmouseup = null;
  1212. document.onmousemove = null;
  1213. }
  1214. }
  1215. //filling GUI DOM
  1216. function hideHiddenGroups() {
  1217. collections.forEach(col => {
  1218. if (col.showing == 0) {
  1219. col.groups.forEach(grID => {
  1220. $("button[groupID='" + grID + "']").hide();
  1221. });
  1222. }
  1223. });
  1224. }
  1225.  
  1226. function insertFilteredGroups(id) {
  1227. insertGroups();
  1228. lastFilter = id;
  1229. let allButs = $("button[type='group']");
  1230. if (collections[id].groups.length == 0) {
  1231. allButs.show();
  1232. } else {
  1233. allButs.hide();
  1234. for (let grID of collections[id].groups) {
  1235. $("button[groupID='" + grID + "']").show();
  1236. }
  1237. }
  1238. hideHiddenGroups()
  1239. }
  1240.  
  1241. function insertMacroGroups(id) {
  1242. insertGroups();
  1243. lastFilter = id;
  1244. let allButs = $("button[type='group']");
  1245. if (macros[id].data.length == 0) {
  1246. allButs.show();
  1247. } else {
  1248. allButs.hide();
  1249. for (let grID of macros[id].data) {
  1250. $("button[groupID='" + grID.groupID + "']").show();
  1251. }
  1252. }
  1253. }
  1254.  
  1255. function insertMacros() {
  1256. let coltab = 0;
  1257. let toAffect = $("div.dgl2_groupdialog").not("[dgl2]").attr("dgl2", 1);
  1258. if (toAffect.length > 0) {
  1259. coltab = $(getCollectionColTemplate());
  1260. toAffect.append(coltab);
  1261. coltab.find("div.buttons")
  1262. .append(getNewColTemplate())
  1263. .append(getExportButTemplate())
  1264. .append(getImportButTemplate());
  1265.  
  1266. coltab.click(Ev_colListClick);
  1267. } else {
  1268. coltab = $("#dgl2_CollTab");
  1269. }
  1270. let colList = coltab.find("ul");
  1271. colList.empty();
  1272. let el, descr;
  1273. for (let col of macros) {
  1274. descr = "";
  1275. for (let gr of col.data) {
  1276. descr += groupNameById(gr.groupID) + "/" + gr.folderName + "\n";
  1277. }
  1278. el = "<li colid=" + col.id + " title='" + escapeHtml(descr) + "' id='dgl2item-" + col.id + "'><span class='handle'></span><span>" + col.name + "</span>" + getEditButTemplate();
  1279. el += getRecButTemplate() + getSubButTemplate() + getDelButTemplate();
  1280. el += "</li>";
  1281. colList.append(el);
  1282. }
  1283. if (macroOrder.length > 0) {
  1284. $.each(macroOrder, function(i, position) {
  1285. let $target = colList.find('#' + position);
  1286. $target.appendTo(colList); // or prependTo for reverse
  1287. });
  1288. }
  1289.  
  1290.  
  1291. makesortable();
  1292. }
  1293.  
  1294. function insertCollections() {
  1295. let coltab = 0;
  1296. let toAffect = $("div.dgl2_groupdialog").not("[dgl2]").attr("dgl2", 1); //group submission container
  1297. if (toAffect.length > 0) {
  1298. coltab = $(getCollectionColTemplate());
  1299. toAffect.append(coltab);
  1300. coltab.find("div.buttons")
  1301. .append(getNewColTemplate())
  1302. .append(getExportButTemplate())
  1303. .append(getImportButTemplate());
  1304.  
  1305. coltab.click(Ev_colListClick);
  1306. } else {
  1307. coltab = $("#dgl2_CollTab");
  1308. }
  1309.  
  1310. let colList = coltab.find("ul");
  1311. colList.empty();
  1312. let el;
  1313. for (let col of collections) {
  1314. el = `<li colid=${col.id} active='${col.showing}' id='dgl2item-${col.id}'><span class='handle'></span><span>${col.name}</span>${getEditButTemplate()}`;
  1315. if (col.id > 0) el += getVisibleButTemplate() + getAddButTemplate() + getSubButTemplate() + getDelButTemplate();
  1316. el += "</li>";
  1317. colList.append(el);
  1318. }
  1319. if (collectionOrder.length > 0) {
  1320. $.each(collectionOrder, function(i, position) {
  1321. let $target = colList.find('#' + position);
  1322. $target.appendTo(colList); // or prependTo for reverse
  1323. });
  1324. }
  1325.  
  1326. makesortable();
  1327. }
  1328.  
  1329. function makesortable() {
  1330. //ul.sortableList
  1331. //li colid=col.id id='dgl2item-" + col.id,
  1332. //<span class='handle'>
  1333.  
  1334. let lists = document.querySelectorAll("ul.sortableList li");
  1335.  
  1336. for (let i = 0; i < lists.length; ++i) {
  1337. lists[i].draggable = "true";
  1338. addDragHandler(lists[i]);
  1339. }
  1340.  
  1341. }
  1342.  
  1343. //drag handler for drag-sortable lists
  1344. function addDragHandler(entry) {
  1345. entry.addEventListener('dragstart', function(e) {
  1346. e.dataTransfer.setData('text/plain', this.id);
  1347. e.dataTransfer.effectAllowed = 'move';
  1348. }, false);
  1349. // entry.addEventListener('dragenter', function(e){}, false)
  1350. entry.addEventListener('dragover', function(e) {
  1351. if (e.preventDefault) {
  1352. e.preventDefault();
  1353. }
  1354. this.classList.add('dgl2_drgover');
  1355. e.dataTransfer.dropEffect = 'move'; // See the section on the DataTransfer object.
  1356. return false;
  1357. }, false);
  1358. entry.addEventListener('dragleave', function(e) {
  1359. this.classList.remove('dgl2_drgover');
  1360. }, false);
  1361. entry.addEventListener('drop', function(e) {
  1362. var dropHTML = e.dataTransfer.getData('text/plain');
  1363. this.parentNode.insertBefore(document.querySelector("#" + dropHTML), this);
  1364. this.classList.remove('dgl2_drgover');
  1365.  
  1366. if (colListMode == 0) {
  1367. collectionOrder = [...this.parentNode.querySelectorAll("[draggable]")].map(el => el.id);
  1368. GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
  1369. } else if (colListMode == 1) {
  1370. macroOrder = [...this.parentNode.querySelectorAll("[draggable]")].map(el => el.id);
  1371. GM.setValue("macroOrder", JSON.stringify(macroOrder));
  1372. }
  1373. return false;
  1374. }, false);
  1375. entry.addEventListener('dragend', function(e) {
  1376. this.classList.remove('dgl2_drgover');
  1377. }, false);
  1378. }
  1379.  
  1380. function insertSearchBar() {
  1381. let bar = $(getSearchBarTemplate());
  1382. let refrBut = $(getRefreshButTemplate());
  1383.  
  1384. $("div.dgl2_titlebar").append(refrBut).append(bar).append(
  1385. $("<div class='dgl2_closeDiag'>X</div>").click(function() {
  1386. $("div.dgl2_groupdialog").hide();
  1387. })
  1388. );
  1389.  
  1390. bar.keyup(function() {
  1391. let search = $(this).val();
  1392. let allButs = $("button[type='group']").show();
  1393. allButs.filter(function() {
  1394. if (lastFilter > 1 && collections[lastFilter].groups.indexOf($(this).attr("groupID")) == -1) return 1;
  1395. if (search == "") return 0;
  1396. let words = search.split(" ");
  1397. for (let word of words) {
  1398. if ($(this).attr('groupName').toLowerCase().indexOf(word) == -1) return 1;
  1399. }
  1400. return 0;
  1401. }).hide();
  1402. });
  1403. //bar.mousedown(function(event){event.stopPropagation(); event.preventDefault();event.target.focus();});
  1404.  
  1405. refrBut.click(Ev_getGroupClick);
  1406.  
  1407. $("span.dgl2_titleText").click(function() {
  1408. if (colListMode == 0) {
  1409. insertGroups();
  1410. $("li[colid=" + lastFilter + "]").click();
  1411. } else {
  1412. macroMode = 0; //abort macro mode add/remove
  1413. insertGroups();
  1414. displayModeText();
  1415. $("div.dgl2_groupWrapper").removeClass("dgl2_addGroup").removeClass("dgl2_remGroup");
  1416. $("button.dgl2_inCollection").removeClass("dgl2_inCollection");
  1417. }
  1418. });
  1419. }
  1420.  
  1421. function pulsing(element) {
  1422. element.animate({ opacity: 0 }, 250, function() {
  1423. $(this).animate({ opacity: 1 }, 250, pulsing);
  1424. });
  1425. }
  1426.  
  1427. function insertSubFolders(subfolders) { //fill view with subfolders //subfolders not stored, request when needed
  1428. let buts = $("button.dgl2_groupButton"); //button wrapper
  1429. subfolders.sort(function(l, u) {
  1430. return l.name.toLowerCase().localeCompare(u.name.toLowerCase());
  1431. });
  1432.  
  1433. if (buts.length > 0) {
  1434. let subf;
  1435. let newBut;
  1436. if ($("#dgl2_grContext").is(":visible")) {
  1437. let cont = $("#dgl2_grContext select");
  1438. let newopt;
  1439. cont.empty();
  1440. for (subf of subfolders) {
  1441. if (subf.thumb == null) continue; //no thumb=db problem, so also no text entry
  1442. newBut = $(getSubFolderOptionTemplate(subf.name, subf.size, subf.owner.userId, subf.folderId, subf.type, subf.thumb));
  1443. cont.append(newBut);
  1444. }
  1445. if (subfolders.length == 0) {
  1446. $("#dgl2_grContext span.desc").html("This group does<br/>not allow submissions<br/>using the gallery<br/>system!");
  1447. } else {
  1448. $("#dgl2_grContext span.desc").text("Submit to a Folder");
  1449. }
  1450.  
  1451. } else {
  1452. let par = buts.first().parent();
  1453. par.empty();
  1454. for (subf of subfolders) {
  1455. if (subf.thumb == null) continue;
  1456. newBut = $(getSubFolderTemplate(subf.name, subf.size, subf.owner.userId, subf.folderId, subf.type, subf.thumb));
  1457. par.append(newBut);
  1458. }
  1459. if (subfolders.length == 0) {
  1460. par.append("This group does not allow submissions using the gallery system!");
  1461. }
  1462. par.not("[dgl2]").attr("dgl2", 1).click(Ev_groupClick);
  1463. }
  1464. }
  1465. }
  1466.  
  1467. function displayModeText() {
  1468. let titl = "Add to Group";
  1469. let descr = "Add this deviation to one of your groups";
  1470. if (targetName != "") {
  1471. if (colListMode == 0) { //colection
  1472. if (colMode == 0) { //show
  1473. titl = "< Add to " + targetName;
  1474. descr = "Add this deviation to Group " + targetName;
  1475. } else if (colMode == 1) { //add
  1476. titl = "< Add to " + targetName;
  1477. descr = "Add groups to Collection " + targetName;
  1478. } else if (colMode == 2) { //remove
  1479. titl = "< Remove from " + targetName;
  1480. descr = "Remove groups from Collection " + targetName;
  1481. }
  1482. } else { //macros
  1483. if (macroMode == 0) {} else if (macroMode == 1) {
  1484. titl = "< Add to Macro";
  1485. descr = "macro " + targetName + " is recording.";
  1486. } else if (macroMode == 3) { //remove
  1487. titl = "< Remove from Macro";
  1488. descr = "remove groups from " + targetName;
  1489. }
  1490. }
  1491. }
  1492. $("span.dgl2_titleText").text(titl);
  1493. $("span.dgl2_descr").text(descr);
  1494. }
  1495.  
  1496. function insertGroups() { //fill view with groups //groups are stored
  1497. lastFilter = 0;
  1498. groups.sort(function(l, u) {
  1499. return l.username.toLowerCase().localeCompare(u.username.toLowerCase());
  1500. });
  1501.  
  1502. displayModeText();
  1503.  
  1504. let par = $("div.dgl2_groupWrapper"); //group list wrapper
  1505. let newBut;
  1506. let hasEmptyId = false;
  1507. par.empty();
  1508. for (let gr of groups) {
  1509. newBut = $(getGroupTemplate(gr.username, gr.usericon, gr.userId));
  1510. newBut.contextmenu(Ev_groupContext);
  1511. if (typeof gr.userId == "undefined") hasEmptyId = true;
  1512. if (listedGroups.includes(parseInt(gr.userId))) {
  1513. newBut.addClass("dgl2_inGroup"); //button div inside wrapper; used in template
  1514. }
  1515. if (par.find("div[groupName='" + gr.username + "']").length == 0) {
  1516. par.append(newBut);
  1517. }
  1518. }
  1519. par.not("[dgl2]").attr("dgl2", 1).click(Ev_groupClick);
  1520. hideHiddenGroups();
  1521.  
  1522. if (hasEmptyId && !notScanThisInstance) {
  1523. requestAllGroupIDs();
  1524. }
  1525. }
  1526.  
  1527. function uniqBy(a, key) {
  1528. let seen = new Set();
  1529. return a.filter(item => {
  1530. let k = key(item);
  1531. return seen.has(k) ? false : seen.add(k);
  1532. });
  1533. }
  1534. let ngrpCnt = 0;
  1535. let fetchItMsg = ""
  1536. let ngrpleft = 0;
  1537.  
  1538. function delayIteratefetchGrId(groups, index, delay) {
  1539. if (index < groups.length) {
  1540. grabIDfromPage(groups[index].username).catch(() => {
  1541. if (fetchItMsg != "") fetchItMsg += ", ";
  1542. fetchItMsg += groups[index].username;
  1543. groups[index].userId = 0;
  1544. groups[index].useridUuid = 0;
  1545. GM.setValue("groups", JSON.stringify(groups));
  1546. }).finally(() => {
  1547. setTimeout(function() { delayIteratefetchGrId(groups, index + 1, delay); }, delay);
  1548. });
  1549. } else {
  1550. if (fetchItMsg != "") myMsgBox("failed fetching IDs for groups: " + fetchItMsg + "<br/>They might be deleted.")
  1551. $("#dgl2_refresh rect").css("fill", "");
  1552. $("#dgl2_refresh").css("cursor", "pointer");
  1553. //$("span.dgl2_descr").text("Add this deviation to a group folder");
  1554. displayModeText();
  1555. }
  1556. }
  1557.  
  1558. function requestAllGroupIDs() {
  1559. let nullgr = groups.filter(gr => typeof gr.userId == "undefined");
  1560. let remtim = nullgr.length * 1.1;
  1561. let remtex = ""
  1562. ngrpleft = nullgr.length;
  1563. if (remtim < 60) { remtex = "seconds" } else if (remtim >= 60 && remtim < 60 * 60) {
  1564. remtex = "minutes";
  1565. remtim /= 60.0;
  1566. } else if (remtim >= 60 * 60) {
  1567. remtex = "hours";
  1568. remtim /= 60.0 * 60.0;
  1569. }
  1570. myMsgBox(`${ngrpleft} group-IDs are not fetched.<br/>Fetching all group IDs now might take a while (est. ${Math.round((remtim + Number.EPSILON) * 100) / 100} ${remtex}).<br/>If you press "cancel" IDs are fetched dynamically when group-buttons are clicked.<br/>Collections, macros and list of already submitted deviations can not display groups without ID.<br/><br/>Fetch all remaining group-IDs now?`, "Fetch Group-IDs", 1).then((choice) => {
  1571. if (choice) {
  1572. ngrpCnt = 0;
  1573. fetchItMsg = "";
  1574. delayIteratefetchGrId(nullgr, 0, 250);
  1575. }
  1576. });
  1577. notScanThisInstance = true;
  1578. }
  1579.  
  1580. function insertHTML() {
  1581.  
  1582. if ($("div.dgl2_groupdialog").length > 0) return;
  1583.  
  1584. addCss();
  1585. $("<div class='dgl2_groupdialog'><div class='dgl2_titlebar'></div><div class='dgl2_groupCol'><div class='dgl2_groupWrapper'></div></div></div>").appendTo($("body"));
  1586. $("div.dgl2_titlebar").html(getTitleBarTemplate());
  1587. insertSearchBar();
  1588.  
  1589. let devInd = location.href.indexOf("?");
  1590. if (devInd == -1) {
  1591. devID = location.href.match(/(\d+)\D*$/)[1];
  1592. } else {
  1593. devID = location.href.substring(0, devInd).match(/(\d+)\D*$/)[1];
  1594. }
  1595.  
  1596. userName = $("a.user-link").attr("data-username"); // "dediggefedde";
  1597.  
  1598. let proms = [
  1599. GM.getValue("collections", ""),
  1600. GM.getValue("collectionOrder", ""),
  1601. GM.getValue("macros", ""),
  1602. GM.getValue("macroOrder", "")
  1603. ];
  1604.  
  1605. Promise.all(proms).then(([cols, colOrder, macs, macOrder]) => {
  1606. if (cols != "") {
  1607. collections = JSON.parse(cols);
  1608. }
  1609. collections.forEach(el => { if (!el.hasOwnProperty("showing")) { el.showing = 1; }; }); //backward-compatibility for collection-showing attribute before v3.0
  1610.  
  1611. if (colOrder != "") {
  1612. collectionOrder = JSON.parse(colOrder);
  1613. }
  1614.  
  1615. if (macs != "") {
  1616. macros = JSON.parse(macs);
  1617. macros.forEach(function(el) { el.data = uniqBy(el.data, JSON.stringify); }); //unique macros
  1618. }
  1619.  
  1620. if (macOrder != "") {
  1621. macroOrder = JSON.parse(macOrder);
  1622. }
  1623. insertCollections();
  1624. }).catch(function(e) {
  1625. errorHndl(err(errtyps.Unknown_Error, "Error Loading Database", e));
  1626. return insertCollections();
  1627. });
  1628.  
  1629. fillListedGroups(devID).then(function() {
  1630. return GM.getValue("groups", "");
  1631. }).then(function(grps) {
  1632. if (grps == "") {
  1633. Ev_getGroupClick();
  1634. } else {
  1635. groups = JSON.parse(grps);
  1636. insertGroups();
  1637. }
  1638. }).catch(function(e) {
  1639. errorHndl(err(errtyps.Unknown_Error, "fillListedGroups error", e));
  1640. });
  1641.  
  1642. }
  1643.  
  1644. //outdated, now I'm using my own template
  1645. //in case classNames (hopefully) change sometime. Also necessary since browsing=other class names than refreshing site.
  1646. // function classNormalizer() { //_2n5r3 _3Isw- _3yw1l _1F3vX _34hvg
  1647. // // if($("div.dgl2_groupdialog").length>0)return;
  1648. // $("<div class='dgl2_groupdialog'><div class='dgl2_groupCol'><div class='dgl2_titlebar'></div><div class='dgl2_groupWrapper'></div></div></div>").appendTo($("body"));
  1649.  
  1650. // // $("div.dgl2_groupdialog div._13bQf").hide();
  1651. // // $("div.dgl2_groupdialog button._2-StF").hide();
  1652.  
  1653. // // $("span.u1km9, span._3M1Kg").addClass("dgl2_descr"); //refr u1km9 browse _3M1Kg //top description //added by template
  1654. // // $("button._3PBqc, button._3UhBt").addClass("dgl2_groupButton"); //refr _3UhBt _3PBqc, browse _3PBqc //button to add to group //added by template
  1655. // // $("div._1cFkE, div._3Lbo4").addClass("dgl2_groupButDiv"); //refr _1cFkE _11Rtb, browse _3Lbo4 _3EbZ8 //div inside button //outdated
  1656. // // $("div.A9uFL, div.JcCFR").addClass("dgl2_groupWrapper"); //refr A9uFL _2IeoY, browse JcCFR _159Ra //div parent of group buttons
  1657. // // $("div._1HNK0, div._1ezbx").addClass("dgl2_titlebar"); //refr _1HNK0, browse _1ezbx //title bar
  1658. // // $("div._2n5r3, div._2qgZz").addClass("dgl2_groupdialog"); //refr _2n5r3, browse _2qgZz // dialog(opening div) for group submission
  1659. // // $("div._2UK-E, div._2kU4L").addClass("dgl2_groupCol"); //refr _2UK, browse _2kU4L//left column (containing groups)
  1660.  
  1661.  
  1662. // // if($("div.dgl2_titlebar").length==0 && $("div.dgl2_groupCol:not([nogroups])").length>0){
  1663. // // $("div.dgl2_groupCol").attr("nogroups",1).html('<div class="_2UK-E"><div class="_38jjU"><div class="_1HNK0">Add to Group<span class="_3g6BC _1Re00 _3YKkm _1d2qE"><svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill-rule="evenodd" d="M8.84210526,13 L8.84210526,21.1578947 L2,21.1578947 L2,9.57894737 L12,3 L22,9.57894737 L22,21.1578947 L15.1578947,21.1578947 L15.1578947,13 L8.84210526,13 Z"></path></svg></span><span class="u1km9">Add this deviation to one of your groups</span></div><div class="_2ZROG"><div class="A9uFL _2IeoY"><button class="_3UhBt"><div class="_1cFkE _11Rtb"><div class="_3_Bqs"><div class="pqF_I"><span class="_1X7Yj _14i4i"><img alt="NatureNation\'s avatar" data-hook="user_avatar" class="_19ZLc" src="https://a.deviantart.net/avatars-big/n/a/naturenation.jpg?2" loading="lazy"></span></div><div class="_3po1a"><span class="_3g6BC _2PY8v _3YKkm _1sm3t"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><path d="M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z"></path></svg></span></div></div><div class="_3j_sQ">NatureNation</div></div></button></div></div></div></div>');
  1664. // // classNormalizer();
  1665. // // }
  1666.  
  1667. // }
  1668.  
  1669. function getLowestFree(collection) {
  1670. collection.sort(function(a, b) {
  1671. return a.id - b.id;
  1672. }); //changing order does not matter thanks to index/order array
  1673. let lowest = -1;
  1674. let i;
  1675. for (i = 0; i < collection.length; ++i) {
  1676. if (collection[i].id != i) {
  1677. lowest = i;
  1678. break;
  1679. }
  1680. }
  1681. if (lowest == -1 && collection.length > 0) {
  1682. lowest = collection[collection.length - 1].id + 1;
  1683. } else if (collection.length == 0) lowest = 0;
  1684. return lowest;
  1685.  
  1686. }
  1687.  
  1688. function escapeHtml(string) {
  1689. return String(string).replace(/[&<>"'`=\/]/g, function(s) {
  1690. return entityMap[s];
  1691. });
  1692. }
  1693.  
  1694. function download(data, filename) {
  1695. let file = new Blob([data], {
  1696. type: "application/json"
  1697. });
  1698. let a = document.createElement("a"),
  1699. url = URL.createObjectURL(file);
  1700. a.href = url;
  1701. a.download = filename;
  1702. document.body.appendChild(a);
  1703. a.click();
  1704. setTimeout(function() {
  1705. document.body.removeChild(a);
  1706. window.URL.revokeObjectURL(url);
  1707. }, 0);
  1708. }
  1709.  
  1710. function upload() {
  1711. return new Promise(function(resolve, reject) {
  1712. let inp = $('<input type="file" id="input">').appendTo("body").click()
  1713. inp.change(function() {
  1714. let reader = new FileReader();
  1715. reader.onload = function(evt) {
  1716. resolve(evt.target.result);
  1717. };
  1718. reader.readAsBinaryString($(this).prop("files")[0]);
  1719. });
  1720. return "";
  1721. });
  1722. }
  1723.  
  1724. function colIndexById(id) {
  1725. for (let i = 0; i < collections.length; ++i) {
  1726. if (collections[i].id == id) return i;
  1727. }
  1728. return -1;
  1729. }
  1730.  
  1731. function makIndexById(id) {
  1732. for (let i = 0; i < macros.length; ++i) {
  1733. if (macros[i].id == id) return i;
  1734. }
  1735. return -1;
  1736. }
  1737.  
  1738. //shows an alert box with text
  1739. //mode 0:alert, 1:confirm, 2 prompt
  1740. function myMsgBox(tex, titl = "Notification", mode = 0, defText = "") {
  1741. let dfd = new $.Deferred();
  1742.  
  1743. let box = $("#dgl2_alertBox");
  1744. if (box.length == 0) {
  1745. box = $("<div id='dgl2_alertBox'></div>").appendTo("body");
  1746. }
  1747.  
  1748. box.html("<div class='dgl2_alertTitle'></div><div class='dgl2_alertText'></div><div class='dgl2_alertButtons'></div>");
  1749. box.find("div.dgl2_alertText").html(tex);
  1750. box.find("div.dgl2_alertTitle").html(titl);
  1751.  
  1752. if (mode == 0) {
  1753. $("<div class='dgl2_alertOKBut'>OK</div>").click(function() {
  1754. dfd.resolve(true);
  1755. $("#dgl2_alertBox").hide();
  1756. }).appendTo(box.find("div.dgl2_alertButtons"));
  1757. } else if (mode == 1) {
  1758. $("<div class='dgl2_alertOKBut'>OK</div>").click(function() {
  1759. dfd.resolve(true);
  1760. $("#dgl2_alertBox").hide();
  1761. }).appendTo(box.find("div.dgl2_alertButtons"));
  1762. $("<div class='dgl2_alertCancelBut'>Cancel</div>").click(function() {
  1763. dfd.resolve(false);
  1764. $("#dgl2_alertBox").hide();
  1765. }).appendTo(box.find("div.dgl2_alertButtons"));
  1766. } else if (mode == 2) {
  1767. $("<input type='text' id='dgl2_promptVal' value='" + defText + "' class='text ui-widget-content ui-corner-all'>").appendTo(box.find("div.dgl2_alertText"));
  1768. $("<div class='dgl2_alertOKBut'>OK</div>").click(function() {
  1769. dfd.resolve($("#dgl2_promptVal").val());
  1770. $("#dgl2_alertBox").hide();
  1771. }).appendTo(box.find("div.dgl2_alertButtons"));
  1772. $("<div class='dgl2_alertCancelBut'>Cancel</div>").click(function() {
  1773. dfd.resolve(false);
  1774. $("#dgl2_alertBox").hide();
  1775. }).appendTo(box.find("div.dgl2_alertButtons"));
  1776. }
  1777. box.show();
  1778.  
  1779. dragElement(box[0]);
  1780.  
  1781. return dfd.promise();
  1782. }
  1783.  
  1784. function showPopup(event) {
  1785. event.preventDefault();
  1786. event.stopPropagation();
  1787. insertHTML(); //does nothing if already inserted
  1788.  
  1789. let el = $("div.dgl2_groupdialog");
  1790.  
  1791. el.show();
  1792. dragElement(el[0]);
  1793. el.attr("tabindex", "0");
  1794. el.keydown(function(event) {
  1795. if (event.target.tagName != "INPUT") {
  1796. highlightLetter(String.fromCharCode(event.which));
  1797. }
  1798. });
  1799. $("div.dgl2_groupdialog, div.dgl2_groupdialog>div").click(function(event) {
  1800. event.stopPropagation();
  1801. if (event.target.tagName != "INPUT")
  1802. $("div.dgl2_groupdialog").focus();
  1803. });
  1804.  
  1805. // $("div.dgl2_groupdialog").dialog({
  1806. // width: "80%",
  1807. // height: (window.innerHeight * 0.8),
  1808. // draggable: false,
  1809. // resizable: false,
  1810. // dialogClass: 'groupPopup',
  1811. // create: function(event) {
  1812. // $(event.target).parent().css({
  1813. // 'position': 'fixed',
  1814. // "left": '10%',
  1815. // "top": '10%'
  1816. // });
  1817. // $(event.target).parent().find("div.ui-dialog-titlebar").prepend($("div.dgl2_titlebar"));
  1818. // $(event.target).parent().find("span.ui-dialog-title").hide();
  1819. // }
  1820. // });
  1821. // $("div.groupPopup").keydown(function(event) {
  1822. // if (event.target.tagName != "INPUT" && !$("#dgl2_grContext").is(":visible")) {
  1823. // highlightLetter(String.fromCharCode(event.which));
  1824. // }
  1825. // });
  1826. }
  1827.  
  1828. function groupNameById(id) {
  1829. for (let g of groups) {
  1830. if (g.userId == id) return g.username;
  1831. }
  1832. return "";
  1833. }
  1834. //bind script to buttons. dynamic browsing compatible
  1835. function addListener() {
  1836. let els = $('*[datahook="group_counter"]:not([dgl2=1]),*[data-hook="group_counter"]:not([dgl2=1])');
  1837. els.attr("dgl2", 1).find("svg").html(
  1838. '<path d="M18.63 17l1.89 5h2l-2.53-7h-6.67l.64 2zM4.04 15l-2.52 7h2l1.88-5h4.23l1.89 5h2l-2.53-7zM7.52 4.33c1.9304.011 3.4873 1.5829 3.48 3.5133-.0074 1.9303-1.5762 3.4903-3.5066 3.4866C5.563 11.3263 4 9.7604 4 7.83c0-1.933 1.567-3.5 3.5-3.5h.02zm-.02-2C4.4624 2.33 2 4.7924 2 7.83s2.4624 5.5 5.5 5.5 5.5-2.4624 5.5-5.5-2.4624-5.5-5.5-5.5zM13 3.37a5.59 5.59 0 0 1 1.5 1.45 3.41 3.41 0 0 1 1.5-.35c1.933 0 3.5 1.567 3.5 3.5s-1.567 3.5-3.5 3.5a3.41 3.41 0 0 1-1.5-.35 5.63 5.63 0 0 1-1.5 1.46c1.968 1.2806 4.532 1.1706 6.3831-.2738 1.8511-1.4445 2.5812-3.9047 1.8175-6.125C20.437 3.9608 18.348 2.4702 16 2.47a5.4102 5.4102 0 0 0-3 .9z"/>' +
  1839. '<path stroke="#0A0" stroke-width="4" stroke-opacity="0.8" d="M12 18H24M18 12V24"/>'
  1840. );
  1841. els.parent().parent().click(showPopup);
  1842. }
  1843. $(document).ready(function() {
  1844. $(document).mousedown(function(event) {
  1845. if ($(event.target).closest("#dgl2_grContext").length == 0) {
  1846. $("#dgl2_grContext").hide().find("select").empty();
  1847. }
  1848. });
  1849. });
  1850.  
  1851. setInterval(addListener, 1000);
  1852.  
  1853. })();
  1854.  
  1855. /* resources:
  1856. group template
  1857. <button class="_3PBqc"> <!--_3UhBt-->
  1858. <div class="_3Lbo4 _3EbZ8">
  1859. <div class="_1lUlZ">
  1860. <div class="_3tZow">
  1861. <span class="_23Ekg _3q2EJ">
  1862. <img data-hook="user_avatar" alt="iterators's avatar" class="dIDzJ" src="https://a.deviantart.net/avatars-big/i/t/iterators.png?3">
  1863. </span>
  1864. </div>
  1865. <div class="_3_Nxk">
  1866. <span class="_3HH04 _3y1P2 _3kEx1 _32s2-">
  1867. <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8">
  1868. <path d="M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z"></path>
  1869. </svg>
  1870. </span>
  1871. </div>
  1872. </div>
  1873. <div class="_22Jp8">iterators</div>
  1874. </div>
  1875. </button>
  1876.  
  1877. group_request_response_structure
  1878. userId 14471598
  1879. useridUuid 55b0c0d3-fed7-45ff-8951-0d8554eb01b1
  1880. username deviantARTSupporters
  1881. usericon https://a.deviantart.net/avatars-big/d/e/deviantartsupporters.gif?12
  1882. type group
  1883. isNewDeviant false
  1884.  
  1885. subfolder request response structure
  1886. commentCount: 0
  1887. description: ""
  1888. folderId: 13361368
  1889. name: "Featured"
  1890. owner: Object { userId: 11209451, useridUuid: "294e75e1-94ec-4009-883a-b13eff743164", username: "iterators", … }
  1891. parentId: null
  1892. size: 5
  1893. thumb:
  1894. author: Object { userId: 7514199, useridUuid: "56613871-de84-49fc-b1c1-f9fd0f796461", username: "Championx91", … }
  1895. blockReason: null
  1896. deviationId: 710763111
  1897. files: Array(9) [ {…}, {…}, {…}, … ]
  1898. 0: Object { type: "150", src: "https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/56613871-de84-49fc-b1c1-f9fd0f796461/dbr64br-58702ee6-5e4e-4bab-b2ea-c5ca2563bb7f.png/v1/fit/w_150,h_150,strp/suggestions_unwatch_fav_by_championx91_dbr64br-150.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9ODIxIiwicGF0aCI6IlwvZlwvNTY2MTM4NzEtZGU4NC00OWZjLWIxYzEtZjlmZDBmNzk2NDYxXC9kYnI2NGJyLTU4NzAyZWU2LTVlNGUtNGJhYi1iMmVhLWM1Y2EyNTYzYmI3Zi5wbmciLCJ3aWR0aCI6Ijw9NjQxIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.vyFqJs1s5cyralkRXS31gJbHrwFsBm72c2q9Hb0uJDs", height: 150, … }
  1899. isAntisocial: true
  1900. isBlocked: false
  1901. isCommentable: true
  1902. isDeleted: false
  1903. isDownloadable: false
  1904. isFavourited: false
  1905. isJournal: false
  1906. isMature: false
  1907. isPublished: true
  1908. isPurchasable: false
  1909. isShareable: false
  1910. isTextEditable: false
  1911. legacyTextEditUrl: null
  1912. printId: null
  1913. publishedTime: "2017-10-20T10:39:40-0700"
  1914. stats: Object { comments: 17, favourites: 19 }
  1915. title: "Suggestions unwatch fav"
  1916. typeId: 1
  1917. url: "https://www.deviantart.com/championx91/art/Suggestions-unwatch-fav-710763111"
  1918. type: "gallery"
  1919.  
  1920. required:
  1921. groupid = groups request
  1922. folderid = subfolders/favourites requests
  1923. deviationid = url \d+$
  1924. csrf_token = $("input[name=validate_token]").value; //deviation page
  1925. moduleID = window.document.body.innerHTML.match(/{\\\"id\\\":(\d+),\\\"type\\\":\\\"group_list_members/i) //https://www.deviantart.com/dediggefedde/about
  1926.  
  1927. groups GET
  1928. https://www.deviantart.com/_napi/da-user-profile/api/module/groups/members?username=Dediggefedde&moduleid=1725444339&offset=30&limit=24
  1929. Limit max 60
  1930. finished if hasMore==false
  1931. needs moduleID, username
  1932.  
  1933. subfolders GET
  1934. https://www.deviantart.com/_napi/shared_api/deviation/group_folders?groupid=13042771&type=gallery
  1935. needs groupID
  1936.  
  1937. favourites GET
  1938. https://www.deviantart.com/_napi/shared_api/deviation/group_folders?groupid=28209298&type=collection
  1939. needs groupID
  1940.  
  1941. add to subfolder POST
  1942. {"groupid":11209451,"type":"gallery","folderid":23881334,"deviationid":501848540,"csrf_token":"wPPvYvd4br1JpNjT.pyuz1q.lVPn5jT3ohUO8uD0QLruZb-V9d5NDb1FOyl7OcHe7w"}
  1943. https://www.deviantart.com/_napi/shared_api/deviation/group_add
  1944. needs:
  1945. csrf_token THjPWGH1SRH_-epZ.py75wu.VTYjdFg4YApQBqsZBS5ISBHG1W4aWv-oTkk5WDA4t-w
  1946. deviationid 298766207
  1947. folderid 23881334
  1948. groupid 11209451
  1949. type gallery
  1950. */