Skip to content

Commit

Permalink
fix: respect the top-level copyButtons option (#990)
Browse files Browse the repository at this point in the history
| 🎫 Resolves RM-10635 | 🐙 Closes #964 |
| :------------------: | :------------: |

## 🧰 Changes

The main `RMDX.compile()` method should respect the `copyButtons` param
when rendering our code block components.

- [x] **Accept the `copyButtons` option**—we used to set this param
directly on our custom Unified `processor`, but since Remark actually
freeze's it's processor instance we can achieve a similar effect by
[passing it directly to the `CodeTabsTransformer` plugin as a
configuration
option](https://github.com/readmeio/markdown/blob/96f9644f04e6d8e3ffff6f9c014432f901c0b804/lib/compile.ts#L28):

https://github.com/readmeio/markdown/blob/96f9644f04e6d8e3ffff6f9c014432f901c0b804/lib/compile.ts#L28

- [x] Add a <kbd>**Copy Buttons**</kbd> toggle to the dev
playground.[^1]

- [x] Miscellaneous detailing and minor cleanup.

## 🧬 QA & Testing

Pull this PR down, run the `start` script, and [navigate to the “Code
Blocks” example](http://localhost:9966/#/codeBlockTests?copyButtons=true
"View local dev playground→"). Try hovering over a code block to verify
that the copy button is only shown if/when the <kbd>**Copy
Buttons**</kbd> toggle is checked.

[demo]: https://markdown-pr-PR_NUMBER.herokuapp.com
[prod]: https://SUBDOMAIN.readme.io
[icn]:
https://user-images.githubusercontent.com/886627/160426047-1bee9488-305a-4145-bb2b-09d8b757d38a.svg

[^1]: @kellyjosephprice—do we even respect those other two global config
options? I think `safeMode` and `lazyImages` both have toggles in the
playground, but [there are a bunch of others which I'm not sure are
still
valid](https://github.com/readmeio/markdown/blob/b9502adb306f099cd91e005df17c0be252019814/options.js#L1-L22)
either? Anyways, just an aside, but it would be nice to clean this all
up at some point!

---------

Co-authored-by: Trisha Le <[email protected]>
  • Loading branch information
rafegoldberg and trishaprile authored Oct 11, 2024
1 parent 1c8fc01 commit 1242413
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 61 deletions.
6 changes: 3 additions & 3 deletions components/Code/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ if (typeof window !== 'undefined') {

function CopyCode({ codeRef, rootClass = 'rdmd-code-copy', className = '' }) {
const copyClass = `${rootClass}_copied`;
const button = createRef<HTMLButtonElement>();
const buttonRef = createRef<HTMLButtonElement>();

const copier = () => {
const code = codeRef.current.textContent;

if (copy(code)) {
const el = button.current;
const el = buttonRef.current;
el.classList.add(copyClass);

setTimeout(() => el.classList.remove(copyClass), 1500);
}
};

return <button ref={button} aria-label="Copy Code" className={`${rootClass} ${className}`} onClick={copier} />;
return <button ref={buttonRef} aria-label="Copy Code" className={`${rootClass} ${className}`} onClick={copier} />;
}

interface CodeProps {
Expand Down
6 changes: 3 additions & 3 deletions example/Doc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const Doc = () => {
const ci = searchParams.has('ci');
const lazyImages = searchParams.has('lazyImages');
const safeMode = searchParams.has('safeMode');
const copyButtons = searchParams.has('copyButtons');

const [name, doc] =
fixture === 'edited' ? [fixture, searchParams.get('edit') || ''] : [docs[fixture].name, docs[fixture].doc];
Expand All @@ -76,6 +77,7 @@ const Doc = () => {
const opts = {
lazyImages,
safeMode,
copyButtons,
};

try {
Expand All @@ -91,7 +93,7 @@ const Doc = () => {
};

render();
}, [doc, lazyImages, safeMode]);
}, [doc, lazyImages, safeMode, copyButtons]);

return (
<div className="rdmd-demo--display">
Expand All @@ -111,7 +113,5 @@ const Doc = () => {
</div>
);
};
/*
*/

export default Doc;
9 changes: 9 additions & 0 deletions example/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ const Form = () => {
</fieldset>
<fieldset className="rdmd-demo--fieldset rdmd-demo--options">
<legend>Options</legend>
<div>
<label htmlFor="copy-buttons">Copy Buttons</label>
<input
checked={searchParams.has('copyButtons')}
id="copy-buttons"
onChange={onCheck('copyButtons')}
type="checkbox"
/>
</div>
<div>
<label htmlFor="safe-mode">Safe Mode</label>
<input
Expand Down
15 changes: 11 additions & 4 deletions lib/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { compileSync, CompileOptions } from '@mdx-js/mdx';
import remarkFrontmatter from 'remark-frontmatter';
import remarkGfm from 'remark-gfm';

import transformers, { variablesTransformer } from '../processor/transform';
import { defaultTransforms, variablesTransformer } from '../processor/transform';
import { rehypeToc } from '../processor/plugin/toc';
import MdxSyntaxError from '../errors/mdx-syntax-error';
import { rehypePlugins } from './ast-processor';
Expand All @@ -11,16 +11,23 @@ export type CompileOpts = CompileOptions & {
lazyImages?: boolean;
safeMode?: boolean;
components?: Record<string, string>;
copyButtons?: boolean;
};

const remarkPlugins = [remarkFrontmatter, remarkGfm, ...transformers, variablesTransformer];
const { codeTabsTransformer, ...transforms } = defaultTransforms;

const compile = (text: string, { components, ...opts }: CompileOpts = {}) => {
const compile = (text: string, { components, copyButtons, ...opts }: CompileOpts = {}) => {
try {
const vfile = compileSync(text, {
outputFormat: 'function-body',
providerImportSource: '#',
remarkPlugins,
remarkPlugins: [
remarkFrontmatter,
remarkGfm,
...Object.values(transforms),
[codeTabsTransformer, { copyButtons }],
variablesTransformer,
],
rehypePlugins: [...rehypePlugins, [rehypeToc, { components }]],
...opts,
});
Expand Down
99 changes: 50 additions & 49 deletions processor/transform/code-tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,55 @@ import { NodeTypes } from '../../enums';

const isCode = (node: Node): node is Code => node?.type === 'code';

const codeTabsTransformer = () => (tree: Node) => {
visit(tree, 'code', (node: Code) => {
const { lang, meta, value } = node;

node.data = {
hProperties: { lang, meta, value },
};
});

visit(tree, 'code', (node: Code, index: number, parent: BlockContent) => {
if (parent.type === 'code-tabs' || !('children' in parent)) return;

const length = parent.children.length;
let children = [node];
let walker = index + 1;

while (walker <= length) {
const sibling = parent.children[walker];
if (!isCode(sibling)) break;

const olderSibling = parent.children[walker - 1];
if (olderSibling.position.end.offset + sibling.position.start.column !== sibling.position.start.offset) break;

children.push(sibling);
walker++;
}

// If there is a single code block, and it has either a title or a
// language set, let's display it by wrapping it in a code tabs block.
// Othewise, we can leave early!
if (children.length === 1 && !(node.lang || node.meta)) return;

const codeTabs: CodeTabs = {
type: NodeTypes.codeTabs,
children,
data: {
hName: 'CodeTabs',
},
position: {
start: children[0].position.start,
end: children[children.length - 1].position.end,
},
};

parent.children.splice(index, children.length, codeTabs);
});

return tree;
};
const codeTabsTransformer =
({ copyButtons }: { copyButtons?: boolean } = {}) =>
(tree: Node) => {
visit(tree, 'code', (node: Code) => {
const { lang, meta, value } = node;
node.data = {
hProperties: { lang, meta, value, copyButtons },
};
});

visit(tree, 'code', (node: Code, index: number, parent: BlockContent) => {
if (parent.type === 'code-tabs' || !('children' in parent)) return;

const length = parent.children.length;
let children = [node];
let walker = index + 1;

while (walker <= length) {
const sibling = parent.children[walker];
if (!isCode(sibling)) break;

const olderSibling = parent.children[walker - 1];
if (olderSibling.position.end.offset + sibling.position.start.column !== sibling.position.start.offset) break;

children.push(sibling);
walker++;
}

// If there is a single code block, and it has either a title or a
// language set, let's display it by wrapping it in a code tabs block.
// Othewise, we can leave early!
if (children.length === 1 && !(node.lang || node.meta)) return;

const codeTabs: CodeTabs = {
type: NodeTypes.codeTabs,
children,
data: {
hName: 'CodeTabs',
},
position: {
start: children[0].position.start,
end: children[children.length - 1].position.end,
},
};

parent.children.splice(index, children.length, codeTabs);
});

return tree;
};

export default codeTabsTransformer;
13 changes: 11 additions & 2 deletions processor/transform/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import calloutTransformer from './callouts';
import codeTabsTransfromer from './code-tabs';
import codeTabsTransformer from './code-tabs';
import embedTransformer from './embeds';
import imageTransformer from './images';
import gemojiTransformer from './gemoji+';

import divTransformer from './div';
import injectComponents from './inject-components';
import readmeComponentsTransformer from './readme-components';
Expand All @@ -21,4 +22,12 @@ export {
tablesToJsx,
};

export default [calloutTransformer, codeTabsTransfromer, embedTransformer, imageTransformer, gemojiTransformer];
export const defaultTransforms = {
calloutTransformer,
codeTabsTransformer,
embedTransformer,
imageTransformer,
gemojiTransformer,
};

export default Object.values(defaultTransforms);

0 comments on commit 1242413

Please sign in to comment.