|
| 1 | +{% comment %} |
| 2 | + Copy Page Component |
| 3 | + Provides a dropdown to copy the page as markdown or view the raw markdown |
| 4 | +{% endcomment %} |
| 5 | + |
| 6 | +{% if site.gh_edit_repository and site.gh_edit_branch and page.path %} |
| 7 | + {% comment %} Extract repo path from full URL {% endcomment %} |
| 8 | + {% assign repo_parts = site.gh_edit_repository | split: "github.com/" %} |
| 9 | + {% assign repo_path = repo_parts[1] %} |
| 10 | + {% assign raw_url = "https://raw.githubusercontent.com/" | append: repo_path | append: "/" | append: site.gh_edit_branch | append: "/" | append: page.path %} |
| 11 | + {% assign view_url = site.gh_edit_repository | append: "/raw/refs/heads/" | append: site.gh_edit_branch | append: "/" | append: page.path %} |
| 12 | + |
| 13 | +<div class="copy-page-dropdown"> |
| 14 | + <button class="copy-page-btn" id="copyPageBtn" aria-label="Copy page options" aria-expanded="false"> |
| 15 | + <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"> |
| 16 | + <path fill="currentColor" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path> |
| 17 | + <path fill="currentColor" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path> |
| 18 | + </svg> |
| 19 | + <span>Copy page</span> |
| 20 | + <svg class="dropdown-arrow" viewBox="0 0 16 16" width="12" height="12" aria-hidden="true"> |
| 21 | + <path fill="currentColor" d="M4.427 7.427l3.396 3.396a.25.25 0 00.354 0l3.396-3.396A.25.25 0 0011.396 7H4.604a.25.25 0 00-.177.427z"></path> |
| 22 | + </svg> |
| 23 | + </button> |
| 24 | + |
| 25 | + <div class="copy-page-menu" id="copyPageMenu" role="menu" aria-hidden="true"> |
| 26 | + <button class="copy-page-menu-item" id="copyMarkdownBtn" role="menuitem" data-raw-url="{{ raw_url }}"> |
| 27 | + <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"> |
| 28 | + <path fill="currentColor" d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path> |
| 29 | + <path fill="currentColor" d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path> |
| 30 | + </svg> |
| 31 | + <div class="menu-item-text"> |
| 32 | + <div class="menu-item-title">Copy page</div> |
| 33 | + <div class="menu-item-subtitle">Copy page as Markdown for LLMs</div> |
| 34 | + </div> |
| 35 | + </button> |
| 36 | + |
| 37 | + <a class="copy-page-menu-item" href="{{ view_url }}" target="_blank" rel="noopener noreferrer" role="menuitem"> |
| 38 | + <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"> |
| 39 | + <path fill="currentColor" d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 15H2.75A1.75 1.75 0 0 1 1 13.25Zm1.75-.25a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V2.75a.25.25 0 0 0-.25-.25ZM4.75 5h6.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1 0-1.5Zm0 3h6.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1 0-1.5Zm0 3h3.5a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1 0-1.5Z"></path> |
| 40 | + </svg> |
| 41 | + <div class="menu-item-text"> |
| 42 | + <div class="menu-item-title">View as Markdown →</div> |
| 43 | + <div class="menu-item-subtitle">View this page as plain text</div> |
| 44 | + </div> |
| 45 | + </a> |
| 46 | + </div> |
| 47 | +</div> |
| 48 | + |
| 49 | +<script> |
| 50 | +document.addEventListener('DOMContentLoaded', function() { |
| 51 | + var copyPageBtn = document.getElementById('copyPageBtn'); |
| 52 | + var copyPageMenu = document.getElementById('copyPageMenu'); |
| 53 | + var copyMarkdownBtn = document.getElementById('copyMarkdownBtn'); |
| 54 | + |
| 55 | + if (!copyPageBtn || !copyPageMenu || !copyMarkdownBtn) { |
| 56 | + return; |
| 57 | + } |
| 58 | + |
| 59 | + /* Toggle dropdown */ |
| 60 | + copyPageBtn.addEventListener('click', function(e) { |
| 61 | + e.preventDefault(); |
| 62 | + e.stopPropagation(); |
| 63 | + |
| 64 | + var isExpanded = copyPageBtn.getAttribute('aria-expanded') === 'true'; |
| 65 | + copyPageBtn.setAttribute('aria-expanded', String(!isExpanded)); |
| 66 | + copyPageMenu.setAttribute('aria-hidden', String(isExpanded)); |
| 67 | + copyPageMenu.classList.toggle('show'); |
| 68 | + }); |
| 69 | + |
| 70 | + /* Close dropdown when clicking outside */ |
| 71 | + document.addEventListener('click', function(e) { |
| 72 | + if (!copyPageBtn.contains(e.target) && !copyPageMenu.contains(e.target)) { |
| 73 | + copyPageBtn.setAttribute('aria-expanded', 'false'); |
| 74 | + copyPageMenu.setAttribute('aria-hidden', 'true'); |
| 75 | + copyPageMenu.classList.remove('show'); |
| 76 | + } |
| 77 | + }); |
| 78 | + |
| 79 | + /* Copy markdown to clipboard */ |
| 80 | + copyMarkdownBtn.addEventListener('click', function(e) { |
| 81 | + e.preventDefault(); |
| 82 | + e.stopPropagation(); |
| 83 | + |
| 84 | + var rawUrl = this.getAttribute('data-raw-url'); |
| 85 | + var titleElement = copyMarkdownBtn.querySelector('.menu-item-title'); |
| 86 | + var originalText = titleElement.textContent; |
| 87 | + |
| 88 | + titleElement.textContent = 'Loading...'; |
| 89 | + |
| 90 | + fetch(rawUrl) |
| 91 | + .then(function(response) { |
| 92 | + if (!response.ok) throw new Error('Failed to fetch markdown'); |
| 93 | + return response.text(); |
| 94 | + }) |
| 95 | + .then(function(markdown) { |
| 96 | + return navigator.clipboard.writeText(markdown); |
| 97 | + }) |
| 98 | + .then(function() { |
| 99 | + titleElement.textContent = 'Copied!'; |
| 100 | + setTimeout(function() { |
| 101 | + titleElement.textContent = originalText; |
| 102 | + copyPageBtn.setAttribute('aria-expanded', 'false'); |
| 103 | + copyPageMenu.setAttribute('aria-hidden', 'true'); |
| 104 | + copyPageMenu.classList.remove('show'); |
| 105 | + }, 1500); |
| 106 | + }) |
| 107 | + .catch(function(err) { |
| 108 | + console.error('Error copying markdown:', err); |
| 109 | + titleElement.textContent = 'Error!'; |
| 110 | + setTimeout(function() { |
| 111 | + titleElement.textContent = originalText; |
| 112 | + }, 2000); |
| 113 | + }); |
| 114 | + }); |
| 115 | + |
| 116 | + /* Keyboard navigation */ |
| 117 | + copyPageBtn.addEventListener('keydown', function(e) { |
| 118 | + if (e.key === 'Escape') { |
| 119 | + copyPageBtn.setAttribute('aria-expanded', 'false'); |
| 120 | + copyPageMenu.setAttribute('aria-hidden', 'true'); |
| 121 | + copyPageMenu.classList.remove('show'); |
| 122 | + } |
| 123 | + }); |
| 124 | +}); |
| 125 | +</script> |
| 126 | +{% endif %} |
| 127 | + |
0 commit comments