Cambiamenti imminenti allo stream di post - Come preparare temi e plugin

Stiamo abbandonando il sistema di rendering legacy dei “widget” per sostituirlo con i moderni componenti Glimmer.

Abbiamo recentemente modernizzato il flusso dei post utilizzando i componenti Glimmer. Questa guida vi accompagnerà nella migrazione dei vostri plugin e temi dal vecchio sistema basato sui widget alla nuova implementazione Glimmer.

Non preoccupatevi: questa migrazione è più semplice di quanto possa sembrare a prima vista. Abbiamo progettato il nuovo sistema per essere più intuitivo e potente del vecchio sistema di widget, e questa guida vi aiuterà nel processo.

Tempistiche

Queste sono stime soggette a modifiche

Q2 2025:

  • :white_check_mark: Implementazione principale completata
  • :white_check_mark: Inizio dell’aggiornamento dei plugin ufficiali e dei componenti del tema
  • :white_check_mark: Abilitato su Meta
  • :white_check_mark: Pubblicato il consiglio per l’aggiornamento (questa guida)

Q3 2025:

  • :white_check_mark: Completamento dell’aggiornamento dei plugin ufficiali e dei componenti del tema
  • :white_check_mark: Impostazione di glimmer_post_stream_mode su auto come predefinito e attivazione dei messaggi di deprecazione nella console
  • :white_check_mark: Attivazione dei messaggi di deprecazione con un banner di avviso per gli amministratori (previsto per luglio 2025)
  • I plugin e i temi di terze parti dovrebbero essere aggiornati

Q4 2025:

  • :white_check_mark: Attivazione del nuovo flusso dei post come predefinito
  • :white_check_mark: Rimozione dell’impostazione del feature flag e del codice legacy

Cosa significa per me?

Se uno qualsiasi dei vostri plugin o temi utilizza le API dei “widget” per personalizzare il flusso dei post, dovrete aggiornarli per farli funzionare con la nuova versione.

Come posso provare il nuovo flusso dei post?

Per provare il nuovo flusso dei post, modificate semplicemente l’impostazione glimmer_post_stream_mode su auto nelle impostazioni del sito. Questo attiverà il nuovo flusso dei post se non avete plugin o temi incompatibili.

Quando glimmer_post_stream_mode è impostato su auto, Discourse rileva automaticamente i plugin e i temi incompatibili e, se ne trova, visualizzerà messaggi di avviso utili nella console del browser che identificano esattamente quali plugin o temi necessitano di aggiornamento, insieme agli stack trace per aiutarvi a localizzare il codice rilevante.

Questi messaggi vi aiuteranno a identificare esattamente quali parti dei vostri plugin o temi devono essere aggiornate per essere compatibili con il nuovo flusso dei post Glimmer.

Se riscontrate problemi durante l’uso del nuovo flusso dei post, non preoccupatevi: per ora potete ancora impostare l’opzione su disabled per tornare al vecchio sistema. Se avete installate estensioni incompatibili ma desiderate comunque provarlo, in qualità di amministratore potete impostare l’opzione su enabled per forzare l’uso del nuovo flusso dei post. Usatelo con cautela: il vostro sito potrebbe non funzionare correttamente a seconda delle personalizzazioni che avete.

Ho installato plugin o temi personalizzati. Devo aggiornarli?

Dovrete aggiornare i vostri plugin o temi se eseguono una delle seguenti personalizzazioni:

  • Utilizzano decorateWidget, changeWidgetSetting, reopenWidget o attachWidgetAction su questi widget:

    • actions-summary
    • avatar-flair
    • embedded-post
    • expand-hidden
    • expand-post-button
    • filter-jump-to-post
    • filter-show-all
    • post-article
    • post-avatar-user-info
    • post-avatar
    • post-body
    • post-contents
    • post-date
    • post-edits-indicator
    • post-email-indicator
    • post-gap
    • post-group-request
    • post-links
    • post-locked-indicator
    • post-meta-data
    • post-notice
    • post-placeholder
    • post-stream
    • post
    • poster-name
    • poster-name-title
    • posts-filtered-notice
    • reply-to-tab
    • select-post
    • topic-post-visited-line
  • Utilizzano uno dei seguenti metodi API:

    • addPostTransformCallback
    • includePostAttributes

