Laravel 9.x Laravel Scout

イントロダクション

Laravel Scout(Scout、斥候)は、Eloquentモデルへ、シンプルなドライバベースのフルテキストサーチを提供します。モデルオブサーバを使い、Scoutは検索インデックスを自動的にEloquentレコードと同期します。

現在、ScoutはAlgolia, MeiliSearch, MySQL/PostgreSQL (database) ドライバを用意しています。さらに、Scoutはローカル開発用途に設計された、外部依存やサードパーティサービスを必要としない「コレクション」ドライバも用意しています。加えて、カスタムドライバの作成も簡単で、独自の検索実装でScoutを自由に拡張可能です。

インストール

最初に、Composerパッケージマネージャを使い、Scoutをインストールします。

composer require laravel/scout

Scoutをインストールした後、vendor:publish Artisanコマンドを実行してScout設定ファイルをリソース公開する必要があります。このコマンドは、scout.php設定ファイルをアプリケーションのconfigディレクトリへリソース公開します。

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

最後に、検索可能にしたいモデルにLaravel\Scout\Searchableトレイトを追加します。このトレイトは、モデルを検索ドライバと自動的に同期させるモデルオブザーバを登録します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;
}

ドライバの事前要件

Algolia

Algoliaドライバを使用する場合、Algolia idsecret接続情報をconfig/scout.php設定ファイルで設定する必要があります。接続情報を設定し終えたら、Algolia PHP SDKをComposerパッケージマネージャで、インストールする必要があります。

composer require algolia/algoliasearch-client-php

MeiliSearch

MeiliSearchは、非常に高速なオープンソースの検索エンジンです。ローカルマシンにMeiliSearchをインストールする方法がわからない場合は、Laravelの公式サポートのDocker開発環境であるLaravel Sailを利用できます。

Meilisearchドライバを使用する場合は、Composerパッケージマネージャを使用して、MeiliSearch PHP SDKをインストールする必要があります。

composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle

次に、アプリケーションの.envファイル内のSCOUT_DRIVER環境変数とMeiliSearchhostkey認証情報を設定します。

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey

MeiliSearchの詳細については、MeiliSearchのドキュメントを参照してください。

さらに、MeiliSearchのバイナリ互換のドキュメントを見て、自分が使っているMeiliSearchのバイナリバージョンと互換性のあるバージョンのmeilisearch/meilisearch-phpをインストールしてください。

Note: MeiliSearchを利用しているアプリケーションのScoutをアップグレードする際には、常にMeiliSearchサービス自体に追加の破壊的な変更がないか確認する必要があります。

キュー投入

厳密にはScoutを使用する必要はありませんが、ライブラリを使用する前に、キュードライバの設定を強く考慮する必要があります。キューワーカを実行すると、Scoutはモデル情報を検索インデックスに同期するすべての操作をキューに入れることができ、アプリケーションのWebインターフェイスのレスポンス時間が大幅に短縮されます。

キュードライバを設定したら、config/scout.php設定ファイルのqueueオプションの値をtrueに設定します。

'queue' => true,

queueオプションをfalseに設定している場合でも、AlgoliaやMeilisearchなど、一部のScoutドライバは常に非同期でレコードをインデックスしていることを覚えておくことが重要です。つまり、Laravelアプリケーション内でインデックス操作が完了しても、検索エンジン自体には新しいレコードや更新されたレコードがすぐに反映されない可能性があります。

設定

モデルインデックスの設定

各Eloquentモデルは、検索可能レコードすべてを含む、指定された検索「インデックス」と同期されます。言い換えれば、各インデックスはMySQLテーブルのようなものであると、考えられます。デフォルトで、各モデルはそのモデルの典型的な「テーブル」名に一致するインデックスへ保存されます。通常、モデルの複数形ですが、モデルのsearchableAsメソッドをオーバーライドすることで、このモデルのインデックスを自由にカスタマイズ可能です。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    /**
     * モデルに関連付けられているインデックスの名前を取得
     *
     * @return string
     */
    public function searchableAs()
    {
        return 'posts_index';
    }
}

