[Trello] Advanced Comments

It makes comment groups for repliying comments on Trello.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name           [Trello] Advanced Comments
// @name:tr        [Trello] Gelişmiş Yorumlar
// @description    It makes comment groups for repliying comments on Trello.
// @description:tr Trello'da birbirine cevap olarak yazılmış yorumları gruplar.
// @author         nht.ctn
// @namespace      https://github.com/nhtctn
// @license        MIT
// @version        1.0

// @icon           data:image/png;base64,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

// @match          https://trello.com/*
// @grant          GM_addStyle
// @run-at         document-end

// @require	 https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// ==/UserScript==
/* global $ */
/*jshint esversion: 6 */

(function() {
    'use strict';

	// Bu betiğe özgü fonksiyonlar
	const c = (x) => console.log(x);
	const link = (x) => $(x).closest('.phenom').find('.phenom-date > a.date, .phenom-meta > a.date').attr('href');
	const date = (x) => new Date( $(x).closest('.phenom').find('.phenom-date > a.date, .phenom-meta > a.date').attr('dt') ).getTime();
	const type = (x) => ($(x).closest('.phenom').is('.mod-comment-type')) ? "com" : "attach";
	const getLang = () => $('html').attr("lang");
	const getItem = (x) => {
		if ($.type(x) == "string")       return $('.card-detail-window a.date[href*="' + x + '"]').closest('.phenom');
		else if ($.type(x) == "object")  return $(x).closest('.phenom');
		else                             return c( 'id fonksiyonunda belirsiz tip: ' + $.type(x) );
	};
	const id = (x) => {
		if ($.type(x) == "string")       return x.replace(/.+\#((comment|action)-.+)/, "$1");
		else if ($.type(x) == "object")  return $(x).closest('.phenom').find('a.date').attr('href').replace(/.+\#((comment|action)-.+)/, "$1");
		else                             return c( 'id fonksiyonunda belirsiz tip: ' + $.type(x) );
	};
	const key = (x) => {
		if ($.type(x) == "string")       return x.replace(/(.+)?(comment|action|group|replies|)-(.+)/, "$3");
		else if ($.type(x) == "object")  return $(x).closest('.phenom').find('a.date').attr('href').replace(/.+\#(comment|action)-(.+)/, "$2");
		else                             return c( 'key fonksiyonunda belirsiz tip: ' + $.type(x) );
	};

	let removeSlctr = `p > a:first-child[href*="trello.com"][href*="#comment-"], p > a:first-child[href*="trello.com"][href*="#action-"],
	p > span.atMention:nth-child(2), span.atMention:nth-child(3),
	p > br:nth-child(2), p > br:nth-child(3), p > br:nth-child(4),
	.phenom-reactions .js-attach-link`;

	// Yorum grubu oluştur.
	function groupDiv(target, comfor, link) {
		getItem(target).before('<div id="group-' + key(comfor) + '"><div id="replies-' + key(comfor) + '"></div></div>');
		getItem(comfor).prependTo( '.card-detail-window #group-' + key(comfor) );
		getItem(link).prependTo( '.card-detail-window #replies-' + key(comfor) );
		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");
		dummyCom.find('.js-new-comment-input').removeClass('js-new-comment-input').css("height", "20.0348px").val("");
		dummyCom.appendTo('.card-detail-window #group-' + key(comfor)).wrap('<div class="group-comment-div"></div>');
		$('.card-detail-window #group-' + key(comfor) + ' .new-group-comment').click(function(){ textAreaShifter(this); });
	}

	// Bir kart ilk açıldığındaki işlemler
	let cardUrl = '';
	waitForKeyElements('.card-detail-window p.u-bottom a.show-more.js-show-all-actions', cardWindow);
	function cardWindow () {
		things = [];
		cardUrl = window.location.href.replace(/(.+trello\.com\/c\/.+\/\d+).+/, "$1");
		let intCount = 0;
		// Yeterince bekleyip tüm etkinlikleri göstere tıkla
		let myTimeOut = setInterval(function () {
			let moreButton = $('.card-detail-window p.u-bottom a.show-more.js-show-all-actions').removeAttr("href").css("cursor", "pointer");
			moreButton[0].click();
			if ( $('.card-detail-window .js-show-all-actions').is(":hidden")) clearInterval(myTimeOut);
			else if (intCount++ > 40) clearInterval(myTimeOut);
		}, 500);

		GM_addStyle(`
		.window-overlay > .window {width: 868px;}
		.card-detail-window > .window-main-col {width: 652px;}
		.card-detail-window > .window-sidebar {width: calc(100% - 700px);}
		.mod-card-back > [id^="group-"] {padding: 8px 0 2px 0;}
		[id^="group-"] [id^="group-"] {padding: 0 0 6px 0;}
		[id^="group-"] > [id^="replies-"] {padding-left: 6%;}
		[id^="group-"] .mod-comment-type:not(.mod-highlighted) {padding: 0 0 6px 0}
		[id^="group-"] .mod-comment-type.mod-highlighted {padding: 0 0 6px 48px}
		.group-comment-div {padding-left: 6%; padding-top: 4px}
		.artificialReply {text-decoration: none;}
		.artificialReply:hover {text-decoration: underline;}
		`);
	}

	// Yeni bir etkinlik saptandığındaki işlemler
	let things = [];
	waitForKeyElements('.card-detail-window .mod-card-back .phenom a.date', function(newElement){ addUnseen(newElement); });
	function addUnseen(el) {
		// Yeniyse kaydet.
		let isUnseen = true;
        for (let x = 0; x < things.length; x++) {
            isUnseen = (things[x].link == link(el)) ? false : true;
            if(!isUnseen) break;
        }
        if (isUnseen) {
            let newThings = [];
            $('.card-detail-window .mod-card-back .phenom a.date').each(function(){
                newThings.push({
                    link:   link( $(this) ),
                    id:     id( $(this) ),
                    date:   date( $(this) ),
                    type:   type( $(this) ),
                    comFor: findReply( $(this) ),
                });
                things = uniqArray(newThings);
                arraySorter(things, "object", "date");
            });
            //console.log(things);
			newThing();
        }
	}

	function findReply(this_) { // Buraya başka kartın yorum linki olmasın diye koşul koyulacak.
		if (type(this_) == "com") {
			let firstNd = $(this_.closest('.mod-comment-type').find('.current-comment p')[0].firstChild);
			if (firstNd.is("a") && firstNd.attr("href").search(/trello\.com\/.+\#(comment|action)-/) > 0 ) return id( firstNd.attr("href") );
			else return null;
		}
		else return null;
	}

	// Bir tepkiye ilk raslandığındaki işlemler
	waitForKeyElements('.card-detail-window .mod-comment-type > .phenom-reactions .reactions-add-icon', function(elem) {moveReactions(elem);});
    function moveReactions(this_) {
		// Tepkilerin yerini değiştir
        let react = this_.closest('.phenom-reactions');
		let com = this_.closest('.mod-comment-type');
		com.attr("id", id(this_));
		com.find('.phenom-meta').css("display", "contents");
		if (com.find('.rightOfDate').length <= 0) {
			com.find('.phenom-date').after('<div class="rightOfDate" style="display: contents;"></div>');
			react.css("display", "inline-flex").css("float", "right").css("margin-right", "8px").appendTo('#' + id(this_) + ' .rightOfDate');
		}
		// Yanıtla butonuyla link ekle
		let cardUrl = window.location.href.replace(/(.+trello\.com\/c\/.+\/\d+).+/, "$1");
		react.find('.js-reply-to-action, .js-reply-to-all-action').click(function(){
			$('.js-new-comment textarea.comment-box-input').val( cardUrl + '#' + id(this_) );
		});
        // Silme işleminde yeniden sırala
        react.find(".js-confirm-delete-action").click(function(){
            waitForKeyElements('.js-confirm.nch-button--danger', function(){
                $('.js-confirm.nch-button--danger').one("click", function(){
                setTimeout( function() {newThing();}, 200);
                });
            }, true);
        });
    }

    // Başkası yorum silip sayfa bozulduysa diye arada kontrol et.
    setInterval(function(){
        if($('[id^="group-"]'). length > 0 && $('[id^="group-"] > [id^="replies-"]:first-child'). length > 0) {
            newThing();
        }
    }, 5000);

	function putReply(el) {
		// Actionlara yanıtla butonu ekle.
		if (el.type == "attach" && $('#' + el.id + ' .artificialReply').length <= 0) {
			let reply = ($('html').attr("lang") == "tr") ? "Yanıtla" : "Reply";
			$('#' + el.id + ' a.date').after(' - <a class="js-reply-to-all-action artificialReply" href="#">' + reply + '</a>');
			$('#' + el.id + ' .artificialReply').click(function(){
				let mentions = replyMentions(getItem(this));
				let val = (cardUrl + '#' + id(this) + ' ' + mentions + ' ');
				$('.js-new-comment textarea.comment-box-input').val(val);
				$('.js-new-comment textarea.comment-box-input').focus();
			});
		}
	}

	// Yeni bir eylem saptandığındaki işlemler
	function newThing() {
        if ($('[id^="group-"]').length > 0) clearResiduals();
		for (let x = 0; x < things.length; x++) {
			// Her bir eyleme id ekle.
			let th = things[x];
			if (getItem(th.id).attr("id") == undefined) getItem(th.id).attr("id", th.id);
			// Eylem yorumsa
			let isComForExist = (th.comFor != null && getItem(th.comFor).length > 0);
			if (isComForExist) {
				let isComForNonThreaded = getItem(th.comFor).closest('[id^="group-"]').length <= 0;
				let isComForRegular = getItem(th.comFor).is('[id^="group-"] .mod-comment-type:first-child, [id^="replies-"] .mod-comment-type:last-child');
				if (isComForNonThreaded) {
					// Yorumun atası herhangi bir grupta değil. Yeni grup aç.
					groupDiv(th.id, th.comFor, th.id);
				}
				else {
					// Yorumun atası bir grupta. Toptan grubu yorumun olduğu yere taşı.
					let repliesId = getItem(th.comFor).closest('[id^="group-"]').find('[id^="replies-"]').attr("id");
					if (repliesId != getItem(th.id).closest('[id^="group-"]').find('[id^="replies-"]').attr("id")) { //Birbirini hedef gösteren yorumlarda buga girmesin diye.
						$('#' + repliesId).closest('.mod-card-back > [id^="group-"]').insertBefore(getItem(th.id));
						if (isComForRegular) {
							getItem(th.id).appendTo( '#' + repliesId ); // Ata grubun atası ya son mesajıysa yorumu grubun altına taşı.
						}
						else {
							groupDiv(th.comFor, th.comFor, th.id); // Ata grubun ortasındaysa yeni alt grup aç.
						}
					}
				}
                // Cevap olarak yazılmış bir yorum ise linki, alıntıları, satır atlamayı, bağlantı olarak ekle seçeneğini gizle.
                getItem(th.id).find(removeSlctr).hide();
				// c(comItem(th.id).find(removeSlctr)[0].getAttribute("style"));
                let attachLinks = getItem(th.id).find('.phenom-reactions .js-attach-link');
                let afterAttach = $(attachLinks[0].nextSibling);
                if (!(afterAttach.is('a, span'))) afterAttach.remove();
			}
			else {
				putReply(th);
			}
		}
        // Çift grup kontrol.
        if ($('[id^="group-"] [id^="group-"]').length > 0) doubleGroup();
	}

	function clearResiduals() {
		//c("residual clean");
		$('.group-comment-div, #newCommentWatcher').remove();
		$('[id^="replies-"]').children(':first-child').unwrap('[id^="replies-"]');
		$('[id^="group-"]').children(':first-child').unwrap('[id^="group-"]');
		let residual = $('[id^="group-"], [id^="replies-"], .group-comment-div');
		for (let x = 0; x < residual.length; x++) {
			if ( $(residual[x]).children('.mod-comment-type').length <= 0 ) {
				$(residual[x]).remove();
			}
		}
	}

	// Grup içindeki grupları denetle, gerekliyse dışarı at
	function doubleGroup() {
		//c("doubleGroup");
		$('[id^="group-"] [id^="group-"] > .mod-comment-type .current-comment p').each(function(){
			let firstNd = $(this.firstChild);
			let realGroup = $(this).closest('[id^="group-"] [id^="group-"]');
			if (firstNd.is("a") && firstNd.attr("href").search(/trello\.com\/+c\/.+\/.+\#comment-/) > 0 ) {
				// Burayı henüz test edemedim.
				let href = id( $(firstNd).attr("href") );
				let isRelated = realGroup.closest('.js-list-actions.mod-card-back > [id^="group-"]').find('a.date[href*="' + href + '"]').length > 0;
				if (!isRelated) realGroup.insertBefore( realGroup.closest('.js-list-actions.mod-card-back > [id^="group-"]') );
			}
			else {
                c("doubleGroup problemi çözüldü");
				realGroup.insertBefore( realGroup.closest('.js-list-actions.mod-card-back > [id^="group-"]') );
			}
		});
	}

	// Grupların altındaki klon yorum alanlarına tıklandığında
	function textAreaShifter(dum) {
		let val = '';
		if ( $(dum).closest('[id^="group-"]').length > 0 ) {
			// Klon grubun altındaysa içine yazdırılacak yazıyı oluştur.
			let lastGroupCom = $(dum).closest('[id^="group-"]').children('[id^="replies-"]').children('.mod-comment-type:last-of-type');
			let pageUrl = window.location.href.replace(/(.+trello\.com\/c\/.+\/\d+).+/, "$1");
			let mentions = replyMentions(lastGroupCom);
			val = (pageUrl + '#' + id( link(lastGroupCom) ) + ' ' + mentions + ' ');
			// Grubun altında işi kalmayıp aktifliğini kaybettiyse geri yolla ki yanıtla tuşu kullanıldığında yerinde olsun.
			let groupComTimeOut = setInterval(function () {
				let pasiveGroupCom = $('.card-detail-window .js-new-comment:not(.is-focused, .card-detail-window .is-show-controls)');
				if (pasiveGroupCom.length > 0) {
					swaper($('.card-detail-window .js-new-comment'), $('.card-detail-window .window-module > .new-group-comment'));
					$('.card-detail-window .js-new-comment .js-new-comment-input').val("");
					clearInterval(groupComTimeOut);
				}
			}, 100);
		}
		// Klonla orijinal text editor'ü yer değiştir. Klon grup altına da gidebilir, orijinal yorum alanına da.
		swaper($('.card-detail-window .js-new-comment'), $(dum).closest('.new-group-comment'));
		$('.card-detail-window .js-new-comment .js-new-comment-input').val(val).click().focus();
	}
	// Yanıtlanacak cevap için atıfları ayarla
	function replyMentions(el) {
		if (id(el).search(/action-/) >= 0) return $(el).find('.phenom-creator .member-avatar').attr("title").replace(/(.+) \((.+)\)/, "\@$2");
		let myName = $('.card-detail-window .new-comment .member-avatar').attr("title").replace(/(.+) \((.+)\)/, "\@$2");
		let ment = [];
		el.find('.atMention').each(function(){ment.push($(this).text());});
		ment.push( el.find('.phenom-creator .member-avatar').attr("title").replace(/(.+) \((.+)\)/, "\@$2") );
		ment = arrayRemover(ment, myName);
		return ment.toString().replace(/\,/g, " ");
	}

	// Hazır fonsiyonlar
    function uniqArray(array) {
		if ($.type(array[0]) == "object") {
            let objs = [];
            return array.filter(function(item) {
                return JSON.stringify(objs).search(JSON.stringify(item)) >= 0 ? false : objs.push(item);
            });
        }
        else {
            var seen = {};
            return array.filter(function(item) {
                return seen.hasOwnProperty(item) ? false : (seen[item] = true);
            });
        }
    }
	function arrayRemover(array, removeThis, objectType) {
		let resultArray = [];
		if ($.type(array[0]) == "object") {
			if ($.type(removeThis) == "array") {
				for(let o = 0; o < array.length; o++){
					for (let i = 0; i < removeThis.length; i++) {
						if ( array[o][objectType] == removeThis[i][objectType] )  break;
						else if (i+1 == removeThis.length)                        resultArray.push(array[o]);
					}
				}
			}
			else {
				resultArray = $.grep(array, function(value) {
					return value[objectType] != removeThis;
				});
			}
		}
		else {
			if ($.type(removeThis) == "array") {
				for(let o = 0; o < array.length; o++){
					let newArray = [];
					for (let i = 0; i < removeThis.length; i++) {
						if ( array[o] == removeThis[i] )    break;
						else if (i+1 == removeThis.length)  resultArray.push(array[o]);
					}
				}
			}
			else {
				resultArray = $.grep(array, function(value) {
					return value != removeThis;
				});
			}
		}
		return resultArray;
	}
	function swaper(el1, el2) {
		$(el1).before('<div id="dummyDiv1"></div>');
		$(el2).before('<div id="dummyDiv2"></div>');
		$(el1).appendTo('#dummyDiv2').unwrap('#dummyDiv2');
		$(el2).appendTo('#dummyDiv1').unwrap('#dummyDiv1');
	}
    function arraySorter(array, objectOrNot, objectType) {
        if (objectOrNot == "object") {
            array.sort(function(a, b) {
              var x = ( isFinite(a[objectType]) ) ? Number(a[objectType]) : a[objectType].toString().toLowerCase();
              var y = ( isFinite(b[objectType]) ) ? Number(b[objectType]) : b[objectType].toString().toLowerCase();
              if (x < y) {return -1;}
              if (x > y) {return 1;}
              return 0;
            });
        }
        else if (objectOrNot == "nonObject") {
            array.sort();
        }
    }
    function waitForKeyElements (
        selectorTxt,    /* Required: The jQuery selector string that specifies the desired element(s). */
        actionFunction, /* Required: The code to run when elements are found. It is passed a jNode to the matched element. */
        bWaitOnce,      /* Optional: If false, will continue to scan for new elements even after the first match is found. */
        iframeSelector  /* Optional: If set, identifies the iframe to search. */
    ) {
        var targetNodes, btargetsFound;

        if (typeof iframeSelector == "undefined")
            targetNodes = $(selectorTxt);
        else
            targetNodes = $(iframeSelector).contents().find(selectorTxt);

        if (targetNodes && targetNodes.length > 0) {
            btargetsFound = true;
            /*--- Found target node(s).  Go through each and act if they are new. */
            targetNodes.each(function() {
                var jThis        = $(this);
                var alreadyFound = jThis.data('alreadyFound') || false;

                if (!alreadyFound) {
                    //--- Call the payload function.
                    var cancelFound = actionFunction(jThis);
                    if (cancelFound)
                        btargetsFound = false;
                    else
                        jThis.data('alreadyFound', true);
                }
            });
        }
        else {
            btargetsFound = false;
        }

        //--- Get the timer-control variable for this selector.
        var controlObj  = waitForKeyElements.controlObj  ||  {};
        var controlKey  = selectorTxt.replace(/[^\w]/g, "_");
        var timeControl = controlObj[controlKey];

        //--- Now set or clear the timer as appropriate.
        if (btargetsFound && bWaitOnce && timeControl) {
            //--- The only condition where we need to clear the timer.
            clearInterval(timeControl);
            delete controlObj[controlKey];
        }
        else {
            //--- Set a timer, if needed.
            if (!timeControl) {
                timeControl = setInterval(function() {
                        waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector);
                    },
                    300
                );
                controlObj [controlKey] = timeControl;
            }
        }
        waitForKeyElements.controlObj = controlObj;
    }
})();