Launchpad bug tags helper

LP bugs tags helper

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 or Violentmonkey 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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         Launchpad bug tags helper
// @namespace    https://launchpad.net/~julian-liu
// @version      0.6
// @description  LP bugs tags helper
// @author       Julian Liu
// @match        https://bugs.launchpad.net/*/+filebug
// @match        https://bugs.launchpad.net/*/+bug/*
// @connect      cedelivery.access.ly
// @grant        GM_xmlhttpRequest
// ==/UserScript==

function interceptorSetup() {
    // override submit handling
    HTMLFormElement.prototype.real_submit = HTMLFormElement.prototype.submit;
    HTMLFormElement.prototype.submit = interceptor;

    document.getElementById('filebug-form').addEventListener('submit', function(e) {
        // stop the form from submitting
        e.preventDefault();

        interceptor(e);
    }, true);
}

function interceptor(e) {
    var frm = e ? e.target : this;

    tagNode = frm.elements['field.tags'];

    if (tagNode.value.length === 0) {
        var check = confirm('No tags entered. Are you sure to submit this bug without any tag?');
        if (!check) {
                return;
        }
    }
    // submit default is prevented, so we add new submit node instead
    submitNode = document.createElement('input');
    submitNode.name = 'field.actions.submit_bug';
    submitNode.type = 'text';
    submitNode.value = 'Submit Bug Report';
    frm.appendChild(submitNode);
    HTMLFormElement.prototype.real_submit.apply(frm);
}

function addTagStyle() {
    var menuStyle = `
#wrap {
width: 100px;
height: 50px;
padding-bottom: 10px;
margin: 0;  /* Ensures there is no space between sides of the screen and the menu */
z-index: 1; /* Makes sure that your menu remains on top of other page elements */
background-color: GhostWhite;
}
.navbar	{
height: 50px;
padding: 0;
padding-bottom: 10px;
margin: 0;
border-right: 1px solid #fafaff;
z-index: 12;
}
.navbar li {
padding-bottom: 10px;
height: auto;
width: 100px;  /* Each menu item is 100px wide */
/*float: left;   This lines up the menu items horizontally */
object-position: top;
text-align: center;  /* All text is placed in the center of the box */
list-style: none;  /* Removes the default styling (bullets) for the list */
font: normal bold 12px/1.2em Arial, Verdana, Helvetica;
padding: 0;
margin: 0;
background-color: GhostWhite;
}
.navbar a {
padding: 18px 0;  /* Adds a padding on the top and bottom so the text appears centered vertically */
border-left: 1px solid #fafaff;  /* Creates a border in a slightly lighter shade of blue than the background.  Combined with the right border, this creates a nice effect. */
border-right: 1px solid #fafaff; /* Creates a border in a slightly darker shade of blue than the background.  Combined with the left border, this creates a nice effect. */
text-decoration: none;  /* Removes the default hyperlink styling. */
color: #000; /* Text color is black */
display: block;
}
.navbar li:hover, a:hover {
background-color: #e5f3ff;
}
.navbar li ul {
display: none; /* Hides the drop-down menu */
margin: 0; /* Aligns drop-down box underneath the menu item */
padding: 0; /* Aligns drop-down box underneath the menu item */
margin-left: 100px;
float:left;
margin-top: -45px;
height: 0;
}
.navbar li:hover ul {
display: block; /* Displays the drop-down box when the menu item is hovered over */
z-index: 12;
padding-left: 1px;
}
.navbar li ul li {
background-color: #e1e1e7;
width: 150px;
font: normal 12px/1.2em Arial, Verdana, Helvetica;
position: relative;
z-index: 999;
}
.navbar li ul li a {
border-left: 1px solid #0026ff;
border-right: 1px solid #0026ff;
border-top: 1px solid #0026ff;
z-index: 1001;
}
.navbar li ul li:hover {
background-color: #d1d7e8;
z-index: 1000;
}
.checkedmark:before {
content: '✓';
}
    `;

    var css = document.createElement("style");
    css.type = "text/css";
    css.innerHTML = menuStyle;
    document.body.appendChild(css);
}

function toggleTagValue(formId, tagElement, tag) {
    var tagNode = document.getElementById(formId).elements[tagElement];
    var liNode = document.getElementById('taglist.' + tag);

    if (tagNode.value.indexOf(tag) !== -1) {
        tagNode.value = tagNode.value.replace(' ' + tag, '');
        tagNode.value = tagNode.value.replace(tag, '');
        liNode.className = '';
    }
    else {
        tagNode.value = tagNode.value + ' ' + tag;
        liNode.className = 'checkedmark';
    }
}

// This function is used by tag editing page only
// Filing new bug page will always show tag listing
function toggleTagHidden() {
    var divNode = document.getElementById('wrap');
    if (divNode.className === 'hidden') {
        divNode.className = '';
        divNode.style.display = 'inline-block';

        var inputNode = document.getElementById('tags-form').elements['tag-input'];
        inputNode.size = 30;

        // iterate tag content to show correct checkmark
        inputNode.value.split(' ').forEach(function(tagName) {
            var liNode = document.getElementById('taglist.' + tagName);
            if (liNode !== null) {
                liNode.className = 'checkedmark';
            }
        });
    }
    else {
        divNode.className = 'hidden';
        divNode.style.display = '';
    }
}