検索可能データの設定

デフォルトでは、指定されたモデルのtoArray形態全体が、検索インデックスへ保存されます。検索インデックスと同期するデータをカスタマイズしたい場合は、そのモデルのtoSearchableArrayメソッドをオーバーライドできます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    /**
     * モデルのインデックス可能なデータ配列の取得
     *
     * @return array
     */
    public function toSearchableArray()
    {
        $array = $this->toArray();

        // データ配列をカスタマイズ

        return $array;
    }
}

モデルIDの設定

デフォルトでは、Scoutはモデルの主キーを、検索インデックスに保存されているモデルの一意のID/キーとして使用します。この動作をカスタマイズする必要がある場合は、モデルのgetScoutKeyメソッドとgetScoutKeyNameメソッドをオーバーライドできます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class User extends Model
{
    use Searchable;

    /**
     * モデルのインデックスに使用する値の取得
     *
     * @return mixed
     */
    public function getScoutKey()
    {
        return $this->email;
    }

    /**
     * モデルのインデックスに使用するキー名の取得
     *
     * @return mixed
     */
    public function getScoutKeyName()
    {
        return 'email';
    }
}

ユーザーの識別

Scoutを使用すると、Algoliaを使用するときにユーザーを自動識別することもできます。認証済みユーザーを検索操作に関連付けると、Algoliaのダッシュボード内で検索分析を表示するときに役立つ場合があります。アプリケーションの.envファイルでSCOUT_IDENTIFY環境変数をtrueとして定義することにより、ユーザー識別を有効にできます。

SCOUT_IDENTIFY=true

この機能を有効にすると、リクエストのIPアドレスと認証済みユーザーのプライマリ識別子もAlgoliaに渡されるため、これらのデータはそのユーザーが行った検索リクエストへ関連付けられます。

データベース/コレクションエンジン

データベースエンジン

Note: The database engine currently supports MySQL and PostgreSQL.

中規模のデータベースとやり取りするしたり、作業負荷が軽いアプリケーションでは、Scoutの「データベース」エンジンで始めるのが便利でしょう。データベースエンジンは、既存のデータベースから結果をフィルタリングする際に、「where like」句と全文インデックスを使用して、クエリの検索結果を決定します。

データベースエンジンを使うには、SCOUT_DRIVER環境変数の値をdatabaseに設定するか、アプリケーションのscout設定ファイルに直接databaseドライバを指定してください。

SCOUT_DRIVER=database

データベースエンジンを好みのドライバに指定したら、検索可能なデータの設定を行う必要があります。次に、モデルに対して検索クエリの実行を開始します。データベースエンジンを使用する場合、AlgoliaやMeiliSearchのように、検索エンジンのインデックス作成は必要ありません。

データベース検索戦略のカスタマイズ

デフォルトでは、データベースエンジンは、検索可能として設定したすべてのモデル属性に対して、"WHERE LIKE"クエリを実行します。しかし、この方法では状況により、パフォーマンス低下を招くことがあります。そこで、データベースエンジンの検索戦略を設定することで、指定した一部のカラムでは全文検索クエリを利用し、あるいは文字列全体を検索(%example%)するのではなく、前方一致で検索する(example%)"WHERE LIKE"制約のみを利用できます。

こうした振る舞いを定義するには、モデルのtoSearchableArrayメソッドでPHP属性を割り付けてください。追加の検索戦略動作を割り当てていないカラムには、デフォルトの"WHERE LIKE"戦略を使い続けます。

use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;

/**
 * モデルに対するインデックス可能なデータの配列を取得
 *
 * @return array
 */
