MeFi Neater Bylines

MetaFilter: neaten up comment bylines

  1. // ==UserScript==
  2. // @name MeFi Neater Bylines
  3. // @namespace https://github.com/klipspringr/mefi-userscripts
  4. // @version 2025-08-14-b
  5. // @description MetaFilter: neaten up comment bylines
  6. // @author Klipspringer
  7. // @supportURL https://github.com/klipspringr/mefi-userscripts
  8. // @license MIT
  9. // @match *://*.metafilter.com/*
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_registerMenuCommand
  13. // ==/UserScript==
  14.  
  15. (async () => {
  16. if (
  17. !/^\/(\d|comments\.mefi)/.test(window.location.pathname) ||
  18. /rss$/.test(window.location.pathname)
  19. )
  20. return;
  21.  
  22. const KEY_CUSTOM_LABEL = "custom-quote-button-label";
  23. const SVG_REPLY = `<svg xmlns="http://www.w3.org/2000/svg" hidden style="display:none"><path id="mrql-reply" fill="currentColor" d="M 69.941 48.117 C 72.437 48.117 74.776 47.63 76.959 46.657 C 79.142 45.684 81.043 44.37 82.66 42.715 C 84.279 41.06 85.555 39.134 86.491 36.934 C 87.427 34.735 87.894 32.39 87.894 29.898 C 87.894 27.406 87.427 25.071 86.491 22.891 C 85.555 20.711 84.279 18.803 82.66 17.169 C 81.043 15.532 79.142 14.248 76.959 13.314 C 74.776 12.379 72.437 11.912 69.941 11.912 L 68.071 12.087 L 68.071 0 L 69.941 0 C 74.074 0 77.963 0.779 81.608 2.336 C 85.253 3.892 88.441 6.024 91.169 8.729 C 93.899 11.435 96.053 14.608 97.632 18.249 C 99.211 21.888 100 25.772 100 29.898 C 100 34.025 99.211 37.908 97.632 41.548 C 96.053 45.187 93.899 48.37 91.169 51.095 C 88.441 53.82 85.253 55.972 81.608 57.548 C 77.963 59.125 74.074 59.913 69.941 59.913 L 23.626 59.913 L 44.036 80 L 27.953 80 L 0 53.723 L 27.953 28.028 L 44.036 28.028 L 23.626 48.117 L 69.941 48.117 Z" /></svg>`;
  24. const SVG_USE_REPLY = `<svg xmlns="http://www.w3.org/2000/svg" width="1em" viewBox="0 0 100 100" style="vertical-align:middle"><use href="#mrql-reply" /></svg>`;
  25.  
  26. const getSetting = async (key, def = "") => {
  27. const value = await GM_getValue(key, def);
  28. return String(value);
  29. };
  30.  
  31. const handleEditLabel = async () => {
  32. const existing = await GM_getValue(KEY_CUSTOM_LABEL, "");
  33. const edited = prompt(
  34. "Quote button label (leave blank for reply arrow):",
  35. String(existing)
  36. );
  37. await GM_setValue(KEY_CUSTOM_LABEL, edited || "");
  38. await modifyQuoteButtons();
  39. };
  40.  
  41. GM_registerMenuCommand("Edit quote button", handleEditLabel);
  42.  
  43. const modifyQuoteButton = (quoteButtonNode, label) => {
  44. // replace quote button content
  45. quoteButtonNode.innerHTML = label;
  46.  
  47. if (quoteButtonNode.hasAttribute("data-mrql-moved")) return;
  48.  
  49. // move quote button to before flag button
  50. quoteButtonNode.parentNode.parentNode
  51. .querySelector("span[id^='flag']")
  52. ?.before(quoteButtonNode.parentNode);
  53.  
  54. // mark as done so we don't pick this up again
  55. quoteButtonNode.setAttribute("data-mrql-moved", "");
  56. };
  57.  
  58. const modifyQuoteButtons = async () => {
  59. const start = performance.now();
  60.  
  61. const customLabel = await getSetting(KEY_CUSTOM_LABEL, "");
  62. const label = customLabel || SVG_USE_REPLY;
  63.  
  64. const nodes = document.querySelectorAll("a.quotebutton");
  65.  
  66. nodes.forEach((node) => modifyQuoteButton(node, label));
  67.  
  68. console.log(
  69. "mefi-replace-quote-label",
  70. nodes.length,
  71. Math.round(performance.now() - start) + "ms"
  72. );
  73. };
  74.  
  75. document.body.insertAdjacentHTML("beforeend", SVG_REPLY);
  76.  
  77. // MefiQuote listens for the "mefi-comments" event, but:
  78. // (a) my event listener wasn't picking that up for some reason; and
  79. // (b) there could be timing issues as MefiQuote needs to complete its work first
  80. // hence using MutationObserver instead.
  81. const newCommentsElement = document.getElementById("newcomments");
  82. if (newCommentsElement) {
  83. const observer = new MutationObserver(() => modifyQuoteButtons());
  84. observer.observe(newCommentsElement, { childList: true });
  85. }
  86.  
  87. modifyQuoteButtons();
  88. })();