Ajouter une vidéo de fond à certains profils utilisateur ?

J’essaie actuellement d’ajouter une vidéo aux pages de profil d’utilisateurs spécifiques, de sorte que tous nos mécènes aient une certaine vidéo d’arrière-plan sur leur profil. (Avant que vous ne vous énerviez, ce serait juste une animation en boucle, pas une vidéo complète - cela devrait être assez joli, à l’instar des arrière-plans de profil Steam.)

Le code HTML et CSS suivant fonctionne pour tous les utilisateurs - mais ce n’est évidemment pas vraiment ce que nous recherchons :

// Ceci va dans l'onglet "Header"

<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>
#myVideo {
  position: fixed;
  top: 63px;
  min-height: 1080px;
  margin-left: 50vw;
  transform: translate(-50%);
}

.user-content
{
    background: none;
}

.user-main .about.has-background .details {
    padding-bottom: 15px;
}
.user-main .about
{
    margin-bottom: 0px;
}
.user-content-wrapper
{
    background: rgba(var(--secondary-rgb), 0.8);
}

Contrairement à l’utilisation de body.category-general pour ajouter une image uniquement aux pages de la catégorie “générale”, il ne semble pas y avoir de slugs spécifiques attribués aux pages de profil des utilisateurs d’un groupe spécifique ou d’un nom d’utilisateur spécifique. Nous sommes assez novices en la matière et avons surtout de l’expérience avec CSS plutôt qu’avec le HTML direct, et nous ne savons donc pas s’il existe un moyen facile et pratique de faire fonctionner cela comme nous le souhaiterions.

Nous imaginons que la meilleure approche serait d’ajouter un slug similaire pour les profils d’utilisateurs basé sur leur groupe, mais nous ne savons pas comment y parvenir et comment faire en sorte que la vidéo ne s’affiche que sur les pages avec le contenu correct, et nous ne sommes pas non plus engagés à utiliser spécifiquement cette approche s’il existe une autre méthode plus simple.

Par exemple, nous serions également ouverts à l’idée de le faire par utilisateur plutôt que par groupe, si cela est plus facile d’une manière ou d’une autre.

Nous préférerions simplement ne pas avoir à coder en dur la vidéo sur chaque page, afin qu’elle ne se charge que lorsque vous êtes sur les utilisateurs spécifiques en question.

Modification : Je devrais probablement noter que nous sommes sur la branche stable, au cas où cela changerait quelque chose.

Notre approche actuelle consiste à voir si nous pouvons simplement détecter que nous sommes sur la page d’un certain utilisateur via le lien canonique, et si c’est le cas, à appliquer la vidéo. À ce titre, nous avons ce qui suit :

<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() => {
        determineUser();
    });
    
    function determineUser() {
        var pageURL = document.querySelector("link[rel='canonical']").getAttribute("href");
        var isUserPage = pageURL.includes("https://www.fortressoflies.com/u/");
        document.documentElement.style.setProperty('--currUsername', pageURL);
        if(isUserPage)
        {
            document.documentElement.style.setProperty('--lastUsername', pageURL);
            $('body').css('background-color', '#'+(Math.random()*0xFFFFFF<<0).toString(16));
        }
    }
</script>

Cependant, cela ne semble fonctionner que lors d’un rafraîchissement complet - pour une raison quelconque, cliquer d’une page à l’autre ne met pas à jour la propriété --currUsername, et au lieu d’appliquer un arrière-plan de couleur aléatoire aux pages utilisateur, il applique un arrière-plan de couleur aléatoire à toutes les pages si F5 a été frappé en dernier sur une page utilisateur, tout en n’appliquant rien à aucune page si F5 a été frappé en dernier sur une page non utilisateur.

Je ne suis franchement pas assez expérimenté en JavaScript pour savoir pourquoi cela se produirait - il me semble que, lors d’un changement de page, la fonction devrait se déclencher (ce qu’elle fait), provoquant la mise à jour de la variable pageURL, et cela devrait provoquer la mise à jour de la propriété --currUsername lors du chargement d’une page. Cependant, cela ne se produit que lors d’un rafraîchissement complet, sinon les variables ne semblent pas changer.

Avez-vous une idée de ce que je fais de mal ?

Il semble que cela soit dû au fait que l’URL canonique ne se met pas à jour, tandis que la propriété « og:url » le fait.