#[SearchUsingPrefix(['id', 'email'])]
#[SearchUsingFullText(['bio'])]
public function toSearchableArray()
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'bio' => $this->bio,
    ];
}

Note: あるカラムへ、フルテキストクエリ制約の使用を指定する前に、そのカラムにフルテキストインデックスを割り当て済みであることを確認してください。

コレクションエンジン

ローカル開発時には、AlgoliaやMeiliSearchの検索エンジンを自由に使用することができますが、「コレクション(collection)」エンジンでスタートした方が便利な場合もあります。コレクション・エンジンは、既存データベースからの結果に対して、"where"節とコレクション・フィルタリングを用いて、クエリに該当する検索結果を決定します。このエンジンを使用する場合、Searchableモデルをインデックス化する必要はなく、シンプルにローカル・データベースから検索します。

コレクションエンジンを使用するには,環境変数SCOUT_DRIVERの値をcollectionに設定するか,アプリケーションのscout設定ファイルでcollectionドライバを直接指定します。

SCOUT_DRIVER=collection

コレクションドライバを使用ドライバに指定したら、モデルに対して検索クエリの実行を開始できます。コレクションエンジンを使用する場合、AlgoliaやMeiliSearchのインデックスのシードに必要なインデックス作成などの検索エンジンのインデックス作成は不要です。

データベースエンジンとの違い

一見すると、「データベース」エンジンと「コレクション」エンジンはかなり似ています。どちらも、あなたのデータベースと直接やりとりして、検索結果を取得します。しかし、コレクションエンジンはフルテキストインデックスやLIKE句を利用して一致レコードを探し出しません。その代わりに、可能性があるすべてのレコードを取得し、LaravelのStr::isヘルパを使い、モデルの属性値内に検索文字列が存在しているか判断します。

コレクションエンジンは、Laravelがサポートする(SQLiteやSQL Serverを含む)すべてのリレーショナルデータベースで動作するため、最も使いやすい検索エンジンですが、Scoutのデータベースエンジンに比べると効率は落ちます。

インデックス

バッチ取り込み

Scoutを既存のプロジェクトにインストールする場合は、インデックスへインポートする必要のあるデータベースレコードがすでに存在している可能性があります。Scoutは、既存のすべてのレコードを検索インデックスにインポートするために使用できるscout:import Artisanコマンドを提供しています。

php artisan scout:import "App\Models\Post"

flushコマンドは、検索インデックスからモデルの全レコードを削除するために使用します。

php artisan scout:flush "App\Models\Post"

インポートクエリの変更

バッチインポートで全モデルを取得するために使用されるクエリを変更する場合は、モデルにmakeAllSearchableUsingメソッドを定義してください。これはモデルをインポートする前に、必要になる可能性のあるイエガーリレーションの読み込みを追加するのに最適な場所です。

/**
 * 全モデルを検索可能にするときの、モデル取得に使用するクエリを変更
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $query
 * @return \Illuminate\Database\Eloquent\Builder
 */
protected function makeAllSearchableUsing($query)
{
    return $query->with('author');
}

レコード追加

モデルにLaravel\Scout\Searchableトレイトを追加したら、モデルインスタンスを保存または作成するだけで、検索インデックスに自動的に追加されます。キューを使用するようにScoutを設定した場合、この操作はキューワーカによってバックグラウンドで実行されます。

use App\Models\Order;

$order = new Order;

// ...

$order->save();

クエリによるレコード追加

Eloquentクエリを介してモデルのコレクションを検索インデックスに追加する場合は、searchableメソッドをEloquentクエリにチェーンできます。searchableメソッドはクエリの結果をチャンクし、レコードを検索インデックスに追加します。繰り返しますが、キューを使用するようにScoutを設定した場合、すべてのチャンクはキューワーカによってバックグラウンドでインポートされます。

use App\Models\Order;

Order::where('price', '>', 100)->searchable();

Eloquentリレーションインスタンスで searchableメソッドを呼び出すこともできます。

