Let’s Encrypt(無償SSL)のEメールによる有効期限通知終了(SSLUpdates on Let’s Encrypt Subscriber Agreement & Ending Expiration Notification)

2025/01/31 (金) - 12:00 Server

以下のような表題のメールが来ました。

SSLUpdates on Let’s Encrypt Subscriber Agreement & Ending Expiration Notification

Let’s Encryptではメールアドレスを登録した会員に向けてEメールによる有効期限通知のサービスを提供していましたが、こちらが2025年6月4日に終了するとのことです。

引き続き、メールによる更新通知を受け取りたい場合はサードパーティの監視サービス Red Sift Certificates Lite を利用するなど検討してください。こちらはLet’s Encrypt が推奨しているサービスになります。

WebサーバーでLet’s Encrypt(certbot)のSSL更新を自動化する

Let’s Encrypt(certbot)の更新作業はサーバー側で自動化することもできます。サーバーにログイン後、以下のコマンドを実行すると、certbotのSSL更新のデモンストレーションを行えます。以下で正常(Success)に更新できることっ確認しておきましょう。

$ certbot renew --dry-run

certbotの更新を自動更新する場合はcrontabで定期実行すると便利です。例えば毎月1日の午前2時に更新する場合は以下のように指定します。certbotの更新実行後はwebサーバーの再起動が必要なのでそれも実行します(CentOS7 nginxの例)

$ crontab -u root -e
00 02 01 * * certbot renew && systemctl restart nginx

有償のSSLを契約している場合は定期的な更新手続や支払いが必要で。契約しているサービスからお知らせが来るので忘れづらいですが、レンタルサーバーでLet’s Encryptを使っている場合や、AWSのACM(Certificate Manager)で自動更新に慣れているとふと失念してしまうことがあるので注意しましょう。

Reactで簡易的なカレンダーを作成

2025/01/31 (金) - 09:00 JavaScript

Reactを使用して簡易的なカレンダーを作成しました。デフォルトでは今月のカレンダーを表示、前月と次月のボタンを押下することで月表示を切り替えることができます。実装環境は以下の通り。

  • React 19.0.x
  • Vite 5.x

カレンダーのイメージ

予めプロジェクトを立ち上げておき、App.jsxに以下のように記述します。前月、次月のボタンを押下した際にuseStateでカレンダーの表示を切り替える仕組みです。

import { useState } from 'react'
import './App.css'
const App = () => {
 const [currentDate, setCurrentDate] = useState(new Date());
 const getDaysInMonth = (year, month) => {
  return new Date(year, month + 1, 0).getDate();
 };
 const getFirstDayOfMonth = (year, month) => {
  return new Date(year, month, 1).getDay();
 };
 const renderCalendar = () => {
  const year = currentDate.getFullYear();
  const month = currentDate.getMonth();
  const daysInMonth = getDaysInMonth(year, month);
  const firstDayOfMonth = getFirstDayOfMonth(year, month);
  const blanks = Array(firstDayOfMonth).fill(null);
  const daysArray = Array.from({ length: daysInMonth }, (_, i) => i + 1);
  const allDays = [...blanks, ...daysArray];
  const totalCells = Math.ceil(allDays.length / 7) * 7;
  const remainingBlanks = totalCells - allDays.length;
  allDays.push(...Array(remainingBlanks).fill(null));
  const weeks = [];
  let week = [];
  allDays.forEach((day, index) => {
   week.push(day);
   if ((index + 1) % 7 === 0) {
    weeks.push(week);
    week = [];
   }
  });
  return weeks.map((week, weekIndex) => (
   <tr key={weekIndex}>
    {week.map((day, dayIndex) => {
     const dayOfWeek = (firstDayOfMonth + dayIndex) % 7;
     return (
      <td key={dayIndex}>
       {day || ''}
      </td>
     );
    })}
   </tr>
  ));
 };
 const goToPreviousMonth = () => {
  setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1));
 };
 const goToNextMonth = () => {
  setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1));
 };
 return (
  <div className="calendar">
   <div className="calendar-header">
    <button onClick={goToPreviousMonth}>前の月</button>
    <h2>
     <span className="calendar-year">{currentDate.getFullYear()}年</span>
     <span className="calendar-month">{currentDate.toLocaleString('default', { month: 'long' })}</span>
    </h2>
    <button onClick={goToNextMonth}>次の月</button>
   </div>
   <table>
    <thead>
     <tr>
      <th>日</th>
      <th>月</th>
      <th>火</th>
      <th>水</th>
      <th>木</th>
      <th>金</th>
      <th>土</th>
     </tr>
    </thead>
    <tbody>{renderCalendar()}</tbody>
   </table>
  </div>
 );
};
export default App;

市販の一般的なカレンダーに習い、日曜日を開始としてtd要素で並べます。今回日曜日を赤文字・土曜日を青文字としたかったので、こちらはCSSのセレクタを用いて色変更を行いました。

table{
 width: 100%;
 border-collapse: collapse;
 table-layout: fixed;
}
th,
td{
 padding: 10px;
 border: 1px solid #ccc;
 text-align: center;
 vertical-align: middle;
}
th:first-child,
td:first-child{
 color: red;
}
th:last-child,
td:last-child{
 color: blue;
}

細かいスタイルの反映については個別に適宜実装ください。

Grek,Gemini,Claude,GPT,DeepSeekの回答を比較してみた(物語のあらすじ編)

2025/01/24 (金) - 09:00 Others

大規模言語モデル(LLM)を用いた対話型AIや文章生成AIが群雄割拠の時代です。今回はとある作品のあらすじを聞いて比較してみました。なおプロンプトは、“初代『機動戦士ガンダム』(通称:ファーストガンダム)のあらすじを教えて下さい。”と、簡単なものとしました。

以下に回答文を羅列します。

Grek

『機動戦士ガンダム』(ファーストガンダム)は、1979年から1980年にかけて放送されたアニメシリーズで、リチャード・ガンダムの世界観を基にしたストーリーです。以下にそのあらすじを解説します。

あらすじ

時代背景:

宇宙世紀(UC)0079年。人類は地球上だけでなく、宇宙空間にもコロニーと呼ばれる人工衛星都市を建設し、生活していました。しかし、その中でも特にジオン公国と地球連邦政府との間で対立が激化し、戦争が勃発します。これが「一年戦争」と呼ばれることになる戦闘の始まりです。

物語の概要:

主人公アムロ・レイは、地球連邦軍の新兵で、偶然にも新型のモビルスーツ「ガンダム」に乗り込むことになります。ガンダムは、ジオン公国の開発したモビルスーツに対抗するために作られた最新鋭の兵器でした。

アムロとシャアの対決:

アムロは、ジオン公国のエースパイロットであるシャア・アズナブルと何度も戦闘を繰り広げます。シャアは、冷酷かつ戦略的な戦闘スタイルで知られ、アムロにとって最大のライバルとなります。

白兵戦と精神的成長:

アムロは戦闘を通じて、ニュータイプ(次世代の人類)としての能力を発揮し始めます。これは、他人の思考を感じ取る能力や、モビルスーツを超人的に操る力などを含みます。しかし、その一方で、彼は戦争の非情さや仲間との別れ、そして心の傷を深く受けます。

戦争の終結:

一年戦争は、ジオン公国の首都ソロモンで最終決戦が行われ、地球連邦軍が勝利します。しかし、戦争の結果は、勝利者も敗者も大きな犠牲を払ったことを示しています。アムロはシャアとの決戦に勝利しつつも、彼自身も深い傷を負います。

エピローグ:

戦争の終わりと共に、アムロは新たな未来を見据えつつも、過去の影を引きずりながらも生きていくことを選択します。物語は、彼の成長と戦争の悲惨さを描きつつ、次の世代への希望も示唆しています。

このシリーズは、戦争の無意味さや人間の生存と成長を描いた深いテーマを持ちつつ、魅力的なキャラクターとメカニックの戦闘シーンで知られています。

Gemini 1.0 Pro

宇宙世紀0079年、人類は宇宙へと進出し、地球連邦政府がその大半を統治していました。しかし、宇宙に移民した人々の不満が爆発し、ジオン公国が独立戦争を挑みます。

物語は、連邦軍が開発した新型モビルスーツ「ガンダム」を巡って展開されます。ジオン軍の奇襲により、ガンダムの開発拠点であるサイド7が攻撃され、主人公のアムロ・レイは偶然ガンダムに乗り込みます。

ガンダムの圧倒的な力

ガンダムは、ジオン軍の主力兵器であるモビルスーツ「ザク」をはるかに凌ぐ性能を持っていました。アムロは、持ち前の才能とガンダムの力によって、ジオン軍を相手に奮闘します。

ホワイトベース隊の誕生

ガンダムとアムロは、連邦軍の艦船「ホワイトベース」に搭載され、宇宙を転戦します。ホワイトベースには、アムロをはじめ、カイ・シデン、セイラ・マスといった若きパイロットたちが集まり、ジオン軍との戦いを繰り広げます。

ニュータイプの出現

物語が進むにつれて、「ニュータイプ」と呼ばれる特殊な能力を持つ人々の存在が明らかになります。ニュータイプは、通常の感覚を超えた能力を持ち、モビルスーツの操縦に大きな影響を与えます。アムロやシャア・アズナブルもまた、ニュータイプの一人であり、その能力を駆使して戦います。

戦争の悲劇と成長

ホワイトベース隊は、ジオン軍との戦いを経て、数々の苦難を乗り越えます。戦争の残酷さ、仲間の死、そして成長を繰り返しながら、彼らは自分たちの役割に気づき、人類の未来のために戦う決意を固めます。

最終決戦と新たな時代

物語は、ジオン軍の拠点であるア・バオア・クーでの最終決戦へと向かいます。ガンダムとシャアの赤い彗星との戦いは、多くの犠牲を伴いながらも、ついに終結を迎えます。

ファーストガンダムは、単なるロボットアニメにとどまらず、戦争の悲劇、人間の成長、そして未来への希望を描いた壮大な物語です。

キーワード

  • モビルスーツ:人型機動兵器
  • ジオン公国:独立を求める勢力
  • ニュータイプ:特殊な能力を持つ人類
  • ホワイトベース:ガンダムが搭載された艦船
  • シャア・アズナブル:ジオン軍のエースパイロット、赤い彗星

より深く知りたい方へ

ファーストガンダムは、その後のガンダムシリーズの礎を築いた作品です。キャラクターやメカニック、世界観など、魅力的な要素がたくさんあります。

もし興味があれば、ぜひアニメや漫画、小説など、さまざまな形で作品に触れてみてください。

何かもっと知りたいことがあれば、お気軽にご質問ください。

  • 特定のキャラクターについて詳しく知りたい
  • モビルスーツの種類や特徴を知りたい
  • ガンダムシリーズの歴史を知りたい
  • 物語の結末について詳しく知りたい

など、どんな質問でも構いません。

Claude 3.5

1979年に放送された「機動戦士ガンダム」の物語の主なあらすじをご説明します。

物語は宇宙世紀0079年を舞台とし、地球連邦とジオン公国による一年戦争の最中に展開されます。主人公のアムロ・レイは、スペースコロニー「サイド7」に住む15歳の少年です。

ある日、サイド7がジオン軍に襲撃された際、アムロは偶然にも地球連邦軍の新型モビルスーツ「ガンダム」のコックピットに乗り込みます。彼は天性の才能を発揮し、ガンダムを操縦して敵と戦います。

その後、アムロは避難民たちとともに連邦軍の戦艦「ホワイトベース」に乗船することになります。彼らは地球に向かう過程で、シャア・アズナブルをはじめとするジオン軍の追撃を受けながら、数々の戦いを経験します。

物語は単なる戦争物語ではなく、戦争の悲惨さや人間の成長、そして理想と現実の狭間での葛藤などのテーマを深く描いています。アムロは戦いを通じて成長していくと同時に、戦争がもたらす悲しみや苦しみにも直面していきます。

最終的には、地球連邦軍とジオン公国の決戦が宇宙要塞「ア・バオア・クー」で行われ、壮絶な戦いが繰り広げられます。

この作品は、それまでのロボットアニメの常識を覆し、より現実的な戦争描写とヒューマンドラマを展開したことで、アニメ史に大きな影響を与えました。

Chat GPT 4o

『機動戦士ガンダム』(通称「ファーストガンダム」)は、宇宙世紀0079年を舞台に、地球連邦と独立を宣言したジオン公国との間で繰り広げられる「一年戦争」を描いた作品です。

物語は、地球連邦が開発した新型モビルスーツ「ガンダム」を搭載した戦艦ホワイトベースが、サイド7のコロニーでジオン軍の襲撃を受ける場面から始まります。

民間人であった少年アムロ・レイは、偶然にもガンダムを操縦し、敵の攻撃を退けます。

その後、彼はホワイトベースのクルーとして戦いに身を投じ、成長していきます。

物語の中で、アムロはジオン軍のエースパイロットであるシャア・アズナブルと幾度も対峙します。

また、ニュータイプと呼ばれる新たな人類の可能性が示唆され、アムロ自身もその能力に目覚めていきます。

物語は、地球上での戦闘から宇宙へと舞台を移し、最終的にはジオン公国の本拠地であるア・バオア・クーでの決戦へと至ります。

アムロとシャアの因縁、そして人類の新たな可能性であるニュータイプの存在を通じて、戦争の悲惨さや人間の成長が描かれています。

この作品は、リアルな戦争描写や人間ドラマ、複雑なキャラクター設定などで高い評価を受け、多くの続編や関連作品が制作されるきっかけとなりました。

さらに詳しい解説や映像での紹介をお求めであれば、以下の動画も参考になるでしょう。

DeepSeek R1

初代「機動戦士ガンダム」(1979年放送)は、宇宙世紀(U.C.)0079年を舞台にした戦争ドラマで、後のロボットアニメの礎を築いた作品です。以下に簡潔なあらすじをまとめます。

【物語の背景】

人類が宇宙へ進出した「宇宙世紀」、地球連邦政府と宇宙コロニー側の独立勢力「ジオン公国」の間で「一年戦争」が勃発。ジオン軍は新型兵器「モビルスーツ(MS)」を投入し、戦争は苛烈化していました。

【あらすじ】

中立コロニー「サイド7」に住む少年アムロ・レイは、ジオン軍の奇襲攻撃に巻き込まれます。その際、偶然にも連邦軍の試作モビルスーツ「ガンダム」に乗り込み、撃退に成功。この出来事をきっかけに、アムロは地球連邦軍の宇宙艦「ホワイトベース」に乗組員として参加します。

ホワイトベースは、ガンダムや他のMSを運用しつつ、地球連邦の拠点である「ジャブロー」を目指して戦いながら撤退を続けます。アムロは戦いの中で「ニュータイプ」と呼ばれる特殊な能力に目覚め、成長していきます。

一方、ジオン軍のエースパイロット「シャア・アズナブル」(赤い彗星)は、ガンダムとホワイトベースを執拗に追撃。シャアはジオン公国の支配層「ザビ家」への復讐を企てるなど、複雑な思惑を抱えていました。

物語は、アムロとホワイトベースの仲間たち(ブライト艦長、カイ・シデン、フラウ・ボゥら)が数々の戦闘を経て絆を深め、最終的にジオンの要塞「ア・バオア・クー」での決戦に臨む様子を描きます。戦争の終結と共に、アムロはニュータイプとしての可能性を示しつつ、深い喪失感と共に物語は幕を閉じます。

【テーマと特徴】

戦争の現実: 英雄視点ではなく、市井の少年が戦争に巻き込まれる過程や、戦争の悲惨さをリアルに描写。

人間の成長: アムロの精神的成長や、仲間との関係性の変化に焦点。

ニュータイプ: 宇宙時代の新たな人類像としての思想や可能性が探られる。

政治的駆け引き: ジオン公国内の権力闘争や地球連邦の腐敗も背景に。

【影響】

「ガンダム」は「ロボットアニメ」の概念を変え、リアルなメカデザインや重厚なストーリーで社会現象に。続編や派生作品が多数生まれ、現在もシリーズが続く「ガンダム宇宙世紀」の原点となりました。

さらに詳しいポイントがあれば、ぜひお聞かせください!

まとめ

基本的にはどの解説もほぼ同じで、物語の始まりから終わりまで忠実に解説し、世界観や作品のモチーフ、ファーストが人気である理由などほぼ正確に解説されていますが、プラットフォーム毎に以下のような特徴がありました。

Grek

それぞれ項目ごとに細かく説明されており、ファーストの流れや世界観や人気の理由など要点を抑えて細かくまとめています。物語の特徴を分けて解説してくれているのは便利です。ただ最終決戦は宇宙要塞のア・バオア・クーだった気もしますが。

Gemini

Grek同様に項目ごとに説明されており、さらに作品の詳細(モビルスーツや人物の詳細)を聞くように誘導する流れになっています。AIアシスタントとしてはこの流れは非常に親切設計です。

Claude

作品全体の流れと世界観、人気の理由などを簡潔にまとめています。短いですがまとめとしては非常にわかりやすいです。簡単に概要だけ知りたい場合はこのほうがわかりやすいです。

Chat GPT

Claudeのように完結に作品の流れを解説しています。また参考文献や情報ソースへの誘導も行われました。

DeepSeek

Geminiのように各分類ごとに解説され、物語の流れや背景、世に与えた影響など詳しいことも把握できるようになっています。最後には続けてプロンプトを打ち込ませるように誘導するので親切です。

以上を見ると同じプロンプトでも、物語のあらすじだけを簡潔に説明するものから作品の特徴や人気の秘密などについて補足されるものまで様々で、一長一短といったところです。さて、何故ファーストガンダムのあらすじ?最近とある理由でまた機動戦士ガンダムをサブスク配信で見返し始めたので…。いずれはPythonやJavaなどコード生成の比較してみようと思います。

