このページで分かること

  • 管理画面でのメニューの作成、リンクの追加(コンテンツ、カスタムリンク)、並べ替えと入れ子
  • メニュー項目の種類ごとに、テンプレートに返されるURLがどう決まるか
  • getMenu() によるレイアウトでの描画、ロケールの選ばれ方、ウィジェットエリアでの使い方
難易度
実践
読む時間
3分
前提知識
Astroとは
このページの目次

メニューを使うと、編集者はサイトのテンプレートを変更せずに、順番の決まったリンクを管理できます。メニューは primaryfooter のような変更しない前提の名前を持ち、翻訳したメニューごとに別々の項目を持ちます。

メニューの作成

メニューは、EmDashの管理画面の「メニュー」で作成し、並べ替えます。

  1. 「メニューを作成」をクリックし、名前とラベルを入力します。テンプレートは名前でクエリし、ラベルは管理画面でメニューを見分けるために使います。

  2. エントリーへのリンクを追加するには「コンテンツを選択」を、外部URLまたはルートからの相対パスを入力するには「カスタムリンクを追加」をクリックします。

  3. 「上に移動」と「下に移動」で順番を設定します。項目を入れ子にするには、項目を編集して「親」を選びます。

すべてのロケールで同じメニュー名を使います。多言語サイトでは、メニューを開き、「翻訳」パネルから別のロケールの版を作成・編集します。EmDashは同じコンテンツやタクソノミータームの翻訳どうしをつないでおくため、getMenu() は要求されたロケールのラベルとスラッグを使えます。

本サイトの補足 やさしい解説

WordPressの「外観」→「メニュー」にあたる機能です。管理画面の「メニュー」でリンクを並べておけば、テンプレートのコードを変えずにナビゲーションを更新できます。メニューには、テンプレートから呼び出すための「名前」(例:primary)と、管理画面で見分けるための「ラベル」の2つがあります。テンプレートが参照するのは名前のほうです。

コンテンツとタクソノミーのメニュー項目は、完成したURLではなく参照を保存します。テンプレートが getMenu() を呼び出すと、EmDashはその時点のコレクションとロケールのデータを使って、参照からURLを決めます。

メニュー項目の種類 テンプレートに返されるURL
コンテンツのエントリー コレクションの urlPattern。コレクションにパターンがない場合は /{collection}/{slug}
タクソノミーのターム 解決したタームの翻訳を使った /{taxonomy}/{slug}
コレクションのアーカイブ /{collection}/
カスタムリンク 編集者が入力した外部URLまたはルートからの相対パス

返される各項目には、ラベル、任意のターゲット、title属性、CSSクラス、入れ子の children も含まれます。次の描画の例では、これらの値をそのまま使います。

本サイトの補足 やさしい解説

メニューにコンテンツやタームを追加しても、URLそのものは保存されません。保存されるのは「どのエントリーか」「どのタームか」という参照で、ページを表示するたびにURLが組み立てられます。そのため、あとから記事のスラッグやコレクションの urlPattern を変えても、メニューのリンクは新しいURLになります。カスタムリンクだけは、入力したURLがそのまま使われます。

メニューの描画

getMenu() は、サーバーで描画するAstroコンポーネントで呼び出します。その名前のメニューが存在しない場合は null を返します。

次のレイアウトは、primaryメニューと、1階層分の入れ子の項目を描画します。

src/layouts/Base.astro
---
import { getMenu } from "emdash";
import { getRelativeLocaleUrl } from "astro:i18n";

const locale = Astro.currentLocale;
const menu = await getMenu("primary", { locale });

function menuHref(url: string) {
  return locale && url.startsWith("/")
    ? getRelativeLocaleUrl(locale, url)
    : url;
}
---

{menu && menu.items.length > 0 && (
  <nav aria-label="Primary navigation">
    <ul>
      {menu.items.map((item) => {
        const href = menuHref(item.url);

        return (
          <li class:list={item.cssClasses}>
            <a
              href={href}
              target={item.target}
              rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
              title={item.titleAttr}
              aria-current={Astro.url.pathname === href ? "page" : undefined}
            >
              {item.label}
            </a>

            {item.children.length > 0 && (
              <ul>
                {item.children.map((child) => {
                  const childHref = menuHref(child.url);

                  return (
                    <li class:list={child.cssClasses}>
                      <a
                        href={childHref}
                        target={child.target}
                        rel={child.target === "_blank" ? "noopener noreferrer" : undefined}
                        title={child.titleAttr}
                        aria-current={Astro.url.pathname === childHref ? "page" : undefined}
                      >
                        {child.label}
                      </a>
                    </li>
                  );
                })}
              </ul>
            )}
          </li>
        );
      })}
    </ul>
  </nav>
)}

getMenu() は、明示的に指定した locale を最初に選び、次に現在のリクエストのロケール、次に設定された既定のロケールを選びます。そのロケールにメニューや参照先のエントリーがない場合は、設定されたフォールバックの順番に従って検索します。

メニュー項目のURLには、コレクションのURLパターンやタクソノミーのパスは含まれますが、Astroのロケールの接頭辞は含まれません。menuHref() ヘルパーは、ルートからの相対リンクにこの接頭辞を付け、外部リンクはそのままにします。

この例は子を1階層分だけ描画します。一般的なドロップダウンにはこれで足ります。編集者がもっと深いナビゲーションを作れる場合は、項目のマークアップを再帰的なコンポーネントに移し、各項目の children を同じルールで描画します。

ウィジェットエリアでのメニューの使用

メニューウィジェットは、既存のメニューをウィジェットエリアの中に配置します。このウィジェットは、現在のリクエストのロケールのメニューを読み込みます。独自の入れ子のマークアップや、ロケールのURLの明示的な処理が必要な場合は、上の直接描画する方法を使います。

メニューのデータの直接取得

1つのメニューの項目ではなく、利用できるメニューの定義が必要なテンプレートでは、getMenus() を使います。クエリの引数と戻り値はランタイムAPIリファレンスで説明しています。

プログラムからメニューを変更する場合は、Bearerトークンで認証し、状態を変更するすべてのリクエストに X-EmDash-Request: 1 を付けます。リクエストの本文とレスポンスはメニューのエンドポイントを参照してください。

ロケールのルーティングとフォールバックの設定は多言語対応を参照してください。