dev_group_list2

Better Submit-to-group dialog

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