Справочник API фронтенда
Эта страница перечисляет каждое имя хука, слот представления, событие жизненного цикла, помощник навигации и экспорт @panomc/sdk, которые может использовать UI вашего дополнения — в одном месте. Это справочная страница, а не руководство.
Новичок здесь? Четыре вещи, каталогом которых является эта страница, простыми словами:
- Хук = именованное место в странице хоста, где отрисовывается ваш компонент.
- Слот представления = как хук, но элементы упорядочены (по числу приоритета) и могут быть индивидуально скрыты или показаны.
- Событие жизненного цикла = момент во время загрузки страницы, в который вы можете выполнить код.
- Помощник навигации = API для добавления или редактирования ссылок в меню сайта или боковой панели администратора.
Как читать эту страницу
Это набор плотных справочных таблиц, сгруппированных по области API (§1–§10). Вы не читаете её сверху вниз — вы прыгаете к разделу о том, что связываете. Каждый раздел открывается одним простым предложением о том, для чего он и где работает (тема, панель или оба). Если термин в ячейке таблицы незнаком, он почти наверняка определён в блоке «Понятия, которые предполагает эта страница» чуть ниже — прочтите этот блок один раз сначала.
Понятия, которые предполагает эта страница (прочтите один раз, ~60 секунд)
Эта страница переиспользует горстку слов десятки раз каждое. Вот каждое в одном простом предложении:
- Хост — работающий фронтенд Pano, который загружает и выполняет JavaScript вашего дополнения. Это одно из двух приложений: тема или панель (следующий пункт). «Хост» здесь никогда не означает сервер, который вы арендуете.
- Тема против панели — два фронтенда. Тема — публичный сайт, который видят игроки, по адресу
/. Панель — админ-панель, по адресу/panel. Ваше дополнение может работать в обоих, и каждый предоставляет разные API.pano.isPanelговорит вашему коду, в котором из них он находится. - Хранилище Svelte — значение, на которое вы можете подписаться. В компоненте
.svelteпоставьте$перед ним, чтобы читать его реактивно ($myStore). В обычном JS вызовитеstore.subscribe(fn)и сохраните функцию, которую он возвращает, чтобы позже прекратить слушать (протолкните эту функцию вthis._unsubscribers, смотрите §1). Несколько API ниже «возвращают хранилище». (Документация Svelte: хранилища) load(event)— необязательная функция, которую может экспортировать страница или хук. Хост вызывает её при подготовке страницы.eventописывает запрос (его URL, параметры, cookies). Смотрите блок «три видаload()» ниже.- props — значения, которые компонент Svelte получает от того, что его отрисовало. Любой объект, который возвращает ваш
load(), становится props вашего компонента. - SSR / гидрация — SSR («отрисовка на стороне сервера») означает, что при первом открытии посетителем страницы её HTML строится на сервере; последующие переходы строятся в браузере. Гидрация — это когда Svelte подключает построенный на сервере HTML в браузере, чтобы кнопки и т. д. стали интерактивными.
- голый спецификатор / внешний — голый спецификатор — путь импорта без
./или/, напримерimport { x } from 'svelte'. Ваша сборка оставляет разрешённые из них внешними — она не копирует их в JS вашего дополнения; хост поставляет их во время выполнения. Смотрите §10. - узел разрешения — строка разрешения вроде
x.y.z, которая решает, кому разрешено что-то видеть или делать. Список узлов и то, как дополнения определяют свои собственные, смотрите в Справочнике Backend API.
Три вида load() на этой странице
Слово load появляется в трёх разных ролях. Они не взаимозаменяемы — путаница между ними — классический баг первой недели:
| Где | Вы пишете | Он получает | Что означает его возврат |
|---|---|---|---|
| Страница (§2) | export function load(event) в модуле страницы | event (запрос) | props компонента страницы (четыре специальных ключа вроде pageTitle извлекаются для хоста) |
| Хук (§3) | export function load(event) в модуле хука | event (запрос) | props компонента хука; верните { hookOptions: { invisible: true } }, чтобы скрыть хук |
| Обработчик жизненного цикла (§4, §6, §7) | (data, event) => { … }, переданный вызову onLoad(...) / lifecycle.on(...) | data (данные страницы, иногда изменяемые) и event | обычно ничего — вы читаете, или для помеченных событий подправляете, data на месте |
Если вы хотите увидеть эти API в использовании в реальном дополнении, начните с Разработки фронтенда, которая строит UI Shoutbox шаг за шагом. Всё здесь — поверхность, из которой черпает та страница.
Для Kotlin-половины вашего дополнения у этой страницы есть родственница: Справочник Backend API делает ту же работу для поверхности бэкенда — жизненный цикл плагина, база данных, эндпоинты, разрешения и события.
Откуда всё это берётся
Вы пишете import { X } from 'svelte', но ваша сборка никогда не упаковывает Svelte в вывод вашего дополнения — она оставляет этот импорт внешним, а работающий сайт Pano (хост) поставляет одну общую копию во время выполнения. Вот почему разрешены только определённые импорты (смотрите §10). Дерево pano.*, задокументированное ниже, внедряется в ваш плагин как this.pano; модули @panomc/sdk — это замороженный список импортов в конце этой страницы. (Полный механизм: Архитектура.)
0. Объект pano
Ко всему обращаются через объект pano, который хост внедряет в ваш плагин как this.pano. Один флаг живёт наверху; остальное свисает с pano.ui.*.
| Свойство | Тип | Что это |
|---|---|---|
pano.isPanel | boolean | true, когда ваш код выполняется внутри админ-панели, false внутри темы. Используйте его, чтобы выполнять разные регистрации в каждой — то есть if (this.pano.isPanel) { /* panel registrations */ } else { /* theme registrations */ }. |
Панель и тема предоставляют разные деревья pano.ui — помощник, существующий в одном, может не существовать в другом. Поэтому по всей этой странице каждый раздел говорит, где живут его члены: тема + панель, только тема или только панель. Когда весь раздел односторонний (как §4, только тема), это говорят его заголовок и блок вверху.
1. Контракт входа плагина
Ваш src/main.js экспортирует по умолчанию класс, расширяющий PanoPlugin (из @panomc/sdk). Хост создаёт один его экземпляр, внедряет this.pano и вызывает методы жизненного цикла ниже.
Наименьший возможный входной файл — это просто вот это:
import { PanoPlugin } from '@panomc/sdk';
export default class extends PanoPlugin {
onLoad() {
// your register(...) calls from sections 2–5 go here
}
}Каждую таблицу дальше вниз легче читать, представив этот скелет.
| Член | Вид | Назначение |
|---|---|---|
onLoad() | метод (переопределение) | Вызывается один раз после загрузки плагина. Делайте здесь все ваши регистрации (вызовы register(...) из разделов 2–5 ниже). this.pano доступен. Не трогайте this.pano в конструкторе — он внедряется только прямо перед выполнением onLoad(). |
onUnload() | метод (переопределение) | Вызывается при уничтожении плагина. Отмените всё, что не должно задерживаться (например, pano.ui.page.unregister(...)). |
this.pano | свойство | Внедрённый объект API, задокументированный на этой странице. |
this.context | свойство | Простой объект, где ваш плагин хранит состояние, которым хочет поделиться со своими компонентами (например, настройки, которые вы получили). |
this.setContext(partial) | метод | Копирует ключи partial в this.context (как Object.assign — поверхностное слияние, на один уровень) и уведомляет всё, подписанное на контекст, что он изменился. |
this._unsubscribers | массив | Проталкивайте сюда функции отписки от хранилищ (смотрите пункт о хранилищах в блоке «Понятия»); хост выполняет их все при уничтожении плагина, так что ваши подписки не утекают. |
Две функции приходят из @panomc/sdk вместе с базовым классом:
| Экспорт | Назначение |
|---|---|
viewComponent(importer) | Правило: всегда пишите viewComponent(() => import('./X.svelte')), когда передаёте компонент любому вызову register — никогда голый () => import(...) и никогда сам компонент. (Он присоединяет правильные mount/hydrate/unmount общей среды выполнения для хоста; эта сантехника — причина, почему обёртка обязательна.) |
getPanoContext() | Возвращает текущий контекст хоста Pano. Можете игнорировать это, если только вам не нужен сырой контекст хоста из кода, который не является методом PanoPlugin (у обычного метода вместо этого есть this.context) — редко нужно. |
onContextUpdate не вызывается
Если класс, который вы создали из boilerplate, содержит метод onContextUpdate(), удалите его — ни один хост его не вызывает. Это мёртвый код; не стройте на нём поведение. Используйте onLoad() для настройки, а подписки на хранилища (смотрите блок «Понятия») для реагирования на изменения.
2. Страницы — pano.ui.page (тема + панель)
Используйте это, чтобы регистрировать собственные целые страницы — под темой (/…) или панелью (/panel/…).
| Вызов | Назначение |
|---|---|
pano.ui.page.register(options) | Зарегистрировать страницу по пути. |
pano.ui.page.unregister(path) | Убрать страницу, которую вы зарегистрировали (используется для очистки — смотрите Разработку фронтенда). |
pano.ui.page.isPluginPage(path) | true, если какой-то плагин зарегистрировал этот путь. |
register(options):
| Опция | Тип | Значение |
|---|---|---|
path | string | Маршрут (смотрите формы пути ниже). |
component | viewComponent(...) | Компонент страницы. |
systemLayout | string | Обернуть вашу страницу в один из встроенных макетов хоста (имена перечислены ниже). Используйте MainLayout для обычной страницы; остальные имена соответствуют конкретным разделам хоста (например, ProfileLayout, SettingsLayout). |
layout | viewComponent(...) | Использовать собственный компонент макета вместо встроенного. |
resetLayout | boolean | Отрисовать без шапки, боковой панели или футера хоста — ваш компонент получает всю страницу. («Chrome» — общее слово для этого окружающего UI хоста.) |
permission | string | Узел разрешения (строка разрешения вроде x.y.z — смотрите Справочник Backend API для списка), необходимый для просмотра. Если у текущего пользователя его нет, страница отрисовывает 404. |
Формы пути:
| Форма | Пример | Совпадает с |
|---|---|---|
| буквальная | /shoutbox | ровно этим путём |
| динамический сегмент | /shout/[id] или /shout/:id | одним сегментом, захваченным как параметр — читайте его в вашем load(event) как event.params.id |
| перехват всего | /docs/[...rest] | оставшимися сегментами (должен быть финальным сегментом) |
| регэксп | re:/shout/\d+ | паттерн должен совпасть со всем путём, а не только частью — хост оборачивает его в ^…$ («полностью заякорено») за вас |
Модуль страницы также может экспортировать load(event) (смотрите блок «три вида load()» вверху). event описывает запрос — его URL, параметры и cookies. Любой объект, который вы возвращаете, передаётся вашему компоненту страницы как его props. Четыре специальных ключа удаляются из этого возвращённого объекта и используются chrome хоста вместо передачи как props:
pageTitle— заголовок, показываемый для страницы (например, вкладка браузера).breadcrumbs— цепочка хлебных крошек, которую хост отрисовывает над страницей.sidebar— боковая панель для этой страницы.sidebarProps— props, переданные этой боковой панели.
Имена systemLayout — тема: AppLayout, AuthLayout, MainLayout, ProfileLayout, ThemeSettingsLayout, TicketsLayout.
Имена systemLayout — панель: AddonDetailLayout, AddonsLayout, AppLayout, MainLayout, MigrationLayout, PermissionsLayout, PlayerDetailLayout, PlayersLayout, PostsLayout, ServerLayout, ServerSettingsLayout, SettingsLayout, TicketsLayout, TranslationsLayout, ViewLayout.
Контрольная точка — зарегистрировалась ли моя страница?
После вызова pano.ui.page.register({ path: '/your-path', component }) пересоберите ваше дополнение и перезагрузите сайт. Посещение /your-path теперь должно показывать ваш компонент. Ничего там нет? Проверьте, что component обёрнут в viewComponent(() => import('./X.svelte')) и что register выполнился внутри onLoad().
3. Хуки — pano.ui.hook (тема + панель)
Хук — именованная дыра в странице хоста: всё, что вы регистрируете под именем этого хука, отрисовывается в этом фиксированном месте. Хук — плоский список компонентов под одним именем. (Слоты представлений в §4 добавляют упорядочивание и скрытие поверх этой же идеи.)
| Вызов | Где | Назначение |
|---|---|---|
pano.ui.hook.register(options) | тема + панель | Смонтировать компонент в именованный хук. |
pano.ui.hook.get(name) | тема + панель | Возвращает хранилище (смотрите блок «Понятия») компонентов, зарегистрированных для name. |
pano.ui.hook.setVisible(name, component, visible) | только тема | Переключить видимость записи хука. Передайте ту же ссылку на компонент, которую вы дали register. |
register(options):
| Опция | Тип | Значение |
|---|---|---|
name | string | Имя хука (таблицы ниже). |
component | viewComponent(...) | Компонент для монтирования. |
permission | string | Отрисовывать только для пользователей, держащих этот узел разрешения. |
skipLoad | boolean | Не выполнять load() компонента во время загрузки страницы. Используйте это, когда ваш хук получает собственные данные при монтировании, так что выполнение при загрузке страницы было бы потрачено впустую или упало бы. |
invisible | boolean | Зарегистрировать, но начать скрытым. |
Контракт load() / hookProps хука: модуль компонента хука может экспортировать load(event). Хост выполняет его во время загрузки страницы — на сервере при первом открытии страницы (SSR) и в браузере при переходе между страницами — и передаёт результат вашему компоненту как его props (этот объединённый объект props — это hookProps компонента). Компонент может скрыть себя, вернув { hookOptions: { invisible: true } } из своего load().
Чтение имени хука
Имя хука говорит вам примерно, где он отрисовывается. Префикс — это область: theme: = публичный сайт, page: = область содержимого страницы, panel: = админ-панель. Середина называет страницу или таблицу. Суффикс называет место: :top, :bottom, :content, :sidebar или позицию в таблице вроде :header:... / :row:.... Не уверены, где именно один приземляется? Зарегистрируйте компонент, отрисовывающий видимую метку, пересоберите и посмотрите.
Столбец Extra prop ниже перечисляет любой prop, который хост передаёт вашему компоненту в дополнение к результату вашего load().
Имена хуков темы
| Имя хука | Extra prop |
|---|---|
theme:top | — |
page:top | — |
page:home:top | — |
theme:post-detail:bottom | post |
theme:support:content | — |
Имена хуков панели
Табличные хуки и prop tag
Хуки панели, чьи имена содержат table:header или table:row, отрисовываются внутри строк <tr> хоста. Хост передаёт вам tag (либо 'th' для ячейки заголовка, либо 'td' для ячейки тела); отрисовывайте <svelte:element this={tag}>…</svelte:element>, чтобы ваша ячейка была валидным HTML таблицы.
| Имя хука | Extra prop |
|---|---|
panel:plugin-detail:content | addon |
panel:plugin-detail:content:<pluginId> | addon |
panel:player-detail:bottom | playerData |
panel:player-detail:sidebar | playerData |
panel:post-editor:actions:right | post |
panel:post-editor:sidebar:before | post |
panel:post-editor:sidebar:after | post |
panel:post-editor:content:bottom | post |
panel:posts:layout:actions:right | — |
panel:posts:table:header:start | tag="th" |
panel:posts:table:header:after-title | tag="th" |
panel:posts:table:header:after-category | tag="th" |
panel:posts:table:header:after-views | tag="th" |
panel:posts:table:header:after-author | tag="th" |
panel:posts:table:header:end | tag="th" |
panel:posts:table:row:start | post, tag="td" |
panel:posts:table:row:after-thumbnail | post, tag="td" |
panel:posts:table:row:after-title | post, tag="td" |
panel:posts:table:row:after-category | post, tag="td" |
panel:posts:table:row:after-views | post, tag="td" |
panel:posts:table:row:after-author | post, tag="td" |
panel:posts:table:row:end | post, tag="td" |
panel:players:table:header:start | tag="th" |
panel:players:table:header:after-name | tag="th" |
panel:players:table:header:after-perm-group | tag="th" |
panel:players:table:header:after-status | tag="th" |
panel:players:table:header:after-last-login | tag="th" |
panel:players:table:header:end | tag="th" |
panel:players:table:row:start | player, tag="td" |
panel:players:table:row:after-name | player, tag="td" |
panel:players:table:row:after-perm-group | player, tag="td" |
panel:players:table:row:after-status | player, tag="td" |
panel:players:table:row:after-last-login | player, tag="td" |
panel:players:table:row:end | player, tag="td" |
panel:post-categories:table:header:start | tag="th" |
panel:post-categories:table:header:after-category | tag="th" |
panel:post-categories:table:header:after-description | tag="th" |
panel:post-categories:table:header:after-url | tag="th" |
panel:post-categories:table:header:end | tag="th" |
panel:post-categories:table:row:start | category, tag="td" |
panel:post-categories:table:row:after-category | category, tag="td" |
panel:post-categories:table:row:after-description | category, tag="td" |
panel:post-categories:table:row:after-url | category, tag="td" |
panel:post-categories:table:row:end | category, tag="td" |
Props post, playerData, addon, player и category выше — это те же объекты, что уже использует страница хоста для этого поста / игрока / дополнения / категории. Их точные поля здесь не перечислены — сделайте console.log prop, чтобы осмотреть его, или откройте код соответствующей страницы хоста.
Хуки с суффиксом :<pluginId>
panel:plugin-detail:content:<pluginId> отрисовывается только на странице деталей вашего дополнения — подставьте свой собственный pluginId (id, который объявляет ваш плагин — смотрите Справочник Backend API). Это стандартное место для панели настроек дополнения.
Контрольная точка — отрисовался ли мой хук?
После pano.ui.hook.register({ name: 'theme:top', component }) пересоберите и перезагрузите страницу темы. Ваш компонент должен появиться в месте этого хука. Если нет, подтвердите, что вы использовали реальное имя хука из таблиц выше и обернули компонент в viewComponent(...).
4. Слоты представлений — pano.ui.view (только тема)
Слот представления — именованный контейнер, отрисовывающий упорядоченный по приоритету список компонентов плагина (дополнительные методы входа, дополнительные строки профиля и так далее). Как хук, но каждый элемент слота несёт id и priority, так что элементы можно индивидуально скрывать, переупорядочивать или заменять.
Хуки против слотов представлений одним взглядом:
| Хук (§3) | Слот представления (§4) | |
|---|---|---|
| Упорядочивание | нет (плоский список) | по priority (выше отрисовывается первым) |
| id на элемент | нет | да (id) — позволяет скрыть/переместить/заменить один элемент |
| Где работает | тема + панель | только тема |
pano.ui.view / pano.ui.sidebar существуют только в теме
- Строите для панели? Пропустите весь этот раздел — панель вообще не предоставляет
view.register/hide/show/move/get/onLoad/loadилиpano.ui.sidebar. - Исключение панели 1: дополнительные строки в модальном окне редактирования игрока имеют собственный выделенный API — смотрите «Панель: строки модального окна редактирования игрока» ниже.
- Исключение панели 2: единственный член
pano.ui.viewпанели — этоpano.ui.view.themes.editMenu— смотрите §8.
| Вызов | Назначение |
|---|---|
pano.ui.view.register({ viewId, id, component, priority }) | Добавить компонент в слот viewId. priority по умолчанию 10; повторная регистрация того же id заменяет его. |
pano.ui.view.hide(viewId, id) | Скрыть элемент, не удаляя его. |
pano.ui.view.show(viewId, id) | Снова показать его. |
pano.ui.view.move(viewId, id, priority) | Изменить приоритет элемента. |
pano.ui.view.get(viewId) | Возвращает хранилище (смотрите блок «Понятия») видимых, упорядоченных элементов. |
pano.ui.view.onLoad(viewId, handler) | Выполнять ваш обработчик каждый раз, когда данные этого слота загружаются. (Под капотом это событие жизненного цикла theme:view:<viewId>:load — смотрите §6.) |
pano.ui.view.load(viewId, event) | Выполнить конвейер загрузки слота и получить разрешённые элементы (для страницы плагина, которая сама содержит слот). |
pano.ui.sidebar.* — псевдоним того же реестра с теми же методами, за исключением того, что ключ контейнера — sidebarId, а не viewId (и onLoad срабатывает theme:sidebar:<id>:load).
Форма элемента слота: { id, component, priority, props? }. Более высокий priority отрисовывается первым. Нет поля permission на элемент — элементы слота не фильтруются по разрешениям. Чтобы ограничить один, проверьте разрешение внутри вашего компонента — import { hasPermission } from '@panomc/sdk/utils/auth' — и не отрисовывайте ничего, если оно не проходит. (Хуки и ссылки навигации поддерживают опцию permission.)
ID слотов темы
| ID слота | Где отрисовывается |
|---|---|
login-content | тело страницы входа |
login-alt-methods | альтернативные методы входа |
register-content | тело страницы регистрации |
register-alt-methods | альтернативные методы регистрации |
profile-content | тело страницы профиля |
profile-card-rows | строки на карточке профиля |
settings-content | тело страницы настроек |
settings-card-rows | строки на карточке настроек |
tickets-content | тело страницы тикетов поддержки |
navbar-right | правая сторона навбара |
navbar-profile-dropdown | выпадающее меню профиля |
support-content | тело страницы поддержки |
support-options | список опций страницы поддержки |
reset-password-content | тело страницы сброса пароля |
renew-password-content | тело страницы обновления пароля |
activate-content | тело страницы активации аккаунта |
activate-new-email-content | тело страницы активации нового email |
Панель: строки модального окна редактирования игрока
У панели нет реестра слотов pano.ui.view. Её единственная точка расширения такого рода — дополнительные строки в модальном окне редактирования игрока — вместо этого имеет собственный выделенный API:
| Вызов | Назначение |
|---|---|
pano.ui.player.editModal.cardRows.edit(callback) | Отредактировать список строк карточки, показываемых в модальном окне редактирования игрока. callback получает текущий массив строк; измените его на месте и верните. |
pano.ui.player.editModal.cardRows.get() | Прочитать текущие строки карточки. |
Объекты строк зеркалят существующие строки модального окна; их точные поля здесь не перечислены — вызовите cardRows.get() (или сделайте console.log массива, который получает ваш колбэк edit), чтобы осмотреть их. Дополнения в стиле аватаров и социального входа используют это, чтобы добавить строку в это модальное окно.
Соглашения о приоритетах
Соблюдайте эти числа, чтобы ваши элементы приземлились в разумном порядке относительно других установленных дополнений. Напоминание: выше приоритет отрисовывается первым, так что отрицательный -100 намеренно ставит инъекции поддержки последними.
| Вид слота | Соглашение |
|---|---|
| строки модального окна редактирования игрока | 100 |
| строки карточки настроек | 105 |
| строки карточки профиля | 90 |
| альтернативные методы аутентификации | 50 |
| инъекция поддержки | -100 |
| всё остальное | 10 (по умолчанию) |
5. Навигация — pano.ui.nav
Добавляйте или редактируйте ссылки в меню сайта (тема) или боковой панели администратора (панель). Тема и панель предоставляют разные помощники.
Тема (только тема):
| Вызов | Назначение |
|---|---|
pano.ui.nav.site.editNavLinks(callback) | Синхронный. Получает текущий массив ссылок; либо измените его на месте, либо верните новый массив. Результат пересортировывается хостом по priority каждой ссылки. |
pano.ui.nav.site.getNavLinks() | Хранилище текущих навигационных ссылок сайта. |
pano.ui.nav.profileDropdown.edit(callback) / .get() | Редактировать / читать элементы выпадающего меню профиля (это редактирует слот navbar-profile-dropdown из §4). |
pano.ui.nav.rightComponents.edit(callback) / .get() | Редактировать / читать компоненты правой части навбара (это редактирует слот navbar-right из §4). |
pano.ui.nav.onLoad(handler) | Подписаться на theme:navbar:load (событие жизненного цикла — смотрите §6). |
Форма навигационной ссылки темы — { href, text, icon?, target?, startsWith, loginRequired?, permission?, priority? }, поле за полем:
| Поле | Обязательно | Значение |
|---|---|---|
href | да | Куда указывает ссылка, например /shoutbox. |
text | да | Видимая надпись. Если строка содержит ., она трактуется как ключ перевода и прогоняется через _ (смотрите §9 / Локализация); иначе показывается как есть. |
startsWith | да | Boolean, контролирующий подсветку активной ссылки. Когда true, ссылка подсвечивается всякий раз, когда путь текущего URL начинается с её href (так /shoutbox остаётся подсвеченной на /shoutbox/123); когда false, подсвечивает только точное совпадение пути. |
icon | нет | Иконка как строка CSS-класса, отрисовываемая на <i>, например 'fa-solid fa-comments'. |
target | нет | Стандартный target якоря, например '_blank' для новой вкладки. |
loginRequired | нет | Если true, показывать ссылку только вошедшим пользователям. |
permission | нет | Узел разрешения; показывать ссылку только пользователям, которые его держат. |
priority | нет | Порядок сортировки среди ссылок (меньшее число идёт первым; ссылки без него сортируются последними). |
Панель (только панель):
| Вызов | Назначение |
|---|---|
pano.ui.nav.site.editNavLinks(async handler) | Асинхронный, должен вернуть массив. Редактирует ссылки главной боковой панели панели. |
pano.ui.nav.server.editNavLinks(async handler) | Асинхронный, должен вернуть массив. Редактирует ссылки боковой панели раздела сервера. |
Колбэки навигации панели должны вернуть массив
editNavLinks темы принимает изменение на месте, но editNavLinks панели (и server.editNavLinks) — асинхронные и устанавливают список тем, что вы возвращаете — забудьте return, и вы сотрёте меню. Это выглядит так:
pano.ui.nav.site.editNavLinks(async (links) => {
links.push({ href: '/panel/shoutbox', text: 'Shoutbox' });
return links; // forget this line and the sidebar goes blank
});6. События жизненного цикла
События времени загрузки, которые хост генерирует, пока подготавливаются данные страницы. Каждый обработчик имеет сигнатуру async (data, event) — event — тот же объект события запроса, который получает ваш load() (§2), а data — данные страницы в процессе подготовки. В большинстве обработчиков вы просто читаете data; для помеченных событий ниже вы можете изменять его (смотрите столбец «Заметки о data»). Регистрируйте через помощники-ярлыки ниже или через общий примитив:
| Вызов | Назначение |
|---|---|
pano.ui.lifecycle.on(name, handler) | Подписаться на любое событие жизненного цикла по имени (тема + панель). |
pano.ui.lifecycle.execute(name, data, event) | Только тема. Запустить жизненный цикл самостоятельно — например, ваш плагин отрисовывает собственную страницу входа и хочет, чтобы жизненный цикл входа хоста (и обработчики других дополнений) выполнились на ней. pano.ui.lifecycle панели предоставляет только on. |
События жизненного цикла темы
| Событие | Ярлык | Заметки о data |
|---|---|---|
theme:app:load | pano.ui.app.onLoad(h) | — |
theme:navbar:load | pano.ui.nav.onLoad(h) | — |
theme:profile:load | pano.ui.profile.onLoad(h) | — |
theme:settings:load | pano.ui.settings.onLoad(h) | — |
theme:tickets:load | pano.ui.tickets.onLoad(h) | — |
theme:login:load | pano.ui.auth.login.onLoad(h) | data = { error, event } — вы можете установить data.error = '…'; после выполнения вашего обработчика хост читает его и показывает на странице входа |
theme:register:load | pano.ui.auth.register.onLoad(h) | data = { error, username, event } — вы можете установить data.error и data.username; хост читает их обратно |
theme:reset-password:load | pano.ui.auth.resetPassword.onLoad(h) | — |
theme:activate:load | pano.ui.auth.activate.onLoad(h) | data = { token } — token — код активации из ссылки в письме пользователя (взят из URL) |
theme:activate-new-email:load | pano.ui.auth.activateNewEmail.onLoad(h) | data = { token } — та же идея, для подтверждения нового email |
theme:renew-password:load | pano.ui.auth.renewPassword.onLoad(h) | data = { token } — та же идея, для ссылки сброса пароля |
theme:post-detail:load | pano.ui.post.onLoad(h) | — |
theme:support:load | pano.ui.support.onLoad(h) | — |
theme:view:<viewId>:load | pano.ui.view.onLoad(viewId, h) | срабатывает для каждого слота |
theme:sidebar:<id>:load | pano.ui.sidebar.onLoad(id, h) | срабатывает для каждой боковой панели |
События жизненного цикла панели
| Событие | Ярлык | Заметки о data |
|---|---|---|
panel:posts:load | pano.ui.posts.onLoad(h) | — |
panel:addon-detail:load | pano.ui.addon.onLoad(h) | data = { addon } |
panel:player-detail:edit-modal:load | pano.ui.player.onEditLoad(h) | data = { player } |
7. Поверхности аутентификации (только тема)
Помощники для страниц аутентификации. <page> — это одно из login, register, resetPassword, activate, activateNewEmail, renewPassword.
| Вызов | Назначение |
|---|---|
pano.ui.auth.<page>.content.edit(callback) / .get() | Редактировать / читать слот содержимого этой страницы. Это тот же слот, что <page>-content из §4 (например, pano.ui.auth.login.content.edit редактирует слот login-content) — ярлык к нему, а не отдельный механизм. |
pano.ui.auth.<page>.onLoad(handler) | Подписаться на событие загрузки этой страницы (события theme:<page>:load в §6). |
pano.ui.auth.login.alternativeMethods.add(method) / .get() | Добавить / читать альтернативный метод входа (например, кнопку социального входа). |
pano.ui.auth.register.alternativeMethods.add(method) / .get() | То же для регистрации. |
pano.ui.auth.login.load(event) | Запустить поток загрузки входа (для страницы плагина, которая представляет собственный вход). Возвращает { error, username, event }. |
pano.ui.auth.register.load(event) | То же для страницы регистрации плагина. |
pano.ui.auth.login.form.get() | Возвращает собственный компонент тела формы входа темы, чтобы страница плагина могла отрисовать стандартную форму входа внутри собственного макета. |
pano.ui.auth.register.form.get() | То же для формы регистрации. |
resetPassword, activate, activateNewEmail и renewPassword предоставляют только content.edit/content.get и onLoad.
8. Разное
| Вызов | Где | Назначение |
|---|---|---|
pano.ui.avatar.updateVersion() | тема + панель | Увеличить «сбрасыватель кэша» аватара — номер версии, добавляемый к URL изображений аватара — чтобы браузеры перезагрузили картинку вместо показа кэшированной (старой). Вызывайте его после того, как пользователь сменил аватар. |
pano.ui.avatar.getVersion() | тема + панель | Хранилище текущей строки версии аватара. |
pano.ui.view.themes.editMenu(async handler) | только панель | Редактировать элементы контекстного меню страницы тем. Асинхронный; обработчик получает текущие элементы и должен вернуть массив. |
pano.ui.posts.editMenu(async handler) | только панель | Редактировать элементы контекстного меню постов. Асинхронный; должен вернуть массив. |
Для двух вызовов editMenu точные поля элемента меню здесь не перечислены — сделайте console.log массива, который получает ваш обработчик, чтобы увидеть форму каждого элемента, прежде чем добавлять или менять его.
9. Экспорты модулей @panomc/sdk
Это замороженная поверхность импортов @panomc/sdk — каждый спецификатор соответствует стабильному модулю среды выполнения хоста. Импортируйте из этих точных путей и никогда не делайте глубокий импорт внутри этих пакетов (например, @panomc/sdk/utils/api/something не разрешится). (Собственные спецификаторы Svelte и любой npm-пакет, который вы упаковываете, также разрешаются — смотрите §10 для полной картины импортов.)
| Спецификатор | Экспорты |
|---|---|
@panomc/sdk | PanoPlugin, viewComponent, getPanoContext |
@panomc/sdk/utils/api | ApiUtil (по умолчанию), NETWORK_ERROR, networkErrorBody, buildQueryParams |
@panomc/sdk/utils/auth | hasPermission(permission, user) |
@panomc/sdk/utils/tooltip | tooltip (также по умолчанию) |
@panomc/sdk/utils/text | copy |
@panomc/sdk/utils/language | _, languageLoading, currentLanguage, Languages, init, getAcceptedLanguage, loadLanguage, changeLanguage, getLanguageByLocale |
@panomc/sdk/utils/component | viewComponent |
@panomc/sdk/toasts | showToast, limitTitle |
@panomc/sdk/components/theme | PlayerHead, NoContent, Date, Toast, PageTitle, PageActions, Pagination |
@panomc/sdk/components/panel | NoContent, Editor, DragAndDropZone, Date, Toast, PageLoading, PageActions, PageLoader, PageNavItem, PageNav, Pagination, CardFilters, CardFiltersItem, CardHeader, SearchInput |
@panomc/sdk/variables | API_URL, UI_URL, PANEL_URL, SETUP_URL, PANO_WEBSITE_URL, PANO_WEBSITE_API_URL, PRERELEASE, COOKIE_PREFIX, CSRF_TOKEN_COOKIE_NAME, JWT_COOKIE_NAME, CSRF_HEADER, updateApiUrl, updatePanoWebsiteUrl, updatePanoWebsiteApiUrl |
@panomc/sdk/svelte | page, base, navigating, browser, goto, invalidate, invalidateAll, error, redirect |
@panomc/sdk/internal | setPanoContext, getPanoContext |
viewComponent появляется дважды — в @panomc/sdk и @panomc/sdk/utils/component. Это та же функция; импортируйте из любого (@panomc/sdk — обычный путь).
Экспорты, чьи имена не самоочевидны:
_— функция/хранилище перевода:$_('some.key')в компоненте. Смотрите Локализацию.languageLoading,currentLanguage— хранилища для текущего состояния i18n.tooltip— действие Svelte: присоедините его черезuse:tooltipна элементе.copy— копирует строку в буфер обмена.hasPermission(permission, user)— возвращаетtrue, еслиuserдержитpermission; используйте, чтобы показывать/скрывать UI (смотрите §4).showToast/limitTitle— всплывающее уведомление-тост / обрезка длинной строки заголовка для него (смотрите сигнатуруshowToastниже).NoContent— компонент-заглушка «пустого состояния».PageLoading/PageLoader— UI состояния загрузки (два варианта; вставьте каждый, чтобы увидеть, какой подходит вашему случаю).PlayerHead— отрисовывает изображение головы игрока Minecraft.
Какие @panomc/sdk/variables вам реально нужны
Повседневное использование: API_URL, UI_URL, PANEL_URL (и PRERELEASE для определения предрелизной сборки). Остальное — CSRF_TOKEN_COOKIE_NAME, JWT_COOKIE_NAME, CSRF_HEADER, COOKIE_PREFIX, сеттеры update*Url — это сантехника аутентификации/безопасности, которую ApiUtil уже обрабатывает за вас; вы редко их трогаете.
@panomc/sdk/svelte = собственные API SvelteKit
Они зеркалят экспорты SvelteKit — page, navigating, browser (из $app/state / $app/environment), base (из $app/paths), goto, invalidate, invalidateAll (из $app/navigation) и error, redirect (из @sveltejs/kit). Смотрите документацию SvelteKit о том, как каждый ведёт себя. Импортируйте их из @panomc/sdk/svelte, а не из $app/..., чтобы они разрешались в среду выполнения хоста.
Нет Button, Card или Input
@panomc/sdk/components/panel и .../theme экспортируют ровно перечисленные выше компоненты. Нет универсальных Button/Card/Input — некоторые ранние примеры ссылались на компоненты, которых никогда не существовало; список выше — авторитетный. Стройте простые контролы обычной разметкой или переиспользуйте перечисленные компоненты.
Сигнатуры методов ApiUtil (все async, все принимают единственный объект опций):
| Метод | Опции |
|---|---|
ApiUtil.get(...) | { path, request, csrfToken, token, blob, handler } |
ApiUtil.post(...) | { path, request, body, headers, csrfToken, token, blob, handler, onUploadProgress } |
ApiUtil.put(...) | { path, request, body, headers, csrfToken, token, blob, handler, onUploadProgress } |
ApiUtil.delete(...) | { path, request, headers, csrfToken, token, blob, handler } |
ApiUtil.customRequest(...) | { path, data, request, csrfToken, token, blob, handler, onUploadProgress } |
Что означает каждая опция (не каждая опция появляется в каждом методе — используйте таблицу сигнатур выше):
| Опция | Значение |
|---|---|
path | Путь API, относительно /api — передайте 'shoutbox/list', и утилита вызовет /api/shoutbox/list. |
request | Аргумент load(event). Передавайте его всякий раз, когда вызываете из load(), чтобы у запроса был CSRF-токен и он работал во время SSR (смотрите заметку под примером). |
body | Полезная нагрузка запроса (объект, отправляемый как JSON; или FormData для загрузки файлов). Только POST/PUT. |
headers | Дополнительные заголовки запроса. POST/PUT/DELETE. |
csrfToken | CSRF-токен. Обычно вы его опускаете — утилита читает его из сессии через request. |
token | Bearer-токен; когда задан, отправляется как Authorization: Bearer <token>. Опускайте для обычных вызовов вошедшего пользователя — cookies обрабатывают аутентификацию. |
blob | Установите true, когда ответ — файл/бинарник, чтобы он читался как Blob, а не парсился как JSON. |
handler | Необязательный колбэк (data, reject) => data, который постобрабатывает разобранный ответ перед возвратом вам. |
onUploadProgress | Колбэк прогресса загрузки (POST/PUT/customRequest) — используйте его для управления полосой прогресса. |
data | (только customRequest) сырые опции fetch — method, body, headers. Помощники get/post/и т. д. строят это за вас. |
Минимальный GET во время загрузки страницы — обратите внимание на path (относительно /api) и request: event на месте:
import ApiUtil from '@panomc/sdk/utils/api';
export async function load(event) {
// pass request: event so the call works during SSR (the first page view)
const response = await ApiUtil.get({ path: 'your-endpoint', request: event });
return { response }; // this object becomes your page component's props
}Всегда передавайте request: event, когда вызываете их внутри load(). Если забудете, вызов не сможет подхватить CSRF-токен или переиспользовать fetch сервера во время SSR — он может упасть или незаметно перезапуститься в браузере после загрузки страницы. Последний случай — самый запутанный: выглядит так, будто работает, когда вы кликаете по сайту, но ломается при свежей загрузке страницы или жёстком обновлении.
Сигнатура showToast: showToast(text, params = {}, toastComponent).
| Аргумент | Значение |
|---|---|
text | Сообщение. Если это ключ перевода (содержит .), оно переводится; иначе показывается как есть. |
params | С тостом по умолчанию они становятся значениями перевода, интерполируемыми в text. С кастомным toastComponent они передаются этому компоненту как его props. Необязательно (по умолчанию {}). |
toastComponent | Необязательный кастомный компонент Svelte для отрисовки вместо тоста по умолчанию. |
10. Что вы можете импортировать
Только замороженный список разрешается в среду выполнения хоста
Ваша сборка намеренно не упаковывает эти импорты в JS вашего дополнения — она оставляет их внешними, а хост предоставляет их во время выполнения, чтобы каждое дополнение разделяло один экземпляр Svelte. Импортируйте что-либо вне списка, и оно не разрешится во время выполнения.
Разрешённые голые спецификаторы — это ровно:
- Каждый спецификатор
@panomc/sdkиз таблицы §9. - Svelte:
svelte,svelte/store,svelte/transition,svelte/easing,svelte/motion,svelte/animate,svelte/legacy,svelte/events,svelte/attachments,svelte/reactivity,svelte/reactivity/windowиsvelte-i18n. - Фиксированный набор внутренностей Svelte (продвинутое — компилятор вставляет эти импорты в ваш собранный код за вас; вы никогда не пишете их вручную):
svelte/internal,svelte/internal/client,svelte/internal/disclose-version,svelte/internal/flags/legacy,svelte/internal/flags/asyncиsvelte/internal/flags/tracing. Это точный список, не шаблонsvelte/internal/*; любой другой подпутьsvelte/internal/...не разрешается.
Всё остальное — chart.js, svelte-select, любой другой npm-пакет — должно быть упаковано в ваше дополнение вашей сборкой rollup, а не импортировано голым. Вы не делаете для этого ничего особого: сборка rollup из boilerplate уже упаковывает всё, что вы bun add, а затем import обычным образом. (Дополнение market делает именно это, чтобы поставить Chart.js.)
Никогда не добавляйте svelte в ваш package.json
SDK контролирует, с какой версией Svelte все компилируют (его версия зафиксирована), и сборка падает при несовпадении. Вторая копия svelte в вашем package.json незаметно ломает ваши страницы (две копии расходятся во время гидрации). Смотрите Архитектуру.
Известные мёртвые поверхности (не используйте)
Для полноты: два члена существуют на поверхности, но ничего не делают — не стройте на них:
pano.debug— булев флаг на объектеpano. Сейчас он жёстко зашит какfalse, и ни один хост его не устанавливает, так что не используйте его для определения сборки разработки.onContextUpdate()— старый метод boilerplate, который ни один хост не вызывает (смотрите §1).
Куда дальше
- Разработка фронтенда — прохождение Shoutbox, которое пускает эти API в дело.
- Локализация — как хранилище
_и ваши файлы локализации складываются вместе. - Изменение дизайна страниц — модель представлений/хуков со стороны темы, если вы также строите темы.