Contents
Introducción
Este tutorial explica, paso a paso y con todos los detalles prácticos, cómo añadir la funcionalidad de copy-to-clipboard a los bloques de código en un sitio WordPress usando JavaScript. Cubriremos la forma recomendada moderna (Clipboard API), la copia por compatibilidad (execCommand), integración correcta en WordPress (enqueue de scripts y localización), compatibilidad con diferentes plugins/formatos de código y buenas prácticas de accesibilidad y UX (feedback, manejo en móviles, observador para contenido dinámico).
Requisitos previos
- WordPress 5.x o superior (o cualquier instalación donde puedas editar functions.php o crear un plugin simple).
- Acceso a los archivos del tema (functions.php) o a un plugin personalizado para encolar scripts y estilos.
- HTTPS preferible (navigator.clipboard requiere contexto seguro en la mayoría de navegadores).
- Conocimientos básicos de JavaScript, CSS y PHP para integrar el código en WordPress.
Resumen de la estrategia
- Detectar los bloques de código presentes en la página (varias estructuras: pre > code, pre[class=language-], .wp-block-code, plugins como Prism, Highlight.js o Enlighter).
- Insertar un botón Copiar en cada bloque (con marcado accesible y estilos CSS).
- Implementar la acción de copia usando navigator.clipboard.writeText() y proporcionar un fallback a document.execCommand(copy) para navegadores antiguos.
- Proveer retroalimentación visual (tooltip o cambio de texto del botón) y accesible (aria-live, aria-label).
- Encolar los scripts y estilos correctamente en WordPress y proveer traducciones/strings mediante wp_localize_script.
Ejemplo completo de JavaScript (front-end)
Este script detecta bloques de código comunes, añade un botón y gestiona la copia con fallback. También incluye un MutationObserver para detectar contenido cargado dinámicamente.
/ copy-to-clipboard for code blocks - Detects: pre > code, pre[class=language-], .wp-block-code, .enlighter - Uses Clipboard API with fallback / (function () { use strict var strings = { copy: Copiar, copied: Copiado, error: Error } // Utilidades function createButton(label) { var btn = document.createElement(button) btn.type = button btn.className = code-copy-button btn.setAttribute(aria-label, label) btn.textContent = label return btn } function getCodeText(block) { // Si el bloque es...
var codeEl = block.querySelector(code)
if (codeEl) return codeEl.innerText
// Si el bloque es...return block.innerText
}function copyTextToClipboard(text) {
if (!text) return Promise.reject(new Error(No text to copy))
if (navigator.clipboard navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text)
}
// Fallback usando textarea execCommand
return new Promise(function (resolve, reject) {
var textarea = document.createElement(textarea)
textarea.value = text
// Evitar scroll
textarea.style.position = fixed
textarea.style.left = -9999px
document.body.appendChild(textarea)
textarea.select()
try {
var successful = document.execCommand(copy)
document.body.removeChild(textarea)
if (successful) return resolve()
return reject(new Error(execCommand failed))
} catch (err) {
document.body.removeChild(textarea)
return reject(err)
}
})
}function showTemporaryFeedback(btn, text) {
var original = btn.textContent
btn.textContent = text
btn.setAttribute(aria-pressed, true)
setTimeout(function () {
btn.textContent = original
btn.setAttribute(aria-pressed, false)
}, 2000)
}function attachButtonToBlock(block) {
// Evitar duplicados
if (block.querySelector(.code-copy-button)) returnvar btn = createButton(strings.copy)
btn.classList.add(code-copy-button-installed)
btn.addEventListener(click, function (e) {
e.preventDefault()
var text = getCodeText(block)
copyTextToClipboard(text).then(function () {
showTemporaryFeedback(btn, strings.copied)
}).catch(function () {
showTemporaryFeedback(btn, strings.error)
})
})// Insertar el botón al inicio del
o como primer hijo del bloque // Algunos themes bloquean insertar elementos fuera, pero normalmente funciona. block.insertBefore(btn, block.firstChild) } function findCodeBlocks() { var blocks = [] // pre > code document.querySelectorAll(pre > code).forEach(function (el) { blocks.push(el.parentNode) }) // pre[class=language-] document.querySelectorAll(pre[class=language-]).forEach(function (el) { if (!blocks.includes(el)) blocks.push(el) }) // wp-block-code (Gutenberg) document.querySelectorAll(.wp-block-code).forEach(function (el) { if (!blocks.includes(el)) blocks.push(el) }) // EnlighterJS / Highlight.js wrappers - intentar capturarigualmente document.querySelectorAll(pre).forEach(function (el) { if (!blocks.includes(el)) blocks.push(el) }) return blocks } function init() { var blocks = findCodeBlocks() blocks.forEach(function (block) { attachButtonToBlock(block) }) } // Inicializa inmediatamente if (document.readyState === loading) { document.addEventListener(DOMContentLoaded, init) } else { init() } // Observador para contenido dinámico (AJAX o carga diferida) var observer = new MutationObserver(function (mutations) { mutations.forEach(function (m) { if (m.addedNodes m.addedNodes.length) { // Re-run init para nuevos nodos init() } }) }) observer.observe(document.documentElement document.body, { childList: true, subtree: true }) // Export para pruebas si es necesario window.__WPCodeCopy = { init: init } })()Estilos CSS recomendados
Un CSS ligero para posicionar el botón dentro del bloque de código. Adáptalo al diseño de tu tema.
/ Estilos mínimos para el botón de copiar / .code-copy-button { position: absolute right: 0.5rem top: 0.5rem background: rgba(0,0,0,0.6) color: #fff border: none padding: 0.25rem 0.5rem border-radius: 3px cursor: pointer font-size: 0.9rem z-index: 10 } / Si el pre no es positioning relative, es mejor hacerlo / pre { position: relative overflow: auto padding-top: 2.2rem / espacio para el botón / } / Opcional: mejor visibilidad en hover / .code-copy-button:hover { background: rgba(0,0,0,0.8) } / Ajustes móviles / @media (max-width: 480px) { .code-copy-button { top: 0.2rem right: 0.2rem padding: 0.2rem 0.4rem font-size: 0.8rem } }Integración en WordPress (functions.php)
Encola el script y los estilos correctamente para que estén disponibles en el front-end. Además, utiliza wp_localize_script para pasar los textos traducibles al script.
__(Copiar, tu-text-domain), copied => __(Copiado, tu-text-domain), error => __(Error, tu-text-domain), ) wp_localize_script(theme-code-copy, WPCodeCopyStrings, strings) wp_enqueue_script(theme-code-copy) } add_action(wp_enqueue_scripts, theme_enqueue_code_copy) ?>En el JavaScript anterior puedes leer WPCodeCopyStrings para sustituir los textos hardcodeados (en el ejemplo del script general puedes reemplazar la variable strings por WPCodeCopyStrings):
// Dentro del IIFE, reemplazar la asignación de strings por: var strings = window.WPCodeCopyStrings { copy: Copiar, copied: Copiado, error: Error }Compatibilidad con plugins y editores
- Gutenberg: los bloques de código quedan con la clase .wp-block-code el script los detecta.
- Prism / Highlight.js / Enlighter: la mayoría envuelven el código en ltpregt o ltpregtltcodegt, por eso el script comprueba ambas estructuras.
- Si tu plugin usa una estructura HTML distinta, adapta el selector en findCodeBlocks() (ej. document.querySelectorAll(.mi-clase-de-codigo)).
Accesibilidad y UX
- Usar un ltbuttongt nativo en lugar de un ltdivgt hace que el control sea focoable y operativo con teclado.
- Proveer aria-label con el texto Copiar y cambiar aria-pressed o usar aria-live para anunciar el estado si es necesario.
- Para usuarios de lectores de pantalla, wp_localize_script permite traducir Copiar / Copiado.
- Evitar seleccionar texto visualmente abrupto el script usa textarea temporal y lo limpia inmediatamente en fallback.
Manejo de casos especiales
- Contenido cargado dinámicamente: el MutationObserver en el script re-evalúa y añade botones a nuevos bloques.
- Bloques muy largos o con scroll: colocar el botón posicionado con position: absolute dentro del pre evita romper el flujo.
- Evitar duplicado al re-renderizar: el script comprueba la presencia del botón antes de insertar uno nuevo.
Seguridad y privacidad
- La Clipboard API solo funciona en contextos seguros (HTTPS) y el navegador puede requerir interacción del usuario (click) — lo cual es exactamente lo que hacemos.
- No copiar ni leer contenido del portapapeles del usuario sin su acción explícita.
- No envíes el contenido copiado a servidores externos. El ejemplo mantiene la operación en el cliente.
Alternativas y mejoras
- Integración directa con highlighters: algunos plugins (Prism) ofrecen hooks o plugins de copia. Si usas Prism, considera usar su plugin oficial prism-copy-to-clipboard.
- Mostrar un tooltip en lugar de cambiar el texto del botón puedes usar aria-live para anunciar Copiado a lectores de pantalla.
- Permitir copia de una selección específica (p. ej. copiar sólo la línea activa) con data attributes por bloque.
- Implementar animaciones o iconos SVG para mejor experiencia visual.
Depuración
- Verifica en consola si el archivo JS se ha encolado correctamente (buscar theme-code-copy en los assets cargados).
- Si el botón no aparece, inspecciona si los selectores detectan el bloque. Prueba en la consola: document.querySelectorAll(pre > code).
- Si la copia falla, intenta verificar si navigator.clipboard está presente y si la acción ocurre tras un evento de usuario (click).
Conclusión
La implementación de un botón copy-to-clipboard en los bloques de código en WordPress es relativamente sencilla: requiere insertar un botón accesible, usar la Clipboard API con fallback y encolar el script y estilos mediante las funciones de WordPress. Con las pequeñas mejoras (observador para contenido dinámico, localización de textos y ajustes responsivos) tendrás una solución robusta y compatible con la mayoría de temas y plugins de sintaxis.
Recursos útiles
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |