Incrustar widget dentro del texto en un tema

¡Hola!! :slight_smile:

Espero que esto sea de interés para alguien más: estoy intentando crear un tema de “feed de redes sociales” en el que quiero incrustar los widgets de diferentes plataformas para que los usuarios (¡y los administradores!) puedan tener una vista rápida de todos ellos en una sola página, así pueden permanecer dentro del foro, evitando la sobrecarga que significa tener que cambiar entre tantas plataformas sociales solo para actualizaciones rápidas (considerando también que suelen compartir el mismo contenido), aumentando así la motivación para usar el foro como un centro para el desarrollo de la comunidad.

Un generador de widgets que encontré y que me parece bueno es Woxo, el cual es lo suficientemente limpio y sencillo para este propósito. El problema ahora es que no logro averiguar cómo incrustar el widget en el tema. Estoy tratando de ver si hay alguna solución alternativa con iframes o algo similar, pero ahora quería preguntar para ver si esto es posible en absoluto.

Este es el código que obtengo de Woxo para el feed de Instagram:

<div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>
        
<script 
  src="https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619" 
  async data-usrc>
</script>

Lo que he intentado hasta ahora:

  • Colocar el <script> en la sección <body>, <footer> y <header> (lo dejé en el encabezado)
  • Asegurarme de que la URL de donde proviene el script esté en la lista blanca (https://cdn2.woxo.tech/ en este caso)
  • Agregar defer no ayuda (lo mantengo por si acaso)

Si inspecciono la página, el script aparece en la parte inferior de la sección del cuerpo (dentro), y como el origen está en la lista blanca, debería tener efecto. Verifiqué si podría ser mi navegador, pero si ejecuto el HTML aquí W3Schools Tryit Editor funciona perfectamente.

He reducido el error a una función específica dentro del script de JS. La siguiente llamada devuelve un valor nulo. Es el único error en tiempo de ejecución:

e=document.querySelector("div[data-mc-src]")
e es null

Ese div está escrito en el tema (la parte <div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>). Permanece como código HTML puro, por lo que debería ser legible. Por alguna razón, el script no logra localizarlo.

Con el atributo defer, y ubicado en el pie de página, los scripts no arrojan error (el hecho de que antes arrojaran un error prueba que la URL del archivo JS está efectivamente en la lista blanca), así que ahora estoy a ciegas sobre por qué no hace nada.

Cualquier aporte será más que apreciado, ¡gracias de antemano por tu tiempo! :slight_smile:
Lisandro

EDIT: finalmente tuve que desistir. Como solo se admiten iframes, actualmente estoy buscando un buen servicio web que pueda proporcionar uno. La mayoría de los servicios gratuitos son demasiado limitados, y las versiones de pago cuestan más del doble que el servicio de alojamiento del foro. Perdón por el quejido, tuve que gritarlo aquí por no poder simplemente insertar el código HTML gratuito :cry:

2 Me gusta

Discourse es una aplicación de una sola página. El problema que estás experimentando ocurre porque el script que estás utilizando no es consciente de ello. Cuando visitas la página de inicio o cualquier otra página en Discourse, obtienes algo como esto.

<html>
  <head>
    contenido del head, incluido tu script
  </head>
  <body>
    <section id="main">
      contenido de la página
    </section>
  </body>
</html>

Cuando navegas a otra página, lo único que se vuelve a cargar es el contenido dentro de

<section id="main">

Por lo tanto, el DOM ha cambiado y tu script personalizado no se ejecuta nuevamente. Si intentas visitar la página del tema directamente, verás que se carga correctamente.

.

Entonces, ahora la pregunta es cómo hacer que funcione con Discourse.

La API de plugins tiene un método que puedes usar para “decorar” publicaciones.

https://github.com/discourse/discourse/blob/main/app/assets/javascripts/discourse/app/lib/plugin-api.js#L282-L318

Puedes usarlo para ejecutar scripts de terceros cuando se renderiza una publicación.

Aquí está el código que necesitarías. Agrega esto a la pestaña common > header de tu tema.

<script type="text/discourse-plugin" version="0.8">
const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

const loadScript = require("discourse/lib/load-script").default;
const { iconHTML } = require("discourse-common/lib/icon-library");

const composerPreviewIcon = iconHTML(PREVIEW_ICON, {
  class: "woxo-preview-icon"
});

const previewMarkup = () => {
  const markup = `<div class="woxo-preview">${composerPreviewIcon}</div>`;
  return markup;
};

// crea un decorador de publicaciones
api.decorateCookedElement(
  post => {
    const woxoWidgets = post.querySelectorAll("div[data-mc-src]");

    if (woxoWidgets.length) {
      woxoWidgets.forEach(woxoWidget => {
        if (post.classList.contains("d-editor-preview")) {
          woxoWidget.innerHTML = previewMarkup();
          return;
        }

        loadScript(WOXO_SCRIPT_SRC).then(() => {
          const script = document.head.querySelector(
            `script[src*="cdn2.woxo.tech"]`
          );
          script.dataset.usrc = "";
          window.MC.Loader.init();
        });
      });
    }
  },
  { id: "render-woxo-widgets" }
);
</script>

Luego necesitarías agregar un par de dominios para CSP. Agrega estos a tu

content_security_policy_script_src

configuración del sitio

https://*.woxo.tech/
https://us-central1-core-period-259421.cloudfunctions.net/availableComponentTracks

finalmente, necesitarías agregar un poco de CSS para la vista previa estática del compositor

Esto va en la pestaña common > CSS de tu tema.

.woxo-preview {
  height: 400px;
  width: 100%;
  background: var(--primary-low);
  display: flex;
  align-items: center;
  justify-content: center;
  .woxo-preview-icon {
    font-size: var(--font-up-4);
    color: var(--primary-high);
  }
}

Luego puedes simplemente agregar

<div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>

a cualquier publicación, y los widgets se renderizarán y serán totalmente funcionales.

Si observas el JavaScript, notarás que tiene dos opciones en la parte superior.

const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

Cambia WOXO_SCRIPT_SRC por el src que te proporcione Woxo. Debería ser el mismo para todos los incrustados que crees.

Cambia PREVIEW_ICON por el nombre del ícono que deseas usar en la vista previa del compositor. Ejecutar este código en el compositor es un poco costoso, por lo que el compositor tiene una vista previa estática que se ve así.

El ícono que elijas aparecerá en el centro.

Aquí tienes una copia comentada del código si quieres seguir lo que está sucediendo

código comentado
<script type="text/discourse-plugin" version="0.8">
// opciones
const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

// usamos la librería Load script de Discourse para asegurar que los scripts se carguen
// correctamente. No te preocupes, es lo suficientemente inteligente como para no duplicar el script
// si ya está cargado
const loadScript = require("discourse/lib/load-script").default;

// cargamos la función iconHTML de Discourse para obtener el SVG del ícono
// que queremos usar en la vista previa estática del compositor
const { iconHTML } = require("discourse-common/lib/icon-library");

// configuramos el ícono de vista previa del compositor
const composerPreviewIcon = iconHTML(PREVIEW_ICON, {
  class: "woxo-preview-icon"
});

// creamos una función auxiliar para el marcado de vista previa del compositor
const previewMarkup = () => {
  const markup = `<div class="woxo-preview">${composerPreviewIcon}</div>`;

  return markup;
};

// creamos un decorador de publicaciones
api.decorateCookedElement(
  post => {
    // ¿tiene esta publicación widgets de woxo?
    const woxoWidgets = post.querySelectorAll("div[data-mc-src]");

    // Sí, entonces hagamos algo de trabajo.
    if (woxoWidgets.length) {
      // para cada widget de woxo
      woxoWidgets.forEach(woxoWidget => {
        // si es un widget del compositor, reemplázalo por una vista previa estática y
        // salimos temprano
        if (post.classList.contains("d-editor-preview")) {
          woxoWidget.innerHTML = previewMarkup();
          return;
        }

        // si no está en el compositor, cargamos el script de woxo.
        loadScript(WOXO_SCRIPT_SRC).then(() => {
          // El script de woxo es muy extraño. No funcionará a menos que la etiqueta
          // script tenga un atributo data-usrc vacío. Así que agregémoslo
          const script = document.head.querySelector(
            `script[src*="cdn2.woxo.tech"]`
          );
          script.dataset.usrc = "";

          // todo está listo, llamemos al método init en el script de woxo
          window.MC.Loader.init();
        });
      });
    }
  },
  // agrega un id al decorador para evitar fugas de memoria
  { id: "render-woxo-widgets" }
);

</script>
3 Me gusta

Primero que nada: O-M-G ¡MUCHÍSIMAS GRACIAS TANTAS! :exploding_head:
Me asombra el nivel de la respuesta; mi única consuelo al ver tal cantidad de trabajo es que es seguro que esto será de gran ayuda para muchos. Esto abre muchas nuevas posibilidades, todo en beneficio del desarrollo de un foro como un centro para una comunidad.

Segundo: Lamento mucho haber respondido tan tarde, especialmente porque ustedes respondieron tan rápido. Primero no pude acceder a mi computadora antes, y segundo, solo quería responder una vez que realmente hubiera revisado el código que tan amablemente comentaste (línea por línea :flushed:, Dios mío, muchas gracias). Por supuesto, todo funcionó perfectamente, toda la fuente se carga tan rápido… Estoy en deuda :pray:

Gracias de nuevo, Johan. Realmente espero poder contribuir con mi propia experiencia :pray:

PD: Definitivamente mantendré el corazón para la vista previa estática.

1 me gusta

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.