Le seul problème est que l’utilisation de var pageURL = document.querySelector("meta[property='og:url']").getAttribute("content"); se met à jour avant que la balise meta elle-même ne soit mise à jour – c’est-à-dire que ce code me donne l’URL de la page précédente, pas celle de la page actuelle.

Si vous souhaitez ajouter du contenu à une page spécifique, votre meilleure option est un plugin-outlet. En bref, les plugin-outlets sont des espaces réservés dans les modèles Discourse que vous pouvez utiliser pour ajouter du nouveau contenu.

La première chose à faire est de vérifier si un plugin-outlet existe sur la page que vous visez. Il existe un composant de thème que vous pouvez installer pour vous aider dans cette tâche.

(deprecated) Plugin outlet locations theme component

Une fois ce composant installé, activez-le, rendez-vous sur la page cible et vérifiez ce dont vous disposez. Dans votre cas, un tel plugin-outlet existe (mis en évidence en vert).

Celui que nous recherchons est donc above-user-profile.

Supposons qu’il n’existe pas… que faire ? Dans ce cas, la meilleure option est de demander son ajout ou de soumettre une PR pour l’ajouter au cœur du système. Il sera généralement accepté si votre cas d’usage est pertinent.

Quoi qu’il en soit, comme je l’ai dit, il existe déjà dans ce cas. Voyons donc comment y ajouter du code HTML. Vous n’aurez plus besoin du composant mentionné ci-dessus pour la suite, vous pouvez donc le désactiver maintenant puisque vous avez déjà le nom du plugin-outlet.

Tout ce que vous avez à faire est d’ajouter quelque chose comme ceci dans l’onglet En-tête de votre thème.

<script type="text/x-handlebars" data-template-name="/connectors/NOM_DU_OUTLET/NOM_DE_LA_PERSONNALISATION">
  Votre code HTML va ici...
</script>

Vous devez remplacer NOM_DU_OUTLET par le nom de l’outlet que vous visez. Ensuite, remplacez NOM_DE_LA_PERSONNALISATION par le nom que vous souhaitez donner à cette personnalisation. Le nom peut être n’importe quoi, mais essayez d’être descriptif si possible. C’est une bonne pratique. Nous obtenons donc ceci :

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Votre code HTML va ici... comme
  <h1>Bonjour le monde !!</h1>
</script>

Essayons cela et voyons ce qui se passe… rappelez-vous, l’extrait ci-dessus va dans l’onglet common > header de votre thème.

et…

Jusqu’ici tout va bien, mais creusons un peu plus.

Vous ne voulez pas que vos vidéos s’affichent sur tous les profils, mais uniquement selon certaines conditions. Comment faire ? Vous aurez besoin de deux choses : des données à consommer et un peu de JavaScript.

Trouvons les données. Vous vous souvenez quand j’ai dit que les plugin-outlets sont des espaces réservés ? Quel est l’intérêt de les avoir sans contexte ? C’est pourquoi Discourse transmet les éléments de contexte pertinents à chaque plugin-outlet… mais d’abord, faisons un pas en arrière. Lorsque vous ajoutez ceci :

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Votre code HTML va ici, comme...
  <h1>Bonjour le monde !!</h1>
</script>

Cela ressemble à du HTML – et les balises script le sont – mais ce qu’elles contiennent est traité comme du code Handlebars.

Cela signifie que vous pouvez faire quelque chose comme ceci à la place :

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

et vérifiez la console du navigateur. Vous verrez ceci chaque fois que l’outlet est rendu, c’est-à-dire lorsque vous êtes sur une page utilisateur.

Maintenant, est-ce que l’un de ces éléments est utile ? Oui… mais pas pour le moment. Nous y reviendrons. Faisons un autre pas en arrière et voyons comment Discourse transmet le contexte à l’outlet. Si vous recherchez le nom de l’outlet sur GitHub – ou localement – vous obtiendrez ceci :

Repository search results · GitHub

Ouvrons ce fichier. La première chose que vous voyez est cette ligne :

Regardez la dernière partie de cette ligne :

args=(hash model=model)

Vous verrez que Discourse transmet model en tant qu’argument à l’outlet. Pour tous les intents et purposes et pour garder les choses simples, model = data.

Donc, l’un des arguments de notre outlet est model, et c’est là que se trouvent les données que nous voulons. Revenons donc à notre extrait.

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

et changeons-le en ceci :

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

Nous obtenons maintenant ceci dans la console.

Vous pouvez parcourir ces données et voir si elles contiennent ce dont vous avez besoin. Elles devraient, car elles contiennent toutes les données sur l’utilisateur utilisées dans d’autres éléments de cette page. C’est le « modèle » pour la page utilisateur de cet utilisateur particulier.

