🏠 Home 

Slack: Quick Edit Button

Add quick edit button to hover toolbar


Install this script?
  1. // ==UserScript==
  2. // @name Slack: Quick Edit Button
  3. // @namespace Violentmonkey Scripts
  4. // @match https://app.slack.com/client/*
  5. // @grant none
  6. // @version 1.1
  7. // @author GorvGoyl
  8. // @supportURL https://github.com/gorvGoyl/
  9. // @description Add quick edit button to hover toolbar
  10. // @description 7/9/2024, 9:54:40 PM
  11. // @license MIT
  12. // ==/UserScript==
  13. var sender = "";
  14. // Function to create and insert the button
  15. function insertButton() {
  16. // Check if the button already exists to avoid duplicates
  17. if (isEditPresent()) {
  18. console.debug("btn already present so skipping");
  19. return;
  20. }
  21. // Create the new button element
  22. const newButton = document.createElement("button");
  23. // Set the button classes
  24. newButton.classList.add(
  25. "c-button-unstyled",
  26. "c-icon_button",
  27. "c-icon_button--size_small",
  28. "c-message_actions__button",
  29. "c-icon_button--default",
  30. "custom-edit-button"
  31. );
  32. // Set the SVG content inside the button
  33. newButton.innerHTML = `
  34. <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24">
  35. <path fill="currentColor" d="M5 19h1.425L16.2 9.225L14.775 7.8L5 17.575zm-1 2q-.425 0-.712-.288T3 20v-2.425q0-.4.15-.763t.425-.637L16.2 3.575q.3-.275.663-.425t.762-.15t.775.15t.65.45L20.425 5q.3.275.437.65T21 6.4q0 .4-.138.763t-.437.662l-12.6 12.6q-.275.275-.638.425t-.762.15zM19 6.4L17.6 5zm-3.525 2.125l-.7-.725L16.2 9.225z"/>
  36. </svg>
  37. `;
  38. // Set the onClick behavior
  39. newButton.onclick = async () => {
  40. // Find and click the "more_message_actions" button
  41. const moreActionsButton = document.querySelector(
  42. 'button[data-qa="more_message_actions"]'
  43. );
  44. if (moreActionsButton) {
  45. moreActionsButton.click();
  46. const editMessageButton = await getElement(
  47. 'button[data-qa="edit_message"]'
  48. );
  49. if (editMessageButton) {
  50. editMessageButton.click();
  51. setTimeout(() => {
  52. getMessageActionsContainer()?.remove();
  53. }, 400);
  54. }
  55. }
  56. };
  57. // Find the "start_thread" button
  58. const startThreadButton = document.querySelector(
  59. 'button[data-qa="start_thread"]'
  60. );
  61. // Insert the new button before the "start_thread" button
  62. if (startThreadButton) {
  63. startThreadButton.parentNode.insertBefore(newButton, startThreadButton);
  64. console.debug("btn added");
  65. }
  66. }
  67. function debounce(func, wait) {
  68. let timeout;
  69. return function (...args) {
  70. clearTimeout(timeout);
  71. timeout = setTimeout(() => func.apply(this, args), wait);
  72. };
  73. }
  74. // Function to observe mutations with debounce
  75. function observeMutations(targetNode) {
  76. // console.debug("observeMutations");
  77. const debouncedInsertButton = debounce(insertButton, 50); // 200ms debounce
  78. // Create a mutation observer
  79. const observer = new MutationObserver((mutationsList) => {
  80. for (const mutation of mutationsList) {
  81. if (mutation.type === "childList" || mutation.type === "attributes") {
  82. const messageActionsContainer = getMessageActionsContainer();
  83. if (
  84. messageActionsContainer &&
  85. isSender(messageActionsContainer) &&
  86. !isEditPresent()
  87. ) {
  88. debouncedInsertButton();
  89. }
  90. }
  91. }
  92. });
  93. // Configure the observer to watch for changes in the subtree
  94. observer.observe(targetNode, {
  95. attributes: true,
  96. childList: true,
  97. subtree: true,
  98. });
  99. }
  100. function getMessageActionsContainer() {
  101. return document.querySelector(
  102. "div.c-message_actions__container.c-message__actions>div.c-message_actions__group"
  103. );
  104. }
  105. // Find the slack kit list element and add a hover event listener to start observing
  106. async function init() {
  107. const slackKitList = await getElement("div.p-workspace__primary_view_body");
  108. if (slackKitList) {
  109. observeMutations(slackKitList);
  110. } else {
  111. console.error("couldnt find element");
  112. }
  113. }
  114. init();
  115. function isSender(messageActionsContainer) {
  116. if (!sender) {
  117. sender = document
  118. .querySelector('[data-qa="user-button"]')
  119. ?.getAttribute("aria-label")
  120. ?.replace("User: ", "");
  121. }
  122. if (!sender) {
  123. return;
  124. }
  125. const kitactions = messageActionsContainer?.closest(
  126. "div.c-message_kit__actions"
  127. );
  128. if (!kitactions) {
  129. return false;
  130. }
  131. // if it's the first row
  132. let name = kitactions
  133. ?.querySelector('button[data-qa="message_sender_name"]')
  134. ?.innerText?.trim();
  135. // if it's the 2nd row
  136. if (!name) {
  137. name = kitactions
  138. ?.querySelector("div.c-message_kit__gutter__right>span.offscreen")
  139. ?.innerText?.trim();
  140. }
  141. return name == sender;
  142. }
  143. function isEditPresent() {
  144. return document.querySelector("button.custom-edit-button");
  145. }
  146. // helper menthod: get element whenever it becomes available
  147. function getElement(selector) {
  148. return new Promise((resolve, reject) => {
  149. // Check if the element already exists
  150. const element = document.querySelector(selector);
  151. if (element) {
  152. resolve(element);
  153. return;
  154. }
  155. // Create a MutationObserver to listen for changes in the DOM
  156. const observer = new MutationObserver((mutations, observer) => {
  157. // Check for the element again within each mutation
  158. const element = document.querySelector(selector);
  159. if (element) {
  160. observer.disconnect(); // Stop observing
  161. resolve(element);
  162. }
  163. });
  164. // Start observing the document body for child list changes
  165. observer.observe(document.body, { childList: true, subtree: true });
  166. // Set a timeout to reject the promise if the element isn't found within 10 seconds
  167. const timeoutId = setTimeout(() => {
  168. observer.disconnect(); // Ensure to disconnect the observer to prevent memory leaks
  169. resolve(null); // Resolve with null instead of rejecting to indicate the timeout without throwing an error
  170. }, 10000); // 10 seconds
  171. // Ensure that if the element is found and the observer is disconnected, we also clear the timeout
  172. observer.takeRecords().forEach((record) => {
  173. clearTimeout(timeoutId);
  174. });
  175. });
  176. }