Skip to content

Commit eb2b741

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

File tree

10 files changed

+320
-212
lines changed

10 files changed

+320
-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: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
// Check if markdown formatter is enabled
21+
if (!window.exdocConfig?.formatters?.includes('markdown')) {
22+
return
23+
}
24+
25+
const markdownUrl = getMarkdownUrl()
26+
27+
qsAll('.top-heading.heading-with-actions').forEach(container => {
28+
if (container.querySelector('.copy-markdown-button')) return
29+
30+
if (!buttonTemplate) {
31+
const div = document.createElement('div')
32+
div.innerHTML = buttonHtml
33+
buttonTemplate = div.firstChild
34+
}
35+
36+
const button = buttonTemplate.cloneNode(true)
37+
button.addEventListener('click', () => handleCopyMarkdown(button, markdownUrl))
38+
container.appendChild(button)
39+
console.log('Added copy-markdown button')
40+
})
41+
}
42+
43+
function getMarkdownUrl () {
44+
const url = new URL(window.location.href)
45+
// Replace .html with markdown/filename.md
46+
// e.g., /doc/ExDoc.Markdown.html -> /doc/markdown/ExDoc.Markdown.md
47+
const markdownPath = url.pathname.replace(/([^/]+)\.html$/, 'markdown/$1.md')
48+
return url.origin + markdownPath
49+
}
50+
51+
async function handleCopyMarkdown (button, markdownUrl) {
52+
const originalInnerHTML = button.innerHTML
53+
54+
try {
55+
const response = await fetch(markdownUrl)
56+
57+
if (!response.ok) {
58+
throw new Error(`Failed to fetch markdown: ${response.status}`)
59+
}
60+
61+
const markdownContent = await response.text()
62+
63+
await navigator.clipboard.writeText(markdownContent)
64+
65+
button.classList.add('clicked')
66+
button.disabled = true
67+
68+
setTimeout(() => {
69+
button.classList.remove('clicked')
70+
button.disabled = false
71+
button.innerHTML = originalInnerHTML
72+
}, 3000)
73+
} catch (error) {
74+
console.error('Failed to copy markdown:', error)
75+
button.disabled = false
76+
}
77+
}

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-GJGETVXV.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.

lib/ex_doc/formatter/html/templates/head_template.eex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
<% end %>
2323
<script defer src="<%= Assets.rev config.output, "dist/sidebar_items-*.js" %>"></script>
2424
<script defer src="docs_config.js"></script>
25+
<script>
26+
window.__EXDOC__ = { formatters: <%= inspect(config.formatters) %> };
27+
</script>
2528
<script defer src="dist/<%= Assets.js_filename() %>"></script>
2629
<%= before_closing_head_tag(config, :html) %>
2730
</head>

0 commit comments

Comments
 (0)