L’une des propriétés disponibles là-bas est… roulement de tambour :drum: … les groupes auxquels l’utilisateur appartient.

Donc, si vous faites :

{{log args.model.groups}}

vous obtiendrez tous les groupes auxquels l’utilisateur appartient dans la console.

Bon, maintenant nous avons les données dont nous avons besoin, il ne nous reste plus qu’à ajouter une ou plusieurs conditions basées là-dessus.

Vous pourriez être tenté de penser que nous pouvons le faire dans le même extrait, mais malheureusement, nous ne pouvons pas. Handlebars est un langage de modélisation. Il prend en charge très, très basiquement la logique – rien au-delà de simples conditions vrai/faux et de boucles. Vous ne pouvez pas faire de comparaisons et autres choses comme ça.

Alors où exactement pouvez-vous le faire ? Dans une classe de connecteur, ça sonne fancy… je sais.

En bref, une classe de connecteur est essentiellement un peu de JavaScript attaché à l’outlet. C’est beaucoup plus nuancé que cela, mais c’est tout ce dont vous avez vraiment besoin pour l’instant.

Créons-en une. Nous le faisons ainsi :

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

});
</script>

NOM_DU_OUTLET et NOM_DE_LA_PERSONNALISATION ici doivent être les mêmes que ceux utilisés ci-dessus. Changeons-les donc :

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

});
</script>

Cet extrait va également dans l’onglet common > header de votre thème. Vous devriez donc maintenant avoir quelque chose qui ressemble à ceci :

<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>

Dans notre classe de connecteur, nous pouvons faire du travail… mais… nous devons être conscients que ce n’est pas comme n’importe quel fichier JavaScript. Par manque de meilleure description… pensez-y comme un composant Ember en régime. Développer cela est un peu hors du cadre ici, alors passons.

Il y a quatre méthodes connectées par défaut :

actions vous permet de définir des actions comme suit :

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

Vous pouvez ensuite appeler cette action depuis l’outlet, par exemple lorsqu’un bouton est pressé. Nous n’en aurons pas besoin ici, alors passons.

api.registerConnectorClass("above-user-profile", "add-profile-videos", {
  shouldRender(args, component) {
    // renvoyer true ou false ici
  }
});

Nous n’utiliserons pas celle-ci non plus, car l’outlet ne se rend que sur les pages de profil, et nous n’avons pas d’autres exigences pour l’instant. Cependant, vous pouvez l’utiliser pour ajouter toutes les conditions que vous souhaitez tester avant que l’outlet ne soit rendu. Par exemple, le niveau de confiance de l’utilisateur actuel ou autre chose comme ça. Continuons…

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

C’est celle sur laquelle nous voulons nous concentrer. Toutes les conditions JavaScript ou variables que vous souhaitez définir vont ici. Avant d’approfondir celle-ci, couvrons d’abord la dernière méthode pour être complet :

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

Cela se déclenche lorsque l’outlet va être supprimé. Cela vous permet donc d’effectuer tout nettoyage nécessaire, comme supprimer les écouteurs d’événements, etc.

Ok, revenons à setupComponent :

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

Vous pouvez voir qu’il y a deux éléments qui lui sont passés. D’abord, il y a args, puis component.

args ici est la même chose que nous avons examinée plus tôt. Ce sont les données de contexte que Discourse a transmises à l’outlet. Donc, si vous faites :

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

vous verrez les mêmes informations dans la console du navigateur que nous avons vues précédemment. Les groupes auxquels appartient le propriétaire du profil. C’est là que ça devient intéressant, vous avez maintenant les données et le bon crochet. Vous pouvez donc faire ce que vous voulez ici. Donc, si je veux que la vidéo ne s’affiche que sur les profils des membres appartenant à un certain groupe, je peux faire ceci :

  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 vous essayez cela sur une page de profil appartenant à un utilisateur du groupe staff, cela affichera true dans la console. Donc, maintenant, la seule chose qui nous reste à faire est de transmettre cela au modèle de l’outlet. Voici comment vous pouvez le faire.

component passé à setupComponent ici est partagé entre le connecteur et l’outlet. Vous pouvez transmettre des choses à l’outlet en les définissant comme des propriétés sur le composant comme suit :

  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})
    }
  });

Maintenant, si nous retournons au modèle et faisons quelque chose comme :

{{log showVideo}}