$user->orders()->searchable();

または、メモリ内にEloquentモデルのコレクションが既にある場合は、コレクションインスタンスでsearchableメソッドを呼び出して、モデルインスタンスを対応するインデックスに追加できます。

$orders->searchable();

Tip!! searchableメソッドは、「アップサート(upsert)」操作と考えるられます。つまり、モデルレコードがすでにインデックスに含まれている場合は、更新され、検索インデックスに存在しない場合は追加されます。

レコード更新

検索可能モデルを更新するには、モデルインスタンスのプロパティを更新し、saveでモデルをデータベースへ保存します。Scoutは自動的に変更を検索インデックスへ保存します。

use App\Models\Order;

$order = Order::find(1);

// 注文を更新…

$order->save();

Eloquentクエリインスタンスでsearchableメソッドを呼び出して、モデルのコレクションを更新することもできます。モデルが検索インデックスに存在しない場合は作成されます。

Order::where('price', '>', 100)->searchable();

リレーションシップ内のすべてのモデルの検索インデックスレコードを更新する場合は、リレーションシップインスタンスでsearchableを呼び出すことができます。

$user->orders()->searchable();

または、メモリ内にEloquentモデルのコレクションが既にある場合は、コレクションインスタンスでsearchableメソッドを呼び出して、対応するインデックスのモデルインスタンスを更新できます。

$orders->searchable();

レコード削除

インデックスからレコードを削除するには、データベースからモデルをdeleteするだけです。これは、ソフト削除モデルを使用している場合でも実行できます。

use App\Models\Order;

$order = Order::find(1);

$order->delete();

レコードを削除する前にモデルを取得したくない場合は、Eloquentクエリインスタンスでunsearchableメソッドを使用できます。

Order::where('price', '>', 100)->unsearchable();

リレーション内のすべてのモデルの検索インデックスレコードを削除する場合は、リレーションインスタンスでunsearchableを呼び出してください。

$user->orders()->unsearchable();

または、メモリ内にEloquentモデルのコレクションが既にある場合は、コレクションインスタンスでunsearchableメソッドを呼び出して、対応するインデックスからモデルインスタンスを削除できます。

$orders->unsearchable();

インデックスの一時停止

モデルデータを検索インデックスに同期せずに、モデルに対してEloquent操作のバッチを実行する必要がある場合があります。これは、withoutSyncingToSearchメソッドを使用して行うことができます。このメソッドは、すぐに実行される単一のクロージャを引数に取ります。クロージャ内で発行するモデル操作は、モデルのインデックスに同期されません。

use App\Models\Order;

Order::withoutSyncingToSearch(function () {
    // モデルアクションを実行
});

条件付き検索可能モデルインスタンス

特定の条件下でのみ、モデルを検索可能にする必要がある場合も起きるでしょう。たとえば、App\Models\Postモデルが、"draft"か"published"の2つのうち、どちらか1つの状態を取ると想像してください。「公開済み:published」のポストのみ検索可能にする必要があります。これを実現するには、モデルにshouldBeSearchableメソッドを定義してください。

/**
 * モデルを検索可能にする判定
 *
 * @return bool
 */
public function shouldBeSearchable()
{
    return $this->isPublished();
}

shouldBeSearchableメソッドは、saveおよびcreateメソッド、クエリ、またはリレーションを通してモデルを操作する場合にのみ適用されます。searchableメソッドを使用してモデルまたはコレクションを直接検索可能にすると、shouldBeSearchableメソッドの結果が上書きされます。

Note: 検索可能なデータは常にデータベースへ保存されるため、shouldBeSearchableメソッドはScoutの「データベース」エンジンを使用する際には適用されません。データベースエンジン使用時に同様の動作をさせるには、代わりにWHERE句を使用する必要があります。

検索

