banner
Vinking

Vinking

你写下的每一个BUG 都是人类反抗被人工智能统治的一颗子弹

Shiroにシンプルなスタンプ機能を追加する

Typecho プログラムを使用してブログを構築する際、一般的に OwO ライブラリ を使用してブログに絵文字機能を提供します。次のように:

OwO の絵文字機能

OwO ライブラリが提供する統合方法は、従来のフロントエンド開発モデルにより適していますが、現在の Shiro オープンソース版のテーマは NextJS フレームワークに基づいて開発されており、直接テーマに統合するのには適していません。したがって、簡略版の絵文字を自分で開発する必要があります。

絵文字データファイルの作成#

OwO では、絵文字データは OwO.json ファイルから取得されます。Shiro テーマの場合、src/lib ディレクトリに owo.ts ファイルを作成して絵文字データを定義できます。このファイル内で、owo オブジェクトを作成し、その内容を OwO.json の構造と一致させて、以前の Typecho テーマの絵文字データを継承できるようにします。

export const owo = {
  顔文字: {
    type: "emoticon",
    container: [
      {
        icon: 'OωO',
        text: 'OωO',
      },
      //...
    ],
  },
  絵文字: {
    type: "emoji",
    container: [
      {
        icon: '😂',
        text: '喜びの涙を流す顔',
      },
      //...
    ],
  },
  画像絵文字: {
    type: "image",
    container: [
      {
        icon: "https://raw.githubusercontent.com/url8/CDN-EMOTE/main/2233/chijing.png",
        text: "face1",
      },
      //...
    ],
  },
};

絵文字ボタンの追加#

Shiro テーマでは、絵文字選択器として Emoji Picker ライブラリを使用しています。

しかし、私のコンピュータでこの選択器をスライドさせるとフレーム落ちが発生するため、自分で作成した絵文字選択器コンポーネントで Emoji Picker を直接置き換えることにしました。

1. カスタム絵文字選択器コンポーネントをインポート:

まず、src\components\modules\comment\CommentBox\UniversalTextArea.tsx で、元の EmojiPicker コンポーネントを自分の絵文字選択器コンポーネント Stickers に置き換えます:

const Stickers = dynamic(() =>
  import('../../shared/Stickers').then((mod) => mod.Stickers),
)

2. 既存の EmojiPicker コンポーネントを置き換え:

UniversalTextArea コンポーネントの返り値の <EmojiPicker onEmojiSelect={handleInsertEmoji} /><Stickers handleInsertEmoji={handleInsertEmoji} /> に置き換えます:

return (
  <TextArea
    bordered={false}
    wrapperClassName={className}
    ref={taRef}
    defaultValue={value}
    onKeyDown={handleKeyDown}
    onChange={(e) => setter('text', e.target.value)}
    placeholder={placeholder}
    onCmdEnter={(e) => {
      e.preventDefault()
      sendComment()
    }}
  >
    <CommentBoxSlotPortal>
      <>
        {/* {!isMobile && (
          <FloatPopover
            mobileAsSheet
            trigger="click"
            TriggerComponent={EmojiButton}
            headless
            popoverClassNames="pointer-events-auto"
          >
            <EmojiPicker onEmojiSelect={handleInsertEmoji} />
          </FloatPopover>
        )} */}
        <FloatPopover
          mobileAsSheet
          trigger="click"
          TriggerComponent={EmojiButton}
          headless
          popoverClassNames="pointer-events-auto shadow-perfect relative z-[2] rounded-xl border border-zinc-400/20 bg-white/80 p-4 outline-none backdrop-blur-lg dark:border-zinc-500/30 dark:bg-neutral-900/80 max-w-lg"
        >
          <Stickers handleInsertEmoji={handleInsertEmoji} />
        </FloatPopover>
      </>
    </CommentBoxSlotPortal>
  </TextArea>
)

3. 絵文字ボタンを変更:

最後に、テキストボックスの左下隅にある絵文字ボタンを変更します:

const EmojiButton = () => {
  return (
    <div className="ml-4 text-base md:text-xs" role="button" tabIndex={0}>
      <span>OwO</span>
    </div>
  )
}

これで、コメントエリアのテキストボックスの変更が完了しました。

絵文字選択器コンポーネント#

次に、先ほどインポートした絵文字選択器コンポーネント Stickers を作成する必要があります。このコンポーネントは、絵文字選択器をレンダリングし、ユーザーのクリックイベントを処理します。

src\components\modules\shared ディレクトリに移動し、Stickers.tsx という新しいファイルを作成します。

コンポーネント内部では、useState を使用して現在の絵文字カテゴリ currentCategory(つまり 顔文字絵文字 またはその他のカテゴリ)を管理し、useMemo を使用して絵文字カテゴリと現在選択されている絵文字データをキャッシュします。

注意が必要なのは、顔文字の長さが異なることを考慮し、現在の絵文字パッケージが 顔文字 の場合は Grid レイアウトで要素を整列させる必要があることです:

<div
  className={`max-h-80 gap-4 overflow-x-hidden overflow-y-scroll pb-4 md:pb-0 ${
    currentCategory === '顔文字'
      ? 'grid grid-cols-4'
      : 'flex flex-wrap'
  }`}
>
  { ... }
</div>

そうしないと、以下のように不揃いになります:

Grid レイアウトを使用しない

Stickers.tsx の完全なコードは以下の通りです:

import { memo, useCallback, useMemo, useState } from 'react'
import type { FC } from 'react'

import { owo } from '~/lib/owo'

type EmojisType = typeof owo
type CategoryKey = keyof EmojisType

export const Stickers: FC<{
  handleInsertEmoji: (emoji: string) => void
}> = memo(({ handleInsertEmoji }) => {
  const [currentCategory, setCurrentCategory] = useState<CategoryKey>('顔文字')

  const emojis: EmojisType = owo

  const handleClickEmoji = useCallback(
    (emoji: { icon: string; text: any }) => {
      if (emoji.icon.startsWith('http')) {
        handleInsertEmoji(`![${emoji.text}](${emoji.icon})`)
      } else {
        handleInsertEmoji(emoji.icon)
      }
    },
    [handleInsertEmoji],
  )

  const categories = useMemo(
    () => Object.keys(emojis) as CategoryKey[],
    [emojis],
  )

  const currentEmojis = useMemo(
    () => emojis[currentCategory]?.container || [],
    [emojis, currentCategory],
  )

  return (
    <>
      {emojis ? (
        <>
          <div className="mb-2 flex flex-wrap gap-4 text-xs">
            {categories.map((category) => (
              <button
                key={category}
                className={`rounded-md px-2 py-1 ${
                  currentCategory === category ? 'text-accent' : 'opacity-50'
                }`}
                onClick={() => setCurrentCategory(category)}
              >
                {category}
              </button>
            ))}
          </div>
          <div
            className={`max-h-80 gap-4 overflow-x-hidden overflow-y-scroll pb-4 md:pb-0 ${
              currentCategory === '顔文字'
                ? 'grid grid-cols-4'
                : 'flex flex-wrap'
            }`}
          >
            {currentEmojis.map((item: { icon: string; text: string }) => (
              <button
                key={item.text}
                title={item.text}
                onClick={() => handleClickEmoji(item)}
                style={{
                  backgroundImage: item.icon.startsWith('http')
                    ? `url(${item.icon})`
                    : '',
                }}
                className={
                  item.icon.startsWith('http')
                    ? 'h-10 w-10 bg-cover bg-center bg-no-repeat'
                    : ''
                }
              >
                {!item.icon.startsWith('http') && item.icon}
              </button>
            ))}
          </div>
        </>
      ) : (
        <div>絵文字を読み込み中...</div>
      )}
    </>
  )
})

Stickers.displayName = 'Stickers'

最後に、プロジェクトを再構築するだけで、Shiro で絵文字機能を使用できるようになります😊。

この記事は Mix Space によって xLog に同期更新されました。元のリンクは https://www.vinking.top/posts/codes/integrating-stickers-into-shiro-theme

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。