はじめに

Gutenbergエディターで長文を書く際、章ごとにグループ化すると並び替えや移動が便利になります。
しかし、そのままではリストビューが「グループ」という表示で埋まり、目的の章を見つけにくくなります。
そこで、グループ直下にある見出しテキストを自動取得し、グループ名として反映します。
この記事では、この方法について説明します。
目的
長文記事を効率よく編集するために「章ごと」にまとめてグループ化する運用では、デフォルトのままでは主に2つの課題があります。
- リストビュー上でどのグループが何の章(見出し)か識別できない
章ごとにグループ化しても、リストビュー上には「グループ」とだけ並ぶため、目的の章を探すために毎回アコーディオンを開閉して中身を確認しなければなりません。
- 手動での名前付け作業が面倒で持続しない
リストビューから「グループ名」を1つずつ手動設定することは可能ですが、執筆のたびに章の名前を入力するのは手間がかかります。
今回紹介するコードを導入すれば、グループ直下1階層目の「見出し」テキストを自動検出してグループ名へ自動割り当てされるため、手動入力の手間なくリストビューが一目で識別できるようになります。
また、すでに手動設定した名前はそのまま保持しつつ、タイピングの手が止まったタイミングで更新処理を動かすため、エディターの動作を重くすることなく快適に執筆へ集中できます。
完成イメージ

実装手順
以下のコードをfunctions.phpに追加します。
// 対象の処理を実行
add_action('init', function () {
if (!isset($_GET['auto_group_naming'])) {
return;
}
// 権限チェック
if (!is_user_logged_in() || !current_user_can('edit_posts')) {
return;
}
// nonceチェック
if (!isset($_GET['auto_group_naming_nonce']) || !wp_verify_nonce($_GET['auto_group_naming_nonce'], 'auto_group_naming_action')) {
wp_die('セキュリティ検証に失敗しました。');
}
// 現在の状態を取得
$user_id = get_current_user_id();
$status = (bool) get_user_meta($user_id, 'my_auto_group_naming', true);
update_user_meta($user_id, 'my_auto_group_naming', !$status);
// 現在のURLから不要なパラメータを削除
$current_url = (is_ssl() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$redirect_url = remove_query_arg(
['auto_group_naming', 'auto_group_naming_nonce'],
$current_url
);
wp_safe_redirect($redirect_url);
exit;
});
// ツールバーにメニューを追加
add_action('admin_bar_menu', function ($wp_admin_bar) {
if (!is_admin_bar_showing() || !current_user_can('edit_posts')) {
return;
}
// 現在の自動命名状態を取得
$user_id = get_current_user_id();
$status = (bool) get_user_meta($user_id, 'my_auto_group_naming', true);
// 表示ラベルと色を設定
$label = $status ? 'グループ自動命名:ON' : 'グループ自動命名:OFF';
$color = $status ? 'color: #00e676;' : 'color: #fff;';
// 現在のURLにnonceを付与
$current_url = (is_ssl() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$toggle_url = wp_nonce_url(
add_query_arg('auto_group_naming', '1', $current_url),
'auto_group_naming_action',
'auto_group_naming_nonce'
);
// ツールバーにメニューを追加
$wp_admin_bar->add_node([
'id' => 'my_auto_group_naming_switch',
'title' => '<span style="' . esc_attr($color) . '">' . esc_html($label) . '</span>',
'href' => $toggle_url,
'meta' => [
'title' => 'グループブロック自動命名モード切り替え',
'class' => 'my-auto-group-naming-switch',
],
]);
}, 100);
// エディター用JS・CSSを追加
add_action('enqueue_block_editor_assets', function () {
$user_id = get_current_user_id();
$status = (bool) get_user_meta($user_id, 'my_auto_group_naming', true);
ob_start();
?>
(function (wp) {
'use strict';
if (!wp?.data || !wp?.domReady) {
return;
}
const { select, dispatch, subscribe } = wp.data;
const autoNamingEnabled = <?php echo $status ? 'true' : 'false'; ?>;
/**
* 直下の見出しテキストを取得
*
* @param {Array} blocks グループ直下の子ブロック配列
* @return {string} 見出しのプレーンテキスト
*/
function getDirectHeadingText(blocks = []) {
for (const block of blocks) {
if (block.name === 'core/heading') {
const content = block.attributes?.content || '';
const text = content
.replace(/<[^>]*>?/gm, '')
.replace(/\s+/g, ' ')
.trim();
if (text) {
return text;
}
}
}
return '';
}
/**
* ブロックツリーを走査してグループ名を設定
*
* @param {Array} blocks 走査対象のブロック配列
* @return {void}
*/
function processBlocks(blocks = []) {
const { updateBlockAttributes } = dispatch('core/block-editor');
for (const block of blocks) {
if (block.name === 'core/group') {
const currentName = block.attributes?.metadata?.name;
if (!currentName?.trim()) {
const headingText = getDirectHeadingText(block.innerBlocks);
if (headingText) {
updateBlockAttributes(block.clientId, {
metadata: {
...block.attributes?.metadata,
name: headingText
}
});
}
}
}
if (block.innerBlocks?.length) {
processBlocks(block.innerBlocks);
}
}
}
/**
* ツールバーの切り替えURLを現在のURLに更新
*
* @return {void}
*/
function updateToggleUrl() {
const link = document.querySelector(
'#wp-admin-bar-my_auto_group_naming_switch > a'
);
if (!link) {
return;
}
// PHPで生成されたURLからnonceを取得
const toggleUrl = new URL(link.href, window.location.origin);
const nonce = toggleUrl.searchParams.get('auto_group_naming_nonce');
if (!nonce) {
return;
}
// 現在のブラウザURLを取得
const currentUrl = new URL(window.location.href);
// 自動命名用パラメータを一旦削除
currentUrl.searchParams.delete('auto_group_naming');
currentUrl.searchParams.delete('auto_group_naming_nonce');
// 現在のURLに自動命名用パラメータとnonceを追加
currentUrl.searchParams.set('auto_group_naming', '1');
currentUrl.searchParams.set('auto_group_naming_nonce', nonce);
link.href = currentUrl.toString();
}
wp.domReady(() => {
let isUpdating = false;
let timerId = null;
updateToggleUrl();
subscribe(() => {
// URL更新はOFFでも実行
updateToggleUrl();
// 自動命名処理はONの場合のみ実行
if (!autoNamingEnabled || isUpdating) {
return;
}
clearTimeout(timerId);
timerId = setTimeout(() => {
const blocks = select('core/block-editor')?.getBlocks();
if (!blocks?.length) {
return;
}
isUpdating = true;
processBlocks(blocks);
Promise.resolve().then(() => {
isUpdating = false;
});
}, 500);
});
});
})(window.wp);
<?php
wp_add_inline_script('wp-block-editor', ob_get_clean());
});
さいごに
今回紹介した方法を使えば、「グループ」ブロック直下の「見出し」ブロックのテキストがそのまま「グループ名」として反映されるため、章単位での並び替えや目的のセクションの特定がスムーズに行えるようになります。
レイアウト調整目的ではなく、文章構造の管理や編集作業の効率化として非常に有効です。