:light_bulb: Se avete estensioni che utilizzano una delle personalizzazioni sopra elencate, vedrete un avviso nella console che identifica quale plugin o componente deve essere aggiornato quando visitate una pagina di argomento.

L’ID di deprecazione è: discourse.post-stream-widget-overrides

:warning: Se utilizzate più di un tema nella vostra istanza, assicuratevi di controllarli tutti, poiché gli avvisi appariranno solo per i plugin attivi e i temi e i componenti del tema attualmente in uso.

Quali sono le sostituzioni?

Il nuovo flusso dei post Glimmer offre diversi modi per personalizzare l’aspetto dei post:

  1. Plugin Outlets: Per aggiungere contenuto in punti specifici del flusso dei post.
  2. Trasformatori: Per personalizzare gli elementi, modificare le strutture dei dati o cambiare il comportamento dei componenti.

Sostituire includePostAttributes con addTrackedPostProperties

Se il vostro plugin utilizza includePostAttributes per aggiungere proprietà al modello del post, dovrete aggiornarlo per utilizzare addTrackedPostProperties.

Prima:

api.includePostAttributes('can_accept_answer', 'accepted_answer', 'topic_accepted_answer');

Dopo:

api.addTrackedPostProperties('can_accept_answer', 'accepted_answer', 'topic_accepted_answer');

La funzione addTrackedPostProperties segna le proprietà come tracciate per gli aggiornamenti dei post. Questo è importante se il vostro plugin aggiunge proprietà a un post e le utilizza durante il rendering. Garantisce che l’interfaccia utente si aggiorni automaticamente quando queste proprietà cambiano.

Pattern comuni di migrazione

Utilizzo dei Plugin Outlets

I plugin outlets sono il vostro strumento principale per aggiungere contenuto in punti specifici del flusso dei post. Pensateli come punti designati dove potete iniettare il vostro contenuto personalizzato.

Sono la chiave per migrare dalle decorazioni dei widget.

Il flusso dei post Glimmer incapsula spesso contenuto personalizzato in outlets. Utilizzate le funzioni dell’API dei plugin renderBeforeWrapperOutlet e renderAfterWrapperOutlet per inserire contenuto prima o dopo di essi.

1. Sostituire le decorazioni dei widget con i Plugin Outlets

La personalizzazione più comune consiste nell’aggiungere contenuto ai post. Nel sistema dei widget, si utilizzava decorateWidget. Con Glimmer, utilizzerete invece i plugin outlets.

Prima:

// Parte di un inizializzatore in un plugin

import { withPluginApi } from "discourse/lib/plugin-api";
// ... altri import

function customizeWidgetPost(api) {
  api.decorateWidget("post-contents:after-cooked", (helper) => {
    const post = helper.getModel();
    if (post.post_number === 1 && post.topic.accepted_answer) {
      return helper.attach("solved-accepted-answer", { post });
    }
  });
}

export default {
  name: "extend-for-solved-button",
  initialize() {
    withPluginApi((api) => {
      // ... altre personalizzazioni
      customizeWidgetPost(api);
    });
  }
};

Dopo:

// Parte di un inizializzatore .gjs in un plugin

import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
import SolvedAcceptedAnswer from "../components/solved-accepted-answer";
// ... altri import

function customizePost(api) {
  api.renderAfterWrapperOutlet(
    "post-content-cooked-html",
    class extends Component {
      static shouldRender(args) {
        return args.post?.post_number === 1 && args.post?.topic?.accepted_answer;
      }

      <template>
        <SolvedAcceptedAnswer 
          @post={{@post}} 
        />
      </template>
    }
  );
}

export default {
  name: "extend-for-solved-button",
  initialize() {
    withPluginApi((api) => {
      // ... altre personalizzazioni
      customizePost(api);
    });
  }
};

2. Aggiungere contenuto dopo il nome dell’autore

Se il vostro plugin aggiunge contenuto dopo il nome dell’autore, utilizzerete l’API renderAfterWrapperOutlet con l’outlet post-meta-data-poster-name.

Prima:

// Parte di un inizializzatore in un plugin

