Skip to content

Изменение дизайна страниц

Цвета и стилизация позволяют перекрасить весь сайт без кода. Когда вам нужно, чтобы у страницы были другой макет или разметка — а не только другие цвета, — вы меняете её представление (view). Эта страница показывает, как.

Идея простыми словами

Каждая страница в Pano состоит из двух частей:

  • Логика — загрузка данных, обработка входов, запуск плагинов. Этим владеет движок, и вы к этому никогда не прикасаетесь.
  • Представление (view) — то, как эта страница выглядит: разметка и макет. Это ваше, чтобы менять.

Поскольку эти две части разделены, вы можете взять внешний вид любой страницы под свой контроль, не трогая её логику. Данные по-прежнему приходят, плагины по-прежнему работают, входы по-прежнему происходят — вы лишь заново стилизуете представление.

Есть 26 представлений, которые вы можете взять под контроль, по одному на каждый вид страницы (главная, вход, регистрация, профиль и так далее).

Шаг 1 — посмотрите, что доступно

Выведите список каждого представления, которое можно переопределить, вместе с данными, которые получает каждое:

sh
bunx @panomc/theme-core list-views

Шаг 2 — возьмите представление под свой контроль

Чтобы взять представление под контроль, извлеките (eject) его. Извлечение копирует версию по умолчанию из движка в вашу собственную папку src/views/ и регистрирует её в theme.config.js:

sh
bunx @panomc/theme-core eject-view HomeView

После этого у вас будет рабочий файл src/views/HomeView.svelte, который вы можете свободно редактировать.

TIP

Извлечённые файлы начинаются как рабочие копии настоящего файла по умолчанию — а не как пустая страница. Вы редактируете существующий дизайн, а не пишете его с нуля. Начните с изменения мелочей и обновления страницы.

Шаг 3 — прочитайте заголовок («материалы, которые вам даны»)

Каждое извлечённое представление начинается с комментария-заголовка, который документирует каждый проп (prop) — данные и функции, которые движок передаёт вашему представлению. Считайте это списком материалов, с которыми вам предстоит работать. Вот реальный фрагмент из HomeView темы blaze-theme:

svelte
<!--
  @view HomeView (blaze override)
  Controller: $pano/lib/pages/HomePage.svelte
  Props:
    data.posts       array — записи текущей страницы
    data.postCount   number — общее число записей
    data.page        number — номер текущей страницы
    data.totalPage   number — общее число страниц для пагинации
    themeSettings    object — настройки темы из контекста
    onPageClick      function(data, page) — обработчик пагинации
-->

Хранилища (stores) приходят как объекты-хранилища (читайте их с префиксом $, например $_), а действия (actions) приходят как функции, которые вы вызываете. Всё, что перечислено в заголовке, — это то, что у вас есть; вам не нужно знать, откуда это берётся.

Разобранный пример — переработка главной страницы

Давайте действительно это сделаем. После eject-view HomeView ваш файл src/views/HomeView.svelte выглядит так (немного сокращён для удобства чтения):

