From 29d07301bd69b6a5a1fc72a9ca30cdb149ba8055 Mon Sep 17 00:00:00 2001 From: Paige Calvert Date: Fri, 23 May 2025 14:11:28 -0600 Subject: [PATCH] remove redundancies in dropdown js --- src/components/CopyMarkdown.js | 9 + static/js/components/CopyMarkdown.js | 242 ------------------ static/js/copy-markdown.js | 366 --------------------------- 3 files changed, 9 insertions(+), 608 deletions(-) delete mode 100644 static/js/components/CopyMarkdown.js delete mode 100644 static/js/copy-markdown.js diff --git a/src/components/CopyMarkdown.js b/src/components/CopyMarkdown.js index 8718bad661..9a7f287eb7 100644 --- a/src/components/CopyMarkdown.js +++ b/src/components/CopyMarkdown.js @@ -1,3 +1,12 @@ +/** + * CopyMarkdown Component + * + * React component that provides a dropdown menu to: + * - Copy the current page as markdown + * - View the page as plain text + * - Open the page in ChatGPT + */ + import React, { useState, useEffect, useCallback, useRef } from 'react'; import styles from './CopyMarkdown.module.css'; diff --git a/static/js/components/CopyMarkdown.js b/static/js/components/CopyMarkdown.js deleted file mode 100644 index b55eb893bd..0000000000 --- a/static/js/components/CopyMarkdown.js +++ /dev/null @@ -1,242 +0,0 @@ -// Browser-compatible version of the CopyMarkdown component -(function() { - const React = window.React; - const { useState, useRef, useEffect } = React; - - function CopyMarkdown() { - const [isOpen, setIsOpen] = useState(false); - const dropdownRef = useRef(null); - const buttonRef = useRef(null); - - useEffect(() => { - const handleClickOutside = (event) => { - if (dropdownRef.current && - !dropdownRef.current.contains(event.target) && - !buttonRef.current.contains(event.target)) { - setIsOpen(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [dropdownRef]); - - const toggleDropdown = () => { - setIsOpen(!isOpen); - }; - - const copyMarkdown = async () => { - try { - const markdownContent = await generateMarkdown(); - navigator.clipboard.writeText(markdownContent); - setIsOpen(false); - // Optional: Show a toast notification - showToast('Copied to clipboard'); - } catch (err) { - console.error('Failed to copy: ', err); - showToast('Failed to copy', true); - } - }; - - const viewAsMarkdown = async () => { - try { - const markdownContent = await generateMarkdown(); - const blob = new Blob([markdownContent], { type: 'text/markdown' }); - const url = URL.createObjectURL(blob); - window.open(url, '_blank'); - setIsOpen(false); - } catch (err) { - console.error('Failed to generate markdown: ', err); - showToast('Failed to view as markdown', true); - } - }; - - const openInChatGpt = async () => { - try { - const markdownContent = await generateMarkdown(); - const url = `https://chat.openai.com/g?prompt=${encodeURIComponent(markdownContent)}`; - window.open(url, '_blank'); - setIsOpen(false); - } catch (err) { - console.error('Failed to open in ChatGPT: ', err); - showToast('Failed to open in ChatGPT', true); - } - }; - - const generateMarkdown = async () => { - return window.generateCleanMarkdown ? - window.generateCleanMarkdown() : - 'Markdown content will be generated here'; - }; - - // Simple toast notification - const showToast = (message, isError = false) => { - const toast = document.createElement('div'); - toast.textContent = message; - toast.style.position = 'fixed'; - toast.style.bottom = '1rem'; - toast.style.right = '1rem'; - toast.style.backgroundColor = isError ? '#f44336' : '#4caf50'; - toast.style.color = 'white'; - toast.style.padding = '0.75rem 1.5rem'; - toast.style.borderRadius = '4px'; - toast.style.zIndex = '9999'; - toast.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.2)'; - - document.body.appendChild(toast); - - setTimeout(() => { - toast.style.opacity = '0'; - toast.style.transition = 'opacity 0.5s ease'; - - setTimeout(() => { - document.body.removeChild(toast); - }, 500); - }, 3000); - }; - - // CSS-in-JS styles for the component - const styles = { - container: { - position: 'relative', - display: 'inline-block', - marginLeft: 'auto' - }, - dropdownButton: { - display: 'flex', - alignItems: 'center', - gap: '0.5rem', - fontSize: '0.875rem', - fontWeight: '500', - color: 'var(--ifm-color-primary)', - backgroundColor: 'transparent', - border: '2px solid var(--ifm-color-primary)', - borderRadius: '8px', - padding: '0.5rem 0.75rem', - cursor: 'pointer', - transition: 'all 0.2s ease' - }, - iconSvg: { - width: '1rem', - height: '1rem' - }, - dropdownMenu: { - position: 'absolute', - top: '100%', - right: '0', - marginTop: '0.5rem', - minWidth: '240px', - backgroundColor: 'var(--ifm-background-color)', - border: '1px solid var(--ifm-color-emphasis-300)', - borderRadius: '8px', - boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)', - zIndex: '1000', - overflow: 'hidden' - }, - dropdownList: { - listStyle: 'none', - padding: '0', - margin: '0' - }, - dropdownItem: { - borderBottom: '1px solid var(--ifm-color-emphasis-200)' - }, - dropdownLink: { - display: 'block', - padding: '0.75rem 1rem', - width: '100%', - textAlign: 'left', - color: 'var(--ifm-font-color-base)', - background: 'none', - border: 'none', - cursor: 'pointer', - fontSize: '0.875rem', - transition: 'background-color 0.2s ease' - } - }; - - return React.createElement( - 'div', - { style: styles.container }, - React.createElement( - 'button', - { - ref: buttonRef, - style: styles.dropdownButton, - onClick: toggleDropdown, - 'aria-haspopup': 'true', - 'aria-expanded': isOpen - }, - React.createElement('span', null, 'Copy page'), - React.createElement( - 'svg', - { - style: styles.iconSvg, - viewBox: '0 0 20 20', - fill: 'currentColor' - }, - React.createElement('path', { - fillRule: 'evenodd', - d: 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z', - clipRule: 'evenodd' - }) - ) - ), - isOpen && React.createElement( - 'div', - { - ref: dropdownRef, - style: styles.dropdownMenu - }, - React.createElement( - 'ul', - { style: styles.dropdownList }, - React.createElement( - 'li', - { style: styles.dropdownItem }, - React.createElement( - 'button', - { - onClick: copyMarkdown, - style: styles.dropdownLink - }, - React.createElement('span', null, 'Copy page as Markdown for LLMs') - ) - ), - React.createElement( - 'li', - { style: styles.dropdownItem }, - React.createElement( - 'button', - { - onClick: viewAsMarkdown, - style: styles.dropdownLink - }, - React.createElement('span', null, 'View as plain text') - ) - ), - React.createElement( - 'li', - { style: styles.dropdownItem }, - React.createElement( - 'button', - { - onClick: openInChatGpt, - style: styles.dropdownLink - }, - React.createElement('span', null, 'Open in ChatGPT') - ) - ) - ) - ) - ); - } - - // Export the component - window.CopyMarkdown = CopyMarkdown; -})(); - -// This is necessary to support ES module imports -export default window.CopyMarkdown; \ No newline at end of file diff --git a/static/js/copy-markdown.js b/static/js/copy-markdown.js deleted file mode 100644 index 607352d7ca..0000000000 --- a/static/js/copy-markdown.js +++ /dev/null @@ -1,366 +0,0 @@ -(function() { - // Wait for the DOM content to be fully loaded - document.addEventListener('DOMContentLoaded', function() { - // Only run this script on documentation pages - if (!document.querySelector('.theme-doc-markdown.markdown h1')) { - return; - } - - // Function to generate clean markdown - function generateCleanMarkdown() { - const content = document.querySelector('.theme-doc-markdown.markdown'); - if (!content) { - return 'Could not find content to convert to markdown.'; - } - - // Clone the content to avoid modifying the original - const contentClone = content.cloneNode(true); - - // Get the title - const title = contentClone.querySelector('h1').textContent; - - // Remove elements we don't want in the markdown - const elementsToRemove = contentClone.querySelectorAll('.theme-edit-this-page, .pagination-nav, .table-of-contents, #copy-markdown-container'); - elementsToRemove.forEach(el => el.remove()); - - // Convert the HTML content to markdown - let markdown = `# ${title}\n\n`; - - // Process paragraphs, headers, lists, etc. - const elements = contentClone.querySelectorAll('p, h2, h3, h4, h5, h6, ul, ol, pre, blockquote, table'); - elements.forEach(el => { - // Skip the title as we've already added it - if (el.tagName === 'H1') return; - - // Process different element types - switch (el.tagName) { - case 'H2': - markdown += `\n## ${el.textContent}\n\n`; - break; - case 'H3': - markdown += `\n### ${el.textContent}\n\n`; - break; - case 'H4': - markdown += `\n#### ${el.textContent}\n\n`; - break; - case 'H5': - markdown += `\n##### ${el.textContent}\n\n`; - break; - case 'H6': - markdown += `\n###### ${el.textContent}\n\n`; - break; - case 'P': - markdown += `${el.textContent}\n\n`; - break; - case 'UL': - markdown += processListItems(el, '- '); - break; - case 'OL': - markdown += processListItems(el, (i) => `${i+1}. `); - break; - case 'PRE': - const codeElement = el.querySelector('code'); - const codeClass = codeElement?.className?.match(/language-(\w+)/)?.[1] || ''; - const codeContent = codeElement?.textContent || el.textContent; - markdown += `\`\`\`${codeClass}\n${codeContent}\n\`\`\`\n\n`; - break; - case 'BLOCKQUOTE': - const blockquoteLines = el.textContent.split('\n').map(line => `> ${line}`).join('\n'); - markdown += `${blockquoteLines}\n\n`; - break; - case 'TABLE': - markdown += processTable(el); - break; - default: - markdown += `${el.textContent}\n\n`; - } - }); - - return markdown.trim(); - } - - // Helper function to process list items - function processListItems(listElement, prefix) { - let result = '\n'; - const items = listElement.querySelectorAll('li'); - items.forEach((item, index) => { - // If prefix is a function, call it with the index - const prefixText = typeof prefix === 'function' ? prefix(index) : prefix; - result += `${prefixText}${item.textContent}\n`; - - // Process any nested lists - const nestedLists = item.querySelectorAll('ul, ol'); - if (nestedLists.length > 0) { - nestedLists.forEach(nestedList => { - const nestedPrefix = nestedList.tagName === 'UL' ? ' - ' : (i) => ` ${i+1}. `; - result += processListItems(nestedList, nestedPrefix); - }); - } - }); - return result + '\n'; - } - - // Helper function to process tables - function processTable(tableElement) { - let result = '\n'; - const rows = tableElement.querySelectorAll('tr'); - - // Process header row - const headerCells = rows[0]?.querySelectorAll('th') || []; - if (headerCells.length > 0) { - result += '| ' + Array.from(headerCells).map(cell => cell.textContent).join(' | ') + ' |\n'; - result += '| ' + Array.from(headerCells).map(() => '---').join(' | ') + ' |\n'; - } - - // Process data rows - for (let i = headerCells.length > 0 ? 1 : 0; i < rows.length; i++) { - const cells = rows[i].querySelectorAll('td'); - result += '| ' + Array.from(cells).map(cell => cell.textContent).join(' | ') + ' |\n'; - } - - return result + '\n'; - } - - // Find or create the target container for our dropdown - function findOrCreateTargetContainer() { - const title = document.querySelector('.theme-doc-markdown.markdown h1'); - if (!title) { - console.error('Cannot find title element to add dropdown next to.'); - return null; - } - - // Check if the title is already wrapped in a header element - let header = title.parentElement; - if (!header || !header.matches('.theme-doc-markdown.markdown > header')) { - // Wrap the title in a header element - header = document.createElement('header'); - title.parentNode.insertBefore(header, title); - header.appendChild(title); - } - - return header; - } - - // Simple toast notification function - function showToast(message, isError = false) { - const toast = document.createElement('div'); - toast.textContent = message; - toast.style.position = 'fixed'; - toast.style.bottom = '1rem'; - toast.style.right = '1rem'; - toast.style.backgroundColor = isError ? '#f44336' : '#4caf50'; - toast.style.color = 'white'; - toast.style.padding = '0.75rem 1.5rem'; - toast.style.borderRadius = '4px'; - toast.style.zIndex = '9999'; - toast.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.2)'; - - document.body.appendChild(toast); - - setTimeout(() => { - toast.style.opacity = '0'; - toast.style.transition = 'opacity 0.5s ease'; - - setTimeout(() => { - document.body.removeChild(toast); - }, 500); - }, 3000); - } - - // Create dropdown UI - function createDropdown() { - // Create container - const container = document.createElement('div'); - container.id = 'copy-markdown-container'; - container.style.position = 'relative'; - container.style.display = 'inline-block'; - container.style.marginLeft = 'auto'; - - // Create button - const button = document.createElement('button'); - button.innerHTML = 'Copy page'; - button.style.display = 'flex'; - button.style.alignItems = 'center'; - button.style.gap = '0.5rem'; - button.style.fontSize = '0.875rem'; - button.style.fontWeight = '500'; - button.style.color = 'var(--ifm-color-primary)'; - button.style.backgroundColor = 'transparent'; - button.style.border = '2px solid var(--ifm-color-primary)'; - button.style.borderRadius = '8px'; - button.style.padding = '0.5rem 0.75rem'; - button.style.cursor = 'pointer'; - button.style.transition = 'all 0.2s ease'; - - // Add button to container - container.appendChild(button); - - // Dropdown menu (initially hidden) - const dropdown = document.createElement('div'); - dropdown.style.display = 'none'; - dropdown.style.position = 'absolute'; - dropdown.style.top = '100%'; - dropdown.style.right = '0'; - dropdown.style.marginTop = '0.5rem'; - dropdown.style.minWidth = '240px'; - dropdown.style.backgroundColor = 'var(--ifm-background-color)'; - dropdown.style.border = '1px solid var(--ifm-color-emphasis-300)'; - dropdown.style.borderRadius = '8px'; - dropdown.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.1)'; - dropdown.style.zIndex = '1000'; - dropdown.style.overflow = 'hidden'; - container.appendChild(dropdown); - - // Create dropdown list - const list = document.createElement('ul'); - list.style.listStyle = 'none'; - list.style.padding = '0'; - list.style.margin = '0'; - dropdown.appendChild(list); - - // Add dropdown items - const items = [ - { text: 'Copy page as Markdown for LLMs', action: copyMarkdown }, - { text: 'View as plain text', action: viewAsMarkdown }, - { text: 'Open in ChatGPT', action: openInChatGpt } - ]; - - items.forEach((item, index) => { - const li = document.createElement('li'); - li.style.borderBottom = index < items.length - 1 ? '1px solid var(--ifm-color-emphasis-200)' : 'none'; - - const actionButton = document.createElement('button'); - actionButton.innerHTML = `${item.text}`; - actionButton.style.display = 'block'; - actionButton.style.padding = '0.75rem 1rem'; - actionButton.style.width = '100%'; - actionButton.style.textAlign = 'left'; - actionButton.style.color = 'var(--ifm-font-color-base)'; - actionButton.style.background = 'none'; - actionButton.style.border = 'none'; - actionButton.style.cursor = 'pointer'; - actionButton.style.fontSize = '0.875rem'; - actionButton.style.transition = 'background-color 0.2s ease'; - - actionButton.addEventListener('click', item.action); - actionButton.addEventListener('mouseover', () => { - actionButton.style.backgroundColor = 'var(--ifm-color-emphasis-100)'; - actionButton.style.color = 'var(--ifm-color-primary)'; - }); - actionButton.addEventListener('mouseout', () => { - actionButton.style.backgroundColor = ''; - actionButton.style.color = 'var(--ifm-font-color-base)'; - }); - - li.appendChild(actionButton); - list.appendChild(li); - }); - - // Toggle dropdown on button click - let isOpen = false; - button.addEventListener('click', () => { - isOpen = !isOpen; - dropdown.style.display = isOpen ? 'block' : 'none'; - button.setAttribute('aria-expanded', isOpen); - }); - - // Close dropdown when clicking outside - document.addEventListener('mousedown', (event) => { - if (!container.contains(event.target) && isOpen) { - isOpen = false; - dropdown.style.display = 'none'; - button.setAttribute('aria-expanded', false); - } - }); - - // Button hover effect - button.addEventListener('mouseover', () => { - button.style.backgroundColor = 'rgba(109, 210, 210, 0.1)'; - }); - button.addEventListener('mouseout', () => { - button.style.backgroundColor = 'transparent'; - }); - - // Action functions - function copyMarkdown() { - try { - const markdownContent = generateCleanMarkdown(); - navigator.clipboard.writeText(markdownContent); - isOpen = false; - dropdown.style.display = 'none'; - showToast('Copied to clipboard'); - } catch (err) { - console.error('Failed to copy: ', err); - showToast('Failed to copy', true); - } - } - - function viewAsMarkdown() { - try { - const markdownContent = generateCleanMarkdown(); - const blob = new Blob([markdownContent], { type: 'text/markdown' }); - const url = URL.createObjectURL(blob); - window.open(url, '_blank'); - isOpen = false; - dropdown.style.display = 'none'; - } catch (err) { - console.error('Failed to generate markdown: ', err); - showToast('Failed to view as markdown', true); - } - } - - function openInChatGpt() { - try { - const markdownContent = generateCleanMarkdown(); - const url = `https://chat.openai.com/g?prompt=${encodeURIComponent(markdownContent)}`; - window.open(url, '_blank'); - isOpen = false; - dropdown.style.display = 'none'; - } catch (err) { - console.error('Failed to open in ChatGPT: ', err); - showToast('Failed to open in ChatGPT', true); - } - } - - return container; - } - - // Add the component to the page - const targetContainer = findOrCreateTargetContainer(); - if (targetContainer) { - const dropdown = createDropdown(); - targetContainer.appendChild(dropdown); - - // Make sure the header is styled correctly - targetContainer.style.display = 'flex'; - targetContainer.style.alignItems = 'flex-start'; - targetContainer.style.justifyContent = 'space-between'; - targetContainer.style.marginBottom = '1.5rem'; - - // Make sure the title is styled correctly - const title = targetContainer.querySelector('h1'); - if (title) { - title.style.marginBottom = '0'; - title.style.marginRight = '1rem'; - title.style.flex = '1'; - } - - // Add responsive styles - const styleSheet = document.createElement('style'); - styleSheet.textContent = ` - @media (max-width: 768px) { - .theme-doc-markdown.markdown > header { - flex-direction: column; - align-items: flex-start; - } - - #copy-markdown-container { - margin-top: 1rem; - margin-left: 0; - } - } - `; - document.head.appendChild(styleSheet); - } - }); -})(); \ No newline at end of file