Add Analytics Nav Item for X

Adds an Analytics nav item below Premium in the X.com sidebar

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Add Analytics Nav Item for X
// @namespace    https://github.com/Johnson1602/tampermonkey-scripts
// @version      0.0.2
// @description  Adds an Analytics nav item below Premium in the X.com sidebar
// @author       Weiyi Xu
// @license      MIT
// @match        https://x.com/*
// @match        https://twitter.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=x.com
// @grant        none
// ==/UserScript==

;(function () {
  'use strict'

  const ANALYTICS_URL = '/i/account_analytics'
  const ANALYTICS_LABEL = 'Analytics'

  // Analytics chart icon SVG path
  const ANALYTICS_ICON_PATH =
    'M8.75 21V3h2v18h-2zM18 21V8.5h2V21h-2zM4 21l.004-10h2L6 21H4zm9.248 0v-7h2v7h-2z'

  let analyticsTextContainer = null

  function createAnalyticsNavItem(referenceItem) {
    const analyticsItem = document.createElement('a')
    analyticsItem.href = ANALYTICS_URL
    analyticsItem.setAttribute('aria-label', ANALYTICS_LABEL)
    analyticsItem.setAttribute('role', 'link')
    analyticsItem.className = referenceItem.className
    analyticsItem.setAttribute('data-testid', 'AppTabBar_Analytics_Link')

    // Clone the inner structure from reference item
    const innerDiv = referenceItem.querySelector('div')
    if (innerDiv) {
      const clonedDiv = innerDiv.cloneNode(true)

      // Update the SVG icon
      const svg = clonedDiv.querySelector('svg')
      if (svg) {
        svg.innerHTML = `<g><path d="${ANALYTICS_ICON_PATH}"></path></g>`
      }

      // Update the text and store reference to text container
      const textSpans = clonedDiv.querySelectorAll('span')
      if (textSpans.length > 0) {
        textSpans[0].textContent = ANALYTICS_LABEL
        // The text container is the parent div of the spans
        analyticsTextContainer = textSpans[0].parentElement
      }

      // Add hover state handlers using inline styles
      analyticsItem.addEventListener('mouseenter', () => {
        const isDarkMode = document.documentElement.dataset.theme === 'dark'
        clonedDiv.style.backgroundColor = isDarkMode
          ? 'rgba(231, 233, 234, 0.1)'
          : 'rgba(15, 20, 25, 0.1)'
      })
      analyticsItem.addEventListener('mouseleave', () => {
        clonedDiv.style.backgroundColor = ''
      })

      analyticsItem.appendChild(clonedDiv)
    }

    return analyticsItem
  }

  function syncTextVisibility(referenceItem) {
    if (!analyticsTextContainer) return

    // Check if the reference item has visible text (has span elements)
    const refTextSpan = referenceItem.querySelector('span')
    const hasText = refTextSpan !== null

    if (hasText) {
      analyticsTextContainer.style.display = ''
    } else {
      analyticsTextContainer.style.display = 'none'
    }
  }

  function addAnalyticsNavItem() {
    // Find the Premium nav item, fallback to Home
    const premiumItem = document.querySelector(
      '[data-testid="premium-hub-tab"]'
    )
    const homeItem = document.querySelector(
      '[data-testid="AppTabBar_Home_Link"]'
    )
    const referenceItem = premiumItem || homeItem

    if (!referenceItem) {
      return
    }

    // Check if already added
    const existingItem = document.querySelector(
      '[data-testid="AppTabBar_Analytics_Link"]'
    )
    if (existingItem) {
      const refHasText = referenceItem.querySelector('span') !== null

      // If reference now has text but we don't have a text container, recreate the item
      if (refHasText && !analyticsTextContainer) {
        existingItem.remove()
        analyticsTextContainer = null
      } else {
        // Sync text visibility with reference item
        syncTextVisibility(referenceItem)
        return
      }
    }

    // Create and insert the Analytics nav item
    const analyticsItem = createAnalyticsNavItem(referenceItem)
    referenceItem.insertAdjacentElement('afterend', analyticsItem)
  }

  // Use MutationObserver to handle SPA navigation and responsive changes
  const observer = new MutationObserver(() => {
    addAnalyticsNavItem()
  })

  // Start observing
  observer.observe(document.body, {
    childList: true,
    subtree: true,
  })

  // Initial attempt
  addAnalyticsNavItem()
})()