JavaScriptでスマホの加速度センサーを使用する

2025/01/17 (金) - 09:00 JavaScript

スマホのWebブラウザで加速度センサーを利用し、端末の向きや傾きでゲームを作ったり、ちょっとしたギミックを仕掛けることができます。スマホの加速度を利用するにはDeviceMotionEventで発火でき、傾きや方向などをX軸・Y軸・Z軸で取得できます。詳しくは以下のドキュメントにて。

ただし、iOS(iPhone)ではちょっと細工が必要だったのでメモです。HTMLは以下の通り。

<button id="btn_flg">加速度センサーを許可する</button>
<p id="txt_position"></p>

JavaScript部分は以下の通り。

const btn_flg = document.getElementById('btn_flg'); //iOS専用許可ボタン
const txt_position = document.getElementById('txt_position'); //座標表示用
let x,y,z;
const isMotionEvent = () => {
 DeviceMotionEvent.requestPermission().then((res) => {
  if(res === 'granted'){
   //許可した時
   alert('スマホを動かしてね');
  }else{
   //許可しなかった時
   alert('🥺');
  }
 }).catch((e) => {
  alert(e);
 });
}
const motionEvent = (e) => {
  x = Math.floor(e.accelerationIncludingGravity.x);
  y = Math.floor(e.accelerationIncludingGravity.y);
  z = Math.floor(e.accelerationIncludingGravity.z);
  //それぞれの座標を表示させる
  txt_position.textContent=`X:${x} Y:${y} Z:${z}`;
}
if(window.DeviceMotionEvent){
 if(DeviceMotionEvent.requestPermission){
  //iOS専用 センサー取得の許可ボタン
  btn_flg.addEventListener('click',() => {
   isMotionEvent();
  });
 }else{
  btn_flg.style.display = 'none';
 }
 window.addEventListener('devicemotion', motionEvent);
}

iOS13以降はユーザの許可無く加速度センサーの利用ができず、予めセンサーの利用の承諾をユーザに得なくてはなりません。その許可を行うダイアログを表示させるためにボタンを“タップさせて”、端末のセンサーの利用を許可してもらいます。この時使う関数がDeviceMotionEvent.requestPermissionの部分です。必ずユーザーがタップしてダイアログを表示させる必要があります。

DeviceMotionEvent.requestPermission().then((res) => {
 if(res === 'granted'){
  //許可した時
 }else{
  //許可しなかった時
 }
}).catch((e) => {
 //エラー
 console.log(e);
});

現状のAndroidOSのブラウザではそのまま加速度センサーが使えました。以下は加速度センサーのサンプル。

スマホを降るとプログレスバーのゲージが貯まり、ずんだもんの顔が赤くなります。なお、先述のようにiOSでは先に[Shake Hand!]のボタンを押下してください。2025年現在で最新のiOSのSafari及びAndroidのGoogleChromeで確認済み。

Next.js(App Router)でBASIC認証をかける

2025/01/09 (木) - 09:00 JavaScriptServer

Next.jsのApp RouterでBASIC認証(基本認証)をかける場合は、middlewareという機能を使用します。これを使うとクライアントとサーバーの中間の処理制御を行えます。今回は以下の環境にて実装テスト。

  • Next.js v14.xx

middlewareを使用するにはmiddleware.tsというファイルを作成し処理を記述します。このmiddlewareはクライアントからのリクエストに対し様々なレスポンス処理をするものです。例えばアクセス認証やリダイレクトなどを行うことができ、nginx.confや.htaccessみたいな事ができます。ただしNext.jsでは現時点でmiddlewareを1つしか配置できませんのでsrcディレクトリがあれば、src/middleware.tsに配置するようにします。

middleware.tsでBASIC認証をかける

middleware.tsに以下のように記述しました。

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const username = process.env.BASIC_AUTH_USER;
const password = process.env.BASIC_AUTH_PASS;

export function middleware(request: NextRequest) {
 const authHeader = request.headers.get('authorization');
 if (authHeader) {
  const encodedCredentials = authHeader.split(' ')[1];
  const decodedCredentials = atob(encodedCredentials);
  const [reqUsername, reqPassword] = decodedCredentials.split(':');
  if (reqUsername === username && reqPassword === password) {
   return NextResponse.next();
  }
 }
 const response = new NextResponse('Authentication required', { status: 401 });
 response.headers.set('WWW-Authenticate', 'Basic realm="Secure Area"');
 return response;
}

export const config = {
 matcher: '/((?!api|_next/static|favicon.ico|sitemap.xml|robots.txt).*)'
};

