Skip to content

Commit

Permalink
Added copy code to clipboard button
Browse files Browse the repository at this point in the history
Resolves #2153
  • Loading branch information
Gerrit0 committed Apr 21, 2023
1 parent 7ac546c commit ab975f9
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
### Features

- Added support for discovering a "module" comment on global files, #2165.
- Added copy code to clipboard button, #2153.
- Function `@returns` blocks will now be rendered with the return type, #2180.

### Bug Fixes

- Type parameter constraints now respect the `--hideParameterTypesInTitle` option, #2226.
- Even more contrast fixes, #2248.
- Fix semantic highlighting for predicate type's parameter references, #2249.
- Fixed broken links to heading titles.
- Fixed inconsistent styling between type parameter lists and parameter lists.
- TypeDoc will now warn if more than one `@returns` block is is present in a function, and ignore the duplicate blocks as specified by TSDoc.

Expand Down
37 changes: 34 additions & 3 deletions src/lib/output/themes/MarkedPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { RendererEvent, MarkdownEvent, PageEvent } from "../events";
import { BindOption, readFile, copySync, isFile } from "../../utils";
import { highlight, isSupportedLanguage } from "../../utils/highlighter";
import type { Theme } from "shiki";
import { getTextContent } from "../../utils/html";
import { escapeHtml, getTextContent } from "../../utils/html";

/**
* Implements markdown and relativeURL helpers for templates.
Expand Down Expand Up @@ -190,14 +190,15 @@ output file :

markedOptions.renderer.heading = (text, level, _, slugger) => {
const slug = slugger.slug(text);
// Prefix the slug with an extra `$` to prevent conflicts with TypeDoc's anchors.
// Prefix the slug with an extra `md:` to prevent conflicts with TypeDoc's anchors.
this.page!.pageHeadings.push({
link: `#md:${slug}`,
text: getTextContent(text),
level,
});
return `<a id="md:${slug}" class="tsd-anchor"></a><h${level}><a href="#$${slug}" style="color:inherit;text-decoration:none">${text}</a></h${level}>`;
return `<a id="md:${slug}" class="tsd-anchor"></a><h${level}><a href="#md:${slug}">${text}</a></h${level}>`;
};
markedOptions.renderer.code = renderCode;
}

markedOptions.mangle ??= false; // See https://github.com/TypeStrong/typedoc/issues/1395
Expand All @@ -214,3 +215,33 @@ output file :
event.parsedText = Marked.marked(event.parsedText);
}
}

// Basically a copy/paste of Marked's code, with the addition of the button
// https://github.com/markedjs/marked/blob/v4.3.0/src/Renderer.js#L15-L39
function renderCode(
this: Marked.marked.Renderer,
code: string,
infostring: string | undefined,
escaped: boolean
) {
const lang = (infostring || "").match(/\S*/)![0];
if (this.options.highlight) {
const out = this.options.highlight(code, lang);
if (out != null && out !== code) {
escaped = true;
code = out;
}
}

code = code.replace(/\n$/, "") + "\n";

if (!lang) {
return `<pre><code>${
escaped ? code : escapeHtml(code)
}</code><button>Copy</button></pre>\n`;
}

return `<pre><code class="${this.options.langPrefix + escapeHtml(lang)}">${
escaped ? code : escapeHtml(code)
}</code><button>Copy</button></pre>\n`;
}
23 changes: 23 additions & 0 deletions src/lib/output/themes/default/assets/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { initTheme } from "./typedoc/Theme";

addEventListener("load", () => {
initSearch();
initCopyCode();

registerComponent(Toggle, "a[data-toggle]");
registerComponent(Accordion, ".tsd-index-accordion");
Expand All @@ -21,3 +22,25 @@ addEventListener("load", () => {

Object.defineProperty(window, "app", { value: app });
});

function initCopyCode() {
document.querySelectorAll("pre > button").forEach((button) => {
let timeout: ReturnType<typeof setTimeout>;
button.addEventListener("click", () => {
if (button.previousElementSibling instanceof HTMLElement) {
navigator.clipboard.writeText(
button.previousElementSibling.innerText.trim()
);
}
button.textContent = "Copied!";
button.classList.add("visible");
clearTimeout(timeout);
timeout = setTimeout(() => {
button.classList.remove("visible");
timeout = setTimeout(() => {
button.textContent = "Copy";
}, 100);
}, 1000);
});
});
}
12 changes: 12 additions & 0 deletions src/lib/utils/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,15 @@ function unescapeEntities(html: string) {
export function getTextContent(text: string) {
return unescapeEntities(text.replace(/<.*?(?:>|$)/g, ""));
}

const htmlEscapes: Record<string, string> = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
};

export function escapeHtml(html: string) {
return html.replace(/[&<>'"]/g, (c) => htmlEscapes[c as never]);
}
13 changes: 1 addition & 12 deletions src/lib/utils/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* @module
*/

import { escapeHtml } from "./html";
import type {
IntrinsicElements,
JsxElement,
Expand Down Expand Up @@ -52,18 +53,6 @@ export declare namespace JSX {
export { IntrinsicElements, JsxElement as Element };
}

const htmlEscapes: Record<string, string> = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
};

function escapeHtml(html: string) {
return html.replace(/[&<>'"]/g, (c) => htmlEscapes[c as never]);
}

const voidElements = new Set([
"area",
"base",
Expand Down
34 changes: 27 additions & 7 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,16 @@ h6 {
line-height: 1.2;
}

h1 > a,
h2 > a,
h3 > a,
h4 > a,
h5 > a,
h6 > a {
text-decoration: none;
color: var(--color-text);
}

h1 {
font-size: 1.875rem;
margin: 0.67rem 0;
Expand Down Expand Up @@ -296,12 +306,6 @@ h6 {
text-transform: uppercase;
}

pre {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
}

dl,
menu,
ol,
Expand Down Expand Up @@ -426,13 +430,29 @@ pre {
}

pre {
position: relative;
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
padding: 10px;
border: 0.1em solid var(--color-accent);
border: 1px solid var(--color-accent);
}
pre code {
padding: 0;
font-size: 100%;
}
pre > button {
position: absolute;
top: 10px;
right: 10px;
opacity: 0;
transition: opacity 0.1s;
box-sizing: border-box;
}
pre:hover > button,
pre > button.visible {
opacity: 1;
}

blockquote {
margin: 1em 0;
Expand Down

0 comments on commit ab975f9

Please sign in to comment.