OdooTasksLinks

Adds some features on github.com, gitlab.com, moduon.team, loom.com and youtube.com to integrate it with Moduon Team and Odoo

  1. // ==UserScript==
  2. // @name OdooTasksLinks
  3. // @original https://greasyfork.org/es/scripts/390531-tgithub
  4. // @author Eduardo de Miguel
  5. // @version 16.0.2
  6. // @grant none
  7. // @run-at document-idle
  8. // @namespace moduon
  9. // @include /^https?:\/\/(?:www\.)?github\.com\/?.*$/
  10. // @include /^https?:\/\/(?:www\.)?gitlab\.com\/?.*$/
  11. // @include /^https?:\/\/(?:www\.)?gitlab\.moduon\.team\/?.*$/
  12. // @include /^https?:\/\/(?:www\.)?moduon\.team\/?.*$/
  13. // @include /^https?:\/\/(?:www\.)?loom\.com\/?.*$/
  14. // @include /^https?:\/\/(?:www\.)?youtube\.com\/?.*$/
  15. // @include /^https?:\/\/(?:www\.)?odoo\.com\/?.*$/
  16. // @description Adds some features on github.com, gitlab.com, moduon.team, loom.com and youtube.com to integrate it with Moduon Team and Odoo
  17. // ==/UserScript==
  18.  
  19. /* jshint esversion: 6 */
  20.  
  21. (function (window) {
  22. "use strict";
  23.  
  24. var MTTaskLinks = {
  25. ODOO_SERVER: 'https://www.moduon.team',
  26. COMPANY_NAME: 'Moduon',
  27. COPY_REF_BUTTON_ID: 'OTL_copy_ref',
  28. MENU_ID: 256,
  29. ACTION_ID: 387,
  30.  
  31. REGEX_TEMPLATES: {},
  32. QUERY_SELECTORS: {
  33. 'github': ['.comment-body'],
  34. 'gitlab': ['.note-text', '.description', '.commit-box>.commit-description'],
  35. 'moduon': ['.o_Message_prettyBody', '.o_Activity_summary', '.o_Activity_note'], // '.o_field_html:not(:has(> lt-highlighter))'
  36. 'loom': ['.below-video>span>span'],
  37. 'youtube': ['div[id="title"]>h1.ytd-watch-metadata>yt-formatted-string.ytd-watch-metadata', 'span[id="plain-snippet-text"]', 'yt-formatted-string[id="content-text"]'],
  38. 'odoo': ['div#card_body'], // Unable to track messages due to post-load of page
  39. },
  40. COMMON_QUERY_SELECTORS: '.commit-description,',
  41. init: function () {
  42. this._addRegexTemplate('MT', new RegExp(/\bMT-(\d+)/gi), `<a target="_blank" href="${this.ODOO_SERVER}/web#id=$1&menu_id=${this.MENU_ID}&cids=1&action=${this.ACTION_ID}&model=project.task&view_type=form">${this.COMPANY_NAME} - Task #$1</a>`);
  43. this._addRegexTemplate('OPW', new RegExp(/\bOPW-(\d+)/gi), `<a target='_blank' href='https://www.odoo.com/my/tasks/$1'>Odoo - Ticket #$1</a>`);
  44.  
  45. this._replaceTask();
  46. if (this._isLocationHost('github')) {
  47. this._ghAddNavbarOptions();
  48. }else if (this._isLocationHost('moduon')) {
  49. this._moduonAddNavbarOptions();
  50. }
  51. },
  52.  
  53. /* CORE FUNCTIONS */
  54. _isLocationHost: function (host) {
  55. return this._getLocationHost().includes(host);
  56. },
  57. _getLocationHost: function() {
  58. return document.location.host.toLowerCase();
  59. },
  60. _addRegexTemplate: function (templateName, regex, html) {
  61. this.REGEX_TEMPLATES[templateName] = { regex: regex, html: html };
  62. },
  63. _executeRegexReplace: function (templateName, text) {
  64. if (templateName in this.REGEX_TEMPLATES && text.match(this.REGEX_TEMPLATES[templateName].regex)) {
  65. return text.replace(this.REGEX_TEMPLATES[templateName].regex, this.REGEX_TEMPLATES[templateName].html);
  66. }
  67. return false;
  68. },
  69.  
  70. /* COMMON FUNCTIONS */
  71. _replaceTask: function () {
  72. const searchAndParse = () => {
  73. var hostQuerySelector = this.QUERY_SELECTORS[this._getLocationHost()] ? this.QUERY_SELECTORS[this._getLocationHost()].join(',') : Object.values(this.QUERY_SELECTORS).map(function(v){return v.join(',')}).join(',');
  74. // Replace Company Links
  75. document.querySelectorAll(this.COMMON_QUERY_SELECTORS + hostQuerySelector).forEach((elm) => {
  76. const htmlTemplate = this._executeRegexReplace('MT', elm.innerHTML);
  77. if (htmlTemplate) {
  78. elm.innerHTML = htmlTemplate;
  79. }
  80. });
  81. // Replace Odoo Links
  82. document.querySelectorAll(this.COMMON_QUERY_SELECTORS + hostQuerySelector).forEach((elm) => {
  83. const htmlTemplate = this._executeRegexReplace('OPW', elm.innerHTML);
  84. if (htmlTemplate) {
  85. elm.innerHTML = htmlTemplate;
  86. }
  87. });
  88. };
  89. // Mutation Observer
  90. if (typeof this.observer === 'undefined') {
  91. let targetNode;
  92. if (this._isLocationHost('github')) {
  93. targetNode = document.getElementsByTagName('main')[0];
  94. } else if (this._isLocationHost('gitlab')) {
  95. targetNode = document.getElementById('notes-list');
  96. if (!targetNode){
  97. targetNode = document.getElementsByClassName('js-pipeline-container')[0]; // Pipelines
  98. }
  99. } else if (this._isLocationHost('moduon')) {
  100. targetNode = document.getElementsByClassName('o_web_client')[0];
  101. } else if (this._isLocationHost('loom')) {
  102. targetNode = document.getElementsByClassName('mainContent')[0];
  103. } else if (this._isLocationHost('youtube')) {
  104. targetNode = document.getElementById('content');
  105. }
  106. if (typeof targetNode !== 'undefined') {
  107. this.observer = new MutationObserver(searchAndParse);
  108. this.observer.observe(targetNode, { childList: true, subtree: true });
  109. }
  110. }
  111. searchAndParse();
  112. },
  113.  
  114. /* GITHUB FUNCTIONS */
  115. _ghAddNavbarOptions: function () {
  116. const targetNode = document.getElementsByTagName('nav')[0];
  117. if (typeof targetNode !== 'undefined') {
  118. const exampleItem = targetNode.querySelector("a[href^='/pulls']");
  119. const menuItem = document.createElement("A");
  120. menuItem.className = exampleItem.className;
  121. menuItem.style.cssText = exampleItem.style.cssText
  122. menuItem.textContent = this.COMPANY_NAME;
  123. menuItem.href = `/pulls?q=is%3Aopen+is%3Apr+archived%3Afalse+involves%3A${this.COMPANY_NAME}`;
  124. targetNode.insertAdjacentElement('afterbegin', menuItem);
  125. }
  126. },
  127. _moduonAddNavbarOptions: function () {
  128. let moduonObserver = new MutationObserver((mutations) => {
  129. const in_project_task = window.location.href.includes("model=project.task");
  130. const is_project_task = window.location.href.includes("id=");
  131. if (in_project_task && is_project_task) {
  132. mutations.forEach((mutation) => {
  133. if (!mutation.addedNodes) return
  134. for (let i = 0; i < mutation.addedNodes.length; i++) {
  135. try {
  136. if (mutation.addedNodes[i].querySelector(".o_statusbar_buttons")) {
  137. if (document.getElementById(this.COPY_REF_BUTTON_ID)) return
  138. const statusBar = document.getElementsByClassName("o_statusbar_buttons")[0];
  139. const copyButton = document.createElement("button");
  140. copyButton.className = 'btn btn-light';
  141. copyButton.type = 'button';
  142. copyButton.title = 'Copy Task Reference';
  143. copyButton.id = this.COPY_REF_BUTTON_ID;
  144. copyButton.innerHTML = "<i class='fa fa-fw o_button_icon fa-copy'></i><span>Copiar MT</span>";
  145. copyButton.onclick = function(){
  146. let params = window.location.hash.replaceAll("#", "").split("&");
  147. for (let p of params) {
  148. if (p.startsWith("id=")){
  149. navigator.clipboard.writeText("MT-" + p.slice(3));
  150. break;
  151. }
  152. }
  153. this.innerHTML = "<i class='fa fa-fw o_button_icon fa-check'></i><span>¡Copiado!</span>";
  154. }
  155. statusBar.insertAdjacentElement('afterbegin', copyButton);
  156. this.moduonObserver.disconnect();
  157. }
  158. } catch (e) {}
  159. }
  160. })
  161. }
  162. })
  163. moduonObserver.observe(document.body, {
  164. childList: true,
  165. subtree: true,
  166. attributes: false,
  167. characterData: false
  168. })
  169. },
  170. };
  171.  
  172. MTTaskLinks.init();
  173.  
  174. })(window)