dev_group_list2

Better Submit-to-group dialog

当前为 2022-09-19 提交的版本,查看 最新版本

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