Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions pytket/docs/_templates/components/view-this-page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends "!components/view-this-page.html" %}

{% block link_available %}
{{ super() }}
{% if show_source and has_source and sourcename %}
<div class="copy-as-markdown-container">
<button type="button" class="copy-as-markdown-btn" data-source-url="{{ pathto('_sources/' + sourcename, true) }}" title="{{ _('Copy page as Markdown') }}" aria-label="{{ _('Copy page as Markdown') }}">
<svg class="icon-default"><use href="#svg-markdown-copy"></use></svg>
<svg class="icon-success"><use href="#svg-check"></use></svg>
<span class="btn-text">{{ _('Copy Markdown') }}</span>
</button>
</div>
{% endif %}
{% endblock %}
19 changes: 19 additions & 0 deletions pytket/docs/_templates/page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends "!page.html" %}

{% block extrahead %}
{{ super() }}
<link rel="stylesheet" type="text/css" href="{{ pathto('_static/copy-markdown.css', 1) }}" />
<script src="{{ pathto('_static/copy-markdown.js', 1) }}" defer></script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-markdown-copy" viewBox="0 0 24 24">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<rect x="3" y="5" width="18" height="14" rx="2" stroke="currentColor" fill="none" stroke-width="1.75"/>
<path d="M7 15v-6l2 2l2 -2v6" stroke="currentColor" fill="none" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 13l2 2l2 -2m-2 2v-6" stroke="currentColor" fill="none" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="svg-check" viewBox="0 0 24 24">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M5 12l5 5l10 -10" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
</svg>
{% endblock %}
1 change: 1 addition & 0 deletions pytket/docs/justfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
prepare:
cp -R pytket-docs-theming/_static .
cp -R static/* _static/
cp pytket-docs-theming/conf.py .

install: prepare
Expand Down
91 changes: 91 additions & 0 deletions pytket/docs/static/copy-markdown.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* Copy as Markdown Button Styling */
.copy-as-markdown-container {
display: flex;
align-items: center;
}

.copy-as-markdown-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.35rem;
background: transparent;
border: 1px solid transparent;
border-radius: 0.375rem;
padding: 0.25rem 0.5rem;
font-size: 0.8125rem;
font-weight: 500;
color: var(--color-foreground-secondary, #6b7280);
cursor: pointer;
transition: all 0.15s ease-in-out;
text-decoration: none;
line-height: 1;
}

.copy-as-markdown-btn svg {
width: 1.125rem;
height: 1.125rem;
stroke-width: 1.75;
flex-shrink: 0;
}

.copy-as-markdown-btn .icon-success {
display: none;
color: #16a34a;
}

.copy-as-markdown-btn:hover {
background-color: var(--color-background-hover, rgba(0, 0, 0, 0.05));
color: var(--color-foreground-primary, #111827);
border-color: var(--color-background-border, rgba(0, 0, 0, 0.1));
}

.copy-as-markdown-btn.copy-success {
color: #16a34a !important;
background-color: rgba(22, 163, 74, 0.1) !important;
border-color: rgba(22, 163, 74, 0.25) !important;
}

.copy-as-markdown-btn.copy-success .icon-default {
display: none;
}

.copy-as-markdown-btn.copy-success .icon-success {
display: inline-block;
}

.copy-as-markdown-btn.copy-failure {
color: #dc2626 !important;
background-color: rgba(220, 38, 38, 0.1) !important;
border-color: rgba(220, 38, 38, 0.25) !important;
}

/* Dark mode adjustments */
[data-theme="dark"] .copy-as-markdown-btn,
body[data-theme="dark"] .copy-as-markdown-btn {
color: var(--color-foreground-secondary, #9ca3af);
}

[data-theme="dark"] .copy-as-markdown-btn:hover,
body[data-theme="dark"] .copy-as-markdown-btn:hover {
background-color: var(--color-background-hover, rgba(255, 255, 255, 0.08));
color: var(--color-foreground-primary, #f9fafb);
border-color: var(--color-background-border, rgba(255, 255, 255, 0.15));
}

[data-theme="dark"] .copy-as-markdown-btn.copy-success,
body[data-theme="dark"] .copy-as-markdown-btn.copy-success {
color: #4ade80 !important;
background-color: rgba(74, 222, 128, 0.15) !important;
border-color: rgba(74, 222, 128, 0.3) !important;
}

/* Mobile adjustments */
@media (max-width: 67em) {
.copy-as-markdown-btn .btn-text {
display: none;
}
.copy-as-markdown-btn {
padding: 0.35rem;
}
}
90 changes: 90 additions & 0 deletions pytket/docs/static/copy-markdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Copy as Markdown functionality for TKET documentation pages.
*
* Fetches the clean Markdown source from Sphinx's _sources/ endpoint and
* copies it to the user's clipboard, providing visual feedback.
*/

(function () {
'use strict';

function initCopyAsMarkdown() {
const buttons = document.querySelectorAll('.copy-as-markdown-btn');
if (!buttons.length) return;

buttons.forEach((btn) => {
// Prevent duplicate event listener attachment
if (btn.dataset.initialized === 'true') return;
btn.dataset.initialized = 'true';

btn.addEventListener('click', async function (e) {
e.preventDefault();
const sourceUrl = btn.getAttribute('data-source-url');
if (!sourceUrl) {
console.warn('[Copy as Markdown] No data-source-url attribute found.');
return;
}

const defaultLabel = btn.querySelector('.btn-text');
const defaultTitle = btn.getAttribute('title') || 'Copy page as Markdown';

try {
// Fetch raw markdown source
const response = await fetch(sourceUrl);
if (!response.ok) {
throw new Error(`Failed to load source: HTTP ${response.status}`);
}
const markdownText = await response.text();

// Write to clipboard
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(markdownText);
} else {
// Fallback for older browsers or non-secure contexts
const textarea = document.createElement('textarea');
textarea.value = markdownText;
textarea.style.position = 'fixed';
textarea.style.top = '-9999px';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textarea);
if (!successful) {
throw new Error('Fallback execCommand failed');
}
}

// Visual feedback on success
btn.classList.add('copy-success');
btn.setAttribute('title', 'Copied!');
if (defaultLabel) defaultLabel.textContent = 'Copied!';

setTimeout(() => {
btn.classList.remove('copy-success');
btn.setAttribute('title', defaultTitle);
if (defaultLabel) defaultLabel.textContent = 'Copy Markdown';
}, 2000);
} catch (err) {
console.error('[Copy as Markdown] Error:', err);
btn.classList.add('copy-failure');
btn.setAttribute('title', 'Failed to copy');
if (defaultLabel) defaultLabel.textContent = 'Failed to copy';

setTimeout(() => {
btn.classList.remove('copy-failure');
btn.setAttribute('title', defaultTitle);
if (defaultLabel) defaultLabel.textContent = 'Copy Markdown';
}, 2500);
}
});
});
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initCopyAsMarkdown);
} else {
initCopyAsMarkdown();
}
})();
Loading