Como añadir copy-to-clipboard a bloques de código en JS en WordPress

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

  1. 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).
  2. Insertar un botón Copiar en cada bloque (con marcado accesible y estilos CSS).
  3. Implementar la acción de copia usando navigator.clipboard.writeText() y proporcionar un fallback a document.execCommand(copy) para navegadores antiguos.
  4. Proveer retroalimentación visual (tooltip o cambio de texto del botón) y accesible (aria-live, aria-label).
  5. 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)) return

var 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 capturar 
 igualmente
    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

  1. Verifica en consola si el archivo JS se ha encolado correctamente (buscar theme-code-copy en los assets cargados).
  2. Si el botón no aparece, inspecciona si los selectores detectan el bloque. Prueba en la consola: document.querySelectorAll(pre > code).
  3. 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 🙂



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *