トップへ戻る
BLOGS

WordPress 投稿リストブロック開発

WordPress 投稿リストブロック開発

こんにちは
今回は本サイトで使用しているプラグインとして「投稿リストを表示するブロック」を作成していきます

※本記事はMacを使用したWordPressのローカル環境であり、Node.jsのインストール環境での開発を想定した記事です

このブロックを挿入すると、まずは記事最新リストが6件カードデザインでレンダリングされ、さらにサイドパネルからカテゴリー、タグごとのリストを生成できるようにします

また、記事の表示数の設定や日付の有無、投稿の種類ごとのラベル表示の有無、そしてデザイン設定など様々な設定を設けることで使いやすい、ユーザビリティの向上を目標にしました

投稿ブロック制作の概要

まずはどういったアプローチでこのブロックを作成するか、といった指針を決めます
以下にまとめました

  • 投稿のデータをREST APIで取得する
  • 基本は最新の投稿だがカテゴリーやタグも選択可能にする
  • インスペクターにて追加設定を保存する
  • 保存した設定をレンダリング時に反映する

こんな感じで行きたいと思います
まずは環境構築からです

create-blockで環境構築

今回もプラグインファイルとしてブロックの環境構築を行います
まずは作業用フォルダに移動してターミナルに以下を打ち込みます

ターミナル

npx @wordpress/create-block 

コマンドを打ち込むと対話モードにて、ターミナルとのやり取りが始まるので適宜設定していきます。
設定内容は以下にまとめました

オプション説明デフォルト
slugファイルの出力先ディレクトリ名に使用される文字列esnext-example
namespace名前空間。ブロックをユニークに識別できる文字列create-block
titleプラグインの名前(プラグインヘッダの Plugin Name)及びブロックの表示タイトル(registerBlockType の第2パラメータの title)ESNext Example
descriptionブロックの短い説明を指定。プラグインヘッダの Description 及び egisterBlockType の第2パラメータの description。Example block written with …(省略)
dashiconブロックのアイコン。registerBlockType の第2パラメータの icon。smiley
categoryカテゴリー。registerBlockType の第2パラメータの category。widgets
plugin authorプラグインヘッダに記載されるプラグインの作者。The WordPress Contributors
plugin’s licenseプラグインヘッダに記載されるプラグインのライセンス(short name)GPL-2.0-or-later
link to licenseプラグインヘッダに記載されるライセンスの全文へのリンクhttps://www.gnu.org/licenses/gpl-2.0.html
versionプラグインヘッダに記載されるプラグインのバージョン0.1.0

これでしばらくするとプラグインとしての環境構築が完了するので管理画面から有効化します

まずはプラグインとブロック登録のスクリプトをJSとPHPに記述していきます

post-list

<?php
/**
 * Plugin Name:       Oja Post List Block
 * Description:       記事リストを表示するブロックです
 * Requires at least: 5.8
 * Requires PHP:      7.0
 * Version:           0.1.0
 * Author:            ojako
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       post-list
 *
 * @package           oja
 */


new OjaPostListBlock;
class OjaPostListBlock {
  public function __construct() {
    // ブロック登録
    add_action( 'init', array($this, 'oja_post_list_block_init'));
  }

