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 короткий и интерактивный и охватывает всё, что использует представление.

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

Что дальше? ​

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