banner
Vinking

Vinking

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

Add a simple emoji feature to Shiro

When building a blog using the Typecho program, it is common to use the OwO library to provide emoji functionality for the blog, like this:

OwO emoji functionality

The integration method provided by the OwO library is more in line with traditional front-end development patterns, while the current open-source version of the Shiro theme is developed based on the NextJS framework, which is not suitable for direct integration into the theme. Therefore, we need to develop a simplified version of the emoji package ourselves.

Create Emoji Data File#

In OwO, emoji data is obtained through the OwO.json file. For the Shiro theme, we can create a owo.ts file in the src/lib directory to define the emoji data. In this file, we create an owo object and ensure its content is consistent with the structure in OwO.json to inherit the emoji data from the previous Typecho theme.

export const owo = {
  Emoticons: {
    type: "emoticon",
    container: [
      {
        icon: 'OωO',
        text: 'OωO',
      },
      //...
    ],
  },
  Emoji: {
    type: "emoji",
    container: [
      {
        icon: '😂',
        text: 'Face with Tears of Joy',
      },
      //...
    ],
  },
  ImageEmojis: {
    type: "image",
    container: [
      {
        icon: "https://raw.githubusercontent.com/url8/CDN-EMOTE/main/2233/chijing.png",
        text: "face1",
      },
      //...
    ],
  },
};

Add Emoji Button#

The Shiro theme uses the Emoji Picker library as the emoji selector.

However, because there is frame dropping when scrolling this selector on my computer, I chose to replace the Emoji Picker with my own emoji selector component.

1. Import Custom Emoji Selector Component:

First, in src\components\modules\comment\CommentBox\UniversalTextArea.tsx, replace the originally imported EmojiPicker component with our own emoji selector component Stickers:

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

2. Replace Existing EmojiPicker Component:

Replace <EmojiPicker onEmojiSelect={handleInsertEmoji} /> in the return of the UniversalTextArea component with <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. Modify Emoji Button:

Finally, modify the emoji button in the lower left corner of the text box:

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

This completes the modification of the comment area text box.

Emoji Selector Component#

Next, we need to write the emoji selector component Stickers that we just imported. This component will be responsible for rendering the emoji selector and handling user click events.

We enter the src\components\modules\shared directory and create a new file named Stickers.tsx.

Inside the component, we use useState to manage the current emoji category currentCategory (i.e., Emoticons, Emoji, or other random categories), and use useMemo to cache the emoji categories and the currently selected emoji data.

It is important to note: Considering the varying lengths of emoticons, we need to use a Grid layout to align elements when the current emoji package is Emoticons:

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

Otherwise, it will look uneven like this:

Without Grid Layout

The complete code for Stickers.tsx is as follows:

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>('Emoticons')

  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 === 'Emoticons'
                ? '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>Loading Emojis...</div>
      )}
    </>
  )
})

Stickers.displayName = 'Stickers'

Finally, just rebuild the project, and you can use the emoji functionality in Shiro 😊.

This article was synchronized by Mix Space to xLog. The original link is https://www.vinking.top/posts/codes/integrating-stickers-into-shiro-theme

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.