config.matcherにマッチングルールを指定します。上記の例だとfavicon.icoや画像などの静的ファイルは認証から除外します。

そして、認証に必要なユーザ名とパスワードは安全のため.envファイルに記述します。

BASIC_AUTH_USER=user
BASIC_AUTH_PASS=password

認証がうまく行ったらアクセスしたコンテンツを表示し、認証に失敗したら401ステータスを返します。設定後Next.jsを再起動すると動くと思います。

メディアクエリとコンテナクエリの使い分け

2025/01/06 (月) - 09:00 CSS

レスポンシブデザインで必須となるメディアクエリ(Media Query)とコンテナクエリ(Container Query)。どちらを採用すべきなのか迷うことがありましたので、目的や使用シーンを整理しました。

メディアクエリ(Media Query)とは?

メディアクエリは、主にビューポート(viewport)のサイズに基づいてスタイルを変更するために使用されます。デバイスの画面幅や高さに応じて、異なるCSSスタイルを適用することができます。

ブレークポイントを設定することで、ブラウザの画面幅のピクセル数や、デバイスの向き(縦向き、横向き)などでCSSスタイルを切り替えることが出来ます。

@media screen and (max-width: 768px) {
 body {
  color: red;
 }
}

上記では、ブラウザの表示領域が768px以下だったら、文字色が赤くなります。

コンテナクエリ(Container Query) とは?

コンテナクエリは、親要素(コンテナ)のサイズに基づいてスタイルを変更するための技術です。特定の要素がどのようなサイズに収まるかによって、子要素のスタイルを動的に変更することができます。

<div class="parent">
 <p class="child">子供要素</p>
</div>

コンテナクエリを実現するには必ず親となる要素と、スタイルを適用する子要素が必要です。親要素のセレクタにはcontainer-typeプロパティを指定し、値にはinline-sizesizeのいずれかの値を指定をします。

  • normal : デフォルト値(解除)
  • inline-size : インライン軸(横幅)
  • size : インライン軸とブロック軸(縦横幅)
.parent {
 container-type: inline-size;
}
@container (max-width: 480px) {
 .child {
  color: red;
 }
}

子要素のセレクタには幅指定とスタイルを指定します。上記のコードでは親要素の幅が480px以下の場合、子要素の文字色が赤くなります。

コンテナクエリとメディアクエリの使いどころ

メディアクエリ

ページ全体のレイアウト制御に使うことが多いです。例えば画面幅によってがらっとレイアウトが変わるような制御や、スマホやタブレットの向きでレイアウトを変更する場合は、メディアクエリが最適です。

コンテナクエリ

特定の要素のサイズ変更に応じて細かいデザイン制御したい場合は、コンテナクエリが最適です。例えばナビゲーションやフォームなど、デザインパーツやコンポーネント単位で細かいデザインを制御したい要望ではコンテナクエリが最適です。

コンテナクエリのブラウザの対応状況は、Chromeは105以降、Safariは16以降、Firefoxは110以降で利用でき2025年1月現在の最新ブラウザでは利用することが可能ですので、今後積極的に使うケースは多くなると思います。

Astro+WordPress 記事検索ページをSSRで作る時のポイント

2024/12/27 (金) - 09:00 JavaScriptPHP&CMS

2024年最後の更新かな?

AstroとWordPressを組み合わせてヘッドレスCMSで検索ページを作った際につまづいた点。Next,jsとは異なりAstroは基本SSGで静的書き出しをすることを前提としたシステムで、SSRでサーバーサイド処理する場合は少し設定が必要です。要は公式ドキュメントに書いてあるのでよく読もうね〜というお話です。今回の開発環境は以下の通り。

  • WordPress 6.5.x (PHP 8.2.x)
  • Astro 4.16.x

まず、コンフィグファイルのastro.config.mjsにoutput: 'hybrid'を記述します。

export default defineConfig({
 output: 'hybrid',
 site: 'https://example.com'
})

次にSSRで動作させたい検索結果ページ(/search/index.astro)に以下のように記述します。コード内にexport const prerender = false;を記載することでサーバーレンダリングされるようになります。逆に何も記述していない記事一覧や記事詳細などは静的レンダリングされます。以下は検索フォームからGETパラメータで[s]という値を受け取ってREST APIで検索した例です。

