¿Añadir video de fondo a ciertos perfiles de usuario?

Si quieres agregar contenido a una página específica, tu mejor opción es un plugin-outlet. En pocas palabras, los plugin-outlets son espacios reservados en las plantillas de Discourse que puedes usar para añadir nuevo contenido.

Lo primero que necesitas hacer es averiguar si existe un plugin-outlet en la página que quieres modificar. Hay un componente de tema que puedes instalar para ayudarte con eso.

(deprecated) Plugin outlet locations theme component

Una vez que instales ese componente, actívalo, ve a la página que quieres modificar y revisa qué tienes disponible. En tu caso, existe un plugin-outlet (resaltado en verde)

Así que, el que nos interesa es above-user-profile

Supongamos que no existiera… ¿qué hacer? En ese caso, la mejor opción es solicitar que se añada o enviar una PR para incluirlo en el núcleo. La mayoría de las veces será aceptado si tu caso de uso tiene sentido.

De todos modos, como ya mencioné, en este caso ya existe. Así que veamos cómo puedes agregar marcado a él. No necesitarás el componente anterior para el resto de esto, así que puedes desactivarlo ahora que ya tienes el nombre del plugin outlet.

Todo lo que necesitas hacer es agregar algo como esto en la pestaña de encabezado de tu tema.

<script type="text/x-handlebars" data-template-name="/connectors/OUTLET_NAME/SOME_NAME">
  Tu marcado va aquí...
</script>

Debes cambiar OUTLET_NAME por el nombre del outlet que quieres modificar. Luego cambia SOME_NAME por el nombre que quieras darle a esta personalización. El nombre puede ser cualquier cosa, pero intenta ser descriptivo si es posible. Es una buena práctica. Así que terminamos con esto.

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Tu marcado va aquí... como
  <h1>¡Hola Mundo!!</h1>
</script>

Probémoslo y veamos qué sucede… recuerda, el fragmento anterior va en la pestaña common > header de tu tema.

y…

Hasta aquí todo bien, pero profundicemos.

No quieres que tus videos se muestren en todos los perfiles, sino solo bajo ciertas condiciones. Entonces, ¿cómo lo haces? Necesitarás dos cosas: algunos datos para consumir y un poco de JavaScript.

Encontrémos los datos. ¿Recuerdas cuando dije que los plugin-outlets son espacios reservados? ¿Cuál sería el punto de tenerlos sin contexto? Por eso Discourse pasa los fragmentos relevantes de contexto a cada plugin outlet… pero primero, retrocedamos un paso. Cuando agregas esto

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Tu marcado va aquí, como...
  <h1>¡Hola Mundo!!</h1>
</script>

Parece HTML —y las etiquetas script lo son—, pero lo que hay dentro se trata como código Handlebars.

Eso significa que puedes hacer algo como esto en su lugar:

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  {{log this}}
</script>

y revisa la consola del navegador. Verás esto cada vez que se renderice el outlet, es decir, cuando estés en una página de usuario.

Ahora, ¿algo de esto es útil? Sí… pero no por el momento. Volvemos a esto más adelante. Retrocedamos otro paso y veamos cómo Discourse pasa el contexto al outlet. Si buscas el nombre del outlet en GitHub —o localmente— obtendrás esto:

Repository search results · GitHub

Abramos ese archivo. Lo primero que verás es esta línea:

Observa la última parte de esa línea:

args=(hash model=model)

Verás que Discourse pasa model como argumento al outlet. Para todos los efectos prácticos y para mantener las cosas simples, model = data.

Así que uno de los argumentos de nuestro outlet es model, y ahí estará la información que buscamos. Volvamos a nuestro fragmento.

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  {{log this}}
</script>

y cambiémoslo por esto:

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
-  {{log this}}
+  {{log args}}
</script>

Ahora obtenemos esto en la consola:

Puedes explorar esos datos y ver si tienen lo que necesitas. Deberían, ya que contienen toda la información sobre el usuario utilizada en otros elementos de esa página. Es el “modelo” para la página de usuario de ese usuario en particular.

Una de las propiedades disponibles allí es… redoble de tambores :drum: … los grupos a los que pertenece el usuario.

Así que, si haces:

{{log args.model.groups}}

obtendrás todos los grupos a los que pertenece el usuario en la consola.

Bien, ahora tenemos los datos que necesitamos, así que lo único que queda es agregar alguna(s) condición(es) basada(s) en ellos.

Podrías pensar que podemos hacer eso en el mismo fragmento, pero, desafortunadamente, no podemos. Handlebars es un lenguaje de plantillas. Tiene soporte muy, muy básico para lógica: nada más allá de condiciones simples verdadero/falso y bucles. No puedes hacer comparaciones ni otras cosas como esa.

Entonces, ¿dónde exactamente puedes hacer eso? En una clase de conector, suena sofisticado… lo sé.

En pocas palabras, una clase de conector es esencialmente un poco de JavaScript adjunto al outlet. Es mucho más matizado que eso, pero eso es todo lo que realmente necesitas saber por ahora.

Así que creémos una. Lo hacemos así:

<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('OUTLET_NAME', 'SOME_NAME', {

});
</script>

OUTLET_NAME y SOME_NAME aquí deben ser los mismos que usamos arriba. Así que cambiémoslos:

<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {

});
</script>

Este fragmento también va en la pestaña common > header de tu tema. Así que ahora deberías tener algo que se ve así:

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  {{log args.model.groups}}
</script>

<script type="text/discourse-plugin" version="0.8">
  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {

  });
</script>

Dentro de nuestra clase de conector, podemos hacer algo… pero… debemos tener en cuenta que no es como cualquier archivo de JavaScript. Por falta de una mejor descripción… piénsalo como un componente Ember a dieta. Expandir esto está un poco fuera del alcance aquí, así que sigamos adelante.

