在使用 Typecho 程序搭建博客的時候,一般會使用 OwO 庫 來為博客提供表情包功能,Like this:
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',
},
//...
],
},
Emoji: {
type: "emoji",
container: [
{
icon: '😂',
text: 'Face with Tears of Joy',
},
//...
],
},
图片表情: {
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
(即 颜文字
、 Emoji
或者其他亂七八糟的類別),並且使用 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>
不然就會像下面這樣參差不齊:
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(``)
} 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>Loading Emojis...</div>
)}
</>
)
})
Stickers.displayName = 'Stickers'
最後,只需要重新構建項目,就可以在 Shiro 中使用表情包功能了😊。
此文由 Mix Space 同步更新至 xLog 原始鏈接為 https://www.vinking.top/posts/codes/integrating-stickers-into-shiro-theme