# Eleventy

> Nunjucks、Liquid、Markdown のテンプレートで Ikiform のフォームを描画する Eleventy のショートコード。フォーム ID はグローバルデータで管理します。

Eleventy は静的な HTML を出力するので、フォームはクライアントの JavaScript なしで送信されます。ショートコードならマークアップを 1 か所にまとめられ、Nunjucks、Liquid、Markdown のどのファイルでも同じように動きます。

## セットアップ

### ステップ 1: ショートコードを追加する

`eleventy.config.js`:

```js
const escape = (value) => String(value).replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);

export default function (eleventyConfig) {
  eleventyConfig.addShortcode("ikiform", (formId, thanks = "", subject = "") =>
    [
      `<form class="ikiform" action="https://ikiform.com/f/${encodeURIComponent(formId)}" method="post">`,
      '<label>Email <input name="email" type="email" autocomplete="email" required></label>',
      '<label>Message <textarea name="message" rows="5"></textarea></label>',
      thanks && `<input type="hidden" name="_redirect" value="${escape(thanks)}">`,
      subject && `<input type="hidden" name="_subject" value="${escape(subject)}">`,
      '<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden>',
      '<button type="submit">Send</button>',
      "</form>",
    ]
      .filter(Boolean)
      .join("\n"),
  );
}
```

- 行は意図的に空行なしでつなげています。Markdown では空行で HTML ブロックが終わり、フォームの残りがテキストとして扱われてしまいます。
- これは ES モジュールの構文を使っています。`package.json` に `"type": "module"` がない場合は、ファイル名を `eleventy.config.mjs` にしてください。

### ステップ 2: フォーム ID をグローバルデータに置く

`_data/ikiform.js`:

```js
export default {
  formId: process.env.IKIFORM_FORM_ID ?? "frm_xxxxxxxxxxxxxxxx",
  thanks: "https://example.com/thanks/",
};
```

グローバルデータは、すべてのテンプレートで `ikiform` として使えます。Ikiform はそれ以外のリダイレクトを無視するため、サンクスページの URL は `https:` の絶対 URL にする必要があります。

### ステップ 3: 使う

`contact.md`:

```text
---
title: Contact
---

Questions about pricing? Write to us.

{% ikiform ikiform.formId, ikiform.thanks, "Pricing question" %}
```

Markdown ファイルはデフォルトで Liquid で処理されるので、タグはそのまま動きます。タグは独立した行に置き、4 つのスペースでインデントしないでください。インデントすると Markdown がコードブロックとして描画します。同じタグは `.njk` と `.liquid` のファイルでも動きます。

`_redirect` に従うのは、フォームの許可ドメインに自分のサイトが含まれている場合だけです。[_redirect のルール](https://ikiform.com/ja/docs/html.md#redirect-rules)を参照してください。

## 許可ドメイン

本番とそれぞれのプレビューのホストを登録します。Eleventy のサイトは Netlify や Cloudflare Pages にデプロイされることが多く、プレビューの扱いはそれぞれ異なります。

| フォームが動く場所 | 許可ドメインに追加するもの |
| --- | --- |
| `npx @11ty/eleventy --serve` | `localhost`。`http://localhost:8080` に一致します。Ikiform が比較するのはホスト名だけで、ポートは比較しません。 |
| 自分のドメイン | `example.com`（`www.example.com` も含まれます） |
| Netlify | `your-site.netlify.app`。デプロイプレビュー（`deploy-preview-12--your-site.netlify.app`）やブランチデプロイ（`branch--your-site.netlify.app`）はそのサブドメインではないので、テストする間はプレビューの正確なホストを追加してください。 |
| Cloudflare Pages | `your-project.pages.dev`。`abc123.your-project.pages.dev` のようなプレビュー URL はそのサブドメインなので、すでに一致します。 |

- `netlify.app` や `pages.dev` だけを追加しないでください。他の人のサイトも含め、そのホスト上のすべてのサイトが一致してしまいます。

## テストのチェックリスト

1. 開発サーバーを実行し、ページのソースを表示します。フォームは `<pre><code>` ブロックの中ではなく、プレーンな HTML になっているはずです。
2. 許可ドメインに `localhost` を入れてローカルで送信し、送信データが Ikiform に表示されることを確認します。
3. プレビューをデプロイし、プレビューのホストからもう一度送信します。
4. 本番で送信し、サンクスページに移動することを確認します。

## トラブルシューティング

| 症状 | 原因 | 対処 |
| --- | --- | --- |
| フォームがページにコードとして表示される | Markdown ファイルでショートコードが 4 つのスペースでインデントされている | タグを行の先頭に移動します。 |
| フォームの半分が段落として描画される | ショートコードの出力に空行が含まれていて、Markdown の HTML ブロックが終わってしまう | 上の設定のように、空行なしで行をつなげます。 |
| Eleventy の起動時に `SyntaxError: Unexpected token 'export'` が出る | CommonJS のプロジェクトで、設定ファイルが ES モジュールの構文を使っている | ファイル名を `eleventy.config.mjs` にするか、`package.json` に `"type": "module"` を追加します。 |
| Nunjucks や Liquid が `ikiform` タグを見つけられない | Eleventy が設定ファイルを読み込んでいない | ファイルがプロジェクトのルートにあり、名前が `eleventy.config.js` か `.mjs` になっているか確認します。 |
| 送信データが `origin_mismatch` でスパムフィルターの下に届く | 送信したページが、フォームの許可ドメインに含まれていない | 上の表のホストを追加します。**Reject** を選んでいる場合、同じ送信には代わりに `403 origin_not_allowed` が返ります。 |

## 関連ページ

- [Hugo](https://ikiform.com/ja/docs/guides/hugo.md): パーシャルと、コンテンツファイル用のショートコード。
- [Jekyll](https://ikiform.com/ja/docs/guides/jekyll.md): インクルードと、GitHub Pages の設定。
- [HTML フォーム](https://ikiform.com/ja/docs/html.md): リダイレクト、予約済みフィールド、ハニーポット。
- [許可ドメイン](https://ikiform.com/ja/docs/spam.md#allowed-domains): オリジンの照合方法と、一致しない場合の動作。