  public function oja_post_list_block_init() {
    if ( !function_exists('register_block_type')) {
      return;
    }
    $dir = dirname( __FILE__ );

    $script_asset_path = "$dir/build/index.asset.php";

    $index_js     = 'build/index.js';
    $script_asset = require( $script_asset_path );
    wp_register_script(
      'oja-post-list-script',
      plugins_url( $index_js, __FILE__ ),
      $script_asset['dependencies'],
      $script_asset['version']
    );
  
    $editor_css = 'build/index.css';
    wp_register_style(
      'oja-post-list-editor-style',
      plugins_url( $editor_css, __FILE__ ),
      array(),
      filemtime( "$dir/$editor_css" )
    );
  
    $style_css = 'build/style-index.css';
    wp_register_style(
      'oja-post-list-style',
      plugins_url( $style_css, __FILE__ ),
      array(),
      filemtime( "$dir/$style_css" )
    );
  
    register_block_type( "oja/post-list", array(
      'editor_script' => 'oja-post-list-script',
      'editor_style'  => 'oja-post-list-editor-style',
      'style'         => 'oja-post-list-style',
      'render_callback' => 'post_list_render_func',
      'attributes' => [
        'postListType' => [
          'type'    => 'string',
          'default' => 'post'
        ],
        'isTimeStamp' => [
          'type'    => 'boolean',
          'default' => true
        ],
        'isPostLabel' => [
          'type'    => 'boolean',
          'default' => true
        ],
        'showPostNumber' => [
          'type'    => 'number',
          'default' => 6
        ],
        'postDesign' => [
          'type'    => 'string',
          'default' => 'cade'
        ],
        'postOrder' => [
          'type' => 'string',
          'default' => 'DESC'
        ],
        'postOrderBy' => [
          'type' => 'string',
          'default' => 'date'
        ],
        'termId' => [
          'type'    => 'number',
          'default' => -1
        ]
      ]
    ) );
  }

} // class

//ダイナミックブロックによるレンダリング
function post_list_render_func($attributes, $content) {
  //画像ブロックレンダリング
  require_once dirname(__FILE__) . '/src/views/post_list_render.php';
  return post_list_render($attributes, $content);
}

attributes以外はほぼ決り文句なようなもので、以下のindex.jsと共に私はいつもコピペして名前だけ変更してます

index.js

import { registerBlockType } from "@wordpress/blocks";
import "./style.scss";
import Edit from "./edit";

registerBlockType("oja/post-list", {
  title: "Oja Post List Block",
  description: "記事リストをブロックとして表示します",
  category: "common",
  icon: "smiley",
  keywords: ["post", "oja", "list"],
  supports: {
    customClassName: false,
    anchor: false,
    html: false,
  },
  edit: Edit,
  save: () => {
    return null;
  },
});

今回はPHPでレンダリングするダイナミックブロックであるため、JSでのattributes属性は不要です

customClassNameは高度な設定自体を非表示にします
ここからは、より厳密にワークフローを組み立てたいと思います

WP REST API

WordPressでREST APIを使用したいと思います。

RESTful API(REST API)とは、Webシステムを外部から利用するためのプログラムの呼び出し規約(API)の種類の一つで、RESTと呼ばれる設計原則に従って策定されたもの、、、、

ということで噛み砕いてみると「ガイドラインに沿った情報のやり取り」ということですが、
WordPressもこれを用意してくれていて、ブロックエディターハンドブックによると
apiFetch()を使用するのが良さそうです。これはfetchリクエストを作成するwindow.fetchのラッパーのようです

カスタム投稿の場合

カスタム投稿タイプ・カスタムタクソノミーで使用する場合は初期設定が必要ですregister_post_typeregister_taxonomy関数を使うときに、引数のパラメータに設定します

'show_in_rest'  => true,

これでREST APIが使用できるようになりました

apiFetch()

まずはモジュールを読み込みます

import apiFetch from "@wordpress/api-fetch";

そして、カスタム投稿のスラッグを指定してFetchします

apiFetch 例文

const newBlogs = [{ label: "すべて", value: -1 }];
apiFetch({ path: "/wp/v2/blogs" }).then((blogs) => {
  blogs.forEach((blog) => {
    newBlogs.push({ label: blog["title"]["rendered"], value: blog["id"] });
  });
});

apiFetchの返り値はPromiseインスタンスであるため、thenで繋いでいきます
コールバックのblogsをコンソールに出力してみるとわかりやすいです

環境変数path:には現在のサイトのRESTAPIルートURLが設定されます

WordPressのREST APIの使い方

投稿データが取得することが確認できたら、次はサイドバーの設定を行います

Inspectorを設定する

まず、プラグインPHPファイルで設定しておいたattributesの役割を確認します

  • postListType,投稿のタイプ(投稿、カテゴリー、タグ)を条件分岐
  • isTimeStamp,日付を表示するかフラグ
  • isPostLabel,ラベルを表示するかフラグ
  • showPostNumber,投稿の表示数
  • postDesign,デザイン設定
  • termId,タクソノミーが選択されていた場合絞り込み
  • postOrder,昇順?降順?
  • postOrderBy,表示基準

