「グループ」ブロックの名前に直下の「見出し」ブロックのテキストを自動設定する方法

Cocoonカスタマイズ集
この記事は約11分で読めます。
記事内に広告が含まれています。

はじめに

Gutenbergエディ-ター

Gutenbergエディターで長文を書く際、章ごとにグループ化すると並び替えや移動が便利になります。

しかし、そのままではリストビューが「グループ」という表示で埋まり、目的の章を見つけにくくなります。

そこで、グループ直下にある見出しテキストを自動取得し、グループ名として反映します。

この記事では、この方法について説明します。

目的

長文記事を効率よく編集するために「章ごと」にまとめてグループ化する運用では、デフォルトのままでは主に2つの課題があります。

  1. リストビュー上でどのグループが何の章(見出し)か識別できない
    章ごとにグループ化しても、リストビュー上には「グループ」とだけ並ぶため、目的の章を探すために毎回アコーディオンを開閉して中身を確認しなければなりません。
  1. 手動での名前付け作業が面倒で持続しない
    リストビューから「グループ名」を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());
});

さいごに

今回紹介した方法を使えば、「グループ」ブロック直下の「見出し」ブロックのテキストがそのまま「グループ名」として反映されるため、章単位での並び替えや目的のセクションの特定がスムーズに行えるようになります。
レイアウト調整目的ではなく、文章構造の管理や編集作業の効率化として非常に有効です。

タイトルとURLをコピーしました