function insertAfter(newNode, referenceNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

function readExternalTags(url, callback) {
    GM_xmlhttpRequest({
        method: "GET",
        url: url,
        onload: function (response) {
            if (response.status == 200) {
                callback(response.responseText);
            }
        }
    });
}

function tagList(formId, tagElement, targetNode) {
    // ?q= to avoid cache
    var extTagUrl = 'https://cedelivery.access.ly/tag.json?q=';
    var intTagurl = 'https://bugs.launchpad.net/somerville/+bug/1713956?q=';
    var pubTags = {
        ihv: ['ihv-amd', 'ihv-broadcom', 'ihv-intel', 'ihv-nvidia', 'ihv-realtek', 'ihv-related'],
        status: ['task', 'staging', 'waiting', 'cqa-verified', 'not-fixed-at-gm']
    };
    var tagDiv = document.createElement('div');
    tagDiv.id = 'wrap';
    var ulLevel1 = document.createElement('ul');
    ulLevel1.className = 'navbar';
    ulLevel1.id = 'navbartop';
    tagDiv.appendChild(ulLevel1);

    function appendCategory(tagData) {
        var topNode = document.getElementById('navbartop');

        Object.keys(tagData).forEach(function(key, index) {
            var liCategory = document.createElement('li');
            topNode.appendChild(liCategory);
            liCategory.innerHTML = liCategory.innerHTML + key + ' →';

            var ulLevel2 = document.createElement('ul');
            for (var i = 0; i < tagData[key].length; i++) {
                var liItem = document.createElement('li');
                ulLevel2.appendChild(liItem);
                liItem.innerHTML = liItem.innerHTML + tagData[key][i];
                liItem.id = 'taglist.' + tagData[key][i];
                (function(value){
                    liItem.addEventListener("click", function() {
                        toggleTagValue(formId, tagElement, value);
                    }, false);})(tagData[key][i]);
            }
            liCategory.appendChild(ulLevel2);
        });
    }

    insertAfter(tagDiv, targetNode);
    appendCategory(pubTags);
    addTagStyle();

    loadBugDescription(intTagurl, function(text){
        console.log('External data loaded');
        var data = JSON.parse(text);
        appendCategory(data.tags);
        loadPlatformPlan(data.plan);
    });
}

function loadExtHtml(url, callback) {
    var ajaxReq = new XMLHttpRequest();
    ajaxReq.open('GET', url, true);
    ajaxReq.onreadystatechange = function() {
        if (ajaxReq.readyState === 4 && ajaxReq.status == '200') {
            callback(ajaxReq.responseText);
        }
    };
    ajaxReq.send(null);
}

function loadBugDescription(url, callback) {
    loadExtHtml(url, function(text){
        var doc = document.implementation.createHTMLDocument("");
        doc.write(text);
        var children = doc.getElementById('edit-description').childNodes;
        for (var i=0, len=children.length; i < len; i++){
            if (children[i].className == 'yui3-editable_text-text'){
                callback(children[i].textContent);
                break;
            }
        }
    });
}

function setupOberver() {
    var attrObserver = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.attributeName === 'class') {
                toggleTagHidden();
            }
        });
    });

    var childObserver = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.type === 'childList') {
                attrObserver.observe(document.getElementById('tags-form'),  {
                    attributes: true,
                });
            }
        });
    });

    var formNode = document.getElementById('tags-form');
    if (formNode === null) {
        // bind dom insert event first, since the target element we want to
        // listen for attribute change is not existed yet
        childObserver.observe(document.getElementById('bug-tags'),  {
            childList: true
        });
    }
    else {
        attrObserver.observe(formNode,  {
            attributes: true,
        });
    }
}

function loadPlatformPlan(data) {
    var tagsDiv = document.getElementById('bug-tags');
    if (tagsDiv !== null) {
        var planDiv = document.createElement('div');
        var planHead = document.createElement('span');
        var planList = document.createElement('span');
        planDiv.id = 'bug-plan';

        var planContent = '';
        document.getElementById('tag-list').textContent.split(' ').forEach(function(tagName) {
            var tagNameTrimmed = tagName.trim();
            if (tagNameTrimmed.length > 0 && tagNameTrimmed in data) {
                var platformLink = document.createElement('a');
                platformLink.href = '/' + window.location.href.split('/')[3] + '/+bugs?field.tag=' + tagNameTrimmed;
                platformLink.textContent = tagNameTrimmed;
                planContent = ' ';
                for (var milestone in data[tagNameTrimmed]) {
                    planContent = planContent + `[${milestone}](${data[tagNameTrimmed][milestone]}), `;
                }
                if (planContent.length > 1) {
                    // exclude last ', '
                    planList.textContent = planContent.substr(0, planContent.length - 2);
                }

                planHead.textContent = 'Plan: ';
                tagsDiv.appendChild(planDiv);
                planDiv.appendChild(planHead);
                planDiv.appendChild(platformLink);
                planDiv.appendChild(planList);
            }
        });
    }
}

(function() {
    'use strict';

    //debugger;
    var anchorNode, tagNode;
    var curUrl = window.location.href;
    if (/\+filebug$/.test(curUrl)) {
        anchorNode = document.getElementById('filebug-form').elements['field.tags'].parentNode.parentNode.parentNode;
        tagNode = document.getElementById('filebug-form').elements['field.tags'];

        // enlarge text input size
        tagNode.size = '40';
        tagList('filebug-form', 'field.tags', anchorNode);
        interceptorSetup();
    }
    else {
        anchorNode = document.getElementById('bug-tags').childNodes[8];

        tagList('tags-form', 'tag-input', anchorNode);
        toggleTagHidden();
        setupOberver();
    }
})();