Se visiti /tags/category-slug/tag-name e fai clic sul pulsante Nuovo argomento, il compositore ha il tag preimpostato, come descritto qui:
È fantastico. Ma ora io (e almeno un’altra persona) vorremmo poter impostare questo comportamento con un tag predefinito quando visitiamo /c/cat-slug/cat-id. Sembra che un componente del tema dovrebbe essere in grado di individuare quel pulsante e modificarlo oppure nasconderlo e aggiungere un nuovo pulsante (c’è un’uscita per plugin proprio lì, che non riesco a trovare ora, ma ho visto un minuto fa).
Dovrebbe funzionare solo su una categoria specifica, o avresti bisogno che supporti un “tag predefinito” per molte categorie, dove quel tag è diverso per ciascuna di esse?
Immagino che potrei creare un’impostazione per avere un tag predefinito per alcune categorie. Probabilmente posso farlo, ma non so dove o come modificare il pulsante ‘Crea argomento’ in modo che includa il tag predefinito.
Gli ID sugli elementi HTML dovrebbero essere unici, ovvero… nessun due elementi nella stessa vista possono condividere lo stesso attributo ID HTML. Quindi, questo è sufficiente per iniziare.
Se cerco "create-topic" su Github, ecco cosa vedo…
C’è solo un risultato lì dentro, quindi siamo fortunati. Se ci fossero più risultati, ci sarebbero cose che si possono fare per restringere ulteriormente l’elenco, ma questo esula dall’ambito di questo argomento.
Quindi, controlliamo quel file.
Vedrai allora che l’azione del pulsante è impostata così
action=action
Bene… non è molto utile… Quindi, cosa facciamo ora?
Quando vedi action=action, significa che l’azione viene passata al componente da un template genitore.
Proviamo a vedere quali template contengono quel componente. Quindi, andiamo su Github e cerchiamo il nome del componente come verrebbe usato in un template. Per questo esempio, useremmo qualcosa del genere "{{create-topic-button"
Nota che ho aggiunto solo {{NOME_COMPONENTE e ho saltato il resto. Non conosciamo gli altri argomenti passati, quindi vogliamo una ricerca generica.
Ahh… stiamo arrivando vicini. Ora vedi che l’azione per il pulsante è
action=(action "clickCreateTopicButton")
Ora dobbiamo scoprire cosa fa quell’azione. Quindi, cerchiamo il nome dell’azione. Poi filtriamo fino ai file .js perché ora vogliamo vedere la definizione di quell’azione nel file JS del componente.
Ancora una volta, otteniamo un solo risultato, quindi diamo un’occhiata.
Sembra che l’azione faccia una delle due cose. Se la categoria è in sola lettura e l’utente non ha già una bozza, mostra un avviso. Altrimenti, chiama un metodo createTopic().
Siamo interessati al secondo caso, quindi diamoci un’occhiata.
Se cerchi createTopic() in quel file (ricerca interna, non su Github)… noterai che c’è un solo riferimento per esso. Che succede? Come fa questo componente a chiamare un metodo non definito?
Bene, la risposta è più in alto nel file.
Cosa significa questo?
Non voglio perdere troppo tempo qui, ma Ember utilizza le Classi. Pensa alle classi come a pacchetti di codice riutilizzabili. Tutto ciò che la riga evidenziata sopra significa è:
Prendi il pacchetto Component di Ember, aggiungi il pacchetto FilterModeMixin ad esso e permettimi di aggiungere altri metodi, o sovrascrivere alcuni di quelli esistenti, al risultato per creare un nuovo componente Ember per la mia applicazione.
Quindi, torniamo all’azione che stiamo cercando di tracciare.
Chiamando this.createTopic(). questo non è un metodo predefinito del componente Ember. È un metodo Discourse personalizzato, quindi deve provenire da FilterModeMixin. Cos’è FilterModeMixin? Bene… è definito all’inizio del file.
import FilterModeMixin from "discourse/mixins/filter-mode";
Ferma un attimo e fai una ricerca interna per createTopic() in quel file. Davvero. Smetti di leggere e fallo. Aspetterò… non barare… ho gli su di te.
OK. Hai cercato e non ci sono stati risultati. Cosa facciamo ora?
Quello che ho descritto sopra è solo un metodo per passare le cose in basso. Se non trovi quello che cerchi. Fai un passo indietro e prova un approccio diverso.
Quindi, ricapitoliamo… dove siamo ora? Prima di rimanere bloccati, stavamo guardando il file JS per il componente d-navigation. Vediamo il suo template.
Ancora una volta, usiamo "{{NOME_COMPONENTE" e cerchiamo.
Importa? Forse. Importa per questo caso? No. Stiamo solo cercando di capire da dove viene createTopic() o cos’è. Quindi, procediamo con il primo risultato.
Sul serio, però, parliamo delle route actions. Cosa sono? Beh. Sono route… actions? Ovvero azioni definite sulla route. Perché sono utili? Perché le route in Discourse possono essere nidificate
Vediamola così
- route-1
- route-1-1
- route-1-2
- route-1-3
Se ho un componente condiviso che devo far funzionare sulle route 111, 112 e 113 con parametri diversi, non sarebbe più facile se definissi semplicemente lo stesso componente in tutti e lo passassi con la stessa azione? Poi lo modificassi per ogni route se necessario?
Questo è ciò che fanno le route-actions.
OK, torniamo alla domanda. Stavamo guardando
createTopic=(route-action "createTopic")
nel componente navigation/default.
Ora dobbiamo solo capire qual è la route per controllare cosa fa quella route action.
Vuoi modificare il comportamento del pulsante nuovo argomento nelle pagine /c/cat-slug/cat-id. Quindi, visitiamo una di quelle pagine. Ad esempio: http://localhost:4200/c/meta/6
Qual è questa route? A meno che tu non conosca molto bene Discourse, non potresti dirlo. Quindi, cosa facciamo ora?
È qui che l’estensione Ember per il tuo browser diventa utile.
Installala qui se non ce l’hai già. Aspetterò.
(il link è un repository Github, ma la descrizione contiene i link all’estensione per diversi browser)
OK, ora che l’hai installata, visita di nuovo quella pagina /c/cat-slug/cat-id e guarda la pagina dell’estensione.
Una volta caricata, clicca su Routes, poi attiva “Solo route corrente”
Sembra che stia facendo una delle due cose. Se l’utente ha una bozza, la apre. Altrimenti, chiama openComposer() con un parametro. Qual è il passo successivo? Beh, dovresti già conoscere la risposta. Dobbiamo scoprire da dove viene openComposer() o cosa fa.
Quindi cerchiamo nel file openComposer() e… ovviamente non otteniamo risultati. Non c’è un metodo in quella route chiamato openComposer()
Cosa facciamo dopo? Ricordi il discorso sulle Classi Ember? Proviamo quello.
Abbiamo questo in cima al file della route.
Questo significa che questa route eredita tutti i metodi dal pacchetto DiscourseRoute così come quelli definiti nel pacchetto OpenComposer
openComposer è più probabilmente ciò che vogliamo, quindi diamoci un’occhiata. Prima di farlo però… dobbiamo vedere come openComposer è definito in quel file.
import OpenComposer from "discourse/mixins/open-composer";
Guarda l’URL. Non è un componente Ember. Non è una route; non è un modello. È un mixin. Ma diavolo è un mixin? La risposta brevissima… è un pacchetto di funzioni riutilizzabili.
Li definisci nel tuo mixin.
add(number) {
return number + 1
}
substract(number) {
return number - 1
}
poi aggiungi il mixin al tuo componente Ember, poi puoi fare qualcosa del genere
// valore iniziale è 1
myMethod () {
this.add(value) // restituisce 2
this.substract(value) // restituisce 0
}
Quindi, come si relaziona questo a ciò che stiamo cercando di fare?
Beh, open-composer qui.
import OpenComposer from "discourse/mixins/open-composer";
è un mixin. Uno dei metodi in quel mixin è OpenComposer()
Va bene se ti senti confuso a riguardo. Condividono lo stesso nome - tranne che uno inizia con una lettera maiuscola, il che indica che è una Classe.
Significano cose diverse.
Per capire questo, dovresti sapere che il nome che dai ai tuoi moduli importati non conta (in questo caso particolare), purché siano esportati come “default”
Spiegare questo va un po’ oltre l’ambito di questo argomento. Tutto quello che devi sapere è che questo.
ID HTML pulsante Nuovo argomento < Azione pulsante Nuovo argomento < Azione componente d-navigation < Azione route discovery < Mixin OpenComposer < metodo openComposer()
Quindi… questo è il metodo che viene infine chiamato quando clicchi il pulsante + Nuovo Argomento su quella route.
Abbiamo stabilito come puoi capire l’azione per quel pulsante su /c/cat-slug/cat-id, ma sembra diverso da ciò che accade quando visiti /tags/category-slug/tag-name, che è ciò che vuoi fare.
Quindi qual è il prossimo passo? Vediamo cosa fa quella route per gestire l’azione createTopic().
Beh… noterai che gestisce l’azione in modo diverso.
createTopic() {
if (this.get("currentUser.has_topic_draft")) {
this.openTopicDraft();
} else {
const controller = this.controllerFor("tag.show");
const composerController = this.controllerFor("composer");
composerController
.open({
categoryId: controller.get("category.id"),
action: Composer.CREATE_TOPIC,
draftKey: Composer.NEW_TOPIC_KEY
})
.then(() => {
// Pre-compila il campo di input dei tag
if (composerController.canEditTags && controller.get("model.id")) {
const composerModel = this.controllerFor("composer").get("model");
composerModel.set(
"tags",
[
controller.get("model.id"),
...makeArray(controller.additionalTags)
].filter(Boolean)
);
}
});
}
}
Questa differenza è praticamente ciò che stai chiedendo qui.
Quindi, tutto quello che devi fare è… modificare l’azione createTopic() nella route discovery per farla funzionare come fa sulla route tag-show. Quindi come si fa?
Ricordi come abbiamo parlato di Ember che usa le Classi? Sì, dovremo tornare a quello di nuovo.
L’API del plugin ti permette di modificare le classi Ember tramite questo metodo.
Quindi, cosa stiamo cercando di modificare qui? La route discovery… perché… ricorda, è lì che è definita createTopic() quando sei su una pagina come /c/cat-slug/cat-id
Cosa fa? Rompe il pulsante + Nuovo Argomento; tuttavia, ci dice che siamo nella direzione giusta. Se provi ad aggiungere il frammento sopra, noterai che cliccando il pulsante non si apre più il compositore. Invece, stampa solo un messaggio nella console. Questa è una cosa buona perché significa che abbiamo individuato la Classe giusta e l’azione giusta - route:discovery e createTopic()
Quindi, cosa facciamo dopo? Beh, ricorda che il pulsante su /tags/category-slug/tag-name fa esattamente ciò che vogliamo. Quindi, copiamo il codice da quella route - e aggiungiamo gli import necessari.
Funzionerà? No, ma siamo a un passo dalla soluzione. Perché non funziona? Perché i tag che aggiunge quando si apre il compositore non sono definiti. Perché? Perché sono caricati dal controller tag.show - che non è ciò che vogliamo. Modifichiamo il codice per farlo funzionare con la route su cui siamo.
Prima di farlo, però, ci serve una sorta di indice per i nostri tag predefiniti desiderati. Andiamo con un nuovo oggetto così
// category-slug: [ARRAY_TAG_PREDEFINITI]
const defaultTagIndex = {
// slug a parola singola
meta: ["a", "b", "c"],
core: ["g", "h"],
// slug con un trattino
["general-chat"]: ["d", "e", "f"]
};
Questo significa fondamentalmente che se il compositore viene aperto sulla pagina della categoria meta, aggiungi i tag “a,b,c”
Se il compositore viene aperto sulla pagina della categoria core, aggiungi i tag “g,h” e così via.
Ora che ce l’abbiamo, possiamo modificare l’azione per farla apparire così.
Ho avvolto tutto in un blocco try…catch. Se il codice fallisce, eseguiamo this._super(...arguments)
Se sei familiare con Ember, sapresti cosa fa this._super(...arguments). Se non lo sei, ecco una spiegazione semplice. Stiamo sovrascrivendo createTopic() quindi se le sovrascrizioni falliscono a causa di un errore - forse il core è stato aggiornato - allora torniamo al metodo nel core come definito qui
Se l’utente ha una bozza di nuovo argomento, torniamo semplicemente a this._super(...arguments) e lasciamo che il core faccia il suo lavoro.
Questo dovrebbe essere sufficiente. Tutto quello che devi aggiungere ora è un modo per creare l’indice dei tag predefiniti tramite le impostazioni del tema.