コード閲覧機能ブロックの作成

制作背景

このサイトではWordPressのテーマやWeb制作の学習過程を記事としても記録しています。今後類似する部分を制作する際にもスムーズに活用できるよう、HTML・CSS・JavaScript・PHPといったコードも掲載したいと思うようになりました。

そこで、必要なときだけコードを開いて確認できる「コード閲覧ブロック」を自作することにしました。開閉式にすることで、コード量が多くなった場合でも記事本文が長くなりすぎるのを抑えられます。

今回はGutenbergのブロック開発について学ぶことも兼ねて、実際の記事で使いながら機能を作っていきたいと思います。

実装の流れ

① ブロックの登録

まず、WordPressに「コード閲覧ブロック」を認識させるため、ブロックの登録を行います。今回は、ブロックに関するPHPの処理を分離するため、テーマ内に inc/code-viewer.php を作成しました。

<?php

// コード閲覧ブロックの登録

function atsu_register_code_viewer_block() {
    register_block_type(
        'atsu-portfolio/code-viewer'
    );
}

add_action( 'init', 'atsu_register_code_viewer_block' );

作成したPHPファイルを functions.php から読み込みます。

// 記事ページ・コード閲覧ブロックの設定

require_once get_theme_file_path( '/inc/code-viewer.php' );

これでWordPress側へのブロック登録は完了です。ただし、この段階ではGutenbergのブロック一覧から「コード閲覧」を追加することはできません。

そこで次に、Gutenbergの編集画面でブロックとして認識させるため、JavaScript側の登録を行います。

② Gutenbergへの登録

WordPress側でブロックを登録しただけでは、Gutenbergのブロック一覧から「コード閲覧」を追加できません。そこで、エディター側のJavaScriptでもブロックを登録します。

assets/js/code-viewer.js を作成し、registerBlockType() を使ってブロックを登録しました。

( function ( blocks, element, i18n ) {
    const { createElement } = element;
    const { __ } = i18n;

    blocks.registerBlockType( 'atsu-portfolio/code-viewer', {
        title: __( 'コード閲覧', 'atsu-portfolio' ),
        icon: 'editor-code',
        category: 'text',

        edit: function () {
            return createElement(
                'p',
                {},
                __( 'コード閲覧ブロック', 'atsu-portfolio' )
            );
        },

        save: function () {
            return null;
        },
    } );
} )( window.wp.blocks, window.wp.element, window.wp.i18n );

また、作成したJavaScriptファイルをGutenbergの編集画面で読み込むため、functions.php にも設定を追加します。

wp_enqueue_script(
    'atsu-code-viewer-editor',
    get_theme_file_uri(
        'assets/js/code-viewer.js'
    ),
    array(
        'wp-blocks',
        'wp-element',
        'wp-i18n',
    ),
    filemtime(
        get_theme_file_path(
            'assets/js/code-viewer.js'
        )
    ),
    true
);

これでGutenbergのブロック検索から「コード閲覧」を検索すると、作成したブロックが表示されるようになりました。まずはブロックを登録し、Gutenbergから呼び出せる状態を作るところまでを確認しました。

HTML
/* 記事ブロックパーツ専用CSSファイル */

/* 記事中画像(例外) */
.logo-intro-img{
    padding:40px;
    border:1px solid var(--line-black-color);
    background-color: #fff;
    box-shadow: rgba(255, 255, 255, 0.12) 0px 1px 1px 0px inset, rgba(50, 50, 93, 0.12) 0px 50px 100px -20px, rgba(0, 0, 0, 0.12) 0px 30px 60px -30px;
}

.site-image{
    border:1px solid var(--line-black-color);
    box-shadow: rgba(255, 255, 255, 0.12) 0px 1px 1px 0px inset, rgba(50, 50, 93, 0.12) 0px 50px 100px -20px, rgba(0, 0, 0, 0.12) 0px 30px 60px -30px;
}

/* 画像にボーダーとシャドウを入れたい場合クラス追加で実装 */
.image-border-shadow{
    box-shadow: rgba(0, 0, 0, 0.08) 0px 25px 20px -20px;
    border: 1px solid var(--line-black-color) !important;
}