設計として、PHPであらかじめなんとなく定義しておいた属性値は、実際に組み立てる際に追加したり詳細を決めたりします

まず最初に設定してもらうべきはpostListTypeで投稿、カテゴリー、タグを選択してもらい投稿であれば即時レンダリング。 そして投稿以外であればどのタクソノミーで表示するかを選択してもらえば良さそうです
ということで、「postListTypeが投稿以外であればtermIdを選択してもらう」とします

また、postOrderpostOrderByshowPostNumberは最終的にレンダリングする際にここで設定した文字列をそのままWP_Queryのパラメータとして使用することが想定されるため、これに合わせます

これ踏まえてedit.jsを組み立てます

edit.js(全文)

import { InspectorControls } from "@wordpress/block-editor";
import ServerSideRender from "@wordpress/server-side-render";
import {
  PanelBody,
  PanelRow,
  RadioControl,
  SelectControl,
  ToggleControl,
} from "@wordpress/components";
import SelectTaxonomy from "./components/SelectTaxonomy";
import PostNumber from "./components/PostNumber";
import apiFetch from "@wordpress/api-fetch";
import { useBlockProps } from "@wordpress/block-editor";
import "./editor.scss";

export default function Edit(props) {
  const {
    attributes: {
      postListType,
      isTimeStamp,
      isPostLabel,
      showPostNumber,
      postDesign,
      postOrder,
      postOrderBy,
      termId,
    },
    className,
    setAttributes,
  } = props;
  return [
    <InspectorControls>
      <PanelBody title="投稿リスト設定" initialOpen={true}>
        <PanelRow>
          <RadioControl
            className="ojaPostListType"
            label="投稿のタイプ"
            selected={postListType}
            options={[
              { label: "最新の投稿", value: "post" },
              { label: "カテゴリー", value: "category" },
              { label: "タグ", value: "tag" },
            ]}
            onChange={(val) => setAttributes({ postListType: val })}
          />
        </PanelRow>
        {postListType !== "post" && (
          <PanelRow>
            <SelectTaxonomy
              postListType={postListType}
              termId={termId}
              setAttributes={setAttributes}
            />
          </PanelRow>
        )}
      </PanelBody>
      <PanelBody title="表示設定" initialOpen={true}>
        <PanelRow>
          <PostNumber
            showPostNumber={showPostNumber}
            setAttributes={setAttributes}
          />
        </PanelRow>
        <PanelRow>
          <ToggleControl
            label={isTimeStamp ? "日付を表示する" : "日付を表示しない"}
            checked={isTimeStamp}
            onChange={(val) => setAttributes({ isTimeStamp: val })}
          />
        </PanelRow>
        <PanelRow>
          <ToggleControl
            label={
              isPostLabel ? "投稿にラベルを表示する" : "ラベルは表示しない"
            }
            checked={isPostLabel}
            onChange={(val) => setAttributes({ isPostLabel: val })}
          />
        </PanelRow>
        <PanelRow>
          <RadioControl
            className="ojaPostListDesign"
            label="表示タイプ (デザイン)"
            selected={postDesign}
            options={[
              { label: "カード", value: "cade" },
              { label: "シンプル", value: "simple" },
              { label: "テキスト", value: "text" },
            ]}
            onChange={(val) => setAttributes({ postDesign: val })}
          />
        </PanelRow>
        <PanelRow>
          <SelectControl
            label="表示基準"
            value={postOrderBy}
            options={[
              { label: "日付順", value: "date" },
              { label: "更新順", value: "modified" },
              { label: "ランダム", value: "rand" },
              { label: "タイトル順", value: "title" },
              { label: "著者順", value: "author" },
            ]}
            onChange={(val) => setAttributes({ postOrderBy: val })}
          />
        </PanelRow>
        <PanelRow>
          <SelectControl
            label="表示順"
            value={postOrder}
            options={[
              { label: "昇順", value: "ASC" },
              { label: "降順", value: "DESC" },
            ]}
            onChange={(val) => setAttributes({ postOrder: val })}
          />
        </PanelRow>
      </PanelBody>
    </InspectorControls>,
    <div className={className}>
      <ServerSideRender
        block={props.name}
        attributes={{
          postListType,
          isTimeStamp,
          isPostLabel,
          showPostNumber,
          postDesign,
          termId,
          postOrder,
          postOrderBy,
        }}
        className="oja-server-siderender"
      />
    </div>,
  ];
}