import { withPluginApi } from "discourse/lib/plugin-api";
// ... altri import

function customizeWidgetPost(api) {
  api.decorateWidget(`poster-name:after`, (dec) => {
    if (!isGPTBot(dec.attrs.user)) {
      return;
    }

    return dec.widget.attach("persona-flair", {
      personaName: dec.model?.topic?.ai_persona_name,
    });
  });
}

export default {
  name: "ai-bot-replies",
  initialize() {
    withPluginApi((api) => {
      // ... altre personalizzazioni
      customizeWidgetPost(api);
    });
  }
};

Dopo:

// Parte di un inizializzatore .gjs in un plugin

import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// ... altri import

function customizePost(api) {
  api.renderAfterWrapperOutlet(
    "post-meta-data-poster-name", 
    class extends Component {
      static shouldRender(args) {
        return isGPTBot(args.post?.user);
      }

      <template>
        <span class="persona-flair">{{@post.topic.ai_persona_name}}</span>
      </template>
    }
  );
}

export default {
  name: "ai-bot-replies",
  initialize() {
    withPluginApi((api) => {
      // ... altre personalizzazioni
      customizePost(api);
    });
  }
};

3. Aggiungere contenuto prima del contenuto del post

// Parte di un inizializzatore .gjs in un tema

import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// ... altri import

function customizePost(api) {
  api.renderBeforeWrapperOutlet(
    "post-article",
    class extends Component {
      static shouldRender(args) {
        return args.post?.topic?.pinned;
      }

      <template>
        <div class="pinned-post-notice">
          Questo è un argomento fissato
        </div>
      </template>
    }
  );
}

export default {
  name: "pinned-topic-notice",
  initialize() {
    withPluginApi((api) => {
      // ... altre personalizzazioni
      customizePost(api);
    });
  }
};

4. Aggiungere contenuto dopo il contenuto del post

// Parte di un inizializzatore .gjs in un tema

import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";
// ... altri import

function customizePost(api) {
  api.renderAfterWrapperOutlet(
    "post-article",
    class extends Component {
      static shouldRender(args) {
        return args.post?.wiki;
      }

      // In un componente reale, usereste un template come questo:
      <template>
        <div class="wiki-post-notice">
          Questo post è un wiki
        </div>
      </template>
    }
  );
}

export default {
  name: "wiki-post-notice",
  initialize() {
    withPluginApi((api) => {
      customizePost(api);
      // ... altre personalizzazioni
    });
  }
};

Utilizzo dei Trasformatori

I trasformatori sono un modo potente per personalizzare i componenti di Discourse. Consentono di modificare i dati o il comportamento dei componenti senza dover sovrascrivere interi componenti.

Ecco alcuni dei trasformatori di valore più rilevanti per la personalizzazione del flusso dei post:

Nome del Trasformatore Descrizione Contesto
post-class Personalizza le classi CSS applicate all’elemento principale del post. { post }
post-meta-data-infos Personalizza l’elenco dei componenti di metadati visualizzati per un post. Consente di aggiungere, rimuovere o riordinare elementi come la data del post, l’indicatore di modifica, ecc. { post, metaDataInfoKeys }
post-meta-data-poster-name-suppress-similar-name Decide se sopprimere la visualizzazione del nome completo dell’utente quando è simile al suo nome utente. Restituisci true per sopprimere. { post, name }
post-notice-component Personalizza o sostituisce il componente utilizzato per renderizzare un avviso del post. { post, type }
post-show-topic-map Controlla la visibilità del componente mappa dell’argomento sul primo post. { post, isPM, isRegular, showWithoutReplies }
post-small-action-class Aggiunge classi CSS personalizzate a un post con piccola azione. { post, actionCode }
post-small-action-custom-component Sostituisce un post con piccola azione standard con un componente Glimmer personalizzato. { post, actionCode }
post-small-action-icon Personalizza l’icona utilizzata per un post con piccola azione. { post, actionCode }
poster-name-class Aggiunge classi CSS personalizzate al contenitore del nome dell’autore. { user }

1. Aggiungere una classe personalizzata ai post

// Parte di un inizializzatore in un plugin
import { withPluginApi } from "discourse/lib/plugin-api";

