Freshdesk Enhancement

add buttons: open user's stripe account, mixpanel page

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Freshdesk Enhancement
// @scriptURL   https://greasyfork.org/en/scripts/486895-freshdesk-enhancement
// @namespace   Violentmonkey Scripts
// @match       https://*.freshdesk.com/a/tickets/*
// @grant       none
// @version     1.5
// @author      GorvGoyl
// @supportURL  https://github.com/gorvGoyl/
// @description add buttons: open user's stripe account, mixpanel page
// @license MIT
// ==/UserScript==
async function init() {
  console.log("adding buttons");
  const existingButton = await getElement(
    'button[data-test-actions="tktDelete"]'
  );
  console.log("got reference btn");
  addStripeBtn(existingButton);
  addMixpanelBtn(existingButton);
  copyEmailBtn(existingButton);
  
  const styles = `.ticket_note>div>.gmail_quote>blockquote.gmail_quote {
    max-height: 200px;
    overflow: auto;
    background: #f5f5f4;
    border-radius: 6px;
}`;
  addCSS(styles);
}

function addStripeBtn(existingButton) {
  const id = "stripeAccountBtn";

  const btn = document.getElementById(id);

  if (btn) return;

  const newButton = document.createElement("button");
  newButton.id = id;
  newButton.innerHTML = "Stripe";

  newButton.style.border = "1px solid gray";
  newButton.style.padding = "4px 8px";
  newButton.style.borderRadius = "4px";
  newButton.style.margin = "0px 4px";

  newButton.addEventListener("click", openStripeAccount);

  newButton.setAttribute("title", "Open Stripe Account");

  existingButton.parentNode.insertBefore(newButton, existingButton.nextSibling);
}

function addMixpanelBtn(existingButton) {
  const id = "mixpanelUserPageBtn";

  const btn = document.getElementById(id);

  if (btn) return;

  const newButton = document.createElement("button");
  newButton.id = id;
  newButton.innerHTML = "Mixpanel";

  newButton.style.border = "1px solid gray";
  newButton.style.padding = "4px 8px";
  newButton.style.borderRadius = "4px";
  newButton.style.margin = "0px 4px";

  newButton.addEventListener("click", openMixpanelPage);

  newButton.setAttribute("title", "Open mixpanel users page");

  existingButton.parentNode.insertBefore(newButton, existingButton.nextSibling);
}

function copyEmailBtn(existingButton) {
  const id = "copyEmailBtn";

  const btn = document.getElementById(id);

  if (btn) return;

  const newButton = document.createElement("button");
  newButton.id = id;
  newButton.innerHTML = "Copy Email";

  newButton.style.border = "1px solid gray";
  newButton.style.padding = "4px 8px";
  newButton.style.borderRadius = "4px";
  newButton.style.margin = "0px 4px";

  newButton.addEventListener("click", copyEmail);

  newButton.setAttribute("title", "Copy email");

  existingButton.parentNode.insertBefore(newButton, existingButton.nextSibling);
}

async function openStripeAccount(e) {
  console.log("opening stripe account");
  e.preventDefault();

  const email = parseEmail();
  showToast("email copied: " + email);

  const url =
    `https://dashboard.stripe.com/search?query=` + encodeURIComponent(email);

  window.open(url, "stripeWindow");
}

async function copyEmail(e) {
  e.preventDefault();

  const email = parseEmail();

  await navigator.clipboard.writeText(email);

  showToast("email copied: " + email);
}

async function openMixpanelPage(e) {
  console.log("opening mixpanel page...");
  e.preventDefault();

  await copyEmail(e);

  const url = `https://mixpanel.com/project/2952645/view/3474349/app/users#CRgQ4fCX6zKj`;

  window.open(url, "mixpanelWindow");
}

function parseEmail() {
  const email = document
    .querySelector(
      "div.info-details-content.text__content.text--semibold._ar_redact_"
    )
    .innerText.trim();
  console.log("email", email);

  return email;
}
window.addEventListener("urlchange", function (e) {
  console.log(
    "URL changed to:",
    e.detail.url,
    "with state:",
    e.detail.state,
    "via:",
    e.detail.type
  );

  if (e.detail.url.includes("/tickets")) {
    init();
  }
});

init();

//-----------------------------HRLPER METHODS------------------------------//

function addCSS(css) {
    const styleSheet = document.createElement("style")
    styleSheet.innerText = css
    document.head.appendChild(styleSheet)
}

// helper menthod: get element whenever it becomes available
function getElement(selector) {
  return new Promise((resolve, reject) => {
    // Check if the element already exists
    const element = document.querySelector(selector);
    if (element) {
      resolve(element);
      return;
    }

    // Create a MutationObserver to listen for changes in the DOM
    const observer = new MutationObserver((mutations, observer) => {
      // Check for the element again within each mutation
      const element = document.querySelector(selector);
      if (element) {
        observer.disconnect(); // Stop observing
        resolve(element);
      }
    });

    // Start observing the document body for child list changes
    observer.observe(document.body, { childList: true, subtree: true });

    // Set a timeout to reject the promise if the element isn't found within 10 seconds
    const timeoutId = setTimeout(() => {
      observer.disconnect(); // Ensure to disconnect the observer to prevent memory leaks
      resolve(null); // Resolve with null instead of rejecting to indicate the timeout without throwing an error
    }, 10000); // 10 seconds

    // Ensure that if the element is found and the observer is disconnected, we also clear the timeout
    observer.takeRecords().forEach((record) => {
      clearTimeout(timeoutId);
    });
  });
}

// helper menthod: detect url changes in SPA sites
(function (history) {
  var originalPushState = history.pushState;
  var originalReplaceState = history.replaceState;

  history.pushState = function (state, title, url) {
    var fullUrl = url ? window.location.origin + url : window.location.href;
    var returnValue = originalPushState.apply(history, [state, title, url]);

    window.dispatchEvent(
      new CustomEvent("urlchange", {
        detail: {
          state: state,
          url: fullUrl,
          type: "pushState",
        },
      })
    );

    return returnValue;
  };

  history.replaceState = function (state, title, url) {
    var fullUrl = url ? window.location.origin + url : window.location.href;
    var returnValue = originalReplaceState.apply(history, [state, title, url]);

    window.dispatchEvent(
      new CustomEvent("urlchange", {
        detail: {
          state: state,
          url: fullUrl,
          type: "replaceState",
        },
      })
    );

    return returnValue;
  };
})(window.history);

// helper function: show toast

function showToast(text) {
  // Create the toast container div
  const toast = document.createElement("div");
  toast.textContent = text;

  toast.style.position = "fixed";
  toast.style.left = "50%";
  toast.style.bottom = "100px";
  toast.style.transform = "translateX(-50%)";
  toast.style.backgroundColor = "#000000c4";
  toast.style.color = "white";
  toast.style.padding = "10px";
  toast.style.borderRadius = "5px";
  toast.style.textAlign = "center";
  toast.style.zIndex = "9999";
  toast.style.pointerEvents = "none"; // Make it click-through

  document.body.appendChild(toast);

  // Remove the toast after 3 seconds
  setTimeout(() => {
    document.body.removeChild(toast);
  }, 3000);
}