Contributing web UI with Plugins
Плагины могут добавлять свой собственный пользовательский интерфейс в Owncast в двух местах: вкладки внутри админки (для настроек, доступных стримеру) и кнопки действий под потоком (для действий, доступных зрителям). Оба они объявлены в вашем манифесте и управляются хостом. Вы предоставляете контент, Owncast вставляет его в правильную панель.
Объявления манифеста на этой странице написаны простым JSON, идентичны, независимо от языка, на котором вы пишете. Обработчики динамического контента и вызовы во время выполнения показываются для обоих наборов SDK. Смотрите JavaScript или Python для установки и настройки.
Страницы администратора
Для простых, типизированных настроек (строки, числа, переключатели) объявите блок config в манифесте и позвольте Owncast отрисовать форму для вас. Смотрите Конфигурация. Создайте пользовательскую страницу администратора, когда вам нужен интерфейс, который не может быть представлен автоформой.
Плагины могут регистрировать страницы, которые появляются внутри пользовательского интерфейса администраторов Owncast в разделе Плагины. 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 | Заметки |
|---|---|
| object key | Required path glob under /plugins/\<your-slug>/. Примеры: "/admin", "/admin/*", "/admin/api/*". |
название | Required tab label inside the plugin's admin view. |
иконка | Optional short semantic name. Поддерживаются: gear, wrench, user, users, lock, info, apps, docs, bell (также работают псевдонимы, такие как settings и notifications). |
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.
Как они отображаются
Администратор Owncast отображает каждую объявленную страницу как вкладку внутри /admin/plugins/configure?id=\<your-slug>. The tab body is an \<iframe> pointed at the path from the object key under /plugins/\<your-slug>/. Каждый плагин получает URL, доступный для закладки, и запись на боковой панели под Плагины в навигации администратора.
Хост автоматически внедряет базовую таблицу стилей в HTML-ответы на администраторах путей, так что обычные <input> и \<button> управляющие элементы выглядят как родные для админки Owncast, не требуя от вас внедрения CSS. Смотрите Стилизация пользовательского интерфейса плагина, чтобы узнать, что вы получаете бесплатно и какие классы вспомогательных классов доступны. Плагины, предпочитающие свою собственную стилизацию, могут наращивать сверху.
Песочница
Страница работает в песочнице \<iframe>. Ваши скрипты выполняются, формы отправляются, и запросы fetch с тем же источником к вашим собственным /plugins/\<your-slug>/ конечным точкам работают. Страницы могут также открывать всплывающие окна, вызывать загрузку файлов (например, blob или data-URL \<a download>, на который вы нажимаете из скрипта), и использовать confirm() / alert() / prompt() диалоги. Песочница — это единственное ограничение, которое вы обычно заметите. Если какая-то функция браузера кажется незаметно заблокированной, первой вещью для проверки будет песочница.
Авторизация
Запросы к объявленным в манифесте администраторским путям защищены хостом. Неаунтифицированные запросы получают 401 перед выполнением вашего кода плагина. Вам не нужно проверять аутентификацию запроса для этих путей.
Статические файлы и динамичные конечные точки под соответствующими путями также защищены. Та же авторизация применима к вашему public/admin/index.html и к POST /admin/api/save-settings.
Используйте несколько шаблонов, когда у вас есть и страница пользовательского интерфейса, и JSON API:
{
"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.
Поток авторазработчика
- Поместите HTML, CSS и JS администраторов в
public/admin/index.html(и его друзья). - Откройте администраторские API через ваш обработчик запросов по адресу
/admin/api/...(см. Обслуживание HTTP). - Declare the relevant path keys in
manifest.admin.pages. - Посетите
/admin/plugins/configure?id=\<your-slug>в пользовательском интерфейсе администратора. Owncast использует вашу существующую администраторскую аутентификацию для ограничения доступа к странице. Без дополнительных предложений.
Кнопки действий
Owncast отображает ряд кнопок действий в своем пользовательском интерфейсе просмотра. Щелкаемые элементы, которые либо открывают URL (в модальном окне или в новой вкладке), либо отображают обычный HTML. Плагины могут добавить свои собственные.
Кнопки, объявленные в манифесте
{
"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>"
}
]
}
При включенном плагине хост объединяет его записи действий в список, который уже показывает Owncast под потоком. При отключении они исчезают.
Справочник полей
| Поле | Заметки |
|---|---|
название | Обязательно. Этикетка кнопки. |
url | Либо абсолютный https://... URL, либо путь. Взаимоисключающий с html. |
html | Сырой HTML, отображаемый в встроенном модальном окне. Взаимоисключающий с url. |
иконка | Необязательный URL изображения, отображаемый на кнопке. Те же правила пути, что и для url. |
цвет | Необязательный шестнадцатеричный цвет для фона кнопки. |
описание | Необязательно. Отображается в модальном окне, которое открывается для действий, основанных на URL. |
открытьВнешне | Если true, URL открывается в новой вкладке вместо встроенного модального окна. |
Правила пути
Два простых правила охватывают все:
- Относительные пути автоматически добавляют префикс к пространству имен вашего плагина.
"/"становится/plugins/my-plugin/."/star.png"становится/plugins/my-plugin/star.png. Это избавляет вас от необходимости жестко кодировать имя вашего плагина. Применимо как дляurl, так и дляicon. - Абсолютные
https://...URLs остаются неизменными. Используйте их для внешних ссылок и иконок, размещенных на CDN.
Хост применяет:
- Требуется разрешение
ui.modify. Манифесты сactions, но безui.modify, отклоняются при загрузке. - Требуется ровно один из
urlилиhtmlна запись. - URLs и иконки, которые разрешаются в ваше пространство имен, требуют
http.serve. Вы тот, кто их обслуживает. - URLs и иконки, указывающие на пространство имен другого плагина, отклоняются. Ловит опечатки и предотвращает рекламу интерфейса одного плагина в интерфейсе другого.
Добавления во время выполнения
Плагин может в любое время добавлять дополнительные кнопки действий без перезагрузки, вызывая owncast.actions.add(...) с одним действием или массивом действий. Каждая запись во время выполнения проходит через ту же проверку, что и manifest.actions, и сохраняется в конфигурации плагина, чтобы добавления не терялись при перезагрузке. owncast.actions.clear() сбрасывает все добавления во время выполнения. Действия, объявленные в манифесте, остаются.
- 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([ { ... }, { ... } ])
Общая схема — это страница администратора, позволяющая стримеру добавлять пользовательские кнопки (этикетка + URL) поверх значений по умолчанию плагина. Пример с action-buttons в SDK поставляется с работающей версией этого.
Стилизация пользовательского интерфейса плагина
Owncast внедряет базовую таблицу стилей в каждую поверхность плагина, которая отображается в iframe: ваши админские страницы и ваши вкладки страниц просмотра. Она основана на собственных токенах дизайна Owncast, так что простой семантический HTML принимает родной вид без CSS с вашей стороны.
- Заголовки, абзацы и ссылки подбирают тематические шрифты и цвета.
<input>,\<textarea>,\<select>и\<button>отображаются как родные элементы управления. A\<button>gets the primary style. Добавьтеclass="secondary"для варианта обводки.\<table>,\<fieldset>и\<code>/\<pre>получают разумное родное оформление.
Ваш контент располагается по поверхности страницы. Фон iframe прозрачный, чтобы панель хоста просвечивала, так же как и встроенные вкладки О приложении и Подписчики. Вы не получаете, и не должны добавлять, непрозрачный фон страницы или оберточный блок вокруг всего. Такое слияние и есть то, что заставляет вкладку плагина восприниматься как часть Owncast, а не как встроенный фрейм.
Базовые стили для страниц, отображаемых с помощью iframe: админские страницы и вкладки страниц просмотра. Контент, который вы внедряете прямо на страницу просмотра (extraPageContent, scripts), отображается в реальном DOM страницы и наследует фактические стили Owncast.
Вспомогательные классы
Для родных строительных блоков, выходящих за пределы простых элементов, базовая таблица стилей включает несколько классов, которые можно использовать. Они ссылаются на те же тематические токены, что и остальное в Owncast, поэтому они автоматически изменяются, когда администратор настраивает тему.
| Класс | Что это делает |
|---|---|
карта | Родная поверхность карточки, аналогичная виду карточек подписчиков и представленных потоков. Простая \<section> / \<article> остается на месте, так что воспользуйтесь class="card", когда хотите, чтобы поверхность была обрамленной. |
интерактивная карта | Добавьте interactive к щелкаемой карточке для родного эффекта подъема. |
карта-сетка | Адаптивная сетка, которая заполняет столько ~260px колонок, сколько поместится, и сжимается до одной колонки на узком экране. Вставьте дочерние элементы card сразу. |
тег | Бирка или значок, совпадающий с ярлыками на карточках родного потока. |
стек | Вертикальная гибкая колонна с постоянными промежутками. |
строка | Горизонтальный гибкий ряд, который оборачивается, с постоянными промежутками. |
беззвучный | Сниженный текст, для заголовков и второстепенных деталей. |
<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>
Все здесь по желанию. Вкладка, которая содержит только семантический HTML, уже выглядит нативно. Reach for the helpers when you want cards, grids, or tags without hand-copying Owncast's values, and layer your own CSS on top (see Viewer stylesheets) whenever you need something the baseline doesn't cover.
Стилевые таблицы для зрителей
Плагины могут тематизировать страницу зрителя, подбирая CSS-файлы и перечисляя их в manifest.styles. Хост встраивает содержимое каждого файла в один блок стилей плагина на странице, так что плагины расширяют CSS страницы, не требуя для каждого вклада своего <link> тега.
{
"permissions": ["ui.modify"],
"styles": ["theme.css", "overrides.css"]
}
Требует только ui.modify (плагин рисует внутри интерфейса Owncast). http.serve не требуется: хост считывает каждый файл из каталога assets/ вашего плагина и встраивает байты в блок стилей плагина на /api/config, а не по URL.
Правила для путей
- Пустые пути, такие как
"theme.css", автоматически префиксируются пространством имен вашего плагина. "/theme.css"разрешается аналогичным образом.- Полностью определенные пути
/plugins/\<your-slug>/...проходят. - Пути в пространстве имен другого плагина отклоняются.
- URLs с
http://иhttps://отклоняются. Соберите внешние ресурсы и ссылайтесь на них с помощью@font-faceилиurl(...)из вашего CSS, вместо этого, так что администратор, просматривающий манифест, видит каждый файл, который будет загружен. - Каждая запись должна заканчиваться на
.css.
Как рендерятся вклады
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. Отключение плагина приводит к тому, что его вклад исчезает при следующей загрузке страницы.
Тело CSS работает с живым DOM для зрителей, так что ваши селекторы нацеливаются на то, что отрисовка страницы. Обрамление каждого правила под единственным корневым ID - это защитная привычка, которую стоит придерживаться. Без этого ваши правила могут соответствовать элементам, которые отрисовывает главная страница, и производить неожиданные регрессии.
Где стили плагина находятся в каскаде
Страница зрителя формирует свой внешний вид из четырех слоев, которые применяются в этом порядке. Поздние слои выигрывают.
- Встроенные настройки Owncast.
- Стили плагина: ваши
manifest.stylesфайлы сначала, затем ваш выводonPageStyles. - Переменные внешнего вида администратора, цвета устанавливаются с помощью выборок в Общие параметры → Внешний вид.
- Пользовательские CSS администратора, редактор на той же странице.
Ваши стили - это слой 2, так что явные выборы администратора в слоях 3 и 4 переопределяют ваши по любой собственности, которую вы оба устанавливаете. Считайте тему базовой линией, а не окончательным словом:
- Токен, который вы установили, который администратор оставил по умолчанию, показывает ваше значение.
- Токен, который вы установили, который администратор также установил, показывает значение администратора.
Как частичные, так и полные темы приемлемы. Плагин, который только перекрашивает ссылки, оставляет все остальные цвета нетронутыми. Плагин, который устанавливает всю палитру, все равно поддается любому отдельному цвету, который выбрал администратор. Администратор остается под контролем своего экземпляра, а страница Внешний вид сообщает им, что плагин участвует: она показывает уведомление с названием вашего плагина и пометкой каждого цвета, который вы установили, с примечанием также установлено \<плагином>. 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.
Одно исключение нарушает порядок: правило плагина с отметкой !important превосходит обычные объявления администратора независимо от слоя. Избегайте этого в CSS темы, если хотите, чтобы администратор имел последнее слово по своим цветам.
Предостережение: относительные URL в CSS
Ссылки url(...) внутри CSS плагина разрешаются относительно URL страницы зрителя, а не относительно пространства имен вашего плагина. Если вы хотите сослаться на упакованное изображение, используйте абсолютный путь /plugins/\<your-slug>/logo.png, а не ./logo.png. То же самое касается источников @font-face. Статическое пространство URL плагина остается доступным, так что прямые ссылки работают, даже если нет <link>, указывающего на файл.
Динамические стили: onPageStyles
Когда CSS зависит от состояния плагина, той темы, которую выбрал администратор, или значения в хранилище KV, возвращайте это из обработчика onPageStyles вместо (или вместе) статического файла. Для этого не существует поля в манифесте. Хост вызывает обработчик один раз для каждого /api/config для любого плагина, который имеет ui.modify и экспортирует его, а затем добавляет то, что он возвращает, в блок стилей вашего плагина после статических файлов manifest.styles. В пределах собственных стилей вашего плагина позднее правило выигрывает, так что возврат только активного переопределения из onPageStyles уже достаточно. Целый блок все еще находится ниже настроек внешнего вида администратора (см. где находятся стили плагина в каскаде).
- 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}; }}"
Требует ui.modify. The examples above also read the KV store, which separately requires storage.kv. Возвращайте ничего (голое return, то же самое, что возвращать "") при отсутствии чего-либо для вклада при данном запросе. Вызов не требует аргументов на один просмотр, так что ответ на /api/config остается кешируемым. Пример theme-hub в SDK использует это для применения выбранной администратором темы к интерфейсу всего зрителя.
Скрипты для зрителей
Плагины могут расширять время выполнения страницы зрителя, собирая JavaScript-файлы и перечисляя их в manifest.scripts. Содержимое каждого файла добавляется к ответу /customjavascript, который Owncast уже предоставляет для пользовательского JS администратора, так что плагины расширяют поведение страницы, не требуя, чтобы каждое участие имело свой собственный \<script> тег.
{
"permissions": ["ui.modify"],
"scripts": ["client.js"]
}
Те же правила о разрешениях и путях, что и для styles, применяются к .js файлам (требуется только ui.modify, а хост считывает из assets/ и встраивает в /customjavascript). Каждое участие предваряется комментарием // plugin: \<your-slug> ....
Это скрипты страницы зрителя, которые выполняются в браузере, всегда JavaScript, независимо от языка, на котором вы написали серверный плагин.
Контекст выполнения
Страница зрителя загружает /customjavascript как один тег \<script async>. JavaScript всех плагинов запускается в одном общем окне с пользовательским JS администратора и остальной частью интерфейса Owncast. Три следствия:
- Верхнеуровневые объявления
varиfunctionпопадают вwindow. 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. - Хост оборачивает каждое участие плагина в свой собственный try/catch, так что ошибка времени выполнения перекидывается в консоль браузера (с префиксом
owncast plugin \<your-slug> script error:), не останавливая скрипты других плагинов. Синтаксическая ошибка не изолирована: она нарушает разбор одного конкатенированного скрипта перед выполнением любого try/catch, так что отправляйте допустимый JavaScript. - Относительный
fetch('./data.json')разрешается относительно URL страницы зрителя, а не вашего плагина. Используйте абсолютные пути, такие как/plugins/\<your-slug>/data.json, для файлов, которые вы отправляете вpublic/.
Динамические скрипты: onPageScripts
Скрипт аналогичен onPageStyles. Возвращайте JavaScript, вычисленный во время запроса, из обработчика onPageScripts, без поля в манифесте. Хост вызывает его один раз для каждого /api/config для любого плагина, который содержит ui.modify и экспортирует его, и добавляет результат к /customjavascript после статических файлов manifest.scripts, завернутых в такой же try/catch на уровне плагина.
Это для JavaScript, вычисляемого во время запроса, а не только для тем. Используйте это для выполнения кода на стороне зрителя, который вычисляется для каждого запроса, например, отображая значение, которое администратор установил в хранилище KV плагина. Пример ниже показывает это значение зрителям:
- 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)});"
The output runs in the shared viewer window, so the IIFE and absolute-path advice above still applies. Экранируйте все недоверенные строки, которые вы вставляете: JSON.stringify в JavaScript и json.dumps в Python производят безопасную строку, поэтому примеры оборачивают уведомление в одну перед передачей его в alert. Like the styles examples, reading the KV store requires storage.kv on top of ui.modify. Возвращайте ничего (голое return, то же самое, что возвращать ""), чтобы ничего не внести.
Когда использовать это
scripts - это правильный инструмент для плагинов, которые нуждаются в реакции на состояние на стороне зрителей, установки своего интерфейса сверху страницы или общения с бэкендом, который плагин запускает на /plugins/\<your-slug>/. Для ботов на основе чата, фильтров сообщений и всякой логики, которая должна работать на стороне сервера, обычные обработчики плагинов лучше подходят. Они работают внутри песочницы хоста, могут обращаться к API Owncast, к которым страница зрителей не имеет доступа, и не доверяют пользовательскому DOM.
Дополнительный контент на странице
Плагины могут добавлять HTML к блоку дополнительного контента страницы зрителя. Объявите manifest.extraPageContent как объект с обязательным slug и необязательным content путем:
{
"permissions": ["ui.modify"],
"extraPageContent": { "slug": "banner", "content": "content.html" }
}
| Поле | Заметки |
|---|---|
slug | Обязательно. Стабильный идентификатор, передаваемый обработчику контента страницы, когда хост запрашивает отрисованный HTML. Строчные буквы, цифры и дефисы, начиная с буквы. |
содержимое | Необязательно. Относительный путь к статическому HTML файлу в assets/. Если он присутствует, байты этого файла встраиваются непосредственно. Если опущено, хост вместо этого вызывает ваш обработчик контента страницы. |
Статический против динамического
Используйте content, когда HTML одинаков для каждого зрителя: полосы уведомлений, рекламные баннеры, блоки прозы. Оставьте content и реализуйте обработчик контента страницы, когда контент должен изменяться для каждого зрителя или основываться на живых данных. Хост вызывает обработчик с запрашиваемым slug и идентичностью зрителя, а ваш обработчик возвращает HTML-строку для рендеринга:
- 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>'
Смотрите Обработчики: содержимое страницы для формы полезной нагрузки. Идентичность зрителя присутствует, когда зритель аутентифицирован, и отсутствует для анонимных зрителей.
Требует ui.modify. http.serve не требуется: HTML встраивается в ответ на /api/config, а не предоставляется по URL.
Байты попадают в верхнюю часть блока дополнительного контента, над любым текстом, который настроил администратор. Each contribution is wrapped with an <!-- plugin: <your-slug> ... --> comment for attribution. Вклады нескольких плагинов складываются в порядке загрузки хоста.
Правила путей
То же самое, что и для styles и scripts, применимое к одному .html элементу. Один файл на плагин. Если вы хотите несколько отдельных блоков, свяжите или \<iframe> их из одного файла, который вы отправляете.
Markdown против HTML
Дополнительный контент страницы администратора проходит через процессор разметки Owncast перед рендерингом. HTML плагина не: хост сначала запускает процессор разметки на содержимом администратора, затем добавляет ваши необработанные байты. Теги, атрибуты и встроенные скрипты проходят через систему как написано.
Это означает, что HTML плагина может использовать любой элемент, который принимает страница зрителя. Это также означает, что неверно оформленный тег может сломать окружающий интерфейс, так что экранируйте любые недоверенные строки, которые вы вставляете (имена пользователей, извлеченные тексты, все, что не в вашем контроле).
Сочетание со scripts
extraPageContent процветает в сочетании с scripts: отправляйте разметку в виде HTML, где ее можно быстро просмотреть, и настраивайте взаимодействия с помощью вашего JavaScript, запрашивая элементы, которые вы объявили. Хост загружает HTML перед выполнением скрипта, поэтому скрипт, нацеленный на document.getElementById(...) для элемента, добавленного плагином, работает без хитростей времени.
{
"permissions": ["ui.modify", "http.serve"],
"extraPageContent": { "slug": "panel", "content": "panel.html" },
"scripts": ["panel.js"]
}
Шаблон, который часто выглядит более аккуратно, чем построение одного и того же DOM императивно из плагина только со scripts:
panel.htmlобъявляет структуру, классы и ID, которые вы можете рассматривать как обычный HTML.panel.css(объявленный вstyles) оформляет его.panel.jsприкрепляет слушатели событий, извлекает данные, изменяет состояние.
Когда использовать HTML-плюс-JS вместо чистого JavaScript: все, что связано с нетривиальной компоновкой, атрибутами ARIA или сторонними виджетами, которые ожидают загрузки из существующего DOM. Чистые скрипты по-прежнему имеют смысл для плагинов, которые строят свой интерфейс только при определённых условиях (после получения данных, после действия пользователя), когда отображение ничего при первоначальной загрузке является правильным поведением.
Когда extraPageContent достаточно само по себе
Отдельное extraPageContent — самый простой путь для полос объявлений, спонсорских баннеров и любых блоков, которые не нуждаются в реакциях на события: он отправляет разметку напрямую, не требует скрипта и сохраняется в случае отключённых JavaScript-пользователей.
Закладки страницы просмотрщика
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 | Заметки |
|---|---|
| object key | Required stable slug passed to the tab-content handler. Строчные буквы, цифры и дефисы, начиная с буквы. |
название | Обязательное. Этикетка, отображаемая на вкладке. Должен быть уникальным в рамках вкладок плагина. |
содержимое | Необязательное. Относительный путь к статическому HTML-файлу в assets/. Когда он присутствует, байты этого файла встраиваются напрямую. Когда он опущен, хост вызывает обработчик содержимого вкладки вместо этого. |
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.
Требуется 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.
Как отображаются вкладки
Хост создает массив pluginTabs[] на /api/config. Страница просмотрщика сопоставляет каждую запись с вкладкой, чье тело — это встроенный HTML, отображаемый в санбоксе с внедрённым базовым стилем, так что простой HTML выглядит как родной без вашего CSS.
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.
Динамическое содержимое вкладки
When a tab value has no content file, the host calls your tab-content handler to produce it. Реализуйте его, когда содержимое должно изменяться в зависимости от пользователя или получать живые данные. 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>"
Смотрите Обработчики: содержимое вкладки для структуры полезной нагрузки. Идентификатор пользователя присутствует, когда аутентифицирован и отсутствует для анонимных пользователей.
Правила путей
То же самое, что и extraPageContent, применяется для каждой записи:
- Простые пути, такие как
"music.html", автоматически добавляют префикс к пространству имен вашего плагина. - Полностью квалифицированные пути
/plugins/\<your-slug>/...проходят. - Пути в пространстве имен другого плагина отклоняются.
http(s)://URL отклоняются.- Каждая запись должна заканчиваться на
.html.
Название вкладки
Поле title отображается дословно в строке вкладок. Держите его коротким: длинные названия обрезаются интерфейсом вкладки. Нет ограничений схемы по длине, но все, что превышает ~16 символов, не будет правильно отображаться на мобильных устройствах.
Когда использовать вкладки против extraPageContent
extraPageContent: один блок HTML, который находится выше строки вкладок. Хорошо для полос объявлений, спонсорских баннеров, всего, что должно всегда быть видимым.tabs: специальные панели, на которые пользователи кликают. Хорошо для контента, которому не нужно конкурировать с чатом за внимание: музыкальные списки, расписания событий, страницы ссылок, разделы спонсоров, которые вы хотите, чтобы пользователи находили, но не обязательно видели первыми.
Improve this page
See something missing or incorrect? Edit this page and improve the documentation for everyone.
Gabe Kangas