Custom Title

【使用前先看介绍/有问题可反馈】自定义标题 (Custom Title): 支持自定义标题

  1. // ==UserScript==
  2. // @name Custom Title
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.0
  5. // @description 【使用前先看介绍/有问题可反馈】自定义标题 (Custom Title): 支持自定义标题
  6. // @author cc
  7. // @include *
  8. // @license MIT
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // storage
  17. let storage;
  18. const _VERSION_ = '1.0.0';
  19. const focusKey = 'KeyT'; // T
  20. const clearKey = 'Digit1'; // 1
  21. const expiredTime = 30 * 86400 * 1000; // 30 day
  22. const interval = 86400 * 1000; // 1 day
  23.  
  24. function notify(msg) {
  25. console.log(`%c${msg}`, 'background-color: yellow; color: black;');
  26. }
  27.  
  28. function getDefaultStorage() {
  29. return {
  30. lastUpdateTime: new Date().getTime(),
  31. titleInfoDict: {},
  32. }
  33. }
  34.  
  35. function loadStorage() {
  36. storage = GM_getValue('storage');
  37. if (!storage) {
  38. storage = getDefaultStorage();
  39. GM_setValue('storage', storage);
  40. notify('[Custom Title]: storage initialized');
  41. console.log(storage);
  42. } else {
  43. notify('[Custom Title]: storage down here');
  44. console.log(storage);
  45. clearExpiredTitle();
  46. }
  47. }
  48.  
  49. function applyTitle() {
  50. let titleInfo = storage.titleInfoDict[location.href];
  51. if (titleInfo && document.title !== titleInfo.newTitle) {
  52. document.title = titleInfo.newTitle;
  53. notify('[Custom Title]: applied title');
  54. } else if (titleInfo) {
  55. notify('[Custom Title]: no need to replace title');
  56. } else {
  57. notify('[Custom Title]: no title can be appied');
  58. }
  59. }
  60.  
  61. // functions
  62. function updateTitle() {
  63. let titleInfo = storage.titleInfoDict[location.href];
  64. let newTitle = prompt('请输入需要为此网页自定义的标题,可通过 \'Ctrl+Shift+T\' 或直接输入空以取消:', titleInfo ? titleInfo.newTitle : document.title);
  65. if (newTitle) {
  66. storage.titleInfoDict[location.href] = {
  67. oldTitle: titleInfo ? titleInfo.oldTitle : document.title,
  68. newTitle: newTitle,
  69. lastUpdateTime: new Date().getTime(),
  70. };
  71. document.title = newTitle;
  72. GM_setValue('storage', storage);
  73. notify('[Custom Title]: updated title');
  74. } else if (newTitle !== null && newTitle.constructor === String) {
  75. removeTitle();
  76. }
  77. }
  78.  
  79. function removeTitle() {
  80. let titleInfo = storage.titleInfoDict[location.href];
  81. if (titleInfo) {
  82. let res = confirm('是否为此网页移除自定义标题?');
  83. if (res) {
  84. document.title = titleInfo.oldTitle;
  85. delete storage.titleInfoDict[location.href];
  86. GM_setValue('storage', storage);
  87. notify('[Custom Title]: removed title');
  88. }
  89. } else {
  90. alert('你还未为此网页设置自定义标题');
  91. }
  92. }
  93.  
  94. function removeAllTitles() {
  95. let res = confirm('是否清除所有自定义标题?');
  96. if (res) {
  97. let titleInfo = storage.titleInfoDict[location.href];
  98. if (titleInfo) {
  99. document.title = titleInfo.oldTitle;
  100. }
  101. storage = getDefaultStorage();
  102. GM_setValue('storage', storage);
  103. alert('已清除所有自定义标题');
  104. notify('[Custom Title]: removed all titles');
  105. }
  106. }
  107.  
  108. function clearExpiredTitle() {
  109. let currentTime = new Date().getTime();
  110. if (storage.lastUpdateTime + interval < currentTime) {
  111. let titleInfoDict = storage.titleInfoDict;
  112. for (let key of Object.keys(titleInfoDict)) {
  113. if (titleInfoDict[key].lastUpdateTime + expiredTime < currentTime) {
  114. delete titleInfoDict[key];
  115. }
  116. }
  117. storage.titleInfoDict = titleInfoDict;
  118. storage.lastUpdateTime = currentTime;
  119. GM_setValue('storage', storage);
  120. notify('[Custom Title]: cleared expired titles');
  121. }
  122. }
  123.  
  124. function bindObserver() {
  125. // key observer
  126. document.onkeydown = function(e) {
  127. console.log(e);
  128.  
  129. if (e.ctrlKey && e.shiftKey && e.code == focusKey) {
  130. e.preventDefault();
  131. removeTitle();
  132. notify('[Custom Title]: trigger remove title');
  133. } else if (e.ctrlKey && e.code == focusKey) {
  134. e.preventDefault();
  135. updateTitle();
  136. notify('[Custom Title]: trigger update title');
  137. } else if (e.ctrlKey && e.code == clearKey) {
  138. e.preventDefault();
  139. removeAllTitles();
  140. notify('[Custom Title]: trigger remove all titles');
  141. }
  142. }
  143. // title observer
  144. let title = document.querySelector('title');
  145. let observer = new MutationObserver(function(mutations) {
  146. console.log(mutations);
  147. applyTitle();
  148. notify('[Custom Title]: trigger title change event');
  149. });
  150. let config = { subtree: true, characterData: true, childList: true };
  151. observer.observe(title, config);
  152.  
  153. // hash observer
  154. window.onhashchange = function() {
  155. applyTitle();
  156. notify('[Custom Title]: trigger hash change event');
  157. };
  158.  
  159. // call apply again
  160. applyTitle();
  161. }
  162.  
  163. // main function
  164. document.onreadystatechange = function() {
  165. if (document.readyState === 'complete') {
  166. notify(`[Custom Title]: version ${_VERSION_}`);
  167. loadStorage();
  168. applyTitle();
  169. bindObserver();
  170. }
  171. }
  172. })();