---
export const prerender = false; /* ←これを付加する */
import Layout from '../../layouts/Layout.astro';
import { format } from 'date-fns';
const seaechText = Astro.url.searchParams.get('s');
const JSON_URL = `${import.meta.env.WP_JSON_URL}?_embed&search=${seaechText}`;
const posts = await getPostSearch();
const seaechTotal = await getPostSearchTotal();
async function getPostSearch() {
 const res = await fetch(JSON_URL);
 return res.json()
}
async function getPostSearchTotal() {
 const count = await fetch(JSON_URL).
 then((res) => {return parseInt(res.headers.get('X-WP-Total'))});
 return count;
}
---
<Layout title=`記事検索:${seaechText}件`>
<main>
<h1>{seaechText}の検索結果{seaechTotal}件 見つかりました</h1>
<ul>
 { posts.map((post) =>
  <li>
   <a href={`/${post.slug}`}>
    <p>{post.title.rendered}</p>
    〜〜中略〜〜
   </a>
  </li>
 )}
</ul>
</main>
</Layout>

検索結果の例

また、Astroでサーバーサイドレンダリングで実行するときは、アダプターと呼ばれる追加ランタイムをインストールする必要があります。astro addコマンドでインストールが可能で、例えばサーバがnode.jsであれば以下のコマンドで実行できます。

$ npx astro add node

参考リンク:@astrojs/node | Docs

ChatGPTとGASを連携しスプレッドシートのセルの文章を解析・要約する

2024/12/24 (火) - 09:00 JavaScript

顧客からの問い合わせやレビューなどの文章をAIを使いざっくりと解析と要約をして効率化する方法。GoogleスプレッドシートのGoogle Apps Script(GAS)と、ChatGPT APIを連携することで半自動化をすることが出来ます。実装する前に予めOpenAI developer platformからAPI Keysを取得しておきましょう。

前回作ったメールフォームから送信された内容をGoogleスプレッドシートに集約している場合などにも効果的です。

ChatGPTが要約した内容を実際に表示させた例

GASにスクリプトを実装する

予めスプレッドシートを用意して開いておき、メニューから[拡張機能]→[Apps Script]を選択します。コード入力欄が表示されるので以下のようにコードを入力します。

function cellSummary(targetCell) {
 const apiUrl = 'https://api.openai.com/v1/chat/completions';
 const apiKey = '************'; // ここに取得したAPIキーを入力
 const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
 const promptText = targetCell;
 if (!promptText) {
  return '入力されていません。';
 }
 const payload = {
  model: 'gpt-4-turbo',
  messages: [
   { role: 'system', content: '以下のテキストを分析し、100文字程度で簡潔に要約し、[満足][不満]で分類してください' },
   { role: 'user', content: promptText }
  ],
  max_tokens: 1000
 };
 const options = {
  method: 'post',
  headers: {
   'Authorization': `Bearer ${apiKey}`,
   'Content-Type': 'application/json'
  },
  payload: JSON.stringify(payload)
 };
 try {
  const response = UrlFetchApp.fetch(apiUrl, options);
  const json = JSON.parse(response.getContentText());
  const result = json.choices[0].message.content.trim();
  return result; // 要約結果をスプレッドシートに表示
 } catch (error) {
  return `エラーが発生しました:${error.message}`; // エラー時はエラー文をスプレッドシートに表示
 }
}

スクリプトに構文エラーがないことを確認したら、保存ボタンをクリックして保存します。この時実行権限とスプレッドシートへのアクセス許可、および、外部ネットワークへの連携をしていいか聞いてくるのですべて許可しておきます。

スプレッドシートに数式を入力する

セルに数式を記入する

次に用意されたスプレッドシートで解析内容を表示したいセルにカスタム関数を入力します。このとき引数ととして[解析したいセル番号]を指定します。例えば、解析したい文章がF6セルだったら以下のようになります。

=cellSummary(F6)

入力すると通信が始まり、GPTが解析してくれたようやく内容がそのセルに表示されます。以上、ざっくりとしたChatGPT APIとGoogleスプレッドシート(GAS)の連携方法でした。

transitionでheight:autoを動かす2つの方法。

2024/12/20 (金) - 09:00 CSSJavaScript

アコーディオンメニューをCSSで実装する際、height:0からheight:autoにすることで開閉はしますがトランジションは動きません。それを解決するためにmax-heightを使うテクニックやjQueryのslideToggleを使うことが多かったと思いますが、どうもしっくりこない。今回はそれを暫定的に解決する方法を見つけたので紹介します。

HTMLは以下。dtをタップしたら閉じているdd要素を開閉するようにします。

<dl>
<dt>レシピを見る</dt>
<dd><ul>
<li>パスタ:200グラム</li>
<li>キャベツ:1〜2枚</li>
<li>...</li>
<li>にんにく:1欠</li>
<li>オリーブオイル:大さじ2</li>
<li>塩コショウ:適量</li>
</ul></dd>
</dl>