svelte
<div class="vstack gap-3">
  <Hook name="page:home:top" />

  <!-- Posts -->
  <Posts posts={data.posts} />

  <!-- Pagination -->
  {#if data.postCount > 0}
    <Pagination
      page={data.page}
      totalPage={data.totalPage}
      on:pageLinkClick={(event) => onPageClick(data, event.detail.page)} />
  {/if}
</div>

<script>
  import { _ } from "svelte-i18n";
  import Hook from "$pano/lib/components/Hook.svelte";
  import Pagination from "$pano/lib/components/Pagination.svelte";
  import Posts from "$pano/lib/components/Posts.svelte";

  export let data;
  export let themeSettings;
  export let onPageClick;
</script>

Читайте сверху вниз: область плагинов (<Hook>), список записей и пагинация. Это вся главная страница. Теперь давайте изменим её, по одной небольшой правке за раз.

Правка 1 — добавьте свою собственную разметку

Всё, что вы пишете в разметке, просто появляется на странице. Добавьте приветственный баннер над записями:

svelte
<div class="vstack gap-3">
  <Hook name="page:home:top" />

  <div class="welcome-banner">
    <h1>Welcome, adventurer!</h1>
    <p>Grab your pickaxe — the server awaits.</p>
  </div>

  <!-- Posts -->
  <Posts posts={data.posts} />
  ...

Сохраните, обновите → баннер на вашей главной странице. Стилизуйте .welcome-banner в SCSS вашей темы, как любой другой CSS-класс. В этом и заключается большая часть работы над темой: обычные HTML и CSS, написанные внутри представления.

Правка 2 — используйте данные, которые вам даны

Заголовок сказал нам, что data.posts — это массив записей. Вам не обязательно использовать готовый компонент <Posts> — вы можете расположить записи по-своему с помощью цикла {#each}:

svelte
  <!-- Posts — replaced with our own card grid -->
  <div class="post-grid">
    {#each data.posts as post}
      <a class="post-card" href="/post/{post.url}">
        <h3>{post.title}</h3>
      </a>
    {/each}
  </div>

Сохраните, обновите → те же записи, совершенно другой макет, и вы владеете каждым его пикселем. Движок по-прежнему загружает данные, по-прежнему делает пагинацию, по-прежнему запускает плагины — вы лишь решили, как выглядит запись.

Как узнать, что внутри post?

Два простых способа: посмотрите, как это использовала разметка по умолчанию, или на минутку вставьте <pre>{JSON.stringify(post, null, 2)}</pre> внутрь цикла — он выведет весь объект на страницу. Удалите его, когда закончите.

Правка 3 — реагируйте на настройку

themeSettings хранит то, что владелец сайта настроил в панели. Используйте это, чтобы сделать части вашего дизайна необязательными:

svelte
  {#if themeSettings.welcomeBannerVisible !== false}
    <div class="welcome-banner">
      <h1>Welcome, adventurer!</h1>
    </div>
  {/if}

Теперь баннер можно отключить из панели — смотрите ниже раздел Собственные настройки темы о том, как объявить ключ, чтобы он корректно сохранялся.

Вот и весь цикл

Каждое представление работает именно так, какой бы ни была страница: eject → прочитайте заголовок, чтобы увидеть свои материалы → отредактируйте разметку → обновите. Страница входа, профиль, детали записи — тот же рецепт, другие пропы. Когда что-то ломается, отмените последнюю правку; когда сомневаетесь, сравните с представлением движка по умолчанию (оно всегда доступно в node_modules/@panomc/theme-core/src/lib/views/).

API плагинов внутри ваших представлений

Установленные плагины появляются на странице через маркеры, которые находятся внутри представлений. Их два вида:

  • Маркеры <Hook> — именованные области, куда плагины могут вставлять свои собственные компоненты. Хук выглядит как <Hook name="page:home:top" /> в разметке. Сегодня представления движка несут следующие имена хуков:

    Имя хукаГде появляются плагины
    theme:topВ самом верху каждой страницы
    page:topВверху содержимого каждой страницы
    page:home:topВверху главной страницы
    theme:post-detail:bottomПод содержимым записи
    theme:support:contentВнутри страницы поддержки
  • Слоты <ViewComponent> — места, где представление отрисовывает список компонентов, зарегистрированных плагинами, например дополнительные способы входа на странице входа или дополнительные строки на карточке профиля. Они приходят через пропы, документированные в заголовке представления (хранилища, такие как contentItems или altMethods), и отрисовываются через <ViewComponent component={item.component} … />.

Что нельзя удалять ни в коем случае

WARNING

Когда вы перерабатываете представление, сохраняйте каждый <Hook> и каждый слот <ViewComponent>, которые были в оригинале — перемещайте их, меняйте стили вокруг них, оборачивайте их в свою собственную разметку, но не удаляйте. Если вы уберёте хоть один, любой плагин, полагавшийся на него, незаметно исчезнет с сайтов ваших пользователей. Кроме того, имя хука должно присутствовать только в одном представлении одновременно — размещение одного и того же хука в двух местах отрисует каждый плагин там дважды.

Вам не нужно отслеживать это вручную: bun run check завершается с ошибкой, если переопределённое представление потеряло точку подключения или имя хука подключено дважды, поэтому инструмент защищает вас, прежде чем вы сможете отправить сломанную тему.

Добавление собственных точек подключения

Вы не ограничены встроенными хуками — ваша тема может расширять API плагинов, добавляя свои собственные новые области-хуки. В любом месте представления, которым вы владеете, поставьте новый маркер со свежим именем:

svelte
<script>
  import Hook from "$pano/lib/components/Hook.svelte";
</script>

<Hook name="my-theme:hero:bottom" />

Любой плагин, который зарегистрирует компонент для my-theme:hero:bottom, теперь будет отрисован там. Два правила делают это безопасным:

  • Используйте пространство имён для ваших имён. Начинайте их с id вашей темы (my-theme:…), чтобы они никогда не могли столкнуться с хуками движка или другой темы.
  • Не переназначайте существующие имена. У встроенных имён из таблицы выше есть фиксированное значение, на которое полагаются плагины — добавляйте новые имена вместо повторного использования старых где-то ещё.

После публикации относитесь к своим пользовательским хукам как к обещанию: плагины могут начать полагаться на них, поэтому сохраняйте их в будущих версиях вашей темы так же, как встроенные.

SSR и загрузка плагинов — откуда берутся данные плагинов

Содержимое плагинов не прикручивается к странице позже в браузере — оно является частью серверного рендеринга (SSR): когда страница рендерится на сервере, компоненты плагинов, смонтированные в хуках, рендерятся вместе с ней, так что посетители (и поисковые системы) получают полную страницу в первом же ответе.

За кулисами это обеспечивают два API плагинов, и оба запускаются контроллерами движка — ваша тема никогда их не вызывает, но полезно знать, что они существуют:

  • Функции load() хуков. Компонент плагина, смонтированный в хуке, может экспортировать собственную функцию load(); движок выполняет её во время загрузки страницы (на сервере для SSR, на клиенте при навигации) и автоматически передаёт результаты компоненту как hookProps — возможно, вы заметили hookProps в data заголовков некоторых представлений. Это происходит без каких-либо действий с вашей стороны.
  • События жизненного цикла. Плагины также могут подписываться на события времени загрузки, которые движок вызывает при подготовке данных страницы — theme:app:load, theme:navbar:load, theme:profile:load, theme:post-detail:load, theme:support:load, theme:tickets:load, theme:settings:load и подобные. Именно так, например, плагины добавляют элементы в навбар достаточно рано, чтобы те появлялись в отрендеренном на сервере HTML, а не возникали после загрузки страницы.

Что это значит для вас как автора темы:

  • Ничего настраивать не нужно — пока ваши переопределённые представления сохраняют точки монтирования, всё вышеперечисленное продолжает работать, включая SSR.
  • Одна честная оговорка о пользовательских хуках: серверный конвейер load() работает только для встроенных имён хуков. Плагин, смонтированный в добавленном вами пользовательском хуке (например, my-theme:hero:bottom), всё равно рендерится — включая SSR — но его данные load() не готовятся движком, поэтому такие плагины обычно загружают свои данные на клиенте.

Собственные настройки темы

Если ваше переработанное представление добавляет новые опции, которые владелец сайта должен иметь возможность менять (скажем, заголовок hero на главной странице), эти опции нужно объявить, чтобы панель могла их сохранять и сбрасывать. Это делается в theme.config.js в разделе settingsSchema.

Правила просты: записи только добавляют (additive) — ваши ключи добавляются во вкладку (новая вкладка создаётся, если её нет), и вы не можете удалить или переместить базовый ключ. defaultTab необязателен; задавайте его, только если ваше представление не показывает базовую вкладку по умолчанию. Вот компактный пример в стиле blaze, добавляющий ключи hero во вкладку header:

js
// theme.config.js
export default {
  views: {
    HomeView: () => import("./src/views/HomeView.svelte"),
  },
  settingsSchema: {
    tabs: {
      header: ["heroSubtitle", "heroSubtitleVisibility"],
    },
    defaultTab: "logo",
  },
};

Без этого ваши новые поля будут отображаться в панели, но никогда не будут сохраняться на самом деле. Ключу, который вы только читаете в разметке (без поля ввода в представлении настроек), запись здесь не нужна.

Честное замечание

Этот уровень требует базового Svelte — языка шаблонов, на котором написаны представления. Если вы никогда его не использовали, официальный учебник по Svelte короткий и интерактивный и охватывает всё, что использует представление.

Помните: вы никогда не начинаете с пустой страницы. Каждое извлечённое представление — это рабочая копия настоящего дизайна: вы редактируете, обновляете и повторяете.

Что дальше?

Когда ваша тема выглядит так, как вы хотите, руководство Начало работы охватывает сборку, проверку контракта, упаковку и публикацию.