最初のRadioControlで投稿のタイプを設定します。ここでのvalue値は3パターンに分岐できれば良いというもので特に問題ありません。その属性値に応じて<SelectTaxonomy>というコンポーネントを作成しています

SelectTaxonomyコンポーネント

ここではpostListTypeの状態を判定してタクソノミーを出し分けるロジックを組んで行きます

import SelectTaxonomy from "./components/SelectTaxonomy";

上記のディレクトリでコンポーネントを作成していきますが、ここは適宜お好みで変更して下さい

components/SelectTaxonomy.js

import { SelectControl } from "@wordpress/components";
import apiFetch from "@wordpress/api-fetch";

const categories = [{ label: "選択して下さい", value: -1 }];
apiFetch({ path: "/wp/v2/oja_cat?per_page=-1_fields=name,slug,id" }).then(
  (cates) => {
    cates.forEach((cate) => {
      categories.push({ label: cate["name"], value: cate["id"] });
    });
  }
);

const postTags = [{ label: "選択して下さい", value: -1 }];
apiFetch({ path: "/wp/v2/oja_tags?per_page=-1_fields=name,slug,id" }).then(
  (tags) => {
    tags.forEach((tag) => {
      postTags.push({ label: tag["name"], value: tag["id"] });
    });
  }
);

const SelectTaxonomy = (props) => {
  const { termId, postListType, setAttributes } = props;
  return (
    <SelectControl
      label={postListType === "category" ? "カテゴリーを選択" : "タグを選択"}
      value={termId}
      options={postListType === "category" ? categories : postTags}
      onChange={(val) => setAttributes({ termId: val })}
    />
  );
};
export default SelectTaxonomy;

ここではタクソノミーを取得するためのapiFetchを実行しています
apiFetchのパラメータですがper_page=-1は全件取得、
_fields=name,slug,id
パラメータは大量に取得される情報に対して制限をかけてパフォーマンスチューニングを行っています

後はpostListTypeの状態に応じて表示するオプションの変更やラベルの動的な変更を行っています

※ このタクソノミーをだし分けるロジックですが、React.momeやコールバックフックなどでもう少しパフォーマンスチューニングが可能なような気がします
引き続きアップデートしていきます

PostNumberコンポーネント

ここではNumberControlをHTMLタグでラップするためのコンポーネントです

NumberControlは標準ではinputタグをレンダリングするだけなので疑似要素を追加したりなどスタイリングがしにくい為コンポーネント化します
属性値として保存された数値は文字列となってしまうためparseInt()で整数値にしています

./components/PostNumber

import {	__experimentalNumberControl as NumberControl} from "@wordpress/components";

const PostNumber = (props) => {
	const { showPostNumber, setAttributes } = props;
	return (
    <div className="postsNumber">
      <NumberControl
        label="表示数"
        isShiftStepEnabled="true"
        shiftStep="2"
        min="2"
        max="8"
        value={showPostNumber}
        onChange={(value) => setAttributes({ showPostNumber: parseInt(value) })}
      />
    </div>
	);
};
export default PostNumber;

<ServerSideRender>

ServerSideRenderは編集画面でPHP の render_callback 関数に基づいたレンダリングを行うことが出来るため、パラメータの変更などに対して動的にプレビューを変更することが可能です

<ServerSideRender
  block= ブロックの識別子 '名前空間/ブロック名'
  attributes={{  
   PHP のレンダリングで使用している属性を指定(指定しないと表示されない)
  }}
  className='クラス名'
 />