function customizePostClasses(api) {
  api.registerValueTransformer(
    "post-class",
    ({ value, context }) => {
      const { post } = context;

      // Aggiunge una classe personalizzata ai post di un utente specifico
      if (post.user_id === 1) {
        return [...value, "special-user-post"];
      }

      return value;
    }
  );
}

export default {
  name: "custom-post-classes",
  initialize() {
    withPluginApi((api) => {
      // ... altre personalizzazioni
      customizePostClasses(api);
    });
  }
};

2. Aggiungere metadati personalizzati ai post

Il trasformatore post-meta-data-infos consente di aggiungere componenti personalizzati alla sezione dei metadati del post.

// Parte di un inizializzatore in un plugin
import { withPluginApi } from "discourse/lib/plugin-api";

function customizePostMetadata(api) {
  // Definisce un componente da utilizzare nella sezione dei metadati
  // Il componente deve essere creato al di fuori della callback del trasformatore,
  // altrimenti potrebbe causare problemi di memoria.
  const CustomMetadataComponent = <template>...</template>;

  api.registerValueTransformer(
    "post-meta-data-infos",
    ({ value: metadata, context: { post, metaDataInfoKeys } }) => {
      // Aggiunge il componente solo per post specifici
      if (post.some_custom_property) {
        metadata.add(
          "custom-metadata-key",
          CustomMetadataComponent,
          {
            // Posizionalo prima della data
            before: metaDataInfoKeys.DATE,
            // e dopo la scheda di risposta
            after: metaDataInfoKeys.REPLY_TO_TAB,
          }
        );
      }
    }
  );
}

export default {
  name: "custom-post-metadata",
  initialize() {
    withPluginApi((api) => {
      // ... altre personalizzazioni
      customizePostMetadata(api);
    });
  }
};

Ecco un esempio reale dal plugin discourse-activity-pub:

// Parte di un inizializzatore nel plugin discourse-activity-pub
import { withPluginApi } from "discourse/lib/plugin-api";
import ActivityPubPostStatus from "../components/activity-pub-post-status";
import {
  activityPubPostStatus,
  showStatusToUser,
} from "../lib/activity-pub-utilities";

function customizePost(api, container) {
  const currentUser = api.getCurrentUser();

  const PostMetadataActivityPubStatus = <template>
    <div class="post-info activity-pub">
      <ActivityPubPostStatus @post={{@post}} />
    </div>
  </template>;

  api.registerValueTransformer(
    "post-meta-data-infos",
    ({ value: metadata, context: { post, metaDataInfoKeys } }) => {
      const site = container.lookup("service:site");
      const siteSettings = container.lookup("service:site-settings");

      if (
        site.activity_pub_enabled &&
        post.activity_pub_enabled &&
        post.post_number !== 1 &&
        showStatusToUser(currentUser, siteSettings)
      ) {
        const status = activityPubPostStatus(post);

        if (status) {
          metadata.add(
            "activity-pub-indicator",
            PostMetadataActivityPubStatus,
            {
              before: metaDataInfoKeys.DATE,
              after: metaDataInfoKeys.REPLY_TO_TAB,
            }
          );
        }
      }
    }
  );
}

export default {
  name: "activity-pub",
  initialize(container) {
    withPluginApi((api) => {
      customizePost(api, container);
      // ... altre personalizzazioni
    });
  }
};

5. Inserire contenuto prima o dopo il contenuto elaborato del post

Se il vostro plugin aggiunge contenuto dopo il testo nel post, utilizzerete l’API renderAfterWrapperOutlet con l’outlet post-content-cooked-html.

Prima:

// Parte di un inizializzatore in un plugin
import { withPluginApi } from "discourse/lib/plugin-api";

function customizeCooked(api) {
  api.decorateWidget("post-contents:after-cooked", (helper) => {
    const post = helper.getModel();
    if (post.wiki) {
      const banner = document.createElement("div");
      banner.classList.add("wiki-footer");
      banner.textContent = "Questo post è un wiki";
      element.prepend(banner);
    }
  });
}

export default {
  name: "wiki-footer",
  initialize() {
    withPluginApi((api) => {
      // ... altre personalizzazioni
      customizeCooked(api);
    });
  }
};

