Skip to content

Commit d8f1f76

Browse files
committed
Add copy-as-markdown button functionality
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent e6da7ef commit d8f1f76

File tree

9 files changed

+312
-212
lines changed

9 files changed

+312
-212
lines changed

assets/css/_html.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
@import "autocomplete.css";
2828
@import "tooltips.css"; /* must remain below functions */
2929
@import "copy-button.css";
30+
@import "copy-markdown-button.css";
3031
@import "settings.css";
3132
@import "toast.css";
3233
@import "screen-reader.css";
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.copy-markdown-button {
2+
& svg[aria-live="polite"] {
3+
display: none;
4+
}
5+
}
6+
7+
.copy-markdown-button.clicked {
8+
color: var(--success);
9+
10+
& svg[aria-live="polite"] {
11+
display: block;
12+
}
13+
14+
& svg:first-child {
15+
display: none;
16+
}
17+
}

assets/js/copy-markdown-button.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import buttonHtml from './handlebars/templates/markdown-copy-button.html'
2+
import { qsAll } from './helpers'
3+
4+
/** @type {HTMLButtonElement} */
5+
let buttonTemplate
6+
7+
/**
8+
* Initializes copy-as-markdown buttons.
9+
* Fetches the .md version of the page and copies it to clipboard.
10+
* Only adds buttons if the markdown file exists.
11+
*/
12+
13+
window.addEventListener('exdoc:loaded', initializeCopyMarkdownButtons)
14+
15+
function initializeCopyMarkdownButtons () {
16+
if (!('clipboard' in navigator)) {
17+
return
18+
}
19+
20+
const markdownUrl = getMarkdownUrl()
21+
22+
qsAll('.top-heading.heading-with-actions').forEach(container => {
23+
if (container.querySelector('.copy-markdown-button')) return
24+
25+
if (!buttonTemplate) {
26+
const div = document.createElement('div')
27+
div.innerHTML = buttonHtml
28+
buttonTemplate = div.firstChild
29+
}
30+
31+
const button = buttonTemplate.cloneNode(true)
32+
button.addEventListener('click', () => handleCopyMarkdown(button, markdownUrl))
33+
container.appendChild(button)
34+
console.log('Added copy-markdown button')
35+
})
36+
}
37+
38+
function getMarkdownUrl () {
39+
const url = new URL(window.location.href)
40+
// Replace .html with markdown/filename.md
41+
// e.g., /doc/ExDoc.Markdown.html -> /doc/markdown/ExDoc.Markdown.md
42+
const markdownPath = url.pathname.replace(/([^/]+)\.html$/, 'markdown/$1.md')
43+
return url.origin + markdownPath
44+
}
45+
46+
async function handleCopyMarkdown (button, markdownUrl) {
47+
const originalInnerHTML = button.innerHTML
48+
49+
try {
50+
const response = await fetch(markdownUrl)
51+
52+
if (!response.ok) {
53+
throw new Error(`Failed to fetch markdown: ${response.status}`)
54+
}
55+
56+
const markdownContent = await response.text()
57+
58+
await navigator.clipboard.writeText(markdownContent)
59+
60+
button.classList.add('clicked')
61+
button.disabled = true
62+
63+
setTimeout(() => {
64+
button.classList.remove('clicked')
65+
button.disabled = false
66+
button.innerHTML = originalInnerHTML
67+
}, 3000)
68+
} catch (error) {
69+
console.error('Failed to copy markdown:', error)
70+
button.disabled = false
71+
}
72+
}

assets/js/entry/html.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../makeup'
1111
import '../search-bar'
1212
import '../tooltips/tooltips'
1313
import '../copy-button'
14+
import '../copy-markdown-button'
1415
import '../search-page'
1516
import '../settings'
1617
import '../keyboard-shortcuts'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<button class="copy-markdown-button icon-action" title="Copy as Markdown" aria-label="Copy as Markdown">
2+
<svg role="img" aria-label="copy-markdown" viewBox="0 0 24 24" fill="currentColor">
3+
<path d="M0 0h24v24H0z" fill="none"/>
4+
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
5+
</svg>
6+
<svg aria-live="polite" role="img" aria-label="copied" viewBox="0 0 24 24" fill="currentColor">
7+
<path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
8+
</svg>
9+
</button>

formatters/html/dist/html-6MHU7CXW.js

Lines changed: 210 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

formatters/html/dist/html-XBCM4BHM.js

Lines changed: 0 additions & 210 deletions
This file was deleted.

formatters/html/dist/html-elixir-VYWJUHZE.css renamed to formatters/html/dist/html-elixir-KNGKVQPZ.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

formatters/html/dist/html-erlang-TPJSOIRZ.css renamed to formatters/html/dist/html-erlang-KGZROTJ5.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)