Hay cuatro métodos conectados por defecto:

actions te permite definir acciones así:

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  actions: {
    myAction() {
      // hacer algo
    }
  }
});

Luego puedes llamar a esa acción desde dentro del outlet, como cuando se presiona un botón. No la necesitaremos aquí, así que sigamos.

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  shouldRender(args, component) {
    // devolver true o false aquí
  }
});

Tampoco usaremos esta, ya que el outlet solo se renderiza en páginas de perfil y por ahora no tenemos otros requisitos. Sin embargo, puedes usarla para agregar cualquier condición que quieras probar antes de que se renderice el outlet. Por ejemplo, el nivel de confianza del usuario actual o cosas así. Sigamos…

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  setupComponent(args, component) {
    // hacer algo
  }
});

Esta es la que queremos enfocarnos. Cualquier condición o variable de JavaScript que quieras establecer va aquí. Antes de profundizar en esta, cubramos el último método primero por completitud:

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  teardownComponent(args, component) {
    // hacer algo
  }
});

Esto se ejecuta cuando el outlet va a ser eliminado. Así que te permite realizar cualquier limpieza necesaria, como eliminar escuchadores de eventos, etc.

Bien, volvamos a setupComponent:

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  setupComponent(args, component) {
    // hacer algo
  }
});

Puedes ver que se le pasan dos cosas. Primero está args y luego component.

args aquí es lo mismo que vimos antes. Son los datos de contexto que Discourse pasó al outlet. Así que, si haces:

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  setupComponent(args, component) {
    console.log(args.model.groups);
  }
});

verás la misma información en la consola del navegador que vimos antes. Los grupos a los que pertenece el propietario del perfil. Aquí es donde comienza la diversión; ahora tienes los datos y el gancho correcto. Así que puedes hacer lo que quieras aquí. Entonces, si quiero que el video solo se muestre en los perfiles de miembros que pertenecen a un grupo determinado, puedo hacer esto:

  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
    setupComponent(args, component) {
      const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
      const showVideo = inGroup.length ? true : false;

      console.log(showVideo);
    }
  });

Si pruebas esto en una página de perfil que pertenece a un usuario del grupo staff, imprimirá true en la consola. Así que ahora lo único que nos queda es pasar eso a la plantilla del outlet. Así es como puedes hacerlo.

component pasado a setupComponent aquí se comparte entre el conector y el outlet. Puedes pasar cosas al outlet estableciéndolas como propiedades en el componente así:

  const TARGET_GROUP = "staff"

  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
    setupComponent(args, component) {
      const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
      const showVideo = inGroup.length ? true : false;
-     console.log(showVideo);
+     component.setProperties({showVideo})
    }
  });

Ahora, si volvemos a la plantilla y hacemos algo como:

{{log showVideo}}

imprimirá el mismo resultado. Así que ahora lo ponemos en una condición de Handlebars y agregamos tu marcado dentro así:

<script
  type="text/x-handlebars"
  data-template-name="/connectors/above-user-profile/add-profile-videos"
>
  {{#if showVideo}}
    <video playsinline autoplay muted loop id="myVideo" poster="[INSERT LINK]">
      <source src="[INSERT LINK]" type="video/webm">
      <source src="[INSERT LINK]" type="video/mp4">
    </video>
  {{/if}}
</script>

luego revisa una página de perfil de un usuario de staff. Verás que carga el video.

Una vez que navegas fuera del perfil del miembro del staff, el video desaparece. El video no se mostrará en los perfiles de usuarios que no estén en el grupo staff.

Así que, pongamos todo esto junto. Esto es lo mismo que vimos arriba.

Aquí está el CSS que usé. Pestaña common > css:

#myVideo {
  position: fixed;
  top: var(--header-offset);
  min-height: 100vh;
  left: 0;
  z-index: -1;
}

.user-content {
  background: none;
}

.user-main {
  padding: 0.5em;
  background: rgba(var(--secondary-rgb), 0.8);
}

// si también lo quieres en móvil
.mobile-view {
  body[class*="user-"] {
    background: none;
    .user-main,
    .user-content {
      padding: 0.5em;
      background: rgba(var(--secondary-rgb), 0.8);
    }
  }
}

HTML / JavaScript / Handlebars. Esto va en la pestaña common > header de tu tema:

<script
  type="text/x-handlebars"
  data-template-name="/connectors/above-user-profile/add-profile-videos"
>
  {{#if showVideo}}
    <video playsinline autoplay muted loop id="myVideo" poster="[INSERT LINK]">
      <source src="[INSERT LINK]" type="video/webm">
      <source src="[INSERT LINK]" type="video/mp4">
    </video>
  {{/if}}
</script>

<script type="text/discourse-plugin" version="0.8">
  const TARGET_GROUP = "staff"

  api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
    setupComponent(args, component) {
      const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
      const showVideo = inGroup.length ? true : false;
      component.setProperties({showVideo})
    }
  });
</script>

Cambia TARGET_GROUP por el nombre del grupo que quieras modificar y agrega los atributos src para tus videos.

Esta publicación fue un poco larga… no te desanimes por eso. Una vez que comprendas el concepto, todo lo que hicimos arriba se puede hacer en menos de 3-5 minutos.

Lo bueno aquí es que todo lo que hablamos es prácticamente lo mismo para cualquier plugin outlet. Lo único que cambia es el nombre. Así que esto se aplica a cualquier modificación de plugin outlet que quieras hacer en el futuro.

  1. encuentra el nombre del outlet
  2. obtén los datos
  3. procesa los datos en un conector
  4. pasa las propiedades de vuelta a la plantilla