Dopo:

// Parte di un inizializzatore in un plugin (.gjs)
import Component from "@glimmer/component";
import { withPluginApi } from "discourse/lib/plugin-api";

// Definisce un componente da utilizzare nel plugin outlet
class WikiBanner extends Component {
  static shouldRender(args) {
    return args.post.wiki;
  }

  <template>
    <div class="wiki-footer">Questo post è un wiki</div>
  </template>
}

function customizePost(api) {
  // Utilizza renderBeforeWrapperOutlet per aggiungere contenuto prima del contenuto del post
  api.renderAfterWrapperOutlet(
    "post-content-cooked-html",
    WikiBanner
  );
}

export default {
  name: "wiki-footer",
  initialize() {
    withPluginApi((api) => {
      customizePost(api);
      // ... altre personalizzazioni
    });
  }
};

Supporto per entrambi i sistemi vecchi e nuovi durante la transizione

In qualità di autore di plugin o temi, potreste voler supportare entrambi i sistemi durante la transizione per garantire che le vostre estensioni funzionino sia con il vecchio che con il nuovo flusso dei post.

Ecco un pattern utilizzato da molti plugin ufficiali:

// solved-button.js
import Component from "@glimmer/component";
import { withSilencedDeprecations } from "discourse/lib/deprecated";
import { withPluginApi } from "discourse/lib/plugin-api";
import RenderGlimmer from "discourse/widgets/render-glimmer";
import SolvedAcceptedAnswer from "../components/solved-accepted-answer";

function customizePost(api) {
  // personalizzazioni del flusso dei post glimmer
  api.renderAfterWrapperOutlet(
    "post-content-cooked-html",
    class extends Component {
      static shouldRender(args) {
        return args.post?.post_number === 1 && args.post?.topic?.accepted_answer;
      }

      <template>
        <SolvedAcceptedAnswer 
          @post={{@post}} 
          @decoratorState={{@decoratorState}} 
        />
      </template>
    }
  );

  // ...

  // avvolge il vecchio codice dei widget silenziando gli avvisi di deprecazione
  withSilencedDeprecations("discourse.post-stream-widget-overrides", () =>
    customizeWidgetPost(api)
  );
}

// vecchio codice dei widget
function customizeWidgetPost(api) {
  api.decorateWidget("post-contents:after-cooked", (helper) => {
    let post = helper.getModel();

    if (helper.attrs.post_number === 1 && post?.topic?.accepted_answer) {
      // Utilizza RenderGlimmer per renderizzare un componente Glimmer nel sistema dei widget
      return new RenderGlimmer(
        helper.widget,
        "div",
        <template><SolvedAcceptedAnswer @post={{@data.post}} /></template>
        null,
        { post }
      );
    }
  });
}

export default {
  name: "extend-for-solved-button",
  initialize(container) {
    const siteSettings = container.lookup("service:site-settings");

    if (siteSettings.solved_enabled) {
      withPluginApi((api) => {
        customizePost(api);
        // ... altre personalizzazioni
      });
    }
  },
};

Esempi reali

Ecco i link alle richieste di pull reali per la migrazione dai nostri plugin ufficiali. Questi mostrano come abbiamo aggiornato ogni tipo di personalizzazione:

Risoluzione dei problemi

Il mio sito sembra rotto dopo aver abilitato il nuovo flusso dei post

  • Impostate glimmer_post_stream_mode nuovamente su disabled
  • Controllate la console per messaggi di errore specifici
  • Aggiornate i plugin/temi problematici prima di riprovare

Non vedo alcun avviso, ma le mie personalizzazioni non funzionano

  • Controllate che le vostre personalizzazioni mirino ai widget elencati sopra
  • Assicuratevi di testare sulla pagina dell’argomento con argomenti in cui le vostre personalizzazioni sono visibili

Avete bisogno di aiuto?

Se avete trovato un bug o se la vostra personalizzazione non può essere realizzata utilizzando le nuove API che abbiamo introdotto, fatecelo sapere qui sotto.

Lo stream Glimmer Post è abilitato su Meta.

Se scopri problemi, pubblicali qui sotto.