searchメソッドにより、モデルの検索を開始しましょう。searchメソッドはモデルを検索するために使用する文字列だけを引数に指定します。getメソッドを検索クエリにチェーンし、指定した検索クエリに一致するEloquentモデルを取得できます。

use App\Models\Order;

$orders = Order::search('Star Trek')->get();

Scoutの検索ではEloquentモデルのコレクションが返されるため、ルートやコントローラから直接結果を返せば、自動的にJSONへ変換されます。

use App\Models\Order;
use Illuminate\Http\Request;

Route::get('/search', function (Request $request) {
    return Order::search($request->search)->get();
});

Eloquentモデルへ変換する前に素の検索結果を取得したい場合は、rawメソッドを使用できます。

$orders = Order::search('Star Trek')->raw();

カスタムインデックス

検索クエリは通常、モデルのsearchableAsメソッドで指定するインデックスに対して実行されます。ただし、withinメソッドを使用して、代わりに検索する必要があるカスタムインデックスを指定できます。

$orders = Order::search('Star Trek')->paginate();
    ->within('tv_shows_popularity_desc')
    ->get();

Where節

Scoutを使用すると、検索クエリに単純な「where」節を追加できます。現在、これらの節は基本的な数値の同等性チェックのみをサポートしており、主に所有者IDによる検索クエリのスコープに役立ちます。

use App\Models\Order;

$orders = Order::search('Star Trek')->where('user_id', 1)->get();

whereInメソッドを使用すると、指定された値の集合に対して結果を制約できます。

$orders = Order::search('Star Trek')->whereIn(
    'status', ['paid', 'open']
)->get();

検索インデックスはリレーショナルデータベースではないため、より高度な"where"節は現在サポートしていません。

ペジネーション

モデルのコレクションを取得することに加えて、paginateメソッドを使用して検索結果をページ分割することができます。このメソッドは、従来のEloquentクエリをペジネーションする場合と同じように、Illuminate\Pagination\LengthAwarePaginatorインスタンスを返します。

use App\Models\Order;

$orders = Order::search('Star Trek')->paginate();

paginateメソッドの第1引数として、各ページごとに取得したいモデル数を指定します。

$orders = Order::search('Star Trek')->paginate(15);

結果が取得できたら、通常のEloquentクエリのペジネーションと同様に、結果を表示し、Bladeを使用してページリンクをレンダできます。

<div class="container">
    @foreach ($orders as $order)
        {{ $order->price }}
    @endforeach
</div>

{{ $orders->links() }}

もちろん、ペジネーションの結果をJSONとして取得したい場合は、ルートまたはコントローラから直接ペジネータインスタンスを返すことができます。

use App\Models\Order;
use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    return Order::search($request->input('query'))->paginate(15);
});

ソフトデリート

インデックス付きのモデルがソフトデリートされ、ソフトデリート済みのモデルをサーチする必要がある場合、config/scout.php設定ファイルのsoft_deleteオプションをtrueに設定してください。

'soft_delete' => true,

この設定オプションをtrueにすると、Scoutは検索インデックスからソフトデリートされたモデルを削除しません。代わりに、インデックスされたレコードへ、隠し__soft_deleted属性をセットします。これにより、検索時にソフトデリート済みレコードを取得するために、withTrashedonlyTrashedメソッドがつかえます。

use App\Models\Order;

// 結果の取得時に、削除済みレコードも含める
$orders = Order::search('Star Trek')->withTrashed()->get();

// 結果の取得時に、削除済みレコードのみを対象とする
$orders = Order::search('Star Trek')->onlyTrashed()->get();

Tip!! ソフトデリートされたモデルが、forceDeleteにより完全に削除されると、Scoutは自動的に検索インデックスから削除します。

エンジンの検索のカスタマイズ

エンジンの検索動作の高度なカスタマイズを実行する必要がある場合は、 searchメソッドの2番目の引数にクロージャを渡せます。たとえば、このコールバックを使用して、検索クエリがAlgoliaに渡される前に、地理的位置データを検索オプションに追加できます。