/* 目次ブロック */
.table-of-contents{
    margin-top: 72px !important;
    padding: 64px 16px 48px;
    border: 1px solid var(--line-black-color);
}
/* 見出し部分 */
.toc-title{
    display:flex;
    align-items:center;
    font-size: 2.4em !important;
    width: fit-content;
    margin: 0 auto 48px;
    line-height: 1;
}
.toc-title span{
    order: 1;
    font-weight: 400;
}
.toc-title::before{
   order: 2;
    content: '';
    display: block;
    width: 72px;
    height: 1px;
    background: var(--line-black-color);
    transform: rotate(-60deg);
    position: relative;
    top: 4px;
    left: -8px;
}
.toc-title::after{
    order: 3;
    content: '目次';
    display: block;
    font-size: 1.12rem !important;
    opacity: 0.4;
    position: relative;
    top: 5px;
    left: -14px;
}
/* 目次のリスト部分 */
.toc-block{
    list-style: none;
    /* 番号のカウンター */
    counter-reset: toc-number;
}
/* h2 */
.toc-block > li {
    counter-increment: toc-number;
    position: relative;
}
/* 数字部分 */
.toc-block > li::before {
    content: counter(toc-number, decimal-leading-zero);
    font-size: 2.4em;
    position: absolute;
    top: 50%;
    left: 0px;
    transform: translateY(-50%);
    color: rgba(58, 158, 158, 0.32);
}
/* 数字と見出し間の横棒 */
.toc-block > li::after{
    content: '';
    display: block;
    width: 1px;
    height: calc(100% - 32px);
    border-left: 1px dashed var(--line-black-color);
    position: absolute;
    left: 66px;
    top: 50%;
    transform: translateY(-50%);
    opacity: 0.64;
}
/* 各見出し群 */
.toc-block > li{
    padding: 32px 0 24px 96px;
}
/* リスト最初以外のもには上線を指定 */
.toc-block > li:not(:first-child){
    border-top: 1px solid rgba(0,0,0, 0.16);
}
/* h2 */
.toc-h2{
    font-size: 0.96em;
    font-weight: 700 !important;
}
/* h3 */
.toc-block > li > ol{
    padding: 4px 0 0 24px;
    /* border-left: 1px solid var(--line-black-color); */
}
.toc-h3{
    display: block;
    padding: 4px 0 !important;
    font-weight: 500;
    font-size:0.88em ;
    position: relative;
}
/* h3横線 */
.toc-h3::before{
    content: '';
    display: block;
    width: 8px;
    height: 1px;
    background: var(--line-black-color);
    position: absolute;
    left: -24px;
    top: 50%;
    transform: translateY(-50%);
}



/* スクロールした際の位置調整 */
.article h2{
    scroll-margin-top: 48px;
}
.article h3 {
    scroll-margin-top: 32px;
}


/* リストブロック(通常) */
.article .wp-block-post-content ul.wp-block-list{
    padding:32px 0px;
    border-top:1px solid var(--line-black-color) ;
    border-bottom: 1px solid var(--line-black-color);
}
.article .wp-block-post-content ul.wp-block-list li{
	position: relative;
	padding-left: 1.68em;
    font-weight: 600;
    padding: 0 0 0 64px;
    margin-left: 16px;
    margin-right: 16px;
}
.article .wp-block-post-content ul.wp-block-list li:not(:first-child){
    margin-top: 24px;
    padding-top: 24px;
    border-top:1px dashed var(--line-black-color) ;
}
/* チェックアイコン */
.article .wp-block-post-content ul.wp-block-list li::before{
	content: "";
	position: absolute;
	left: 16px;
	top: calc(50% - 8px);
	width: 1em;
	height: 1em;
}
.article .wp-block-post-content ul.wp-block-list li:not(:first-child)::before{
    top: calc(50% + 3px);
}
.article .wp-block-post-content ul.wp-block-list li::before{
    background-image: url('/wp-content/themes/atsu-portfolio-theme/assets/images/icons/check-light.svg');
	background-repeat: no-repeat;
	background-position: center;
	background-size: contain;
}


/* リストブロック(数字) */
.article .wp-block-post-content ol.wp-block-list{
    counter-reset: list-number;
    padding:48px 0px;
    border-top:1px solid var(--line-black-color) ;
    border-bottom: 1px solid var(--line-black-color);
    display: flex;
    flex-direction: column;
    gap: 60px;
    position: relative;
}
.article .wp-block-post-content ol.wp-block-list li {
    margin-left: 16px;
    margin-right: 16px;
	position: relative;
	padding-left: 1.68em;
    font-weight: 600;
    padding: 0px 0 0px 80px;
}
/* 数字アイコン */
.article .wp-block-post-content ol.wp-block-list li{
	counter-increment: list-number;
}
.article .wp-block-post-content ol.wp-block-list li::before{
    content: counter(list-number);
    position: absolute;
    left: 0;
    top: 50%;
    width: 60px;
    height: 60px;
    color: #333;
    font-size: 1.2em;
    font-weight: 400 !important;

    /* 数字の位置調整 */
    padding-left: 1px;
    display: flex;
    align-items: center;
    justify-content: center;
    transform: translateY(-50%);
    font-weight: 700;
    border: 1px solid var(--line-black-color);
    border-radius: 50%;
    box-sizing: border-box;
    background: #fff;
    z-index: 1;
}
/* リスト間の縦線 */
.article .wp-block-post-content ol.wp-block-list li:not(:first-child)::after {
    content: "";
    position: absolute;
    left: 30px;
    bottom: 50%;
    width: 1px;
    height: 100px;
    background: var(--line-black-color);
    z-index: 0;
}