attributesのパラメータは属性名={属性値}としますが分割代入で変数にしているため、オブジェクトの短縮記法を使用することが出来るため以下のように記述出来ます

edit.js (抜粋)

<ServerSideRender
  block={props.name}
  attributes={{
    postListType,
    isTimeStamp,
    isPostLabel,
    showPostNumber,
    postDesign,
    termId,
    postOrder,
    postOrderBy,
  }}
  className="oja-server-siderender"
/>;

これで投稿を表示するロジックは完成したので実際にフロントエンドでレンダリングする処理を書いていきます

PHPでレンダリング

PHPでレンダリングするダイナミックブロックは render_callbackパラメータを指定しindex.jsのsave関数で null を返すことでPHPによる描画が可能です
ということはWordPressの関数が自由に使用できるということで、Wp_Query を使用してパラメータを指定していきます

post-list.php

<?php
function post_list_render($attr, $content) {
  //管理画面でa タグを削除する
  function hrefChecker() {
    if ( !is_admin() ) {
      $href = ' href="'. get_the_permalink().'"';
    } else {
      $href = '  style="pointer-events: none;"';
    }
    return $href;
  }
  //表示数に合わせてレイアウトを変更する
  function content_style_width($show_post_num) {
    $content_width = 'style="flex-basis:';
    if($show_post_num % 4 === 0 || $show_post_num === 7) {
      $content_width .= 'calc((100% - 40px) / 4); margin-right: 10px;"';
    } elseif($show_post_num % 3 === 0 || $show_post_num === 5) {
      $content_width .= 'calc((100% - 30px) / 3); margin-right: 10px;"';
    } else {
      $content_width .= 'calc((100% - 40px) / 2); margin-right: 20px;"';
    }
    return $content_width;
  }
  //ラベルをレンダリングする関数
  function content_label_render($is_post_label, $post_list_type, $ID) {
    $post_label = '<span class="post_label">';
    $term = get_term($ID);
    if(!$is_post_label) {
      return;
    }
    if($post_list_type === "post") {
      $post_label .= "おすすめ記事</span>";
    } elseif($post_list_type === "category") {
      $post_label .= esc_html($term->name)."</span>";
    } else {
      $post_label .= '#'.esc_html($term->name).'</span>';
    }
    return $post_label;
  }

  $args = array(
    'post_type'      => 'blogs',
    'post_status'    => 'publish',
    'order'          => $attr['postOrder'],  //昇順 or 降順の指定
    'orderby'        => $attr['postOrderBy'],  //何順で並べるかの指定
    'posts_per_page' => $attr['showPostNumber'],
  );

  $post_class = ' '.$attr['postDesign'];
  //投稿タイプ以外の場合
  if($attr['postListType'] !== "post") {
      $tax_args[] =  array(
        'taxonomy' => $attr['postListType'] === "category" ? 'oja_cat' :
        'oja_tags',
        'field' => 'id',
        'terms' => $attr['termId'],
        'include_children' => false,//子タクソノミーを含めるかどうか
      );
    $args += Array('tax_query' => array($tax_args));
  }

  $archive   = '<div class="wp-block-oja-post-list'.$post_class.'"><ul class="post-list-container">';
  $the_query = new WP_Query( $args );
  if ( $the_query->have_posts() ) :
    while ( $the_query->have_posts() ) :
      $the_query->the_post();
      $archive .= '<li '.content_style_width($attr['showPostNumber']).'>';
      $archive .= '<a class="post-list-link"'.hrefChecker().'>';
      if (has_post_thumbnail() && $attr['postDesign'] !== "text") $archive .= '<figure>'.get_the_post_thumbnail().'</figure>';
      $archive .= '<div class="post_content">'.content_label_render($attr['isPostLabel'], $attr['postListType'], $attr['termId']);
      $archive .= '<h3>' . get_the_title() . '</h3>';
      if($attr['isTimeStamp']) $archive .= '<time datetime="' . get_the_date( 'Y-m-d' ) . '">' . get_the_date( 'Y年m月d日' ) . '</time>';
      $archive .= '</div></a></li>';
    endwhile;
      $archive .='</ul></div>';

    else:
    $archive = '<p>記事は取得できませんでした</p>';
  endif;
  wp_reset_postdata();
  //出力
  return $archive;

}

