自作Webアプリで文字の表示方法調整

作成している自作Webアプリで文字の表示方法を調整する機会がありましたので、記録に残しておきたいと思います。

自作Webアプリの内容は、IT技術に関する4拓問題を出題して回答するアプリです。

https://probable-computing-machine-g3ut.vercel.app/

# 修正前
![スクリーンショット 2025-05-11 131148.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4078294/1fd2f1ce-9103-40e4-9682-daaef07588a0.png)

問題文で、日本語の文章とコードブロックの文章の見た目が変わらず、見づらくなってしまっていました。

# 修正後
![スクリーンショット 2025-05-11 131701.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4078294/39099053-efcd-4fa4-ab34-30e866c27bae.png)

コードブロックの背景が黒背景になって、見やすくなりました！

# どのように解決したのか
解決したと偉そうに書いていますが、解決はClaude3.7にお任せしました。
Claude3.7はformatTextWithCodeBlocks関数というのを作成してくれました。

後追いになりますが、どのような関数を作ってくれたのか紐解いてみます。

# formatTextWithCodeBlocks関数の概要
この関数は、テキスト内にある「コードブロック」を見つけて、それらを特別なスタイルで表示するためのものです。
コードブロックには２種類あります：

* トリプルバッククォート（```）で囲まれたブロック: 複数行のコードを表示
* シングルバッククォート（`）で囲まれたブロック: 文章内の単語や短いコードを強調表示

**ステップ1: 初期準備**
```jsx
// テキストが空なら何もしない
if (!text) return null;
  
// 結果を格納するための配列を作成
const parts = [];
```

**ステップ2: コードブロックを検出するための正規表現**
```jsx
const regex = /(```[\s\S]*?```)|(`[^`]*?`)/g;
```
* (```[\s\S]*?```): トリプルバッククォート（```）で囲まれた任意の文字列を検出

* [\s\S]*?: 任意の文字（改行を含む）を「最小限」マッチさせる


* |: または
* ([^]*?): シングルバッククォート（）で囲まれた任意の文字列を検出

* [^]*?`: バッククォート以外の任意の文字を「最小限」マッチさせる


* g: グローバルフラグ（テキスト全体を検索する）

**ステップ3: テキストを順番に処理**
```jsx
let lastIndex = 0; // 最後に処理した位置
let match;
  
// パターンにマッチする部分をすべて検出
while ((match = regex.exec(text)) !== null) {
    // コードブロックの前にある普通のテキスト部分を保存
    if (match.index > lastIndex) {
      parts.push({
        type: 'text',
        content: text.substring(lastIndex, match.index)
      });
    }
    
    // トリプルバッククォートの場合
    if (match[1]) {
      // バッククォート自体を取り除く（前後の3文字ずつ）
      const codeContent = match[1].substring(3, match[1].length - 3);
      parts.push({
        type: 'triple-code',
        content: codeContent
      });
    } 
    // シングルバッククォートの場合
    else if (match[2]) {
      // バッククォート自体を取り除く（前後の1文字ずつ）
      const codeContent = match[2].substring(1, match[2].length - 1);
      parts.push({
        type: 'single-code',
        content: codeContent
      });
    }
    
    // 次の検索開始位置を更新
    lastIndex = match.index + match[0].length;
}

// テキストの最後まで処理したら、残りの部分も普通のテキストとして保存
if (lastIndex < text.length) {
  parts.push({
    type: 'text',
    content: text.substring(lastIndex)
  });
}
```
この部分がテキストを分割する核心部分です。テキスト全体をスキャンして：

1. 普通のテキスト部分は「text」タイプとして保存
2. トリプルバッククォートで囲まれた部分は「triple-code」タイプとして保存（バッククォート自体は削除）
3. シングルバッククォートで囲まれた部分は「single-code」タイプとして保存（バッククォート自体は削除）

**ステップ4: 分割したパーツをスタイル付きでレンダリング**
```jsx
// 分割したパーツを適切なスタイルでレンダリング
return parts.map((part, index) => {
  if (part.type === 'single-code') {
    // インラインコード用のスタイル
    return (
      <code 
        key={index} 
        className="px-1.5 py-0.5 mx-0.5 bg-gray-100 dark:bg-gray-700 ..." 
      >
        {part.content}
      </code>
    );
  } else if (part.type === 'triple-code') {
    // ブロックコード用のスタイル
    let language = '';
    let codeContent = part.content;
    
    // 言語名が指定されていたら抽出
    const firstLineMatch = part.content.match(/^(\w+)\s*\n([\s\S]*)$/);
    if (firstLineMatch) {
      language = firstLineMatch[1]; // 言語名
      codeContent = firstLineMatch[2]; // 残りのコード
    }
    
    return (
      <pre className="p-4 my-3 bg-gray-900 text-gray-100 ...">
        <code>{codeContent}</code>
      </pre>
    );
  } else {
    // 普通のテキスト
    return <span key={index}>{part.content}</span>;
  }
});
```
この部分では、分割したパーツをそれぞれ適切なスタイルでレンダリングします：

1. 通常テキスト: 普通の<span>要素として表示
2. シングルコード: 浅い灰色背景の<code>要素として表示（インラインコード用）
3. トリプルコード: 黒背景の<pre><code>要素として表示（ブロックコード用）

* もし最初の行が言語名（例：javascript）なら、その言語をデータ属性に設定し、表示はしない

# まとめ
まだまだ自作Webアプリは改良中ですが、このような見えやすさの部分も改良していくことで、より使いやすいアプリを目指していきたいと思います！