Contributing web UI with Plugins
I plugin possono aggiungere la propria interfaccia utente a Owncast in due luoghi: schede all'interno dell'amministrazione (per le impostazioni relative agli streamer) e pulsanti di azione sotto lo stream (per azioni rivolte agli spettatori). Entrambi sono dichiarati nel tuo manifesto e gestiti dall'host. Tu fornisci il contenuto, Owncast lo inserisce nel giusto chrome.
Le dichiarazioni del manifesto in questa pagina sono JSON semplici, identici in qualsiasi lingua tu scriva. I gestori di contenuto dinamico e le chiamate runtime sono mostrati per entrambi gli SDK. Vedi JavaScript o Python per installazione e configurazione.
Pagine di amministrazione
Per impostazioni semplici e tipizzate (stringhe, numeri, switch), dichiara un blocco config nel manifesto e lascia che Owncast generi il modulo per te. Vedi Configurazione. Costruisci una pagina di amministrazione personalizzata quando hai bisogno di un'interfaccia utente che il modulo automatico non può esprimere.
I plugin possono registrare pagine che appaiono all'interno dell'interfaccia utente di amministrazione di Owncast sotto Plugin. Declare them as an object keyed by plugin-relative path glob:
{
"permissions": ["http.serve"],
"admin": {
"pages": {
"/admin": { "title": "Settings", "icon": "gear" }
}
}
}
Each entry has:
| Part | Note |
|---|---|
| object key | Required path glob under /plugins/\<your-slug>/. Esempi: "/admin", "/admin/*", "/admin/api/*". |
title | Required tab label inside the plugin's admin view. |
icon | Optional short semantic name. Supportati: gear, wrench, user, users, lock, info, apps, docs, bell (alias come settings e notifications funzionano anch'essi). |
The host derives the page path from the object key. Do not add a path member to the value. The host rejects arrays and page values containing the legacy path member.
Come vengono renderizzati
L'amministrazione di Owncast rende ogni pagina dichiarata come una scheda all'interno di /admin/plugins/configure?id=\<your-slug>. The tab body is an \<iframe> pointed at the path from the object key under /plugins/\<your-slug>/. Ogni plugin ottiene un URL con segnalibro e un'entrata nella barra laterale sotto Plugin nella navigazione di amministrazione.
L'host inietta automaticamente il foglio di stile di base nelle risposte HTML sui percorsi di amministrazione, quindi i controlli <input> e \<button> appaiono nativi all'amministrazione di Owncast senza la necessità di fornire CSS. Vedi Stilizzazione dell'interfaccia utente del plugin per cosa ottieni gratuitamente e le classi di aiuto disponibili. I plugin che preferiscono il proprio stile possono sovrapporsi.
Sandbox
La pagina viene eseguita in un \<iframe> sandbox. Your scripts run, forms submit, and same-origin fetch to your own /plugins/\<your-slug>/ endpoints works. Le pagine possono anche aprire finestre popup, attivare download di file (ad esempio un blob o un data-URL \<a download> che clicchi dallo script), e usare confirm() / alert() / prompt() dialoghi. Il sandbox è l'unico vincolo che noterai di solito. Se una funzionalità del browser sembra bloccata senza motivo, il primo aspetto da controllare è il sandbox dell'iframe.
Autenticazione gating
Le richieste ai percorsi di amministrazione dichiarati nel manifesto sono soggette a gating di autenticazione da parte dell'host. Le richieste non autenticate ricevono un 401 prima che il codice del tuo plugin venga eseguito. Non devi controllare l'autenticazione della richiesta per questi percorsi.
I file statici e gli endpoint dinamici sotto percorsi corrispondenti sono entrambi soggetti a gating di autenticazione. Lo stesso gating si applica a public/admin/index.html e a POST /admin/api/save-settings.
Usa più glob quando hai sia una pagina dell'interfaccia utente che un'API JSON:
{
"admin": {
"pages": {
"/admin": { "title": "Settings" },
"/admin/*": { "title": "Settings" }
}
}
}
The admin UI deduplicates tabs by the resolved iframe URL, not by title. /admin and /admin/* both resolve to /admin/, so this pair produces one visible tab that gates the whole subtree. A pair like /admin and /admin/api/* resolves to two different URLs and produces two tabs. JSON object order is not significant. Owncast processes and displays pages in lexicographic path order.
Flusso di autore
- Metti HTML, CSS e JS di amministrazione in
public/admin/index.html(e amici). - Esporre le API di amministrazione tramite il tuo gestore di richieste a
/admin/api/...(vedi Servire HTTP). - Declare the relevant path keys in
manifest.admin.pages. - Visita
/admin/plugins/configure?id=\<your-slug>nell'interfaccia amministrativa. Owncast utilizza il tuo accesso amministrativo esistente per gestire la pagina. Nessun extra prompt.
Pulsanti di azione
Owncast presenta una fila di pulsanti di azione nella sua interfaccia utente per gli spettatori. Voce cliccabili che aprono un URL (in un modal o una nuova scheda) o renderizzano HTML grezzo. I plugin possono contribuire i propri.
Pulsanti dichiarati nel manifesto
{
"permissions": ["ui.modify", "http.serve"],
"actions": [
{
"title": "Chat Overlay",
"description": "Open the live chat overlay",
"url": "/",
"icon": "/star.png",
"color": "#3b82f6"
},
{
"title": "Issue tracker",
"url": "https://github.com/example/my-plugin/issues",
"openExternally": true
},
{
"title": "About this stream",
"html": "<p>Live every weekday at 8pm UTC.</p>"
}
]
}
Mentre il tuo plugin è abilitato, l'host unisce le sue voci di azione nella lista che Owncast già mostra sotto lo stream. Quando disabilitati, scompaiono.
Riferimento al campo
| Campo | Note |
|---|---|
title | Obbligatorio. L'etichetta del pulsante. |
url | Può essere un URL assoluto https://... o un percorso. Mutuamente esclusivo con html. |
html | HTML grezzo renderizzato in un modal inline. Mutuamente esclusivo con url. |
icon | URL di immagine opzionale mostrato sul pulsante. Stesse regole di percorso di url. |
color | Colore esadecimale opzionale per lo sfondo del pulsante. |
description | Opzionale. Mostrato nel modal che si apre per azioni basate su URL. |
openExternally | Se true, l'URL si apre in una nuova scheda invece di un modal inline. |
Regole di percorso
Due semplici regole coprono tutto:
- I percorsi relativi si auto-prefixano con lo spazio dei nomi del tuo plugin.
"/"diventa/plugins/my-plugin/."/star.png"diventa/plugins/my-plugin/star.png. Questo ti salva dall'uso di hard-coding del nome del tuo plugin. Si applica sia aurlche aicon. - Gli URL assoluti
https://...passano inalterati. Usali per link esterni e icone ospitate su CDN.
L'host impone:
- è richiesta l'autorizzazione
ui.modify. I manifest conactionsma senzaui.modifyvengono rifiutati al caricamento. - Esattamente uno di
urlohtmlper voce. - Gli URL e le icone che si risolvono nello spazio dei nomi del tuo richiedono
http.serve. Sei tu a servirli. - Gli URL e le icone che puntano allo spazio dei nomi di un altro plugin vengono rifiutati. Cattura errori di battitura e impedisce a un plugin di pubblicizzare l'interfaccia utente di un altro.
Aggiunte a runtime
Un plugin può aggiungere ulteriori pulsanti di azione a runtime, senza ricaricare, chiamando owncast.actions.add(...) con una singola azione o un array di esse. Ogni voce a runtime passa attraverso la stessa convalida delle manifest.actions, ed è persistita nella configurazione del plugin, in modo che le aggiunte sopravvivano a un caricamento. owncast.actions.clear() elimina ogni aggiunta a runtime. Le azioni dichiarate nel manifesto rimangono.
- JavaScript
- Python
const { definePlugin, owncast } = require('@owncast/plugin-sdk');
module.exports = definePlugin({
onStreamStarted() {
owncast.actions.add({
title: 'Donate',
url: 'https://example.com/donate',
openExternally: true,
});
// or add several at once: owncast.actions.add([ { ... }, { ... } ])
},
});
from owncast_plugin import plugin, owncast
@plugin.on_stream_started
def add_button(info):
owncast.actions.add({
"title": "Donate",
"url": "https://example.com/donate",
"openExternally": True,
})
# or add several at once: owncast.actions.add([ { ... }, { ... } ])
Un modello comune è una pagina di amministrazione che consente allo streamer di aggiungere pulsanti personalizzati (etichetta + URL) sopra i predefiniti del plugin. L'esempio action-buttons nell'SDK fornisce una versione funzionante di questo.
Stilizzazione dell'interfaccia utente del plugin
Owncast inietta un foglio di stile di base in ogni superficie del plugin che viene renderizzata in un iframe: le tue pagine di amministrazione e le tue schede di pagina degli spettatori. È costruita dai token di design di Owncast, quindi HTML semantico semplice adotta l'aspetto nativo senza CSS proprio.
- Le intestazioni, i paragrafi e i link assorbono i font e i colori del tema.
<input>,\<textarea>,\<select>e\<button>vengono renderizzati come i controlli nativi. Un\<button>ottiene lo stile primario. Aggiungiclass="secondary"per la variante outline.\<table>,\<fieldset>e\<code>/\<pre>ottengono uno stile nativo sensato.
Il tuo contenuto rimane in linea con la pagina. Lo sfondo dell'iframe è trasparente in modo che il pannello dell'host si veda, nello stesso modo in cui si renderizzano le schede integrate Informazioni e Follower. Non ottieni, e non dovresti aggiungere, uno sfondo di pagina opaco o una scatola avvolgente attorno a tutto. Quella resa in linea è ciò che fa sembrare una scheda di plugin parte di Owncast piuttosto che un iframe incorporato.
Gli stili di base rendono le superfici renderizzate dall'iframe: pagine di amministrazione e schede di pagina degli spettatori. Il contenuto che inietti direttamente nella pagina degli spettatori (extraPageContent, scripts) si renderizza nel vero DOM della pagina e eredita gli stili reali di Owncast invece.
Classi di aiuto
Per blocchi di costruzione nativi oltre ai semplici elementi, il baseline fornisce alcune classi opzionabili. Fanno riferimento agli stessi token del tema del resto di Owncast, quindi si ri-stilizzano automaticamente quando un amministratore personalizza il tema.
| Classe | Cosa fa |
|---|---|
card | Una superficie di carta nativa, lo stesso aspetto delle carte di follower e stream in evidenza. Una semplice \<section> / \<article> resta in linea, quindi opta per class="card" quando vuoi la superficie in scatola. |
card interactive | Aggiungi interactive a una scheda cliccabile per il sollevamento nativo al passaggio del mouse. |
card-grid | Una griglia reattiva che riempie quante più colonne da ~260px possibile e collassa in una colonna su un telaio stretto. Aggiungi i figli di card direttamente all'interno. |
tag | Un tag a pillola o un badge, che corrisponde ai tag sulle schede dello stream nativo. |
stack | Una colonna flessibile verticale con uno spazio costante. |
row | Una riga flessibile orizzontale che si avvolge, con uno spazio costante. |
muted | Testo de-enfatizzato, per didascalie e dettagli secondari. |
<div class="card-grid">
<article class="card interactive">
<h3>Album A</h3>
<p class="muted">Artist A</p>
<div class="row">
<span class="tag">jazz</span>
<span class="tag">2024</span>
</div>
</article>
<article class="card interactive">
<h3>Album B</h3>
<p class="muted">Artist B</p>
</article>
</div>
Tutto qui è opzionale. Una scheda che non contiene nulla tranne HTML semantico appare già nativa. Affidati ai helper quando vuoi schede, griglie o tag senza copiare a mano i valori di Owncast e sovrapponi il tuo CSS (vedi Stili del visualizzatore) ogni volta che hai bisogno di qualcosa che il baseline non copre.
Stili del visualizzatore
I plugin possono tematizzare la pagina del visualizzatore raggruppando file CSS e elencandoli nel manifest.styles. L'host inietta il contenuto di ogni file in un unico blocco di stili plugin sulla pagina, quindi i plugin estendono il CSS della pagina senza che ogni contributo abbia bisogno del proprio tag <link>.
{
"permissions": ["ui.modify"],
"styles": ["theme.css", "overrides.css"]
}
Richiede solo ui.modify (il plugin si integra all'interno del chrome di Owncast). http.serve non è necessario: l'host legge ogni file dalla directory assets/ del tuo plugin e inietta i byte nel blocco di stili del plugin sulla pagina di /api/config, non a un URL.
Regole del percorso
- Percorsi semplici come
"theme.css"si prefissano automaticamente con lo spazio dei nomi del tuo plugin. "/theme.css"si risolve allo stesso modo.- Percorsi completamente qualificati
/plugins/\<your-slug>/...passano attraverso. - I percorsi nello spazio dei nomi di un altro plugin vengono rifiutati.
- Gli URL
http://ehttps://vengono rifiutati. Raggruppa le risorse esterne e riferiscile con@font-faceourl(...)dal tuo CSS, in modo che un admin che rivede il manifesto veda ogni file che verrà caricato. - Ogni voce deve terminare con
.css.
Come vengono resi i contributi
The host reads each file at request time and concatenates the bytes in front of an /* plugin: \<your-slug> ... */ comment, so devtools "view source" attributes a rule back to the plugin that shipped it. Disabilitare il plugin elimina il suo contributo al successivo caricamento della pagina.
Il corpo CSS opera sul DOM del visualizzatore live, quindi i tuoi selettori mirano a qualunque cosa la pagina rendi. Limitare ogni regola sotto un unico id radice è una abitudine difensiva che vale la pena mantenere. Senza di essa le tue regole possono abbinare elementi che la pagina host rende e produrre sorprendentemente regressioni.
Dove si trovano gli stili del plugin nella cascata
La pagina del visualizzatore costruisce il suo aspetto da quattro strati, applicati in quest'ordine. Gli strati successivi prevalgono.
- I valori di default incorporati in Owncast.
- Stili del plugin: i tuoi file
manifest.stylesprima, poi il tuo outputonPageStyles. - Le variabili di aspetto dell'amministratore, i colori impostati con i selettori sotto Impostazioni Generali → Aspetto.
- Il CSS personalizzato dell'amministratore, l'editor su quella stessa pagina.
I tuoi stili sono il livello 2, quindi le scelte esplicite dell'amministratore nei livelli 3 e 4 sovrascrivono i tuoi su qualunque proprietà impostiate entrambi. Tratta un tema come una base piuttosto che come la parola finale:
- Un token che imposti e che l'amministratore ha mantenuto al suo valore predefinito mostra il tuo valore.
- Un token che imposti e che l'amministratore ha impostato mostra il valore dell'amministratore.
Sia i temi parziali che quelli completi vanno bene. Un plugin che ricororna solo i collegamenti lascia ogni altro colore intatto. Un plugin che imposta l'intera tavolozza cede ancora a qualunque colore individuale scelto dall'amministratore. L'amministratore rimane in controllo della propria istanza, e la pagina Aspetto informa che è coinvolto un plugin: mostra una nota che nomina il tuo plugin e segnala ogni colore impostato da un anche impostato da \<plugin> note. For that flagging to work, declare your colors as --theme-color-* custom properties in a :root { ... } block, the same form the admin's pickers write.
Un'uscita d'emergenza rompe l'ordinamento: una regola del plugin contrassegnata con !important prevale sulle normali dichiarazioni dell'amministratore indipendentemente dal livello. Evitalo nel CSS del tema se desideri che l'amministratore mantenga l'ultima parola sui propri colori.
Avvertenza: URL relativi in CSS
Riferimenti url(...) all'interno del CSS di un plugin si risolvono contro la pagina del visualizzatore, non contro lo spazio dei nomi del plugin. Se vuoi fare riferimento a un'immagine raggruppata, utilizza il percorso assoluto /plugins/\<your-slug>/logo.png invece di ./logo.png. Lo stesso vale per le font @font-face. Lo spazio degli URL statici del plugin rimane servito, quindi i riferimenti diretti funzionano anche se nessun <link> punta al file.
Stili dinamici: onPageStyles
Quando il CSS dipende dallo stato del plugin, da un tema scelto dall'amministratore o da un valore nel negozio KV, restituiscilo da un gestore onPageStyles invece di (o insieme) a un file statico. Non esiste un campo di manifesto per questo. L'host chiama il gestore una volta per ogni /api/config per ogni plugin che detiene ui.modify e lo esporta, quindi aggiunge ciò che restituisce al tuo blocco di stili del plugin dopo i file statici manifest.styles. All'interno degli stili del tuo plugin la regola successiva prevale, quindi restituire solo l'override attivo da onPageStyles è sufficiente. L'intero blocco si trova ancora sotto le impostazioni di aspetto dell'amministratore (vedi dove si trovano gli stili dei plugin nella cascata).
- JavaScript
- Python
const ACCENTS = { ocean: '#2386e2', forest: '#42bea6' };
module.exports = definePlugin({
onPageStyles() {
const accent = ACCENTS[owncast.kv.get('theme')];
if (!accent) return;
return `:root { --theme-color-action: ${accent}; }`;
},
});
ACCENTS = {"ocean": "#2386e2", "forest": "#42bea6"}
@plugin.on_page_styles
def page_styles():
accent = ACCENTS.get(owncast.kv.get("theme"))
if not accent:
return
return f":root {{ --theme-color-action: {accent}; }}"
Richiede ui.modify. The examples above also read the KV store, which separately requires storage.kv. Restituisci nulla (un semplice return, lo stesso che restituire "") quando non c'è nulla da contribuire in una data richiesta. La chiamata non richiede argomento per visualizzatore, quindi la risposta /api/config rimane memorizzabile nella cache. L'esempio theme-hub nell'SDK utilizza questo per applicare un tema selezionato dall'amministratore all'intera UI del visualizzatore.
Script del visualizzatore
I plugin possono estendere il runtime della pagina del visualizzatore raggruppando file JavaScript e elencandoli nel manifest.scripts. Il contenuto di ogni file viene aggiunto alla risposta /customjavascript che Owncast già serve per il JS personalizzato dell'amministratore, quindi i plugin estendono il comportamento della pagina senza che ogni contributo abbia bisogno del proprio tag \<script>.
{
"permissions": ["ui.modify"],
"scripts": ["client.js"]
}
Stesse regole di autorizzazione e percorso di styles, applicate a file .js (è richiesto solo ui.modify, e l'host legge da assets/ e inietta in /customjavascript). Ogni contributo è prefissato con un commento // plugin: \<your-slug> ... .
Questi sono script pagina visualizzatore che vengono eseguiti nel browser, sempre JavaScript, indipendentemente da quale lingua hai scritto il plugin lato server.
Contesto di esecuzione
La pagina del visualizzatore carica /customjavascript come un singolo tag \<script async>. Ogni JS dei plugin viene eseguito nella stessa finestra globale del JS personalizzato dell'amministratore e del resto del chrome di Owncast. Tre implicazioni:
- Le dichiarazioni di alto livello di
varefunctionatterrano suwindow. Wrap your script in an IIFE ((function(){ ... })()) so private state stays private and you don't collide with the admin's JS or other plugins. - L'host avvolge il contributo di ciascun plugin nel proprio try/catch, quindi un errore di runtime viene inviato alla console del browser (con il prefisso
owncast plugin \<your-slug> script error:) senza fermare gli script degli altri plugin. Un errore di sintassi non è isolato: interrompe l'analisi dell'unico tag script concatenato prima che qualsiasi try/catch venga eseguita, quindi invia JavaScript valido. - Richiesta relativa
fetch('./data.json')si risolve contro l'URL della pagina del visualizzatore, non contro il tuo plugin. Utilizza percorsi assoluti come/plugins/\<your-slug>/data.jsonper i file che consegni inpublic/.
Script dinamici: onPageScripts
Il corrispondente script di onPageStyles. Restituisci JavaScript calcolato al momento della richiesta da un gestore onPageScripts, senza campo di manifesto. L'host lo chiama una volta per ogni /api/config per ogni plugin che detiene ui.modify che lo esporta, e aggiunge il risultato a /customjavascript dopo i file statici manifest.scripts, avvolti nello stesso try/catch specifico per il plugin.
Questo è per qualsiasi JavaScript di tempo richiesto, non solo per la tematizzazione. Usalo per eseguire codice lato visualizzatore calcolato per richiesta, ad esempio mostrando un valore che l'amministratore ha impostato nel negozio KV del plugin. L'esempio qui sotto mostra quel valore ai visualizzatori:
- JavaScript
- Python
module.exports = definePlugin({
// Run request-time JavaScript on the viewer page.
onPageScripts() {
const notice = owncast.kv.get('notice');
if (!notice) return;
return `alert(${JSON.stringify(notice)});`;
},
});
import json
@plugin.on_page_scripts
def page_scripts():
notice = owncast.kv.get("notice")
if not notice:
return
return f"alert({json.dumps(notice)});"
L'output viene eseguito nella finestra condivisa del visualizzatore window, quindi IIFE e suggerimenti sui percorsi assoluti sopra si applicano ancora. Escape qualsiasi stringa non attendibile che includi: JSON.stringify in JavaScript e json.dumps in Python producono entrambi un letterale citato in modo sicuro, motivo per cui gli esempi avvolgono l'avviso in uno prima di passarlo ad alert. Like the styles examples, reading the KV store requires storage.kv on top of ui.modify. Restituisci nulla (un semplice return, lo stesso che restituire "") per non contribuire nulla.
Quando usarlo
scripts è lo strumento giusto per i plugin che devono reagire allo stato lato visualizzatore, montare la propria UI sopra la pagina, o comunicare con un backend che il plugin esegue su /plugins/\<your-slug>/. Per bot guidati da chat, filtri di messaggi e qualsiasi logica che dovrebbe funzionare lato server, i gestori del plugin regolari sono una scelta migliore. Funzionano all'interno del sandbox dell'host, possono comunicare con le API di Owncast che la pagina del visualizzatore non può raggiungere, e non si fidano del DOM controllato dall'utente.
Contenuto extra della pagina
I plugin possono anteporre HTML al blocco di contenuto extra della pagina del visualizzatore. Dichiara manifest.extraPageContent come un oggetto con un slug richiesto e un percorso content opzionale:
{
"permissions": ["ui.modify"],
"extraPageContent": { "slug": "banner", "content": "content.html" }
}
| Campo | Note |
|---|---|
slug | Richiesto. Un identificatore stabile passato al gestore del contenuto della pagina quando l'host richiede HTML reso. Lettere minuscole, cifre e trattini, che iniziano con una lettera. |
content | Facoltativo. Percorso relativo a un file HTML statico in assets/. Quando presente, i byte di quel file vengono iniettati direttamente. Quando omesso, l'host chiama invece il gestore del contenuto della pagina. |
Statico vs dinamico
Usa content quando l'HTML è lo stesso per ogni visualizzatore: bande di annuncio, banner di sponsor, blocchi di prosa. Lascia content fuori e implementa un gestore di contenuto della pagina quando il contenuto dovrebbe cambiare per visualizzatore o attingere a dati live. L'host chiama il gestore con lo slug richiesto e l'identità del visualizzatore, e il tuo gestore restituisce la stringa HTML da rendere:
- JavaScript
- Python
module.exports = definePlugin({
onPageContent(ctx) {
if (ctx.slug === 'banner') {
const who = ctx.user ? `, ${ctx.user.displayName}` : '';
return `<div class="banner">Welcome${who}!</div>`;
}
return '';
},
});
@plugin.on_page_content("banner")
def banner(ctx):
name = ctx.user.display_name if ctx.user else None
who = f", {name}" if name else ""
return f'<div class="banner">Welcome{who}!</div>'
Vedi Gestori: contenuto della pagina per la forma del payload. L'identità del visualizzatore è presente quando il visualizzatore è autenticato e assente per i visualizzatori anonimi.
Richiede ui.modify. http.serve non è richiesto: l'HTML viene iniettato nella risposta /api/config, non servito come un URL.
I byte arrivano in cima al blocco di contenuto extra, sopra qualsiasi prosa che l'amministratore ha configurato. Each contribution is wrapped with an <!-- plugin: <your-slug> ... --> comment for attribution. I contributi di più plugin si accumulano nell'ordine in cui l'host li ha caricati.
Regole del percorso
Stesse regole di styles e scripts, applicate a un unico elemento .html. Un file per plugin. Se desideri più blocchi distinti, collegali o \<iframe> da un unico file che consegni.
Markdown vs HTML
Il contenuto extra della pagina dell'amministratore passa attraverso il processore di markdown di Owncast prima del rendering. L'HTML del plugin non lo fa: l'host esegue prima il processore di markdown sul contenuto dell'amministratore, quindi antepone i tuoi byte raw. Tag, attributi e script inline passano attraverso come scritto.
Questo significa che l'HTML del plugin può utilizzare qualsiasi elemento che la pagina del visualizzatore accetta. Significa anche che un tag malformato può rompere il chrome circostante, quindi escape qualsiasi stringa non attendibile che includi (nomi utente, testo recuperato, tutto ciò che non è sotto il tuo controllo).
Accoppiamento con scripts
extraPageContent brilla quando abbinato a scripts: spedisci il markup come HTML in cui è facilmente revisionabile, e collega le interazioni dal tuo JavaScript interrogando gli elementi che hai dichiarato. L'host carica l'HTML prima che il script venga eseguito, quindi uno script che mira a document.getElementById(...) su un elemento fornito dal plugin funziona senza trucchi di temporizzazione.
{
"permissions": ["ui.modify", "http.serve"],
"extraPageContent": { "slug": "panel", "content": "panel.html" },
"scripts": ["panel.js"]
}
Un modello che spesso sembra più pulito rispetto alla creazione dello stesso DOM in modo imperativo da un plugin solo scripts:
panel.htmldichiara struttura, classi e ID su cui puoi riflettere come semplice HTML.panel.css(dichiarato instyles) lo tematizza.panel.jsallega ascoltatori di eventi, recupera dati, muta lo stato.
Quando utilizzare HTML-plus-JS invece di puro JavaScript: qualsiasi cosa con layout non banale, attributi ARIA o widget di terze parti che si aspettano di avviarsi dal DOM esistente. Gli script puri hanno ancora senso per i plugin che costruiscono la loro interfaccia utente solo in determinate condizioni (dopo un recupero, dopo un'azione dell'utente) in cui non rendere nulla al primo rendering è il comportamento corretto.
Quando extraPageContent è sufficiente da solo
Standalone, extraPageContent è il percorso più semplice per le strisce di annuncio, i banner sponsorizzati e qualsiasi blocco che non deve reagire agli eventi: fornisce direttamente il markup, non richiede uno script e sopravvive a un visualizzatore con JavaScript disabilitato.
Schede della pagina visualizzatore
Plugins can add tabs to the viewer page's tab row next to the built-in About and Followers tabs by declaring manifest.tabs as an object. Each object key is the tab's stable slug. Every value requires a title, and content is optional.
{
"permissions": ["ui.modify"],
"tabs": {
"music": { "title": "Music", "content": "music.html" },
"stream-info": { "title": "Stream Info" }
}
}
| Part | Note |
|---|---|
| object key | Required stable slug passed to the tab-content handler. Lettere minuscole, cifre e trattini, iniziando con una lettera. |
title | Obbligatorio. L'etichetta mostrata sulla scheda. Deve essere unico all'interno delle schede del plugin. |
content | Facoltativo. Percorso relativo a un file HTML statico in assets/. Quando è presente, i byte di quel file vengono in linea direttamente. Quando omesso, l'host chiama invece il gestore del contenuto della scheda. |
The host derives the tab slug from the object key. Do not add a slug member to the value. The host rejects arrays and tab values containing the legacy slug member.
Richiede ui.modify. http.serve is not required: each static tab's HTML is read from assets/ and inlined into the tab body. For a dynamic tab, the host passes the object key to the tab-content handler as slug and inlines the returned HTML.
Come vengono renderizzate le schede
L'host emette un array pluginTabs[] su /api/config. La pagina visualizzatore mappa ciascuna voce a una scheda il cui corpo è l'HTML in linea, renderizzato in un iframe sandboxed con il foglio di stile di base iniettato, quindi l'HTML semplice appare nativo senza CSS proprio.
See Styling plugin UI for the baseline and the helper classes. Tabs from each plugin are appended after the built-ins in lexicographic slug order. Ordering between tabs from different plugins is unspecified. JSON object order is not significant. The React key combines the tab slug and title, so changing either value remounts that tab.
The tab object key
The object key is a stable name you control. The host passes it to your tab-content handler as slug, so one handler can serve multiple tabs without guessing which one was requested. It also appears in host logs and future API calls, so pick something clear, like "music" or "stream-info". You can change title freely unless your code depends on it. Changing the key is a breaking change if code depends on the existing slug.
Contenuto dinamico della scheda
When a tab value has no content file, the host calls your tab-content handler to produce it. Implementalo quando il contenuto deve cambiare per visualizzatore o ottenere dati live. The host resolves every dynamic tab while building the viewer's /api/config payload, once per config request rather than on tab click, so keep the handler fast. It passes the tab's object key as slug with the viewer's identity, and expects the HTML string for the tab body:
- JavaScript
- Python
module.exports = definePlugin({
onTabContent(ctx) {
// ctx = { slug, user? }
if (ctx.slug === 'stream-info') {
return '<h2>Stream info</h2><p>Live every weekday at 8pm UTC.</p>';
}
return '';
},
});
@plugin.on_tab_content("stream-info")
def stream_info(ctx):
return "<h2>Stream info</h2><p>Live every weekday at 8pm UTC.</p>"
Vedi Gestori: contenuto della scheda per la forma del payload. L'identità del visualizzatore è presente quando autenticata e assente per visualizzatori anonimi.
Regole del percorso
Stesso come extraPageContent, applicato per voce:
- I percorsi semplici come
"music.html"sono prefissati automaticamente con lo spazio dei nomi del tuo plugin. - Percorsi completamente qualificati
/plugins/\<your-slug>/...passano attraverso. - Percorsi nello spazio dei nomi di un altro plugin vengono rifiutati.
- Gli URL
http(s)://vengono rifiutati. - Ogni voce deve terminare con
.html.
Titolo della scheda
Il campo title appare verbum in barra delle schede. Tieni corto: i titoli lunghi vengono troncati dall'interfaccia della scheda. Non c'è vincolo di schema sulla lunghezza, ma qualsiasi cosa oltre ~16 caratteri non si adatta bene su mobile.
Quando usare le schede vs extraPageContent
extraPageContent: un blocco di HTML che si trova sopra la riga delle schede. Buono per strisce di annuncio, banner sponsorizzati, qualsiasi cosa che dovrebbe essere sempre visibile.tabs: pannelli dedicati su cui il visualizzatore fa clic. Buono per contenuti che non hanno bisogno di competere con la chat per l'attenzione: elenchi musicali, calendari di eventi, pagine di collegamento, sezioni sponsor che desideri che i visualizzatori trovino ma non necessariamente vedano per primi.
Improve this page
See something missing or incorrect? Edit this page and improve the documentation for everyone.
Gabe Kangas