Wp_Queryのパラメータについては適宜検索しています。
すぐに忘れてしまうので、、笑

いっつも忘れるWP_Queryの使用方法とパラメータ一覧。がっつり整理してみた

いくつか関数を定義して、要所で使用するため解説していきます

ラベルの有無に応じて出力を変更する関数

function content_label_render($is_post_label, $post_list_type, $ID) {
  $post_label = '<span class="post_label">';
  $term = get_term($ID);
  if(!$is_post_label) {
    return;
  }
  if($post_list_type === "post") {
    $post_label .= "おすすめ記事</span>";
  } elseif($post_list_type === "category") {
    $post_label .= esc_html($term->name)."</span>";
  } else {
    $post_label .= '#'.esc_html($term->name).'</span>';
  }
  return $post_label;
}

この関数は、ラベルの有無を確認するbool値を判定したのち、$post_list_typeの値に応じて表示名を出し分けます。 もしタクソノミーであるならば、get_term()関数でタームオブジェクトを取得してそのターム名を出力するようにしております

投稿の表示数に応じてレイアウトを変更する関数

function content_style_width($show_post_num) {
  $content_width = 'style="flex-basis:';
  if($show_post_num % 4 === 0 || $show_post_num === 7) {
    $content_width .= 'calc((100% - 40px) / 4); margin-right: 10px;"';
  } elseif($show_post_num % 3 === 0 || $show_post_num === 5) {
    $content_width .= 'calc((100% - 30px) / 3); margin-right: 10px;"';
  } else {
    $content_width .= 'calc((100% - 40px) / 2); margin-right: 20px;"';
  }
  return $content_width;
}

これはインラインスタイルの文字列を作成し返す関数です

基本的にCSSの flexboxが指定されている子要素に対してこの関数を実行しカード型のデザインを整える目的で使用します

管理画面上では aタグのhref属性を無効にする

function hrefChecker() {
  if ( !is_admin() ) {
    $href = ' href="'. get_the_permalink().'"';
  } else {
    $href = '  style="pointer-events: none;"';
  }
  return $href;
}

この関数は正直、うまくいきませんでした、、、、
is_admin()の条件判定が、上手くいっておりません
どういうわけか管理画面でもフロントエンドでもis_admin()の判定がfalseになってしまいます

ドキュメントを読んでもいまいちピンと来ないため、一旦保留にします

SCSSでスタイリング

最後にstyle.scssでデザインを整えます

src/style.scss

@charset "utf-8";

