Greasy Fork 支持简体中文。

Launchpad bug tags helper

LP bugs tags helper

  1. // ==UserScript==
  2. // @name Launchpad bug tags helper
  3. // @namespace https://launchpad.net/~julian-liu
  4. // @version 0.6
  5. // @description LP bugs tags helper
  6. // @author Julian Liu
  7. // @match https://bugs.launchpad.net/*/+filebug
  8. // @match https://bugs.launchpad.net/*/+bug/*
  9. // @connect cedelivery.access.ly
  10. // @grant GM_xmlhttpRequest
  11. // ==/UserScript==
  12.  
  13. function interceptorSetup() {
  14. // override submit handling
  15. HTMLFormElement.prototype.real_submit = HTMLFormElement.prototype.submit;
  16. HTMLFormElement.prototype.submit = interceptor;
  17.  
  18. document.getElementById('filebug-form').addEventListener('submit', function(e) {
  19. // stop the form from submitting
  20. e.preventDefault();
  21.  
  22. interceptor(e);
  23. }, true);
  24. }
  25.  
  26. function interceptor(e) {
  27. var frm = e ? e.target : this;
  28.  
  29. tagNode = frm.elements['field.tags'];
  30.  
  31. if (tagNode.value.length === 0) {
  32. var check = confirm('No tags entered. Are you sure to submit this bug without any tag?');
  33. if (!check) {
  34. return;
  35. }
  36. }
  37. // submit default is prevented, so we add new submit node instead
  38. submitNode = document.createElement('input');
  39. submitNode.name = 'field.actions.submit_bug';
  40. submitNode.type = 'text';
  41. submitNode.value = 'Submit Bug Report';
  42. frm.appendChild(submitNode);
  43. HTMLFormElement.prototype.real_submit.apply(frm);
  44. }
  45.  
  46. function addTagStyle() {
  47. var menuStyle = `
  48. #wrap {
  49. width: 100px;
  50. height: 50px;
  51. padding-bottom: 10px;
  52. margin: 0; /* Ensures there is no space between sides of the screen and the menu */
  53. z-index: 1; /* Makes sure that your menu remains on top of other page elements */
  54. background-color: GhostWhite;
  55. }
  56. .navbar {
  57. height: 50px;
  58. padding: 0;
  59. padding-bottom: 10px;
  60. margin: 0;
  61. border-right: 1px solid #fafaff;
  62. z-index: 12;
  63. }
  64. .navbar li {
  65. padding-bottom: 10px;
  66. height: auto;
  67. width: 100px; /* Each menu item is 100px wide */
  68. /*float: left; This lines up the menu items horizontally */
  69. object-position: top;
  70. text-align: center; /* All text is placed in the center of the box */
  71. list-style: none; /* Removes the default styling (bullets) for the list */
  72. font: normal bold 12px/1.2em Arial, Verdana, Helvetica;
  73. padding: 0;
  74. margin: 0;
  75. background-color: GhostWhite;
  76. }
  77. .navbar a {
  78. padding: 18px 0; /* Adds a padding on the top and bottom so the text appears centered vertically */
  79. 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. */
  80. 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. */
  81. text-decoration: none; /* Removes the default hyperlink styling. */
  82. color: #000; /* Text color is black */
  83. display: block;
  84. }
  85. .navbar li:hover, a:hover {
  86. background-color: #e5f3ff;
  87. }
  88. .navbar li ul {
  89. display: none; /* Hides the drop-down menu */
  90. margin: 0; /* Aligns drop-down box underneath the menu item */
  91. padding: 0; /* Aligns drop-down box underneath the menu item */
  92. margin-left: 100px;
  93. float:left;
  94. margin-top: -45px;
  95. height: 0;
  96. }
  97. .navbar li:hover ul {
  98. display: block; /* Displays the drop-down box when the menu item is hovered over */
  99. z-index: 12;
  100. padding-left: 1px;
  101. }
  102. .navbar li ul li {
  103. background-color: #e1e1e7;
  104. width: 150px;
  105. font: normal 12px/1.2em Arial, Verdana, Helvetica;
  106. position: relative;
  107. z-index: 999;
  108. }
  109. .navbar li ul li a {
  110. border-left: 1px solid #0026ff;
  111. border-right: 1px solid #0026ff;
  112. border-top: 1px solid #0026ff;
  113. z-index: 1001;
  114. }
  115. .navbar li ul li:hover {
  116. background-color: #d1d7e8;
  117. z-index: 1000;
  118. }
  119. .checkedmark:before {
  120. content: '✓';
  121. }
  122. `;
  123.  
  124. var css = document.createElement("style");
  125. css.type = "text/css";
  126. css.innerHTML = menuStyle;
  127. document.body.appendChild(css);
  128. }
  129.  
  130. function toggleTagValue(formId, tagElement, tag) {
  131. var tagNode = document.getElementById(formId).elements[tagElement];
  132. var liNode = document.getElementById('taglist.' + tag);
  133.  
  134. if (tagNode.value.indexOf(tag) !== -1) {
  135. tagNode.value = tagNode.value.replace(' ' + tag, '');
  136. tagNode.value = tagNode.value.replace(tag, '');
  137. liNode.className = '';
  138. }
  139. else {
  140. tagNode.value = tagNode.value + ' ' + tag;
  141. liNode.className = 'checkedmark';
  142. }
  143. }
  144.  
  145. // This function is used by tag editing page only
  146. // Filing new bug page will always show tag listing
  147. function toggleTagHidden() {
  148. var divNode = document.getElementById('wrap');
  149. if (divNode.className === 'hidden') {
  150. divNode.className = '';
  151. divNode.style.display = 'inline-block';
  152.  
  153. var inputNode = document.getElementById('tags-form').elements['tag-input'];
  154. inputNode.size = 30;
  155.  
  156. // iterate tag content to show correct checkmark
  157. inputNode.value.split(' ').forEach(function(tagName) {
  158. var liNode = document.getElementById('taglist.' + tagName);
  159. if (liNode !== null) {
  160. liNode.className = 'checkedmark';
  161. }
  162. });
  163. }
  164. else {
  165. divNode.className = 'hidden';
  166. divNode.style.display = '';
  167. }
  168. }
  169.  
  170. function insertAfter(newNode, referenceNode) {
  171. referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
  172. }
  173.  
  174. function readExternalTags(url, callback) {
  175. GM_xmlhttpRequest({
  176. method: "GET",
  177. url: url,
  178. onload: function (response) {
  179. if (response.status == 200) {
  180. callback(response.responseText);
  181. }
  182. }
  183. });
  184. }
  185.  
  186. function tagList(formId, tagElement, targetNode) {
  187. // ?q= to avoid cache
  188. var extTagUrl = 'https://cedelivery.access.ly/tag.json?q=';
  189. var intTagurl = 'https://bugs.launchpad.net/somerville/+bug/1713956?q=';
  190. var pubTags = {
  191. ihv: ['ihv-amd', 'ihv-broadcom', 'ihv-intel', 'ihv-nvidia', 'ihv-realtek', 'ihv-related'],
  192. status: ['task', 'staging', 'waiting', 'cqa-verified', 'not-fixed-at-gm']
  193. };
  194. var tagDiv = document.createElement('div');
  195. tagDiv.id = 'wrap';
  196. var ulLevel1 = document.createElement('ul');
  197. ulLevel1.className = 'navbar';
  198. ulLevel1.id = 'navbartop';
  199. tagDiv.appendChild(ulLevel1);
  200.  
  201. function appendCategory(tagData) {
  202. var topNode = document.getElementById('navbartop');
  203.  
  204. Object.keys(tagData).forEach(function(key, index) {
  205. var liCategory = document.createElement('li');
  206. topNode.appendChild(liCategory);
  207. liCategory.innerHTML = liCategory.innerHTML + key + ' →';
  208.  
  209. var ulLevel2 = document.createElement('ul');
  210. for (var i = 0; i < tagData[key].length; i++) {
  211. var liItem = document.createElement('li');
  212. ulLevel2.appendChild(liItem);
  213. liItem.innerHTML = liItem.innerHTML + tagData[key][i];
  214. liItem.id = 'taglist.' + tagData[key][i];
  215. (function(value){
  216. liItem.addEventListener("click", function() {
  217. toggleTagValue(formId, tagElement, value);
  218. }, false);})(tagData[key][i]);
  219. }
  220. liCategory.appendChild(ulLevel2);
  221. });
  222. }
  223.  
  224. insertAfter(tagDiv, targetNode);
  225. appendCategory(pubTags);
  226. addTagStyle();
  227.  
  228. loadBugDescription(intTagurl, function(text){
  229. console.log('External data loaded');
  230. var data = JSON.parse(text);
  231. appendCategory(data.tags);
  232. loadPlatformPlan(data.plan);
  233. });
  234. }
  235.  
  236. function loadExtHtml(url, callback) {
  237. var ajaxReq = new XMLHttpRequest();
  238. ajaxReq.open('GET', url, true);
  239. ajaxReq.onreadystatechange = function() {
  240. if (ajaxReq.readyState === 4 && ajaxReq.status == '200') {
  241. callback(ajaxReq.responseText);
  242. }
  243. };
  244. ajaxReq.send(null);
  245. }
  246.  
  247. function loadBugDescription(url, callback) {
  248. loadExtHtml(url, function(text){
  249. var doc = document.implementation.createHTMLDocument("");
  250. doc.write(text);
  251. var children = doc.getElementById('edit-description').childNodes;
  252. for (var i=0, len=children.length; i < len; i++){
  253. if (children[i].className == 'yui3-editable_text-text'){
  254. callback(children[i].textContent);
  255. break;
  256. }
  257. }
  258. });
  259. }
  260.  
  261. function setupOberver() {
  262. var attrObserver = new MutationObserver(function(mutations) {
  263. mutations.forEach(function(mutation) {
  264. if (mutation.attributeName === 'class') {
  265. toggleTagHidden();
  266. }
  267. });
  268. });
  269.  
  270. var childObserver = new MutationObserver(function(mutations) {
  271. mutations.forEach(function(mutation) {
  272. if (mutation.type === 'childList') {
  273. attrObserver.observe(document.getElementById('tags-form'), {
  274. attributes: true,
  275. });
  276. }
  277. });
  278. });
  279.  
  280. var formNode = document.getElementById('tags-form');
  281. if (formNode === null) {
  282. // bind dom insert event first, since the target element we want to
  283. // listen for attribute change is not existed yet
  284. childObserver.observe(document.getElementById('bug-tags'), {
  285. childList: true
  286. });
  287. }
  288. else {
  289. attrObserver.observe(formNode, {
  290. attributes: true,
  291. });
  292. }
  293. }
  294.  
  295. function loadPlatformPlan(data) {
  296. var tagsDiv = document.getElementById('bug-tags');
  297. if (tagsDiv !== null) {
  298. var planDiv = document.createElement('div');
  299. var planHead = document.createElement('span');
  300. var planList = document.createElement('span');
  301. planDiv.id = 'bug-plan';
  302.  
  303. var planContent = '';
  304. document.getElementById('tag-list').textContent.split(' ').forEach(function(tagName) {
  305. var tagNameTrimmed = tagName.trim();
  306. if (tagNameTrimmed.length > 0 && tagNameTrimmed in data) {
  307. var platformLink = document.createElement('a');
  308. platformLink.href = '/' + window.location.href.split('/')[3] + '/+bugs?field.tag=' + tagNameTrimmed;
  309. platformLink.textContent = tagNameTrimmed;
  310. planContent = ' ';
  311. for (var milestone in data[tagNameTrimmed]) {
  312. planContent = planContent + `[${milestone}](${data[tagNameTrimmed][milestone]}), `;
  313. }
  314. if (planContent.length > 1) {
  315. // exclude last ', '
  316. planList.textContent = planContent.substr(0, planContent.length - 2);
  317. }
  318.  
  319. planHead.textContent = 'Plan: ';
  320. tagsDiv.appendChild(planDiv);
  321. planDiv.appendChild(planHead);
  322. planDiv.appendChild(platformLink);
  323. planDiv.appendChild(planList);
  324. }
  325. });
  326. }
  327. }
  328.  
  329. (function() {
  330. 'use strict';
  331.  
  332. //debugger;
  333. var anchorNode, tagNode;
  334. var curUrl = window.location.href;
  335. if (/\+filebug$/.test(curUrl)) {
  336. anchorNode = document.getElementById('filebug-form').elements['field.tags'].parentNode.parentNode.parentNode;
  337. tagNode = document.getElementById('filebug-form').elements['field.tags'];
  338.  
  339. // enlarge text input size
  340. tagNode.size = '40';
  341. tagList('filebug-form', 'field.tags', anchorNode);
  342. interceptorSetup();
  343. }
  344. else {
  345. anchorNode = document.getElementById('bug-tags').childNodes[8];
  346.  
  347. tagList('tags-form', 'tag-input', anchorNode);
  348. toggleTagHidden();
  349. setupOberver();
  350. }
  351. })();