dev_group_list2

Better Submit-to-group dialog

当前为 2024-06-07 提交的版本,查看 最新版本

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