.wp-block-oja-post-list {
  padding: 12px 10px 2px;
  width: 100%;
  min-height: 285px;
  ul.post-list-container {
    border: none;
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;

    li {
      padding: 0;
      margin: 20px 0 0;
      position: relative;
      background: #fff;
      -webkit-box-shadow: 0 1px 3px rgb(0 0 0 / 18%);
      box-shadow: 0 1px 3px rgb(0 0 0 / 18%);
      pointer-events: none;
      transition: all 0.3s;
      &:hover {
        transform: translateY(-2px);
        color: #888888;
        box-shadow: 0 0 10px rgb(125 125 125 / 50%);
      }
      @media screen and (max-width:543px) {
        flex-basis: 100% !important;
      }
      a.post-list-link {
        border-radius: 2px;
        display: flex;
        flex-direction: column;
        text-decoration: none;
        color: #847d76;
        pointer-events: auto;
      }
      figure {
        width: 100%;
        position: relative;
        overflow: hidden;
        &:before {
          content: "";
          display: block;
          padding-top: 28.5%;
        }
        
        img {
          position: absolute;
          top: 0%;
          left: 0%;
          width: 100%;
          height: 100%;
          object-fit: cover;
        }
      }

      .post_content {
        display: block;
        padding: 10px 13px 30px;

        h3 {
          font-family: 'Rounded Mplus 1c',sans-serif;
          font-size: 13px;
          line-height: 1.3rem;
          font-weight: 400;
          color: #847d76;
          margin: 0;
          padding: 0;
          &::before,::after {
            content: none;
          }
          @media screen and (max-width:768px) {
            font-size: 12px;
             line-height: 1.2rem;
          }
          @media screen and (max-width:543px) {
            font-size: 13px;
          }
        }
        span.post_label {
          font-family: Quicksand,'Rounded Mplus 1c',sans-serif;
          position: absolute;
          left: 0%;
          top: 0%;
          padding: 0 3px;
          background-color: #b8e886;
          color: #fff;
          font-size: 10px;
        }
        time {
          position: absolute;
          bottom: 3%;
          left: 5%;
          transform: scale(1.05);
          font-size: 10px;
          margin-top: 5%;
          font-family: Quicksand,'Rounded Mplus 1c',sans-serif;
          &::before {
            margin-right: 0.3em;
            font-family: "Font Awesome 5 Free";
            font-weight: 900;
            content: "\f017";
          }
        }
      }
    } //li
  } //ul

  //シンプル&テキストスタイル
  &.simple,&.text {
    ul.post-list-container {
      flex-direction: column;
      flex-wrap: nowrap;
      li {
        margin-bottom: 2%;
        margin-top: 10px;
        @media screen and (max-width:543px) {
          margin-bottom: 0;
        }
        a.post-list-link {
          flex-direction: row;
        }
        figure {
          width: 30%;
          @media screen and (max-width:768px) {
            width: 40%;
          }
          @media screen and (max-width:543px) {
            width: 90%;
          }
        }
        .post_content {
          padding: 22px 20px 15px;
          @media screen and (max-width:543px) {
            padding: 8px 10px 5px;
          }
        }
        h3 {
          font-size: 16px;
          @media screen and (max-width:768px) {
            font-size: 14px;
          }
          @media screen and (max-width:543px) {
            font-size: 10px;
          }
        }
        time {
          position: absolute;
          right: 3%;
          bottom: 3%;
          left: initial;
          @media screen and (max-width:543px) {
            transform:scale(.8);
            right: 1%;
            bottom: 1%;
          }
        }
      }
    }
  }
  &.text {
    .post_content {
      padding: 25px 10px !important;
      @media screen and (max-width:543px) {
        padding-bottom: 15px !important;
      }
    }
  }
} //wp-block

インスペクターのデザインです
これらはお好みですので、適宜変更を

src/editor.scss

@charset "utf-8";
.wp-block-oja-post-list {
  min-height: initial;
  padding: 0;
  figure {
    margin: 0;
  }
}

.components-radio-control {
  .components-base-control__field {
    display: flex;
    flex-wrap: wrap;
    .components-base-control__label {
      max-width: initial;
      flex-basis: 100%;
    }
  }

  &__option {
    width: 30%;
    display: flex;
    flex-direction: column;

    input {
      display: none;
    }

    input[type="radio"]:checked + label {
      background-color: #0085ba;
      color: #fff;
      text-shadow: 0 -1px 1px #005d82, 1px 0 1px #005d82, 0 1px 1px #005d82, -1px 0 1px #005d82;
    }

    label {
      flex-grow: 1;
      display: block;
      cursor: pointer;
      width: 100%;
      margin: 0;
      padding: 7px 4px;
      background: #f7f7f7;
      color: #555e64;
      text-align: center;
      line-height: 1.3;
      font-size: 12px;
      transition: .2s;
      border: solid 1px #ccc;
      box-shadow: inset 0 -1px 0 #ccc;

      &:hover {
        background: #fafafa;
        border-color: #999;
        box-shadow: inset 0 -1px 0 #999;
        color: #23282d;
      }
    }
  }
  
}
.postsNumber {
  width: 80%;
  position:relative;
  &:after {
    content: '記事表示します';
    display: block;
    position: absolute;
    right: 40%;
    top: 55%;
  }
}



コメントをお待ちしております

お気軽にコメントをどうぞ。

CAPTCHA