[Trello] Advanced Comments

It makes comment groups for repliying comments on Trello.

  1. // ==UserScript==
  2. // @name [Trello] Advanced Comments
  3. // @name:tr [Trello] Gelişmiş Yorumlar
  4. // @description It makes comment groups for repliying comments on Trello.
  5. // @description:tr Trello'da birbirine cevap olarak yazılmış yorumları gruplar.
  6. // @author nht.ctn
  7. // @namespace https://github.com/nhtctn
  8. // @license MIT
  9. // @version 1.0
  10.  
  11. // @icon data:image/png;base64,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
  12.  
  13. // @match https://trello.com/*
  14. // @grant GM_addStyle
  15. // @run-at document-end
  16.  
  17. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
  18. // ==/UserScript==
  19. /* global $ */
  20. /*jshint esversion: 6 */
  21.  
  22. (function() {
  23. 'use strict';
  24.  
  25. // Bu betiğe özgü fonksiyonlar
  26. const c = (x) => console.log(x);
  27. const link = (x) => $(x).closest('.phenom').find('.phenom-date > a.date, .phenom-meta > a.date').attr('href');
  28. const date = (x) => new Date( $(x).closest('.phenom').find('.phenom-date > a.date, .phenom-meta > a.date').attr('dt') ).getTime();
  29. const type = (x) => ($(x).closest('.phenom').is('.mod-comment-type')) ? "com" : "attach";
  30. const getLang = () => $('html').attr("lang");
  31. const getItem = (x) => {
  32. if ($.type(x) == "string") return $('.card-detail-window a.date[href*="' + x + '"]').closest('.phenom');
  33. else if ($.type(x) == "object") return $(x).closest('.phenom');
  34. else return c( 'id fonksiyonunda belirsiz tip: ' + $.type(x) );
  35. };
  36. const id = (x) => {
  37. if ($.type(x) == "string") return x.replace(/.+\#((comment|action)-.+)/, "$1");
  38. else if ($.type(x) == "object") return $(x).closest('.phenom').find('a.date').attr('href').replace(/.+\#((comment|action)-.+)/, "$1");
  39. else return c( 'id fonksiyonunda belirsiz tip: ' + $.type(x) );
  40. };
  41. const key = (x) => {
  42. if ($.type(x) == "string") return x.replace(/(.+)?(comment|action|group|replies|)-(.+)/, "$3");
  43. else if ($.type(x) == "object") return $(x).closest('.phenom').find('a.date').attr('href').replace(/.+\#(comment|action)-(.+)/, "$2");
  44. else return c( 'key fonksiyonunda belirsiz tip: ' + $.type(x) );
  45. };
  46.  
  47. let removeSlctr = `p > a:first-child[href*="trello.com"][href*="#comment-"], p > a:first-child[href*="trello.com"][href*="#action-"],
  48. p > span.atMention:nth-child(2), span.atMention:nth-child(3),
  49. p > br:nth-child(2), p > br:nth-child(3), p > br:nth-child(4),
  50. .phenom-reactions .js-attach-link`;
  51.  
  52. // Yorum grubu oluştur.
  53. function groupDiv(target, comfor, link) {
  54. getItem(target).before('<div id="group-' + key(comfor) + '"><div id="replies-' + key(comfor) + '"></div></div>');
  55. getItem(comfor).prependTo( '.card-detail-window #group-' + key(comfor) );
  56. getItem(link).prependTo( '.card-detail-window #replies-' + key(comfor) );
  57. let dummyCom = $('.card-detail-window .window-module > .new-comment.js-new-comment').clone().removeClass("js-new-comment is-focused is-show-controls").addClass("new-group-comment");
  58. dummyCom.find('.js-new-comment-input').removeClass('js-new-comment-input').css("height", "20.0348px").val("");
  59. dummyCom.appendTo('.card-detail-window #group-' + key(comfor)).wrap('<div class="group-comment-div"></div>');
  60. $('.card-detail-window #group-' + key(comfor) + ' .new-group-comment').click(function(){ textAreaShifter(this); });
  61. }
  62.  
  63. // Bir kart ilk açıldığındaki işlemler
  64. let cardUrl = '';
  65. waitForKeyElements('.card-detail-window p.u-bottom a.show-more.js-show-all-actions', cardWindow);
  66. function cardWindow () {
  67. things = [];
  68. cardUrl = window.location.href.replace(/(.+trello\.com\/c\/.+\/\d+).+/, "$1");
  69. let intCount = 0;
  70. // Yeterince bekleyip tüm etkinlikleri göstere tıkla
  71. let myTimeOut = setInterval(function () {
  72. let moreButton = $('.card-detail-window p.u-bottom a.show-more.js-show-all-actions').removeAttr("href").css("cursor", "pointer");
  73. moreButton[0].click();
  74. if ( $('.card-detail-window .js-show-all-actions').is(":hidden")) clearInterval(myTimeOut);
  75. else if (intCount++ > 40) clearInterval(myTimeOut);
  76. }, 500);
  77.  
  78. GM_addStyle(`
  79. .window-overlay > .window {width: 868px;}
  80. .card-detail-window > .window-main-col {width: 652px;}
  81. .card-detail-window > .window-sidebar {width: calc(100% - 700px);}
  82. .mod-card-back > [id^="group-"] {padding: 8px 0 2px 0;}
  83. [id^="group-"] [id^="group-"] {padding: 0 0 6px 0;}
  84. [id^="group-"] > [id^="replies-"] {padding-left: 6%;}
  85. [id^="group-"] .mod-comment-type:not(.mod-highlighted) {padding: 0 0 6px 0}
  86. [id^="group-"] .mod-comment-type.mod-highlighted {padding: 0 0 6px 48px}
  87. .group-comment-div {padding-left: 6%; padding-top: 4px}
  88. .artificialReply {text-decoration: none;}
  89. .artificialReply:hover {text-decoration: underline;}
  90. `);
  91. }
  92.  
  93. // Yeni bir etkinlik saptandığındaki işlemler
  94. let things = [];
  95. waitForKeyElements('.card-detail-window .mod-card-back .phenom a.date', function(newElement){ addUnseen(newElement); });
  96. function addUnseen(el) {
  97. // Yeniyse kaydet.
  98. let isUnseen = true;
  99. for (let x = 0; x < things.length; x++) {
  100. isUnseen = (things[x].link == link(el)) ? false : true;
  101. if(!isUnseen) break;
  102. }
  103. if (isUnseen) {
  104. let newThings = [];
  105. $('.card-detail-window .mod-card-back .phenom a.date').each(function(){
  106. newThings.push({
  107. link: link( $(this) ),
  108. id: id( $(this) ),
  109. date: date( $(this) ),
  110. type: type( $(this) ),
  111. comFor: findReply( $(this) ),
  112. });
  113. things = uniqArray(newThings);
  114. arraySorter(things, "object", "date");
  115. });
  116. //console.log(things);
  117. newThing();
  118. }
  119. }
  120.  
  121. function findReply(this_) { // Buraya başka kartın yorum linki olmasın diye koşul koyulacak.
  122. if (type(this_) == "com") {
  123. let firstNd = $(this_.closest('.mod-comment-type').find('.current-comment p')[0].firstChild);
  124. if (firstNd.is("a") && firstNd.attr("href").search(/trello\.com\/.+\#(comment|action)-/) > 0 ) return id( firstNd.attr("href") );
  125. else return null;
  126. }
  127. else return null;
  128. }
  129.  
  130. // Bir tepkiye ilk raslandığındaki işlemler
  131. waitForKeyElements('.card-detail-window .mod-comment-type > .phenom-reactions .reactions-add-icon', function(elem) {moveReactions(elem);});
  132. function moveReactions(this_) {
  133. // Tepkilerin yerini değiştir
  134. let react = this_.closest('.phenom-reactions');
  135. let com = this_.closest('.mod-comment-type');
  136. com.attr("id", id(this_));
  137. com.find('.phenom-meta').css("display", "contents");
  138. if (com.find('.rightOfDate').length <= 0) {
  139. com.find('.phenom-date').after('<div class="rightOfDate" style="display: contents;"></div>');
  140. react.css("display", "inline-flex").css("float", "right").css("margin-right", "8px").appendTo('#' + id(this_) + ' .rightOfDate');
  141. }
  142. // Yanıtla butonuyla link ekle
  143. let cardUrl = window.location.href.replace(/(.+trello\.com\/c\/.+\/\d+).+/, "$1");
  144. react.find('.js-reply-to-action, .js-reply-to-all-action').click(function(){
  145. $('.js-new-comment textarea.comment-box-input').val( cardUrl + '#' + id(this_) );
  146. });
  147. // Silme işleminde yeniden sırala
  148. react.find(".js-confirm-delete-action").click(function(){
  149. waitForKeyElements('.js-confirm.nch-button--danger', function(){
  150. $('.js-confirm.nch-button--danger').one("click", function(){
  151. setTimeout( function() {newThing();}, 200);
  152. });
  153. }, true);
  154. });
  155. }
  156.  
  157. // Başkası yorum silip sayfa bozulduysa diye arada kontrol et.
  158. setInterval(function(){
  159. if($('[id^="group-"]'). length > 0 && $('[id^="group-"] > [id^="replies-"]:first-child'). length > 0) {
  160. newThing();
  161. }
  162. }, 5000);
  163.  
  164. function putReply(el) {
  165. // Actionlara yanıtla butonu ekle.
  166. if (el.type == "attach" && $('#' + el.id + ' .artificialReply').length <= 0) {
  167. let reply = ($('html').attr("lang") == "tr") ? "Yanıtla" : "Reply";
  168. $('#' + el.id + ' a.date').after(' - <a class="js-reply-to-all-action artificialReply" href="#">' + reply + '</a>');
  169. $('#' + el.id + ' .artificialReply').click(function(){
  170. let mentions = replyMentions(getItem(this));
  171. let val = (cardUrl + '#' + id(this) + ' ' + mentions + ' ');
  172. $('.js-new-comment textarea.comment-box-input').val(val);
  173. $('.js-new-comment textarea.comment-box-input').focus();
  174. });
  175. }
  176. }
  177.  
  178. // Yeni bir eylem saptandığındaki işlemler
  179. function newThing() {
  180. if ($('[id^="group-"]').length > 0) clearResiduals();
  181. for (let x = 0; x < things.length; x++) {
  182. // Her bir eyleme id ekle.
  183. let th = things[x];
  184. if (getItem(th.id).attr("id") == undefined) getItem(th.id).attr("id", th.id);
  185. // Eylem yorumsa
  186. let isComForExist = (th.comFor != null && getItem(th.comFor).length > 0);
  187. if (isComForExist) {
  188. let isComForNonThreaded = getItem(th.comFor).closest('[id^="group-"]').length <= 0;
  189. let isComForRegular = getItem(th.comFor).is('[id^="group-"] .mod-comment-type:first-child, [id^="replies-"] .mod-comment-type:last-child');
  190. if (isComForNonThreaded) {
  191. // Yorumun atası herhangi bir grupta değil. Yeni grup aç.
  192. groupDiv(th.id, th.comFor, th.id);
  193. }
  194. else {
  195. // Yorumun atası bir grupta. Toptan grubu yorumun olduğu yere taşı.
  196. let repliesId = getItem(th.comFor).closest('[id^="group-"]').find('[id^="replies-"]').attr("id");
  197. if (repliesId != getItem(th.id).closest('[id^="group-"]').find('[id^="replies-"]').attr("id")) { //Birbirini hedef gösteren yorumlarda buga girmesin diye.
  198. $('#' + repliesId).closest('.mod-card-back > [id^="group-"]').insertBefore(getItem(th.id));
  199. if (isComForRegular) {
  200. getItem(th.id).appendTo( '#' + repliesId ); // Ata grubun atası ya son mesajıysa yorumu grubun altına taşı.
  201. }
  202. else {
  203. groupDiv(th.comFor, th.comFor, th.id); // Ata grubun ortasındaysa yeni alt grup aç.
  204. }
  205. }
  206. }
  207. // Cevap olarak yazılmış bir yorum ise linki, alıntıları, satır atlamayı, bağlantı olarak ekle seçeneğini gizle.
  208. getItem(th.id).find(removeSlctr).hide();
  209. // c(comItem(th.id).find(removeSlctr)[0].getAttribute("style"));
  210. let attachLinks = getItem(th.id).find('.phenom-reactions .js-attach-link');
  211. let afterAttach = $(attachLinks[0].nextSibling);
  212. if (!(afterAttach.is('a, span'))) afterAttach.remove();
  213. }
  214. else {
  215. putReply(th);
  216. }
  217. }
  218. // Çift grup kontrol.
  219. if ($('[id^="group-"] [id^="group-"]').length > 0) doubleGroup();
  220. }
  221.  
  222. function clearResiduals() {
  223. //c("residual clean");
  224. $('.group-comment-div, #newCommentWatcher').remove();
  225. $('[id^="replies-"]').children(':first-child').unwrap('[id^="replies-"]');
  226. $('[id^="group-"]').children(':first-child').unwrap('[id^="group-"]');
  227. let residual = $('[id^="group-"], [id^="replies-"], .group-comment-div');
  228. for (let x = 0; x < residual.length; x++) {
  229. if ( $(residual[x]).children('.mod-comment-type').length <= 0 ) {
  230. $(residual[x]).remove();
  231. }
  232. }
  233. }
  234.  
  235. // Grup içindeki grupları denetle, gerekliyse dışarı at
  236. function doubleGroup() {
  237. //c("doubleGroup");
  238. $('[id^="group-"] [id^="group-"] > .mod-comment-type .current-comment p').each(function(){
  239. let firstNd = $(this.firstChild);
  240. let realGroup = $(this).closest('[id^="group-"] [id^="group-"]');
  241. if (firstNd.is("a") && firstNd.attr("href").search(/trello\.com\/+c\/.+\/.+\#comment-/) > 0 ) {
  242. // Burayı henüz test edemedim.
  243. let href = id( $(firstNd).attr("href") );
  244. let isRelated = realGroup.closest('.js-list-actions.mod-card-back > [id^="group-"]').find('a.date[href*="' + href + '"]').length > 0;
  245. if (!isRelated) realGroup.insertBefore( realGroup.closest('.js-list-actions.mod-card-back > [id^="group-"]') );
  246. }
  247. else {
  248. c("doubleGroup problemi çözüldü");
  249. realGroup.insertBefore( realGroup.closest('.js-list-actions.mod-card-back > [id^="group-"]') );
  250. }
  251. });
  252. }
  253.  
  254. // Grupların altındaki klon yorum alanlarına tıklandığında
  255. function textAreaShifter(dum) {
  256. let val = '';
  257. if ( $(dum).closest('[id^="group-"]').length > 0 ) {
  258. // Klon grubun altındaysa içine yazdırılacak yazıyı oluştur.
  259. let lastGroupCom = $(dum).closest('[id^="group-"]').children('[id^="replies-"]').children('.mod-comment-type:last-of-type');
  260. let pageUrl = window.location.href.replace(/(.+trello\.com\/c\/.+\/\d+).+/, "$1");
  261. let mentions = replyMentions(lastGroupCom);
  262. val = (pageUrl + '#' + id( link(lastGroupCom) ) + ' ' + mentions + ' ');
  263. // Grubun altında işi kalmayıp aktifliğini kaybettiyse geri yolla ki yanıtla tuşu kullanıldığında yerinde olsun.
  264. let groupComTimeOut = setInterval(function () {
  265. let pasiveGroupCom = $('.card-detail-window .js-new-comment:not(.is-focused, .card-detail-window .is-show-controls)');
  266. if (pasiveGroupCom.length > 0) {
  267. swaper($('.card-detail-window .js-new-comment'), $('.card-detail-window .window-module > .new-group-comment'));
  268. $('.card-detail-window .js-new-comment .js-new-comment-input').val("");
  269. clearInterval(groupComTimeOut);
  270. }
  271. }, 100);
  272. }
  273. // Klonla orijinal text editor'ü yer değiştir. Klon grup altına da gidebilir, orijinal yorum alanına da.
  274. swaper($('.card-detail-window .js-new-comment'), $(dum).closest('.new-group-comment'));
  275. $('.card-detail-window .js-new-comment .js-new-comment-input').val(val).click().focus();
  276. }
  277. // Yanıtlanacak cevap için atıfları ayarla
  278. function replyMentions(el) {
  279. if (id(el).search(/action-/) >= 0) return $(el).find('.phenom-creator .member-avatar').attr("title").replace(/(.+) \((.+)\)/, "\@$2");
  280. let myName = $('.card-detail-window .new-comment .member-avatar').attr("title").replace(/(.+) \((.+)\)/, "\@$2");
  281. let ment = [];
  282. el.find('.atMention').each(function(){ment.push($(this).text());});
  283. ment.push( el.find('.phenom-creator .member-avatar').attr("title").replace(/(.+) \((.+)\)/, "\@$2") );
  284. ment = arrayRemover(ment, myName);
  285. return ment.toString().replace(/\,/g, " ");
  286. }
  287.  
  288. // Hazır fonsiyonlar
  289. function uniqArray(array) {
  290. if ($.type(array[0]) == "object") {
  291. let objs = [];
  292. return array.filter(function(item) {
  293. return JSON.stringify(objs).search(JSON.stringify(item)) >= 0 ? false : objs.push(item);
  294. });
  295. }
  296. else {
  297. var seen = {};
  298. return array.filter(function(item) {
  299. return seen.hasOwnProperty(item) ? false : (seen[item] = true);
  300. });
  301. }
  302. }
  303. function arrayRemover(array, removeThis, objectType) {
  304. let resultArray = [];
  305. if ($.type(array[0]) == "object") {
  306. if ($.type(removeThis) == "array") {
  307. for(let o = 0; o < array.length; o++){
  308. for (let i = 0; i < removeThis.length; i++) {
  309. if ( array[o][objectType] == removeThis[i][objectType] ) break;
  310. else if (i+1 == removeThis.length) resultArray.push(array[o]);
  311. }
  312. }
  313. }
  314. else {
  315. resultArray = $.grep(array, function(value) {
  316. return value[objectType] != removeThis;
  317. });
  318. }
  319. }
  320. else {
  321. if ($.type(removeThis) == "array") {
  322. for(let o = 0; o < array.length; o++){
  323. let newArray = [];
  324. for (let i = 0; i < removeThis.length; i++) {
  325. if ( array[o] == removeThis[i] ) break;
  326. else if (i+1 == removeThis.length) resultArray.push(array[o]);
  327. }
  328. }
  329. }
  330. else {
  331. resultArray = $.grep(array, function(value) {
  332. return value != removeThis;
  333. });
  334. }
  335. }
  336. return resultArray;
  337. }
  338. function swaper(el1, el2) {
  339. $(el1).before('<div id="dummyDiv1"></div>');
  340. $(el2).before('<div id="dummyDiv2"></div>');
  341. $(el1).appendTo('#dummyDiv2').unwrap('#dummyDiv2');
  342. $(el2).appendTo('#dummyDiv1').unwrap('#dummyDiv1');
  343. }
  344. function arraySorter(array, objectOrNot, objectType) {
  345. if (objectOrNot == "object") {
  346. array.sort(function(a, b) {
  347. var x = ( isFinite(a[objectType]) ) ? Number(a[objectType]) : a[objectType].toString().toLowerCase();
  348. var y = ( isFinite(b[objectType]) ) ? Number(b[objectType]) : b[objectType].toString().toLowerCase();
  349. if (x < y) {return -1;}
  350. if (x > y) {return 1;}
  351. return 0;
  352. });
  353. }
  354. else if (objectOrNot == "nonObject") {
  355. array.sort();
  356. }
  357. }
  358. function waitForKeyElements (
  359. selectorTxt, /* Required: The jQuery selector string that specifies the desired element(s). */
  360. actionFunction, /* Required: The code to run when elements are found. It is passed a jNode to the matched element. */
  361. bWaitOnce, /* Optional: If false, will continue to scan for new elements even after the first match is found. */
  362. iframeSelector /* Optional: If set, identifies the iframe to search. */
  363. ) {
  364. var targetNodes, btargetsFound;
  365.  
  366. if (typeof iframeSelector == "undefined")
  367. targetNodes = $(selectorTxt);
  368. else
  369. targetNodes = $(iframeSelector).contents().find(selectorTxt);
  370.  
  371. if (targetNodes && targetNodes.length > 0) {
  372. btargetsFound = true;
  373. /*--- Found target node(s). Go through each and act if they are new. */
  374. targetNodes.each(function() {
  375. var jThis = $(this);
  376. var alreadyFound = jThis.data('alreadyFound') || false;
  377.  
  378. if (!alreadyFound) {
  379. //--- Call the payload function.
  380. var cancelFound = actionFunction(jThis);
  381. if (cancelFound)
  382. btargetsFound = false;
  383. else
  384. jThis.data('alreadyFound', true);
  385. }
  386. });
  387. }
  388. else {
  389. btargetsFound = false;
  390. }
  391.  
  392. //--- Get the timer-control variable for this selector.
  393. var controlObj = waitForKeyElements.controlObj || {};
  394. var controlKey = selectorTxt.replace(/[^\w]/g, "_");
  395. var timeControl = controlObj[controlKey];
  396.  
  397. //--- Now set or clear the timer as appropriate.
  398. if (btargetsFound && bWaitOnce && timeControl) {
  399. //--- The only condition where we need to clear the timer.
  400. clearInterval(timeControl);
  401. delete controlObj[controlKey];
  402. }
  403. else {
  404. //--- Set a timer, if needed.
  405. if (!timeControl) {
  406. timeControl = setInterval(function() {
  407. waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector);
  408. },
  409. 300
  410. );
  411. controlObj [controlKey] = timeControl;
  412. }
  413. }
  414. waitForKeyElements.controlObj = controlObj;
  415. }
  416. })();