Durante l’aggiornamento dei miei componenti, ho riscontrato del codice strano:

Innanzitutto, sono un po’ perplesso da @user={{@post}}. Potrebbe trattarsi di un errore di battitura?

In secondo luogo, perché il PluginOutlet chiamato post-avatar-flair e UserAvatarFlair sono elementi separati? Inoltre, perché post-avatar-flair non è un wrapper come altri outlet vicini?

Sta funzionando, quindi non penso sia un errore di battitura. Forse in questa posizione, l’oggetto post ha tutti gli attributi che il componente UserAvatarFlair si aspetta di trovare sull’argomento @user? Concordo che ha un aspetto strano!

Sulla base di la descrizione della PR, sembra che tali cose siano state fatte per coerenza con altri ‘avatar-flair’ simili (che sono stati probabilmente introdotti prima che i ‘wrapper plugin outlets’ fossero una cosa).

Abbiamo appena unito una pull request che abilita Glimmer Post Stream per impostazione predefinita.

Dopo il prossimo aggiornamento, i siti compatibili utilizzeranno automaticamente il nuovo Post Stream. I siti che utilizzano estensioni incompatibili torneranno alla versione precedente e visualizzeranno avvisi nella console del browser.

Per ora, puoi forzare manualmente l’uso del vecchio Post Stream impostando glimmer_post_stream_mode su disabled.

Se riscontri problemi, segnalali qui sotto.

Penso di ricevere un avviso a causa di questo codice che poi rimanda a questo thread:

api.changeWidgetSetting('post-avatar', 'size', '120');

Come posso aggiornarlo per il nuovo sistema?

Qualcosa del genere:

api.registerValueTransformer("post-avatar-size", () => {
    return "120";
});

Bello, grazie.

Penso che funzioni. Ho inserito quel codice e poi ho provato a impostare temporaneamente tutte le impostazioni di forza attive per la modalità glimmer e sembrava che la dimensione dell’avatar e l’elenco dei post funzionassero correttamente sul mio sito dopo. Dopo il test, ho disattivato le sovrascritture poiché sembra che non siano ancora raccomandate per l’uso. Ma il mio sito dovrebbe ora essere pronto per il passaggio.

Ciao @Boost. :smiley:

Cosa intendi quando dici che le sovrascritture non sono ancora consigliate per l’uso? Se il tuo sito è pronto, passerà automaticamente allo stream di post Glimmer.

Al prossimo aggiornamento della tua installazione di Discourse, Glimmer Post Stream sarà abilitato per impostazione predefinita anche per i siti che hanno ancora personalizzazioni incompatibili.

Infatti, tutti i sistemi di rendering dei widget sono ora disabilitati, quindi qualsiasi personalizzazione basata su widget non verrà più renderizzata.

Per ora, sui siti incompatibili, l’amministratore può riabilitare il vecchio comportamento modificando i valori delle seguenti impostazioni:

  • glimmer_post_stream_mode
  • deactivate_widgets_rendering

Per riabilitare nuovamente il Glimmer Post Stream, entrambe le impostazioni devono essere modificate.

Questa è la fase finale prima della rimozione del vecchio codice dalla codebase di Discourse, che è prevista tra circa un mese. Successivamente, non sarà più possibile riabilitare i widget.

L’opzione nelle impostazioni sperimentali avverte chiaramente contro il suo utilizzo:

Abilita la nuova implementazione del post stream ‘glimmer’ in modalità ‘auto’ per i gruppi di utenti specificati. Questa implementazione è in fase di sviluppo attivo e non è destinata all’uso in produzione. Non sviluppare temi/plugin basati su di essa finché l’implementazione non sarà finalizzata e annunciata.

Quindi la formulazione è piuttosto forte nel dire che “non è destinata all’uso in produzione. Non sviluppare temi/plugin basati su di essa”.

Questa è la spiegazione dell’opzione Modalità auto gruppi post stream Glimmer.

Ottima osservazione. Ci è sfuggito.

Il Glimmer Post Stream è ora l’impostazione predefinita e la versione widget è considerata legacy e già programmata per la rimozione. Aggiornerò la descrizione dell’impostazione.

La PR che rimuove il widget post-stream è stata unita.