Title Manager

Manage page titles per (sub)domain. On any website, click Greasemonkey > User Script Commands > Title Manager's menu.

  1. // ==UserScript==
  2. // @name Title Manager
  3. // @namespace grom
  4. // @description Manage page titles per (sub)domain. On any website, click Greasemonkey > User Script Commands > Title Manager's menu.
  5. // @include *
  6. // @grant GM_getValue
  7. // @grant GM_setValue
  8. // @grant GM_deleteValue
  9. // @grant GM_registerMenuCommand
  10. // @version 1.2.0.20140927
  11. // @run-at document-end
  12. // ==/UserScript==
  13.  
  14. if ( GM_getValue === 'undefined' || GM_setValue === 'undefined' ) {
  15. alert('=== Title Manager ===\n\nUnable to load or store values.\
  16. \nPlease use supported script managers:\nGreasemonkey, Tampermonkey...');
  17. }
  18.  
  19. // shorthands
  20. function $i(a,b) { return (b||document).getElementById(a); }
  21. function $t(a,b) { return (b||document).getElementsByTagName(a); }
  22.  
  23. var domains = GM_getValue('domains-deletable-buffer', '') || GM_getValue('domains', '').replace(/\./g, '\\.'),
  24. match = window.location.host.toLowerCase().match(domains),
  25. t = document.title;
  26.  
  27. if (match) {
  28. var _find = GM_getValue(match[0] + '-find', '');
  29. if (_find.match(/^regex:/)) {
  30. _find = _find.replace(/^regex:/, '');
  31. _find = new RegExp(_find, 'i');
  32. }
  33. var _with = GM_getValue(match[0] + '-with', '');
  34. }
  35. if (t &&_find) document.title = t.replace(_find, _with);
  36.  
  37. function tm_add(StrReg, escape, isItImport, _domain, _find, _with) {
  38. // StrReg: is "Search for" string or regex - 'str' or 'reg'
  39. // escape: escape dollar signs or not - 'escape' or 'noescape'
  40. var _domainCheck = /^[a-z0-9_\.-]+$/,
  41. tm_error = $i('tm-error-notifier');
  42. if (!isItImport) {
  43. var _domain = $i('tm-domain').value.toLowerCase();
  44. var _find = $i('tm-find').value;
  45. var _with = $i('tm-with').value;
  46. }
  47. if (!_domain.match(_domainCheck)) {
  48. tm_error.innerHTML = 'Domain invalid. Please use letters a-z, numbers 0-9, underscore _, minus - or dot .';
  49. return;
  50. }
  51. // if "Search for" is regex, make sure it starts with "regex:"
  52. if (StrReg === 'reg') {
  53. if (!_find.match(/^regex:/)) _find = _find.replace(/^/, 'regex:');
  54. }
  55. // store values, if "domain" and "Search for" are valid
  56. if (_find) { // we don't need to check for _domain, we passed _domainCheck
  57. var domains = GM_getValue('domains', '');
  58. if (!domains) {
  59. GM_setValue('domains', _domain);
  60. }
  61. else {
  62. var match = _domain.replace(/\./g, '\\.');
  63. var match = new RegExp('(^|\\|)' + match + '($|\\|)');
  64. if (!domains.match(match)) {
  65. var domains = domains + '|' + _domain;
  66. GM_setValue('domains', domains);
  67. }
  68. }
  69. GM_setValue(_domain + '-find', _find);
  70. if (_with) {
  71. // if not adding as regex, escape dollar signs
  72. if (escape === 'escape') var _with = _with.replace(/\$/g, '$$$$');
  73. GM_setValue(_domain + '-with', _with);
  74. }
  75. else if (GM_getValue(_domain + '-with', '')) GM_deleteValue(_domain + '-with');
  76. // if not in importing loop, create buffer for domains
  77. if (!isItImport) {
  78. GM_setValue('domains-deletable-buffer', GM_getValue('domains', '').replace(/\./g, '\\.'));
  79. tm_error.innerHTML = 'Success! Rule added.';
  80. }
  81. }
  82. }
  83.  
  84. function tm_manage() {
  85. var d = document;
  86. d.documentElement.innerHTML = '<head><style>.item{white-space:pre;font-family:monospace;margin:0;padding:0;}html>input{margin-left:1em;}</style></head>';
  87. var domains = GM_getValue('domains', '');
  88. if (domains) {
  89. var domains = domains.split('|');
  90. for(var i = 0, j = domains.length; i < j; i++) {
  91. var _find = GM_getValue(domains[i] + '-find', '');
  92. var _with = GM_getValue(domains[i] + '-with', '');
  93. var box = d.createElement('div');
  94. box.className = 'item';
  95. box.innerHTML = '<span>' + domains[i] + '</span><br /><span>' + _find + '</span>\
  96. <br /><span>' + _with + '</span><br /><button>Remove</button><br /><span>========</span>';
  97. if (d.body) d.body.appendChild(box);
  98. else d.documentElement.appendChild(box);
  99. $t('button', box)[0].addEventListener('click', tm_remove);
  100. }
  101. }
  102. // import
  103. var impList = d.createElement('textarea');
  104. impList.id = 'tm-import-list';
  105. d.documentElement.appendChild(impList);
  106. var imp = d.createElement('input');
  107. imp.type = 'button'; imp.id = 'tm-import'; imp.value = 'Import';
  108. d.documentElement.appendChild(imp);
  109. imp.addEventListener('click', tm_import);
  110. // sort button if 2 or more domains
  111. if (domains.length>1) {
  112. var sor = d.createElement('input');
  113. sor.type = 'button'; sor.id = 'tm-sort'; sor.value = 'Sort';
  114. d.documentElement.appendChild(sor);
  115. sor.addEventListener('click', tm_sort);
  116. // and export button
  117. var exo = d.createElement('input');
  118. exo.type = 'button'; exo.id = 'tm-export'; exo.value = 'Prepare for export';
  119. d.documentElement.appendChild(exo);
  120. exo.addEventListener('click', tm_export);
  121. }
  122. }
  123.  
  124. function tm_remove() {
  125. var item = this.parentNode;
  126. var _domain = $t('span', item)[0].innerHTML;
  127. GM_deleteValue(_domain + '-find');
  128. GM_deleteValue(_domain + '-with');
  129. var domains = GM_getValue('domains', '');
  130. var match = _domain.replace(/\./g, '\\.');
  131. // match: (^ or |) + single/current domain + ($ or |)
  132. var match = new RegExp('(^|\\|)' + match + '($|\\|)');
  133. // replace: matched single domain with ^ or |; replace: || with | or remove |$
  134. var domains = domains.replace(match, '$1').replace(/(\|)\||\|$/g, '$1');
  135. GM_setValue('domains', domains);
  136. item.parentNode.removeChild(item);
  137. // re-create buffer for domains
  138. GM_setValue('domains-deletable-buffer', GM_getValue('domains', '').replace(/\./g, '\\.'));
  139. }
  140.  
  141. function tm_import() {
  142. var d = document,
  143. list = $i('tm-import-list').value;
  144. var list = list.match(/.+/g).join('--------').replace(/(--------)+========$/, '').split('========--------');
  145. for(var i = 0, j = list.length; i < j; i++) {
  146. var listB = list[i].split('--------');
  147. if (listB[0] && listB[1]) tm_add('str', 'noescape', 'true', listB[0], listB[1], listB[2]);
  148. }
  149. // create buffer for domains, refresh page
  150. GM_setValue('domains-deletable-buffer', GM_getValue('domains', '').replace(/\./g, '\\.'));
  151. tm_manage();
  152. }
  153.  
  154. function tm_sort() {
  155. GM_setValue('domains', GM_getValue('domains', '').split('|').sort().join('|'));
  156. GM_setValue('domains-deletable-buffer', GM_getValue('domains', '').replace(/\./g, '\\.'));
  157. tm_manage();
  158. }
  159.  
  160. function tm_export() {
  161. // remove everything except the list
  162. var list = document.querySelectorAll('#grom-TitleManager, button, button+br, textarea, input, body>*:not(.item)');
  163. for (var i = 0, j = list.length; i < j; i++) { list[i].parentNode.removeChild(list[i]); }
  164. alert('=== Title Manager ===\n\nList prepared for export. You\'ll need to:\
  165. \nSelect all text on the page (Ctrl-A) and copy it (Ctrl-C).\nThen paste that text anywhere you want, perhaps in a new file.')
  166. }
  167.  
  168. function tm_QuickMenu() {
  169. var d = document,
  170. box = d.createElement('div'),
  171. isThere = $i('grom-TitleManager');
  172. if (isThere) {
  173. isThere.parentNode.removeChild(isThere);
  174. return;
  175. }
  176. box.style = 'max-width:50em;margin:0 auto;padding:1em;text-align:center;border-top:3px solid;box-shadow:0 0 5px #999;';
  177. box.id = 'grom-TitleManager';
  178. box.innerHTML = '<style scoped>#tm-domain,#tm-find,#tm-with{width:100%;max-width:40em;float:right;}</style>\
  179. <h1 style="margin:initial;">Title Manager<input type="button" id="tm-close" value="X" style="float:right;" /></h1>\
  180. <p>Full title: "<strong>' + t + '</strong>"</p>\
  181. <p id="tm-error-notifier">Regex supported in "Search for" with single backslash <strong>\\</strong> as escaping character.</p>\
  182. <p>Domain: <input type="text" id="tm-domain" value="' + window.location.host.toLowerCase() + '" /></p>\
  183. <p>Search for: <input type="text" id="tm-find" value="' + t + '" /></p>\
  184. <p>Replace with: <input type="text" id="tm-with" value="" /></p>\
  185. <p><input type="button" id="tm-add" value="Add" /> or <input type="button" id="tm-add-regex" value="Add as regex" />\
  186. &nbsp; &nbsp; &nbsp; <input type="button" id="tm-manage" value="View and manage all title rules" /></p>\
  187. <br /><br />';
  188. if (d.body) d.body.appendChild(box);
  189. else d.documentElement.appendChild(box);
  190. box.scrollIntoView();
  191. $i('tm-add') .addEventListener('click', function() { tm_add('str','escape') });
  192. $i('tm-add-regex').addEventListener('click', function() { tm_add('reg','noescape') });
  193. $i('tm-manage') .addEventListener('click', tm_manage);
  194. $i('tm-close') .addEventListener('click', tm_QuickMenu);
  195. }
  196.  
  197. GM_registerMenuCommand("Title Manager's menu", tm_QuickMenu);