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
166 changes: 166 additions & 0 deletions COPY_BUTTON_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Copy to Clipboard Button Implementation

This directory contains the implementation for adding "Copy to Clipboard" buttons to code blocks in the Theatre.js documentation, addressing issue #512.

## Files Created

1. **`docs-copy-button-component.vue`** - Vue component for the copy button (optional, for Vue-based approach)
2. **`docs-copy-button-simple.js`** - Simple JavaScript implementation (recommended)
3. **`docs-copy-button-styles.css`** - Styles for the copy button
4. **`docs-copy-button-integration.md`** - Detailed integration guide
5. **`docs-vuepress-config-example.js`** - Example VuePress configuration

## Quick Start (Simplest Approach)

To integrate this into the theatre-docs repository:

### Step 1: Copy the files

```bash
# In the theatre-docs repository
# Copy the simple JavaScript implementation
cp docs-copy-button-simple.js .vuepress/enhanceApp.js

# Copy the styles
mkdir -p .vuepress/public/styles
cp docs-copy-button-styles.css .vuepress/public/styles/copy-button.css
```

### Step 2: Update VuePress config

Add to your `.vuepress/config.js`:

```javascript
module.exports = {
head: [
// ... existing head tags
['link', { rel: 'stylesheet', href: '/styles/copy-button.css' }]
],
// ... rest of config
}
```

That's it! The copy buttons will automatically appear on all code blocks.

## Features

✅ **Automatic detection** - Works with all code blocks automatically
✅ **Modern Clipboard API** - Uses `navigator.clipboard.writeText()`
✅ **Fallback support** - Works in older browsers with `document.execCommand()`
✅ **Visual feedback** - Shows "Copied!" message for 2 seconds
✅ **Dark mode support** - Adapts to light/dark themes
✅ **Responsive** - Works on mobile devices
✅ **Accessible** - Includes ARIA labels and keyboard support

## How It Works

1. The `enhanceApp.js` script runs after each page navigation
2. It finds all code blocks using `div[class*="language-"]` and `pre[class*="language-"]` selectors
3. For each code block, it:
- Extracts the code text
- Creates a copy button in the top-right corner
- Adds a click handler that copies the code to clipboard
- Shows visual feedback when copied

## Customization

### Change button position

Edit `.vuepress/public/styles/copy-button.css`:

```css
.copy-code-button {
top: 0.5rem; /* Adjust top position */
right: 0.5rem; /* Adjust right position */
}
```

### Change success color

```css
.copy-code-button.copied {
background: #10b981; /* Change to your preferred color */
}
```

### Change button text

Edit `.vuepress/enhanceApp.js` and search for `"Copy"` and `"Copied!"` strings.

## Browser Support

- ✅ Chrome/Edge (latest)
- ✅ Firefox (latest)
- ✅ Safari (latest)
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
- ✅ Older browsers (with fallback)

## Testing Checklist

After integration, verify:

- [ ] Copy buttons appear on all code blocks
- [ ] Clicking a button copies the code correctly
- [ ] "Copied!" feedback appears after clicking
- [ ] Button resets after 2 seconds
- [ ] Works in light mode
- [ ] Works in dark mode (if supported)
- [ ] Works on mobile devices
- [ ] Works with different code block languages

## Example Code Blocks to Test

Test with these code block types:

1. **Shell commands:**
```bash
npm install --save react three @react-three/fiber
```

2. **TypeScript code:**
```typescript
import * as THREE from 'three'
import { createRoot } from 'react-dom/client'
```

3. **JavaScript:**
```javascript
const demoSheet = getProject('Demo Project').sheet('Demo Sheet')
```

## Troubleshooting

### Buttons don't appear

- Check that `enhanceApp.js` is in `.vuepress/enhanceApp.js`
- Check browser console for errors
- Verify CSS file is loaded (check Network tab)

### Copy doesn't work

- Check browser console for errors
- Verify you're using HTTPS or localhost (Clipboard API requires secure context)
- Try the fallback method (should work in older browsers)

### Styling issues

- Check that CSS file is loaded
- Verify code blocks have `position: relative` (should be automatic)
- Check for CSS conflicts with existing styles

## Next Steps

1. Integrate into theatre-docs repository
2. Test on staging/preview environment
3. Deploy to production
4. Close issue #512

## Credits

This implementation follows best practices from:
- React documentation
- Vite documentation
- TailwindCSS documentation

All mentioned in the original issue as inspiration.

156 changes: 156 additions & 0 deletions docs-copy-button-component.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<template>
<button
class="copy-code-button"
:class="{ 'copied': isCopied }"
@click="copyToClipboard"
:aria-label="isCopied ? 'Copied!' : 'Copy to clipboard'"
:title="isCopied ? 'Copied!' : 'Copy to clipboard'"
>
<svg
v-if="!isCopied"
class="copy-icon"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
<svg
v-else
class="check-icon"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span class="copy-text">{{ isCopied ? 'Copied!' : 'Copy' }}</span>
</button>
</template>

<script>
export default {
name: 'CopyCodeButton',
props: {
code: {
type: String,
required: true
}
},
data() {
return {
isCopied: false
}
},
methods: {
async copyToClipboard() {
try {
await navigator.clipboard.writeText(this.code)
this.isCopied = true
setTimeout(() => {
this.isCopied = false
}, 2000)
} catch (err) {
console.error('Failed to copy code:', err)
// Fallback for older browsers
this.fallbackCopyTextToClipboard(this.code)
}
},
fallbackCopyTextToClipboard(text) {
const textArea = document.createElement('textarea')
textArea.value = text
textArea.style.top = '0'
textArea.style.left = '0'
textArea.style.position = 'fixed'
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
try {
const successful = document.execCommand('copy')
if (successful) {
this.isCopied = true
setTimeout(() => {
this.isCopied = false
}, 2000)
}
} catch (err) {
console.error('Fallback: Oops, unable to copy', err)
}
document.body.removeChild(textArea)
}
}
}
</script>

<style scoped>
.copy-code-button {
position: absolute;
top: 0.5rem;
right: 0.5rem;
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.375rem 0.75rem;
background: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.375rem;
font-size: 0.875rem;
color: #666;
cursor: pointer;
transition: all 0.2s ease;
z-index: 10;
}

.copy-code-button:hover {
background: rgba(0, 0, 0, 0.1);
color: #333;
}

.copy-code-button.copied {
background: #10b981;
border-color: #10b981;
color: white;
}

.copy-code-button.copied:hover {
background: #059669;
}

.copy-icon,
.check-icon {
flex-shrink: 0;
}

.copy-text {
font-size: 0.875rem;
font-weight: 500;
}

/* Dark mode support */
@media (prefers-color-scheme: dark) {
.copy-code-button {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2);
color: #ccc;
}

.copy-code-button:hover {
background: rgba(255, 255, 255, 0.15);
color: #fff;
}
}
</style>

Loading