usaidbinkhalidkhan / Translate Selection Sidebar and Tooltip

// ==UserScript==
// @name         Translate Selection Sidebar and Tooltip
// @namespace    https://aveusaid.wordpress.com
// @version      0.9082024
// @description  Translate selected text, show in a tooltip, add to a sidebar list, and store in local storage
// @author       Usaid Bin Khalid Khan
// @match        *://*/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // Add CSS for the tooltip, sidebar, buttons, and dark mode text
  const styleElement = document.createElement('style');
  styleElement.type = 'text/css';
  styleElement.innerHTML = `
        .translator-tooltip {
            font-weight: 700;
            color: #000000;
            position: absolute;
            z-index: 10000;
            padding: 8px 12px;
            max-width: 300px;
            border-radius: 0.3em;
            background-color: #ffffdb;
            border: 1px solid #ccc;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            text-align: center;
            font-size: 18px;
            line-height: 1.4;
            visibility: hidden;
            opacity: 0;
            transition: visibility 0s linear 300ms, opacity 300ms;
        }

        .translator-tooltip.visible {
            visibility: visible;
            opacity: 1;
        }

        .translator-sidebar {
            position: fixed;
            top: 0;
            right: 0;
            width: 280px;
            height: 100%;
            background-color: #f7f7f7;
            overflow-y: auto;
            border-left: 2px solid #3388CC;
            padding: 20px;
            z-index: 10000;
            font-size: 16px;
            overflow: auto;
            box-shadow: -2px 0 5px rgba(0,0,0,0.1);
            box-sizing: border-box;
            resize: horizontal;
            min-width: 200px;
            max-width: 500px;
            display: none;
        }

        .translator-sidebar > * {
            margin-bottom: 20px;
        }

        .translator-entry {
            display: flex;
            flex-direction: column;
            padding: 10px 0;
            margin-bottom: 10px;
            border-bottom: 1px solid #ccc;
        }

        .translator-entry span:first-child {
            margin-bottom: 6px;
            font-weight: bold;
            color: #333;
        }

        .translator-entry span:last-child {
            color: #666;
        }

        .close-sidebar {
            position: absolute;
            top: 10px;
            right: 10px;
            cursor: pointer;
            font-weight: bold;
            color: #555;
            font-size: 20px;
            background-color: transparent;
            border: none;
            z-index: 10001;
        }

        .attractive-text {
            display: block;
            font-size: 14px;
            font-style: italic;
            text-decoration: none;
            color: #ff6666;
            margin-top: 20px;
            text-align: center;
        }

        .attractive-text:hover {
            color: #ff3333;
        }

        .clear-button,
        .copy-all-button,
        .mode-toggle {
            cursor: pointer;
            padding: 10px 15px;
            background-color: #f5f5f5;
            border: 1px solid #ccc;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            font-size: 16px;
            margin: 10px 0;
            width: calc(100% - 20px);
            box-sizing: border-box;
        }

        .clear-button {
            margin-top: 20px;
        }

        .mode-toggle {
            display: flex;
            align-items: center;
            margin-top: 20px;
        }

        .mode-toggle span {
            margin-right: 10px;
        }

        .close-sidebar {
            position: absolute;
            top: 10px;
            right: 10px;
            cursor: pointer;
            font-weight: bold;
            color: #555;
            font-size: 20px;
            background-color: transparent;
            border: none;
            z-index: 10001;
        }

        .attractive-text {
            display: block;
            font-size: 14px;
            font-style: italic;
            text-decoration: none;
            color: #ff6666;
            margin-top: 20px;
            text-align: center;
        }

        .attractive-text:hover {
            color: #ff3333;
        }

        .error-message {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            padding: 20px;
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
            border-radius: 5px;
            z-index: 10002;
            font-size: 18px;
            text-align: center;
            visibility: hidden;
            opacity: 0;
            transition: visibility 0s linear 300ms, opacity 300ms;
        }

        .error-message.visible {
            visibility: visible;
            opacity: 1;
        }

        .open-sidebar-button {
            position: fixed;
            bottom: 140px;
            right: 20px;
            z-index: 10001;
            cursor: pointer;
            padding: 10px 15px;
            background-color: #f5f5f5;
            border: 1px solid #ccc;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            font-size: 16px;
            display: block;
        }

        .empty-sidebar {
            font-style: italic;
            color: grey;
            margin-top: 20px;
            text-align: center;
        }

        .dark-mode .translator-sidebar {
            background-color: #333333; /* Dark background color */
            color: #ffffff; /* White text */
        }

        .dark-mode .translator-entry span:first-child {
            color: #FFD700; /* Sunset yellow */
        }

        .dark-mode .translator-entry span:last-child {
            color: #ffffff; /* White */
        }

        .dark-mode .clear-button,
        .dark-mode .open-sidebar-button,
        .dark-mode .copy-all-button {
            background-color: #444;
            color: #FFD700; /* Sunset yellow */
            border-color: #666;
        }
    `;
  document.head.appendChild(styleElement);

  // Create tooltip element
  const tooltip = document.createElement('div');
  tooltip.className = 'translator-tooltip';
  document.body.appendChild(tooltip);

  // Create sidebar element
  const sidebar = document.createElement('div');
  sidebar.className = 'translator-sidebar resizable';
  document.body.appendChild(sidebar);

  // Create close button for sidebar
  const closeButton = document.createElement('button');
  closeButton.innerHTML = '×';
  closeButton.className = 'close-sidebar';
  closeButton.setAttribute('aria-label', 'Close sidebar');
  sidebar.appendChild(closeButton);

  // Create "Cogito, Ergo Sum" text
  const attractText = document.createElement('a');
  attractText.innerHTML = 'Cogito, Ergo Sum';
  attractText.href = 'https://aveusaid.wordpress.com';
  attractText.className = 'attractive-text';
  attractText.setAttribute('role', 'link');
  attractText.setAttribute('aria-label', 'Cogito, Ergo Sum');
  sidebar.appendChild(attractText);

  // Add two italic lines for empty sidebar
  const emptyLines = document.createElement('div');
  emptyLines.className = 'empty-sidebar';
  emptyLines.innerHTML = `
        <i>The Archives are empty.</i> <br>
        <i>Select some text to add it to the Translation Archives.</i>
    `;
  sidebar.appendChild(emptyLines);

  // Create clear button
  const clearButton = document.createElement('button');
  clearButton.textContent = 'Tabula Rasa';
  clearButton.className = 'clear-button';
  clearButton.setAttribute('aria-label', 'Clear translations');
  sidebar.appendChild(clearButton);

  // Create copy all button
  const copyAllButton = document.createElement('button');
  copyAllButton.textContent = 'Copy All';
  copyAllButton.className = 'copy-all-button';
  copyAllButton.setAttribute('aria-label', 'Copy all translations');
  sidebar.appendChild(copyAllButton);

  // Create mode toggle button
  const modeToggleButton = document.createElement('button');
  modeToggleButton.className = 'mode-toggle';
  modeToggleButton.textContent = 'Dim the Lights';
  sidebar.appendChild(modeToggleButton);

  // Create open sidebar button
  const openSidebarButton = document.createElement('button');
  openSidebarButton.textContent = 'The Archive';
  openSidebarButton.className = 'open-sidebar-button';
  openSidebarButton.setAttribute('aria-label', 'Open translation archive');
  document.body.appendChild(openSidebarButton);

  // Error message element
  const errorMessage = document.createElement('div');
  errorMessage.className = 'error-message';
  document.body.appendChild(errorMessage);

  let translations = [];

  function showError(message) {
    errorMessage.textContent = message;
    errorMessage.classList.add('visible');
    setTimeout(() => {
      errorMessage.classList.remove('visible');
    }, 3000);
  }

  function translateText(text) {
    return fetch(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=${encodeURIComponent(text)}`)
      .then(response => response.json())
      .then(result => result[0][0][0])
      .catch(error => {
        showError('ERROR: The Network Towers Have Fallen!');
        console.error(error);
      });
  }

  function updateSidebar() {
    sidebar.innerHTML = '';
    sidebar.appendChild(closeButton);
    sidebar.appendChild(attractText);
    if (translations.length === 0) {
      sidebar.appendChild(emptyLines);
    }
    else {
      translations.forEach(({
        original,
        translated
      }) => {
        const entry = document.createElement('div');
        entry.className = 'translator-entry';
        entry.innerHTML = `
                    <span>${original}</span>
                    <span>${translated}</span>
                `;
        sidebar.appendChild(entry);
      });
    }
    sidebar.appendChild(clearButton);
    sidebar.appendChild(copyAllButton);
    sidebar.appendChild(modeToggleButton);
  }

  function toggleSidebar() {
    if (sidebar.style.display === 'block') {
      sidebar.style.display = 'none';
      openSidebarButton.style.display = 'block'; // Show "The Archive" button
    }
    else {
      updateSidebar();
      sidebar.style.display = 'block';
      openSidebarButton.style.display = 'none'; // Hide "The Archive" button
    }
  }

  function clearTranslations() {
    translations = [];
    updateSidebar();
  }

  function copyAllTranslations() {
    const allTranslations = translations.map(({
      original,
      translated
    }) => `${original}: ${translated}`).join('\n');
    navigator.clipboard.writeText(allTranslations).then(() => {
      showError('Translations copied to clipboard!');
    }).catch(() => {
      showError('Failed to copy translations.');
    });
  }

  function toggleDarkMode() {
    document.body.classList.toggle('dark-mode');
    const isDarkMode = document.body.classList.contains('dark-mode');
    modeToggleButton.textContent = isDarkMode ? 'Light the Way' : 'Dim the Lights';
  }

  document.addEventListener('mouseup', async (e) => {
    if (window.getSelection().toString()) {
      const selectedText = window.getSelection().toString();
      const translatedText = await translateText(selectedText);
      translations.push({
        original: selectedText,
        translated: translatedText
      });
      updateSidebar();
      tooltip.textContent = translatedText;
      tooltip.style.left = `${e.pageX}px`;
      tooltip.style.top = `${e.pageY + 10}px`;
      tooltip.classList.add('visible');
      setTimeout(() => {
        tooltip.classList.remove('visible');
      }, 3000);
    }
  });

  openSidebarButton.addEventListener('click', toggleSidebar);
  closeButton.addEventListener('click', toggleSidebar);
  clearButton.addEventListener('click', clearTranslations);
  copyAllButton.addEventListener('click', copyAllTranslations);
  modeToggleButton.addEventListener('click', toggleDarkMode);

})();