cela affichera le même résultat. Nous pouvons donc maintenant le mettre dans une condition Handlebars et ajouter votre code HTML à l’intérieur comme ceci :

<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="[INSÉRER LE LIEN]">
  	  <source src="[INSÉRER LE LIEN]" type="video/webm">
  	  <source src="[INSÉRER LE LIEN]" type="video/mp4">
    </video>
  {{/if}}
</script>

Ensuite, vérifiez une page de profil pour un utilisateur staff. Vous verrez que la vidéo se charge.

Une fois que vous naviguez hors du profil du membre du staff, la vidéo disparaîtra. La vidéo ne s’affichera pas sur les profils des utilisateurs qui ne sont pas dans le groupe staff.

Alors, mettons tout cela ensemble. C’est la même chose que ci-dessus.

Voici le CSS que j’ai utilisé. Onglet 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 vous voulez que cela fonctionne aussi sur mobile
.mobile-view {
  body[class*="user-"] {
    background: none;
    .user-main,
    .user-content {
      padding: 0.5em;
      background: rgba(var(--secondary-rgb), 0.8);
    }
  }
}

HTML / JavaScript / Handlebars. Cela va dans l’onglet common > header de votre thème :

<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="[INSÉRER LE LIEN]">
  	  <source src="[INSÉRER LE LIEN]" type="video/webm">
  	  <source src="[INSÉRER LE LIEN]" 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>

Remplacez TARGET_GROUP par le nom du groupe que vous souhaitez cibler et ajoutez les attributs src pour vos vidéos.

Ce post était un peu long… ne vous laissez pas décourager par cela. Une fois que vous avez compris le concept, tout ce que nous avons fait ci-dessus peut être fait en moins de 3 à 5 minutes.

La chose sympa ici est que tout ce dont nous avons parlé est à peu près le même pour n’importe quel plugin-outlet. La seule chose qui change est le nom. Donc, cela s’applique à toutes les modifications de plugin-outlet que vous souhaitez effectuer à l’avenir.

  1. Trouvez le nom de l’outlet
  2. Obtenez les données
  3. Traitez les données dans un connecteur
  4. Renvoyez les propriétés au modèle

C’est incroyablement approfondi et je ne manquerai pas de l’examiner quand j’aurai du temps la semaine prochaine, mais il suffit de dire qu’à première vue, cela semble bien meilleur que mon implémentation actuelle (intégrer la vidéo sur chaque page et ne l’afficher que sur le profil de l’utilisateur, ce que j’ai réalisé avec un script qui ajoute une balise au corps de la page d’un utilisateur si le nom de son compte est quelque chose de spécifique). Merci pour l’explication détaillée, j’ai hâte de m’y mettre !

D’accord, dans l’ensemble, cette approche fonctionne très bien, et l’explication est fantastique. Merci beaucoup d’avoir fait plus que ce qui était attendu.

Travailler sur une base par utilisateur est simple : il suffit de vérifier un nom d’utilisateur donné comme ceci :

      const isUser1 = args.model.username == "User1"
      component.setProperties({isUser1})

Cependant, nous rencontrons un problème avec notre CSS : nous souhaitons apporter des modifications à l’apparence de la page utilisateur pour uniquement ces utilisateurs.

Pour le moment, nous y parvenons via le même code qu’auparavant :

<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() =>{
        window.onload = determineUser();
    });
    
    async function determineUser() {
        await sleep(50);
        var pageURL = document.querySelector("meta[property='og:url']").getAttribute("content");
        var isUserPage = pageURL.includes("https://www.siteurl.com/u/");
        var isUser1 = pageURL.includes("u/User1/");
        document.body.className = document.body.className.replace(" user-page-animated","");

        
        if(isUserPage)
        {
            if(isUser1)
            {
                document.body.className += ' user-page-animated';
            }
        }
    }
    
    function sleep(ms)
    {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
</script>

Cela nous permet de simplement copier-coller le code “User1” pour chaque nouvel utilisateur, mais repose sur un délai de 50 ms après chaque chargement de page avant de se déclencher, ce qui est visible par l’utilisateur final (et, si supprimé, ne fonctionne pas pour une raison quelconque).

Existe-t-il un moyen d’intégrer également cet ajout d’une classe au corps dans le code que vous avez fourni, afin que nous puissions l’utiliser pour styliser différemment les pages avec des vidéos de celles qui n’en ont pas ?

Et, sérieusement, merci encore pour l’explication détaillée.