JavaScriptは以下のように、dt要素をタップした時dt要素自体にclass属性を付けて見た目の動きはCSSで処理するようにします。

const toggleBtn = document.getElementsByTagName('dt');
for (let dt of toggleBtn) {
 dt.addEventListener('click',function(e){
  this.classList.toggle('toggleOn');
  e.preventDefault();
 },false);
}

こちらではdtをタップしたら自分自身に、classList.toggletoggleOnというclass属性値を追加・削除します。

1.gridを使うパターン

少しハックっぽいですが、gridで行の高さを指定するgrid-template-rowsプロパティで指定する方法です。最初は0frで隠しておき、開くときはで1frに切り替えるというものです。実はtransitionが有効なんですね。こちらはgridが使えるブラウザであれば動きます。

dt{
 cursor: pointer;
}
dd{
 display: grid;
 grid-template-rows: 0fr;
 transition: grid-template-rows 0.25s;
}
.toggleOn+dd {
  grid-template-rows: 1fr;
}
ul {
 overflow-y: hidden;
}

2.interpolate-sizeを使うパターン

dd要素にinterpolate-size: allow-keywords;というCSSプロパティを付与し、height: 0;overflow: hidden;で隠しておきます。dtをタップしたらddにheight: auto;の値を付与するとtransitionautoの高さまでトランジションが動きます。このinterpolate-size: allow-keywords;を指定すると、auto/min-content/max-contentといったキーワード系の値に対しても数値が有効になりトランジションが動作します。

dt{
 cursor: pointer;
}
dd{
 interpolate-size: allow-keywords;
 height: 0;
 transition: height 0.25s;
 overflow: hidden;
}
.toggleOn+dd {
  height: auto;
}

非常にシンプルでわかりやすいと思いますが、2024年12月時点ではChrome 129および、Android Chrome 131以降のみ対応しており、SafariやFirefoxでは未対応です。未対応ブラウザではトランジションしないだけでプログレッシブエンハンスメントとして動きはするので問題はありませんが、すべてのブラウザでトランジションさせたい場合はgridを採用するか従来通りmax-heightのテクを採用するのが良さそうです。

参考ページ

WordPressのショートコードで他の記事情報を表示する

2024/12/18 (水) - 09:00 PHP&CMS

こことは別に運用してるブログのWordPressの記事の中に、引用や関連リンクとして他の記事の情報を貼りたくなりました。単純にエディタのリンク機能から記事の内部リンクを指定して貼っても良いのですが、アイキャッチやタイトルなどの細かい情報をいちいちコピペするのが面倒なので、ショートコードを使い動的に取得出来るように自前で実装しました。

WordPressプラグインでも似たようなことを再現できるのもあると思うのですが、探すのが面倒だったのとデザインカスタマイズが難しかったりするので、独自実装する事にしました。

環境は以下の通り。

  • WordPress 6.5.x
  • PHP 8.1.x

記事IDを指定したショートコードをエディタ内に埋め込む

まずショートコードを作り記事内の任意の場所に埋め込みます。idの属性値には引用したい記事の記事ID(PostID)を指定します。

[relatedLink id="256"]

次にfunction.phpに以下のようにショートコードの表示プログラムを実装します。マークアップや呼び出す関数は表示したい内容やデザインに応じてカスタマイズします。

function relatedLinkFunc ( $attr ) {
 extract( shortcode_atts( array(
  'id' => ''
 ), $attr ));
 $link = get_permalink($id);
 $title = get_the_title($id);
 $date = get_the_time('Y年m月d日',$id);
 $thumbnail = '';
 if(has_post_thumbnail($id)){
  $thumbnailURL  = get_the_post_thumbnail_url($id,'full');
  $thumbnail = "<img src=\"{$thumbnailURL}\" alt=\"\" width=\"400\" height=\"400\"  loading=\"lazy\" />";
 }
 $result = "<div><a href=\"{$link}\">{$thumbnail}<span>{$title}</span><time>{$date}</time></p></a></div>";
 return $result;
}
add_shortcode('relatedLink', 'relatedLinkFunc');

その後、プレビューして問題なく記事が表示されていることを確認し、CSSで見た目をカスタマイズすればOKです。

記事引用の表示例

これにより引用先のタイトルや内容が変わっても自動で反映されるので、運営の手間が省けます。またブログ内の回遊率も良くなるので、PVのアップやSEOの底上げにもつながります。

ページの先頭へ