Advertisement
pwtenny

A "read mode" script for Greasemonkey

Jul 2nd, 2025
156
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 18.12 KB | Software | 0 0
  1. // ==UserScript==
  2. // @name         Highlighted Text Popup
  3. // @namespace    http://www.greasespot.net/
  4. // @version      1.5 // Incrementing version number for these updates
  5. // @description  Adds a custom right-click menu to display highlighted text in a popup with font size and font family controls.
  6. // @author       Gemini
  7. // @match        *://*/*
  8. // @grant        none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12.     'use strict';
  13.  
  14.     console.log("GM_Script: Highlighted Text Popup script started.");
  15.  
  16.     // --- CSS for Custom Elements (now injected directly via JavaScript) ---
  17.     const css = `
  18.         #gm-custom-context-menu
  19.         {
  20.             position: fixed; /* Ensures it stays in position on scroll */
  21.             background-color: #f9f9f9;
  22.             border: 1px solid #ccc;
  23.             box-shadow: 2px 2px 8px rgba(0,0,0,0.2);
  24.             padding: 5px 0;
  25.             min-width: 180px;
  26.             z-index: 99999; /* Ensure it's on top of most page content */
  27.             display: none; /* Hidden by default */
  28.             font-family: sans-serif;
  29.             font-size: 14px;
  30.             user-select: none; /* Prevent text selection within the menu itself */
  31.  
  32.             /* NEW POSITIONING: Bottom-right with padding */
  33.             bottom: 20px; /* Padding from the bottom of the viewport */
  34.             right: 20px;  /* Padding from the right of the viewport */
  35.             left: auto;   /* Ensure 'right' takes precedence over 'left' */
  36.             top: auto;    /* Ensure 'bottom' takes precedence over 'top' */
  37.         }
  38.  
  39.         .gm-context-menu-item
  40.         {
  41.             padding: 8px 15px;
  42.             cursor: pointer;
  43.             color: #333;
  44.             white-space: nowrap;
  45.         }
  46.  
  47.         .gm-context-menu-item:hover
  48.         {
  49.             background-color: #e0e0e0;
  50.         }
  51.  
  52.         #gm-text-display-popup
  53.         {
  54.             position: fixed;
  55.             background-color: #ffffff;
  56.             border: 1px solid #a0a0a0;
  57.             box-shadow: 0 4px 12px rgba(0,0,0,0.3);
  58.             padding: 10px; /* Reduced padding to make space for menu bar */
  59.             z-index: 100000; /* Higher than the context menu */
  60.             top: 50%;
  61.             left: 50%;
  62.             transform: translate(-50%, -50%); /* Center the popup */
  63.             max-width: 650px; /* Comfortable reading width */
  64.             width: 90%; /* Responsive width */
  65.             /* UPDATED: More vertical space, 5% buffer top/bottom */
  66.             top: 5%;
  67.             transform: translateX(-50%); /* Only translate horizontally for centering */
  68.             max-height: 90vh; /* 100vh - 5% (top) - 5% (bottom) = 90vh */
  69.             overflow-y: auto; /* Enable vertical scrolling if content exceeds max-height */
  70.             display: none; /* Hidden by default; will be 'flex' when shown */
  71.             font-family: serif; /* Classic reading font fallback */
  72.             line-height: 1.6;
  73.             color: #222;
  74.             box-sizing: border-box; /* Include padding in width/height */
  75.             flex-direction: column; /* Stack children vertically - applied when display:flex */
  76.         }
  77.  
  78.         #gm-popup-close-button
  79.         {
  80.             position: absolute; /* Position relative to #gm-text-display-popup */
  81.             top: 10px;
  82.             right: 15px;
  83.             background: none;
  84.             border: none;
  85.             font-size: 20px;
  86.             cursor: pointer;
  87.             color: #555;
  88.             padding: 0;
  89.             line-height: 1;
  90.         }
  91.  
  92.         #gm-popup-close-button:hover
  93.         {
  94.             color: #000;
  95.         }
  96.  
  97.         #gm-popup-menu-bar
  98.         {
  99.             display: flex;
  100.             align-items: center;
  101.             justify-content: flex-start; /* Align items to the left */
  102.             padding: 5px 10px;
  103.             border-bottom: 1px solid #eee;
  104.             margin-bottom: 10px; /* Space between menu bar and content */
  105.         }
  106.  
  107.         .gm-font-size-button
  108.         {
  109.             background-color: #e0e0e0;
  110.             border: 1px solid #ccc;
  111.             border-radius: 4px;
  112.             padding: 4px 8px;
  113.             margin: 0 5px; /* Default margin for buttons */
  114.             cursor: pointer;
  115.             font-weight: bold;
  116.             font-size: 14px;
  117.             line-height: 1;
  118.             user-select: none;
  119.         }
  120.  
  121.         .gm-font-size-button:hover
  122.         {
  123.             background-color: #d0d0d0;
  124.         }
  125.  
  126.         /* Adjust margin for the first font size button to create left padding */
  127.         .gm-font-size-button:first-of-type {
  128.             margin-left: 15px; /* Moves the '-' button from the left edge */
  129.         }
  130.  
  131.         .gm-font-size-label
  132.         {
  133.             font-size: 13px;
  134.             color: #666;
  135.             margin: 0 5px;
  136.         }
  137.  
  138.         .gm-font-family-select {
  139.             margin-left: 15px; /* Space from the font size controls */
  140.             padding: 5px 8px;
  141.             border: 1px solid #ccc;
  142.             border-radius: 4px;
  143.             background-color: #f9f9f9;
  144.             font-size: 13px;
  145.             color: #333;
  146.             cursor: pointer;
  147.             min-width: 150px; /* Ensure enough width for longer names */
  148.         }
  149.  
  150.         .gm-font-family-select:hover {
  151.             background-color: #efefef;
  152.         }
  153.  
  154.         #gm-popup-content
  155.         {
  156.             flex-grow: 1; /* Allows content to take up remaining space */
  157.             padding: 10px; /* Add internal padding for content */
  158.             white-space: pre-wrap; /* Preserves whitespace and wraps text */
  159.             word-wrap: break-word; /* Breaks long words */
  160.             overflow-y: auto; /* Ensure content itself scrolls if needed */
  161.             margin-top: 0; /* Remove previous margin-top as menu bar handles spacing */
  162.         }
  163.     `;
  164.  
  165.     // Create a style element and append the CSS to it
  166.     const styleElement = document.createElement('style');
  167.     styleElement.textContent = css;
  168.     document.head.appendChild(styleElement);
  169.     console.log("GM_Script: CSS injected directly via <style> element.");
  170.  
  171.  
  172.     // --- Global Variables and Constants ---
  173.     let highlightedText = '';
  174.     let customMenu;
  175.     let textPopup;
  176.     let popupContent;
  177.  
  178.     const MIN_FONT_SIZE_PT = 8;  // Minimum font size in points
  179.     const MAX_FONT_SIZE_PT = 40; // Maximum font size in points
  180.     const DEFAULT_FONT_SIZE_PT = 17; // UPDATED: Default font size in points
  181.  
  182.     const FONT_FAMILIES = [
  183.         { name: "Arial", value: "Arial, sans-serif" },
  184.         { name: "Georgia", value: "Georgia, serif" },
  185.         { name: "Times New Roman", value: "Times New Roman, serif" },
  186.         { name: "Verdana", value: "Verdana, sans-serif" },
  187.         { name: "Adobe Garamond Pro", value: '"Adobe Garamond Pro", serif' } // Default font
  188.     ];
  189.     const DEFAULT_FONT_FAMILY_VALUE = '"Adobe Garamond Pro", serif'; // Value for the default font
  190.  
  191.     // --- Create Custom Context Menu ---
  192.     function createCustomContextMenu()
  193.     {
  194.         console.log("GM_Script: Entering createCustomContextMenu function.");
  195.         customMenu = document.createElement('div');
  196.         customMenu.id = 'gm-custom-context-menu';
  197.         document.body.appendChild(customMenu);
  198.         console.log("GM_Script: Custom menu DIV created and appended to body:", customMenu);
  199.  
  200.         const menuItem = document.createElement('div');
  201.         menuItem.classList.add('gm-context-menu-item');
  202.         menuItem.textContent = 'Show Highlighted Text in Popup';
  203.         customMenu.appendChild(menuItem);
  204.         console.log("GM_Script: Menu item added to custom menu:", menuItem);
  205.  
  206.         menuItem.addEventListener('click', function()
  207.         {
  208.             console.log("GM_Script: Custom menu item clicked.");
  209.             highlightedText = window.getSelection().toString().trim();
  210.             console.log("GM_Script: Captured highlighted text (raw):", window.getSelection().toString());
  211.             console.log("GM_Script: Captured highlighted text (trimmed):", highlightedText);
  212.  
  213.             if (highlightedText)
  214.             {
  215.                 popupContent.textContent = highlightedText;
  216.                 // Set default font size and family when new text is loaded
  217.                 popupContent.style.fontSize = DEFAULT_FONT_SIZE_PT + 'pt';
  218.                 popupContent.style.fontFamily = DEFAULT_FONT_FAMILY_VALUE;
  219.  
  220.                 // Reset font family select dropdown to default value in case it was changed
  221.                 if (window.gmFontFamilySelect)
  222.                 {
  223.                     window.gmFontFamilySelect.value = DEFAULT_FONT_FAMILY_VALUE;
  224.                     console.log("GM_Script: Font family select dropdown reset to default.");
  225.                 }
  226.  
  227.                 textPopup.style.display = 'flex'; // Set display to 'flex' to show the popup and enable flexbox layout
  228.                 console.log("GM_Script: Popup content updated and popup displayed.");
  229.             }
  230.             else
  231.             {
  232.                 alert('No text highlighted. Please highlight some text first.');
  233.                 console.log("GM_Script: No text highlighted, alert shown.");
  234.             }
  235.             customMenu.style.display = 'none'; // Hide menu after selection
  236.             console.log("GM_Script: Custom menu hidden after item click.");
  237.         });
  238.     }
  239.  
  240.     // --- Create Text Display Popup ---
  241.     function createTextDisplayPopup()
  242.     {
  243.         console.log("GM_Script: Entering createTextDisplayPopup function.");
  244.         textPopup = document.createElement('div');
  245.         textPopup.id = 'gm-text-display-popup';
  246.         document.body.appendChild(textPopup);
  247.         console.log("GM_Script: Popup DIV created and appended to body:", textPopup);
  248.  
  249.         // Close Button (direct child of textPopup for absolute positioning)
  250.         const closeButton = document.createElement('button');
  251.         closeButton.id = 'gm-popup-close-button';
  252.         closeButton.textContent = '✖'; // Unicode multiplication sign for 'X'
  253.         textPopup.appendChild(closeButton);
  254.         console.log("GM_Script: Close button added to popup.");
  255.  
  256.         // Menu Bar (contains font controls, direct child of textPopup)
  257.         const menuBar = document.createElement('div');
  258.         menuBar.id = 'gm-popup-menu-bar';
  259.         textPopup.appendChild(menuBar); // Append to textPopup before popupContent
  260.         console.log("GM_Script: Popup menu bar created.");
  261.  
  262.         // Font Size Controls (inside menuBar)
  263.         const decreaseFontButton = document.createElement('button');
  264.         decreaseFontButton.classList.add('gm-font-size-button');
  265.         decreaseFontButton.textContent = '-';
  266.         menuBar.appendChild(decreaseFontButton);
  267.         console.log("GM_Script: Decrease font button added.");
  268.  
  269.         const fontSizeLabel = document.createElement('span');
  270.         fontSizeLabel.classList.add('gm-font-size-label');
  271.         fontSizeLabel.textContent = 'font size';
  272.         menuBar.appendChild(fontSizeLabel);
  273.         console.log("GM_Script: Font size label added.");
  274.  
  275.         const increaseFontButton = document.createElement('button');
  276.         increaseFontButton.classList.add('gm-font-size-button');
  277.         increaseFontButton.textContent = '+';
  278.         menuBar.appendChild(increaseFontButton);
  279.         console.log("GM_Script: Increase font button added.");
  280.  
  281.         // Font Family Control (inside menuBar)
  282.         const fontFamilySelect = document.createElement('select');
  283.         fontFamilySelect.classList.add('gm-font-family-select');
  284.         menuBar.appendChild(fontFamilySelect);
  285.         console.log("GM_Script: Font family select dropdown created.");
  286.  
  287.         FONT_FAMILIES.forEach(font => {
  288.             const option = document.createElement('option');
  289.             option.value = font.value;
  290.             option.textContent = font.name;
  291.             fontFamilySelect.appendChild(option);
  292.         });
  293.         // Make the select element accessible globally (or more carefully scoped if preferred for complex scripts)
  294.         window.gmFontFamilySelect = fontFamilySelect; // This allows access from customMenu's click listener
  295.         console.log("GM_Script: Font family options populated and default set.");
  296.  
  297.         // Content Area (remains same)
  298.         popupContent = document.createElement('div');
  299.         popupContent.id = 'gm-popup-content';
  300.         textPopup.appendChild(popupContent); // Append to textPopup, after menuBar
  301.         console.log("GM_Script: Popup content area added to popup.");
  302.  
  303.         // Event Listeners for Font Size
  304.         decreaseFontButton.addEventListener('click', function()
  305.         {
  306.             console.log("GM_Script: Decrease font size button clicked.");
  307.             let currentSizePx = parseFloat(window.getComputedStyle(popupContent).fontSize);
  308.             // Convert px to pt for comparison with pt-based limits (1pt = 1/72 inch, 1px = 1/96 inch, so 1px = 0.75pt)
  309.             let currentSizePt = currentSizePx * (72 / 96);
  310.             if (currentSizePt > MIN_FONT_SIZE_PT)
  311.             {
  312.                 // Decrement by 1pt and round to nearest whole number
  313.                 popupContent.style.fontSize = Math.round(currentSizePt - 1) + 'pt';
  314.                 console.log("GM_Script: Font size decreased to:", popupContent.style.fontSize);
  315.             }
  316.             else
  317.             {
  318.                 console.log("GM_Script: Minimum font size reached (", MIN_FONT_SIZE_PT, "pt).");
  319.             }
  320.         });
  321.  
  322.         increaseFontButton.addEventListener('click', function()
  323.         {
  324.             console.log("GM_Script: Increase font size button clicked.");
  325.             let currentSizePx = parseFloat(window.getComputedStyle(popupContent).fontSize);
  326.             let currentSizePt = currentSizePx * (72 / 96);
  327.             if (currentSizePt < MAX_FONT_SIZE_PT)
  328.             {
  329.                 // Increment by 1pt and round to nearest whole number
  330.                 popupContent.style.fontSize = Math.round(currentSizePt + 1) + 'pt';
  331.                 console.log("GM_Script: Font size increased to:", popupContent.style.fontSize);
  332.             }
  333.             else
  334.             {
  335.                 console.log("GM_Script: Maximum font size reached (", MAX_FONT_SIZE_PT, "pt).");
  336.             }
  337.         });
  338.  
  339.         // Event Listener for Font Family
  340.         fontFamilySelect.addEventListener('change', function()
  341.         {
  342.             const selectedFont = this.value;
  343.             popupContent.style.fontFamily = selectedFont;
  344.             console.log("GM_Script: Font family changed to:", selectedFont);
  345.         });
  346.  
  347.         closeButton.addEventListener('click', function()
  348.         {
  349.             console.log("GM_Script: Popup close button clicked.");
  350.             textPopup.style.display = 'none';
  351.             console.log("GM_Script: Popup hidden.");
  352.         });
  353.  
  354.         // Make the popup draggable
  355.         let isDragging = false;
  356.         let offsetX, offsetY;
  357.  
  358.         textPopup.addEventListener('mousedown', function(e)
  359.         {
  360.             // Only allow dragging if not clicking on a button, select, or the main content area.
  361.             // Drag should initiate from the menu bar background or other non-interactive parts.
  362.             if (e.target.closest('.gm-font-size-button') || e.target.closest('.gm-font-family-select') || e.target.id === 'gm-popup-close-button' || e.target.id === 'gm-popup-content')
  363.             {
  364.                 console.log("GM_Script: Mousedown on interactive element inside popup, preventing drag.");
  365.                 return;
  366.             }
  367.             isDragging = true;
  368.             offsetX = e.clientX - textPopup.getBoundingClientRect().left;
  369.             offsetY = e.clientY - textPopup.getBoundingClientRect().top;
  370.             textPopup.style.cursor = 'grabbing';
  371.             textPopup.style.transition = 'none'; // Disable transition during drag
  372.             console.log("GM_Script: Popup dragging started. Offset X:", offsetX, "Y:", offsetY);
  373.         });
  374.  
  375.         document.addEventListener('mousemove', function(e)
  376.         {
  377.             if (!isDragging) return;
  378.             textPopup.style.left = (e.clientX - offsetX) + 'px';
  379.             textPopup.style.top = (e.clientY - offsetY) + 'px';
  380.             textPopup.style.transform = 'none'; // Remove transform to allow direct left/top positioning
  381.         });
  382.  
  383.         document.addEventListener('mouseup', function()
  384.         {
  385.             if (isDragging)
  386.             {
  387.                 console.log("GM_Script: Popup dragging ended.");
  388.             }
  389.             isDragging = false;
  390.             textPopup.style.cursor = 'grab';
  391.             textPopup.style.transition = ''; // Re-enable transition
  392.         });
  393.     }
  394.  
  395.     // --- Event Listeners for Page ---
  396.     document.addEventListener('DOMContentLoaded', function()
  397.     {
  398.         console.log("GM_Script: DOMContentLoaded event fired.");
  399.         createCustomContextMenu();
  400.         createTextDisplayPopup();
  401.         console.log("GM_Script: Custom menu and popup created.");
  402.     });
  403.  
  404.     document.addEventListener('contextmenu', function(event)
  405.     {
  406.         console.log("GM_Script: 'contextmenu' event detected.");
  407.         // Prevent default browser context menu
  408.         event.preventDefault();
  409.         console.log("GM_Script: Default context menu prevented.");
  410.  
  411.         // We no longer dynamically position the menu; its position is fixed via CSS (bottom/right)
  412.         customMenu.style.display = 'block';
  413.         console.log("GM_Script: Custom menu displayed at fixed bottom-right position.");
  414.     });
  415.  
  416.     // Hide custom menu when clicking anywhere else on the page
  417.     document.addEventListener('click', function(event)
  418.     {
  419.         if (customMenu && event.target !== customMenu && !customMenu.contains(event.target))
  420.         {
  421.             if (customMenu.style.display === 'block') {
  422.                 console.log("GM_Script: Click outside custom menu detected. Hiding custom menu.");
  423.             }
  424.             customMenu.style.display = 'none';
  425.         }
  426.     });
  427.  
  428.     // Hide custom menu if scrolling occurs (helps prevent it being off-screen)
  429.     document.addEventListener('scroll', function()
  430.     {
  431.         if (customMenu && customMenu.style.display === 'block')
  432.         {
  433.             console.log("GM_Script: Scroll detected. Hiding custom menu.");
  434.             customMenu.style.display = 'none';
  435.         }
  436.     }, true); // Use capture phase for better reliability
  437. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement