Aggiungere video di sfondo a determinati profili utente?

Sto attualmente tentando di aggiungere un video alle pagine del profilo di utenti specifici, in modo che tutti i nostri mecenati abbiano un certo video di sfondo sul loro profilo. (Prima che vi agitiate troppo, sarebbe solo un’animazione in loop, non un video completo - dovrebbe avere un bell’aspetto, simile agli sfondi dei profili di Steam.)

Il seguente codice HTML e CSS funziona per tutti gli utenti, ma ovviamente non è quello che stiamo cercando:

// Questo va nella scheda "Header"

<video playsinline autoplay muted loop id="myVideo" poster="[INSERISCI LINK]">
	<source src="[INSERISCI LINK]" type="video/webm">
	<source src="[INSERISCI 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);
}

A differenza dell’uso di body.category-general per aggiungere un’immagine solo alle pagine della categoria “generale”, non sembrano esserci slug specifici assegnati alle pagine del profilo degli utenti di un gruppo specifico o di un nome utente specifico. Siamo abbastanza nuovi a questo e abbiamo principalmente esperienza con il CSS piuttosto che con lavori diretti sull’HTML, e quindi non siamo sicuri se esista un modo semplice e conveniente per far funzionare le cose come vorremmo.

Immaginiamo che l’approccio migliore sarebbe aggiungere uno slug simile per i profili utente basato sul loro gruppo, ma non siamo sicuri di come farlo accadere e di come far apparire il video solo nelle pagine con il contenuto corretto, e inoltre non siamo impegnati a utilizzare specificamente questo approccio se esiste un altro metodo più semplice.

Ad esempio, saremmo anche aperti all’idea di farlo su base per utente anziché per gruppo, se ciò fosse in qualche modo più semplice.

Preferiremmo semplicemente non dover codificare il video su ogni pagina, in modo che venga caricato solo quando ci si trova sugli utenti specifici in questione.

Modifica: Probabilmente dovrei notare che siamo sulla branch stabile, nel caso in cui ciò cambi qualcosa.

Il nostro approccio attuale è verificare se riusciamo semplicemente a rilevare che ci troviamo sulla pagina di un determinato utente tramite il link canonico e, in tal caso, applicare il video. Pertanto, abbiamo quanto segue:

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

Tuttavia, questo sembra funzionare solo con un aggiornamento completo - per qualche motivo, navigare da una pagina all’altra non aggiorna la proprietà --currUsername e, invece di applicare uno sfondo di colore casuale alle pagine utente, applica uno sfondo di colore casuale a tutte le pagine se F5 è stato premuto l’ultima volta su una pagina utente, mentre non applica nulla a nessuna pagina se F5 è stato premuto l’ultima volta su una pagina non utente.

Francamente, non ho abbastanza esperienza con JavaScript per sapere perché ciò accada: mi sembra che, al cambio di pagina, la funzione dovrebbe essere eseguita (cosa che fa), facendo aggiornare la variabile pageURL, e questo dovrebbe causare l’aggiornamento della proprietà --currUsername al caricamento di una pagina. Tuttavia, ciò si verifica solo con un aggiornamento completo, altrimenti le variabili non sembrano cambiare.

Hai qualche idea su cosa sto sbagliando?

Sembra che ciò sia dovuto al fatto che l’URL canonico non viene aggiornato, mentre la proprietà “og:url” sì.

L’unico problema è che l’uso di var pageURL = document.querySelector("meta[property='og:url']").getAttribute("content"); si aggiorna prima che il tag meta stesso venga aggiornato, ovvero questo codice mi fornisce l’URL della pagina precedente, non quella corrente.

Se vuoi aggiungere contenuto a una pagina specifica, la tua migliore opzione è un plugin-outlet. In poche parole, i plugin-outlet sono spazi riservati nei template di Discourse che puoi utilizzare per aggiungere nuovo contenuto.

La prima cosa da fare è verificare se esiste un plugin-outlet sulla pagina che vuoi targettare. Esiste un componente tema che puoi installare per aiutarti in questo.

(deprecated) Plugin outlet locations theme component

Una volta installato quel componente, attivalo, vai sulla pagina che vuoi targettare e controlla cosa hai a disposizione. Nel tuo caso, un tale plugin-outlet esiste (evidenziato in verde)

Quindi, quello che ci interessa è above-user-profile

Supponiamo che non esistesse… e allora? In tal caso, la migliore opzione è chiedere che venga aggiunto o inviare una PR per inserirlo nel core. Nella maggior parte dei casi verrà accettato se il tuo caso d’uso ha senso.

Comunque, come ho detto, in questo caso esiste già. Quindi, vediamo come puoi aggiungere markup ad esso. Per il resto di questa guida non avrai più bisogno del componente sopra, quindi puoi disattivarlo ora che hai già il nome del plugin outlet.

Tutto quello che devi fare è aggiungere qualcosa di simile nella scheda header del tuo tema.

<script type="text/x-handlebars" data-template-name="/connectors/NOME_OUTLET/NOME_QUALSIA">
  Il tuo markup va qui...
</script>

Devi cambiare NOME_OUTLET con il nome dell’outlet che vuoi targettare. Poi cambia NOME_QUALSIA con il nome che vuoi dare a questa personalizzazione. Il nome può essere qualsiasi cosa, ma cerca di essere descrittivo se possibile. È una buona pratica. Quindi, arriviamo a questo.

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Il tuo markup va qui... come
  <h1>Ciao Mondo!!</h1>
</script>

Proviamo e vediamo cosa succede… ricorda, il frammento sopra va nella scheda common > header del tuo tema.

e…

Fin qui tutto bene, ma approfondiamo.

Non vuoi che i tuoi video appaiano su ogni profilo, ma solo in base a una certa condizione. Quindi, come si fa? Beh, avrai bisogno di due cose: alcuni dati da consumare e un po’ di Javascript.

Troviamo i dati. Ricordi quando ho detto che i plugin-outlet sono spazi riservati? Beh, qual è lo scopo di averli senza contesto? Ecco perché Discourse passa i bit rilevanti di contesto a ogni plugin outlet… ma prima, facciamo un passo indietro. Quando aggiungi questo

<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
  Il tuo markup va qui, come...
  <h1>Ciao Mondo!!</h1>
</script>

Sembra HTML - e i tag script lo sono - ma ciò che c’è al loro interno viene trattato come codice handlebars.

Ciò significa che puoi fare qualcosa di simile invece

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

e controllare la console del browser. Vedrai questo ogni volta che l’outlet viene renderizzato; cioè, quando sei su una pagina utente.

Ora, è utile tutto questo? Sì… ma non al momento. Torneremo su questo. Facciamo un altro passo indietro e vediamo come Discourse passa il contesto all’outlet. Se cerchi il nome dell’outlet su Github - o localmente - otterrai questo.

Repository search results · GitHub

Apriamo quel file. La prima cosa che vedi è questa riga

Guarda l’ultima parte di quella riga

args=(hash model=model)

Vedrai che Discourse passa model come argomento all’outlet. Per tutti gli scopi pratici e per mantenere le cose semplici, model = data.

Quindi, uno degli argomenti per il nostro outlet è model, ed è lì che si troveranno i dati che vogliamo. Quindi, torniamo al nostro frammento.

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

e cambiamolo in questo

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

Ora otteniamo questo nella console.

Puoi navigare in quei dati e vedere se contengono ciò di cui hai bisogno. Dovrebbe essere così, dato che contiene tutti i dati sull’utente utilizzati in altri elementi di quella pagina. È il “model” per la pagina utente di quell’utente specifico.

Una delle proprietà disponibili lì è… tamburo :drum: … i gruppi a cui l’utente appartiene.

Quindi, se fai

{{log args.model.groups}}

otterrai tutti i gruppi a cui l’utente appartiene nella console.

Bene, ora abbiamo i dati di cui abbiamo bisogno, quindi l’unica cosa che resta è aggiungere una o più condizioni basate su di essi.

Potresti essere tentato di pensare che possiamo farlo nello stesso frammento, ma, purtroppo, non possiamo. Handlebars è un linguaggio di templating. Ha un supporto molto, molto basilare per la logica - niente oltre a semplici condizioni vero/falso e cicli. Non puoi fare confronti e altre cose del genere.

Quindi dove esattamente puoi farlo? In una classe connector, suona sofisticato… lo so.

In poche parole, una classe connector è essenzialmente un pezzetto di Javascript attaccato all’outlet. È molto più sfumato di così, ma è tutto quello che devi sapere per ora.

Quindi, creiamone una. Lo facciamo così

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

});
</script>

NOME_OUTLET e NOME_QUALSIA qui devono essere gli stessi che abbiamo usato sopra. Quindi cambiamoli

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

});
</script>

Questo frammento va anche nella scheda common > header del tuo tema. Quindi ora dovresti avere qualcosa che assomiglia a questo

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

All’interno della nostra classe connector possiamo fare del lavoro… ma… dobbiamo essere consapevoli che non è come un qualsiasi file javaScript. Per mancanza di una descrizione migliore… pensala come un componente Ember a dieta. Espandere questo è un po’ al di fuori dello scopo qui, quindi procediamo.

Ci sono quattro metodi collegati ad essa di default

actions ti permette di definire azioni così

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

Puoi quindi chiamare quell’azione dall’interno dell’outlet, ad esempio quando viene premuto un pulsante. Non ne avremo bisogno qui, quindi procediamo.

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

Non useremo nemmeno questo poiché l’outlet viene renderizzato solo sulle pagine dei profili e al momento non abbiamo altri requisiti. Tuttavia, puoi usarlo per aggiungere qualsiasi condizione desideri testare prima che l’outlet venga renderizzato. Ad esempio, il livello di fiducia dell’utente corrente o cose del genere. Procediamo…

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

Questa è quella su cui vogliamo concentrarci. Qualsiasi condizione javaScript o variabile che desideri impostare va qui. Prima di approfondire ulteriormente, copriamo l’ultimo metodo per completezza

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

questo si attiva quando l’outlet sta per essere rimosso. Quindi, ti permette di fare qualsiasi pulizia necessaria, come rimuovere gli ascoltatori di eventi e così via.

Ok, torniamo a setupComponent

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

Puoi vedere che gli vengono passati due elementi. Prima c’è args e poi c’è component.

args qui è la stessa cosa che abbiamo esaminato prima. Sono i dati di contesto che Discourse ha passato all’outlet. Quindi, se fai

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

vedrai le stesse informazioni nella console del browser che abbiamo visto prima. I gruppi a cui appartiene il proprietario del profilo. Qui inizia il divertimento, ora hai i dati e hai il hook corretto. Quindi puoi fare tutto ciò che vuoi qui. Quindi, se voglio che il video appaia solo sui profili dei membri che appartengono a un certo gruppo, posso fare questo

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

Se provi questo su una pagina del profilo che appartiene a un utente nel gruppo staff, stamperà true nella console. Quindi, ora l’unica cosa che ci resta da fare è passare questo al template dell’outlet. Ecco come puoi farlo.

component passato a setupComponent qui è condiviso tra il connector e l’outlet. Puoi passare cose all’outlet impostandole come proprietà sul componente così

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

Ora, se torniamo al template e facciamo qualcosa come

{{log showVideo}}

stamperà lo stesso risultato. Quindi ora lo mettiamo in una condizione Handlebars e aggiungiamo il tuo markup all’interno così

<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="[INSERISCI LINK]">
      <source src="[INSERISCI LINK]" type="video/webm">
      <source src="[INSERISCI LINK]" type="video/mp4">
    </video>
  {{/if}}
</script>

poi controlla una pagina del profilo per un utente staff. Vedrai che carica il video.

Una volta che ti sposti dal profilo del membro dello staff, il video scomparirà. Il video non apparirà sui profili degli utenti che non sono nel gruppo staff.

Quindi, mettiamo tutto insieme. Questo è lo stesso contenuto di prima.

Ecco il CSS che ho usato. Scheda 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);
}

// se vuoi che appaia anche su 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. Questo va nella scheda common > header del tuo 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="[INSERISCI LINK]">
      <source src="[INSERISCI LINK]" type="video/webm">
      <source src="[INSERISCI 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 con il nome del gruppo che vuoi targettare e aggiungi gli attributi src per i tuoi video.

Questo post è stato un po’ lungo… non lasciarti scoraggiare da questo. Una volta compreso il concetto, tutto ciò che abbiamo fatto sopra può essere fatto in meno di 3-5 minuti.

La cosa bella qui è che tutto ciò di cui abbiamo parlato è praticamente lo stesso per qualsiasi plugin outlet. L’unica cosa che cambia è il nome. Quindi, questo si applica a qualsiasi modifica a plugin-outlet che vorrai fare in futuro.

  1. trova il nome dell’outlet
  2. ottieni i dati
  3. elabora i dati in un connector
  4. passa le proprietà indietro al template

È incredibilmente approfondito e mi assicurerò di esaminarlo quando avrò un po’ di tempo la prossima settimana, ma basti dire che da una scorsa sembra molto meglio della mia attuale implementazione (incorporare il video su ogni singola pagina e mostrarlo solo sul profilo utente, cosa che ho realizzato con uno script che aggiunge un tag al corpo della pagina di un utente se il nome del suo account è una certa cosa). Grazie per la spiegazione approfondita, non vedo l’ora di metterci mano!

Ok, per la maggior parte, questo approccio funziona benissimo e la spiegazione è fantastica. Grazie mille per esserti spinto oltre.

Lavorare su base per utente è semplice: controlliamo semplicemente un dato nome utente come questo:

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

Tuttavia, incontriamo un problema con il nostro CSS: desideriamo apportare alcune modifiche all’aspetto della pagina utente solo per questi utenti.

Al momento, lo stiamo realizzando tramite lo stesso codice di prima:

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

Questo ci permette di copiare e incollare semplicemente il codice “User1” per ogni nuovo utente, ma si basa su un ritardo di 50 ms dopo ogni caricamento della pagina prima di attivarsi, il che è visibile all’utente finale (e, se rimosso, non funziona per qualche motivo).

C’è un modo per agganciare anche questa aggiunta di una classe al body al codice che hai fornito, in modo da poterla utilizzare per stilizzare le pagine con video in modo diverso da quelle senza?

E, seriamente, grazie ancora per la spiegazione dettagliata.