AO3: [Wrangling] Find the Tag in the Blurb

Make the tag of the page you're on (and up to 300 synonymous/meta/sub/parent/child tags) more visually distinct

  1. // ==UserScript==
  2. // @name AO3: [Wrangling] Find the Tag in the Blurb
  3. // @namespace https://github.com/RhineCloud
  4. // @version 1.2
  5. // @description Make the tag of the page you're on (and up to 300 synonymous/meta/sub/parent/child tags) more visually distinct
  6. // @grant none
  7. // @author Rhine
  8. // @include /^https?:\/\/[^\/]*archiveofourown.org\/(works|bookmarks)\?.*tag_id=.+/
  9. // @include /^https?:\/\/[^\/]*archiveofourown.org\/tags\/[^\/]+(\/((works|bookmarks).*)?)?$/
  10. // @exclude /^https?:\/\/[^\/]*archiveofourown.org\/tags\/[^\/]+\/(edit|wrangle|comments|troubleshooting).*/
  11. // @license GPL-3.0 <https://www.gnu.org/licenses/gpl.html>
  12. // ==/UserScript==
  13.  
  14.  
  15. // CSS SETTINGS
  16. // DO NOT change the first string in each line
  17. // adjust the second string in each line as you like, any valid CSS works
  18. // delete the entire line if you are not ever at all interested in having a specific kind of related tag highlighted
  19. const STYLE_MAP = new Map([
  20. ["", "color: #fff; background: #900;"],
  21. ["synonym", "color: #fff; background: rgba(153, 0, 0, 75%);"],
  22. ["meta", "color: #fff; text-shadow: 0 0 5px #900;"],
  23. ["sub", "color: #fff; background: #c00;"],
  24. ["parent", "color: #fff; text-shadow: 0 0 5px #099; font-style: oblique;"],
  25. ["child", "color: #fff; background: #090; font-weight: bold;"]
  26. ]);
  27.  
  28. // CACHE SETTINGS
  29. // set these to either true or false
  30. // automatically cache related tags
  31. const AUTO_CACHE_RELATED = false
  32. // show the "Cache related tags" button
  33. const SHOW_CACHE_BUTTON = true
  34.  
  35.  
  36. // MAIN FUNCTION
  37. // find matching tags in blurbs and add styling
  38. const PATHNAME = window.location.pathname;
  39. let tag_id = PATHNAME.startsWith("/tags/") ? PATHNAME.split("/")[2] :
  40. document.querySelector("#main h2.heading a.tag").getAttribute("href").split("/")[2];
  41. style_tag(tag_id, STYLE_MAP.get(""));
  42.  
  43. if (1 < STYLE_MAP.size) {
  44. // find corresponding tag canonical if on tags landing page of a synonym
  45. if (document.querySelector("div.merger.module")) {
  46. tag_id = document.querySelector("div.merger.module a.tag").getAttribute("href").split("/")[2];
  47. }
  48.  
  49. // automatically cache related tags if applicable
  50. if (AUTO_CACHE_RELATED && sessionStorage.getItem("cached_tag") !== tag_id) {
  51. cache_related_tags().then(() => {});
  52. }
  53.  
  54. // find further related tags if applicable
  55. const button = document.createElement("li");
  56. if (sessionStorage.getItem("cached_tag") === tag_id) {
  57. button.innerHTML = `<span class="current">Cached related tags!</span>`;
  58. style_related_tags();
  59. } else {
  60. button.setAttribute("id", "cache_related_tags");
  61. button.innerHTML = "<a>Cache related tags</a>";
  62. button.addEventListener("click", cache_related_tags);
  63. }
  64. if (SHOW_CACHE_BUTTON) {
  65. document.querySelector("#main ul.navigation.actions").prepend(button);
  66. }
  67. }
  68.  
  69.  
  70. // HELPER FUNCTIONS
  71. // apply styling to appearances of a specific tag in all blurbs
  72. function style_tag(tag_id, style) {
  73. document.querySelectorAll(`li.blurb a.tag[href="/tags/${tag_id}/works"], li.blurb a.tag[href="/tags/${tag_id}/bookmarks"]`)
  74. .forEach(tag => tag.setAttribute("style", style));
  75. }
  76.  
  77. // use sessionStorage to effectively cache info on related tags (per window/tab)
  78. // and apply styling on related tags once they're cached
  79. async function cache_related_tags() {
  80. const tag_home = 3 === PATHNAME.split("/").length ? document.querySelector("#main div.tag.home") :
  81. await fetch(`/tags/${tag_id}`).then(response => response.text()).then(html => {
  82. const parser = new DOMParser();
  83. const doc = parser.parseFromString(html, "text/html");
  84. return doc.querySelector("#main div.tag.home");
  85. });
  86.  
  87. for (let type of STYLE_MAP.keys()) {
  88. if (type) {
  89. cache_relation(type, tag_home);
  90. }
  91. }
  92. sessionStorage.setItem("cached_tag", tag_id);
  93.  
  94. if (SHOW_CACHE_BUTTON) {
  95. document.querySelector("#cache_related_tags").innerHTML = `<span class="current">Cached related tags!</span>`;
  96. document.querySelector("#cache_related_tags").removeEventListener("click", cache_related_tags);
  97. }
  98. style_related_tags();
  99. }
  100.  
  101. // cache one type of relation in sessionStorage
  102. function cache_relation(type, tag_home) {
  103. let tags = tag_home.querySelectorAll(`div.${type} a.tag`);
  104. tags = nodes_to_strings(tags);
  105. sessionStorage.setItem(`cached_${type}_tags`, tags.join(","));
  106. }
  107.  
  108. // transform list of nodes (linked tags from the page) into plain strings for sessionStorage
  109. function nodes_to_strings(node_list) {
  110. const strings = [];
  111. for (let node of node_list.values()) {
  112. strings.push(node.getAttribute("href").split("/")[2]);
  113. }
  114. return strings;
  115. }
  116.  
  117. // apply styling to all tags of all relation types in all blurbs
  118. function style_related_tags() {
  119. for (let type of STYLE_MAP.keys()) {
  120. if (type) {
  121. sessionStorage.getItem(`cached_${type}_tags`).split(",")
  122. .forEach(tag => style_tag(tag, STYLE_MAP.get(type)));
  123. }
  124. }
  125. }