use Algolia\AlgoliaSearch\SearchIndex;
use App\Models\Order;

Order::search(
    'Star Trek',
    function (SearchIndex $algolia, string $query, array $options) {
        $options['body']['query']['bool']['filter']['geo_distance'] = [
            'distance' => '1000km',
            'location' => ['lat' => 36, 'lon' => 111],
        ];

        return $algolia->search($query, $options);
    }
)->get();

Customizing The Eloquent Results Query

Scoutがアプリケーションの検索エンジンからマッチするEloquentモデルのリストを取得した後、Eloquentを使用して主キーでマッチするすべてのモデルを取得しようとします。このクエリはqueryメソッドを呼び出し、カスタマイズできます。queryメソッドは、Eloquentクエリビルダのインスタンスを引数とするクロージャを受け取ります。

use App\Models\Order;

$orders = Order::search('Star Trek')
    ->query(fn ($query) => $query->with('invoices'))
    ->get();

このコールバックは、関連モデルがアプリケーションの検索エンジンからあらかじめ取得された後に呼び出されるので、queryメソッドを結果の「フィルタリング」に使用しないでください。代わりに、Scout WHERE句を使用してください。

カスタムエンジン

エンジンのプログラミング

組み込みのScout検索エンジンがニーズに合わない場合、独自のカスタムエンジンを書き、Scoutへ登録してください。エンジンは、Laravel\Scout\Engines\Engine抽象クラスを拡張してください。この抽象クラスは、カスタムエンジンが実装する必要のある、8つのメソッドを持っています。

use Laravel\Scout\Builder;

abstract public function update($models);
abstract public function delete($models);
abstract public function search(Builder $builder);
abstract public function paginate(Builder $builder, $perPage, $page);
abstract public function mapIds($results);
abstract public function map(Builder $builder, $results, $model);
abstract public function getTotalCount($results);
abstract public function flush($model);

これらのメソッドの実装をレビューするために、Laravel\Scout\Engines\AlgoliaEngineクラスが役に立つでしょう。このクラスは独自エンジンで、各メソッドをどのように実装すればよいかの、良い取り掛かりになるでしょう。

エンジンの登録

カスタムエンジンを作成したら、Scoutエンジンマネージャのextendメソッドを使用してScoutへ登録します。Scoutのエンジンマネージャは、Laravelサービスコンテナが依存解決できます。App\Providers\AppServiceProviderクラスのbootメソッドまたはアプリケーションが使用している他のサービスプロバイダからextendメソッドを呼び出せます。

use App\ScoutExtensions\MySqlSearchEngine
use Laravel\Scout\EngineManager;

/**
 * 全アプリケーションサービスの初期起動処理
 *
 * @return void
 */
public function boot()
{
    resolve(EngineManager::class)->extend('mysql', function () {
        return new MySqlSearchEngine;
    });
}

エンジンを登録したら、アプリケーションのconfig/scout.php設定ファイルでデフォルトのスカウトdriverとして指定できます。

'driver' => 'mysql',

ビルダマクロ

カスタムのScout検索ビルダメソッドを定義する場合は、Laravel\Scout\Builderクラスでmacroメソッドが使用できます。通常、「マクロ」はサービスプロバイダbootメソッド内で定義する必要があります。

use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
use Laravel\Scout\Builder;

/**
 * 全アプリケーションサービスの初期起動処理
 *
 * @return void
 */
public function boot()
{
    Builder::macro('count', function () {
        return $this->engine->getTotalCount(
            $this->engine()->search($this)
        );
    });
}

macro関数は、最初の引数にマクロ名、2番目の引数にクロージャを取ります。マクロのクロージャは、Laravel\Scout\Builder実装からマクロ名を呼び出すときに実行されます。

use App\Models\Order;

Order::search('Star Trek')->count();

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる