banner
Vinking

Vinking

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

Add a simple emoji feature to Shiro

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

OwO's emoji functionality

The integration method provided by the OwO library aligns more with traditional front-end development patterns, while the current open-source version of the Shiro theme is developed based on the NextJS framework, making it unsuitable for direct integration into the theme. Therefore, a simplified version of the emoji package needs to be developed manually.

Creating the 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 matches 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",
      },
      //...
    ],
  },
};

Adding the Emoji Button#

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

However, due to frame drops when scrolling this selector on my computer, I chose to replace the Emoji Picker with my own emoji selector component.

1. Import the 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 the 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 the 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 check if the current emoji package is Emoticons and use a Grid layout to align the elements:

<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:

Not using 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 to xLog by Mix Space 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.