/*
定義リストブロック
※追加ブロックで定義と入力すればテンプレートが出てくる。
*/
.articleDefinitionBlock{
    display: grid;
    grid-template-columns: 38% 62%;
    gap: 0;
}
.articleDefinitionBlock dt{
    margin: 0;
    padding: 24px 16px;
    border-top:1px solid #c3c3c3 ;
    border-right:1px solid #c3c3c3 ;
    background-color: #f4f4f4;
    text-align: left;
    font-weight: 500;
    display: flex;
    align-items: center; /* 縦中央 */
}
.articleDefinitionBlock dd{
    margin: 0;
    padding: 24px 16px;
    border-top:1px solid #c3c3c3 ;
    display: flex;
    align-items: center; /* 縦中央 */
}
.articleDefinitionBlock dt:last-of-type,
.articleDefinitionBlock dd:last-of-type{
    border-bottom:1px solid #c3c3c3 ;
}


/* 参照・引用ブロック */
.article .wp-block-quote{
    margin: 80px 0 120px;
    padding: 0;
    position: relative;
}
.article .wp-block-quote::before,
.article .wp-block-quote::after{
    position: absolute;
    width: 4.8em;
	height: 4.8em;
	background-repeat: no-repeat;
	background-position: center;
	background-size: contain;
    display: block;
    content: "";
    opacity: 0.16;
}
.article .wp-block-quote::before{
    top: -34px;
    left:0;
    background-image: url('/wp-content/themes/atsu-portfolio-theme/assets/images/icons/double-quotes-l.svg');
}
.article .wp-block-quote::after{
    bottom: -34px;
    right: 0px;
    background-image: url('/wp-content/themes/atsu-portfolio-theme/assets/images/icons/double-quotes-r.svg');
}
.article .wp-block-quote div{
    position: relative;
    padding: 48px 32px 56px;
    line-height: 1.8;
    font-style: italic;
    font-weight: 600;
    text-align: center;
}
.article .wp-block-quote div::before,
.article .wp-block-quote div::after{
    content: "";
    position: absolute;
    left: 0;
    width: 100%;
    height: 1px;
}
.article .wp-block-quote div::before{
    top: 0;
    background: linear-gradient(
        to right,
        transparent 24%,
        var(--line-black-color) 24%
    );
}
.article .wp-block-quote div::after{
    bottom: 0;
    background: linear-gradient(
        to left,
        transparent 24%,
        var(--line-black-color) 24%
    );
}
.article .wp-block-quote div p{
    font-weight: 400;
    font-size: small;
}
.article .wp-block-quote div p:not(:first-child){
    margin-top: 24px;
    padding-top: 24px;
    border-top: 1px dashed var(--line-black-color);
}
.article .wp-block-quote div p a {
    display: inline-block;
	position: relative;
    font-weight: 600;
    padding-left: 1.76em;
    margin-top: 6px;
    line-height: 2;
}
.article .wp-block-quote div p a::before{
    content: '';
	position: absolute;
	left: 0;
	top: calc(50% - 8px);
	width: 1.28em;
	height: 1.28em;
    background-image: url('/wp-content/themes/atsu-portfolio-theme/assets/images/icons/arrow-square-out-light.svg');
	background-repeat: no-repeat;
	background-position: center;
	background-size: contain;
    opacity: 0.8;
}


/* タブレット以降 */
@media screen and (min-width: 768px) {
    /* 目次ブロック */
    .table-of-contents{
        margin-top: 96px !important;
        padding: 64px 40px 48px;
        border: 1px solid var(--line-black-color);
    }
    
    /* 参照・引用ブロック */
    .article .wp-block-quote div::before{
        background: linear-gradient(
            to right,
            transparent 12%,
            #888 12%
        );
    }
    .article .wp-block-quote div::after{
        background: linear-gradient(
            to left,
            transparent 12%,
            #888 12%
        );
    }
}