Commit a9e9e7d7 authored by Яков's avatar Яков
Browse files

add emoji

parent 6c1501b1
{ {
"name": "react-ag-qeditor", "name": "react-ag-qeditor",
"version": "1.1.37", "version": "1.1.38",
"description": "WYSIWYG html editor", "description": "WYSIWYG html editor",
"author": "atma", "author": "atma",
"license": "MIT", "license": "MIT",
...@@ -95,6 +95,7 @@ ...@@ -95,6 +95,7 @@
"antd": "^5.24.7", "antd": "^5.24.7",
"axios": "^0.27.2", "axios": "^0.27.2",
"babel-loader": "8.1.0", "babel-loader": "8.1.0",
"emojibase-data": "^16.0.3",
"invariant": "^2.2.4", "invariant": "^2.2.4",
"katex": "^0.15.3", "katex": "^0.15.3",
"levenary": "^1.1.1", "levenary": "^1.1.1",
......
...@@ -25,6 +25,7 @@ import Subscript from '@tiptap/extension-subscript' ...@@ -25,6 +25,7 @@ import Subscript from '@tiptap/extension-subscript'
import ToolBar from './components/ToolBar' import ToolBar from './components/ToolBar'
import EditorModal from './components/EditorModal' import EditorModal from './components/EditorModal'
import Uploader from './components/Uploader' import Uploader from './components/Uploader'
import EmojiPicker from './components/EmojiPicker'
import Video from './extensions/Video' import Video from './extensions/Video'
import Iframe from './extensions/Iframe' import Iframe from './extensions/Iframe'
// import CustomLink from './extensions/CustomLink' // import CustomLink from './extensions/CustomLink'
...@@ -263,6 +264,10 @@ const QEditor = ({ ...@@ -263,6 +264,10 @@ const QEditor = ({
title: 'Загрузить изображение', title: 'Загрузить изображение',
onClick: () => modalOpener('image', 'Загрузить изображение') onClick: () => modalOpener('image', 'Загрузить изображение')
}, },
emoji: {
title: 'Эмодзи',
render: ({ key }) => <EmojiPicker key={key} editor={editor} />
},
pdfToText: { pdfToText: {
title: 'Загрузить pdf', title: 'Загрузить pdf',
onClick: () => modalOpener('pdf', 'Загрузить pdf') onClick: () => modalOpener('pdf', 'Загрузить pdf')
......
import React, { useEffect, useMemo, useRef, useState } from 'react'
const emojiSource = require('emojibase-data/en/data.json')
const RECENT_STORAGE_KEY = 'atma-editor-recent-emojis'
const RECENT_LIMIT = 36
const CATEGORY_CONFIG = [
{
id: 'recent',
title: 'Недавние',
icon: '🕘',
groups: []
},
{
id: 'smileys',
title: 'Смайлы и люди',
icon: '😀',
groups: [0, 1]
},
{
id: 'animals',
title: 'Животные и природа',
icon: '🐻',
groups: [3]
},
{
id: 'food',
title: 'Еда и напитки',
icon: '🍔',
groups: [4]
},
{
id: 'activity',
title: 'Активности',
icon: '',
groups: [6]
},
{
id: 'travel',
title: 'Путешествия и места',
icon: '🚗',
groups: [5]
},
{
id: 'objects',
title: 'Объекты',
icon: '💡',
groups: [7]
},
{
id: 'symbols',
title: 'Символы',
icon: '♾️',
groups: [8]
},
{
id: 'flags',
title: 'Флаги',
icon: '🏳️',
groups: [9]
}
]
const createSearchText = (item) => {
const emoticon = Array.isArray(item.emoticon)
? item.emoticon.join(' ')
: item.emoticon || ''
return [
item.label,
item.hexcode,
item.emoji,
emoticon,
...(item.tags || [])
]
.filter(Boolean)
.join(' ')
.toLowerCase()
}
const flattenEmojiSource = () => {
const items = []
emojiSource.forEach((item) => {
if (typeof item.order === 'undefined' || item.group === 2 || item.group === null) {
return
}
const baseItem = {
emoji: item.emoji,
label: item.label,
hexcode: item.hexcode,
group: item.group,
order: item.order,
searchText: createSearchText(item)
}
items.push(baseItem)
if (Array.isArray(item.skins)) {
item.skins.forEach((skin) => {
items.push({
emoji: skin.emoji,
label: skin.label,
hexcode: skin.hexcode,
group: skin.group,
order: skin.order,
searchText: createSearchText({
...item,
...skin,
tags: item.tags
})
})
})
}
})
return items.sort((a, b) => a.order - b.order)
}
const allEmoji = flattenEmojiSource()
const emojiByCategory = CATEGORY_CONFIG.reduce((acc, category) => {
if (category.id === 'recent') {
acc[category.id] = []
return acc
}
acc[category.id] = allEmoji.filter((item) => category.groups.includes(item.group))
return acc
}, {})
const getRecentEmoji = () => {
if (typeof window === 'undefined') {
return []
}
try {
const rawValue = window.localStorage.getItem(RECENT_STORAGE_KEY)
if (!rawValue) {
return []
}
const parsedValue = JSON.parse(rawValue)
return Array.isArray(parsedValue) ? parsedValue : []
} catch (error) {
return []
}
}
const saveRecentEmoji = (items) => {
if (typeof window === 'undefined') {
return
}
window.localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(items.slice(0, RECENT_LIMIT)))
}
const EmojiPicker = ({ editor }) => {
const rootRef = useRef(null)
const [isOpen, setIsOpen] = useState(false)
const [searchValue, setSearchValue] = useState('')
const [activeCategory, setActiveCategory] = useState('smileys')
const [recentEmoji, setRecentEmoji] = useState(getRecentEmoji)
const tabs = recentEmoji.length > 0
? CATEGORY_CONFIG
: CATEGORY_CONFIG.filter((category) => category.id !== 'recent')
useEffect(() => {
if (!isOpen) {
return undefined
}
const onDocumentMouseDown = (event) => {
if (rootRef.current && !rootRef.current.contains(event.target)) {
setIsOpen(false)
}
}
const onDocumentKeyDown = (event) => {
if (event.key === 'Escape') {
setIsOpen(false)
}
}
document.addEventListener('mousedown', onDocumentMouseDown)
document.addEventListener('keydown', onDocumentKeyDown)
return () => {
document.removeEventListener('mousedown', onDocumentMouseDown)
document.removeEventListener('keydown', onDocumentKeyDown)
}
}, [isOpen])
useEffect(() => {
if (recentEmoji.length === 0 && activeCategory === 'recent') {
setActiveCategory('smileys')
}
}, [activeCategory, recentEmoji])
const visibleEmoji = useMemo(() => {
const normalizedQuery = searchValue.trim().toLowerCase()
if (normalizedQuery.length > 0) {
return allEmoji.filter((item) => item.searchText.includes(normalizedQuery))
}
if (activeCategory === 'recent') {
return recentEmoji
}
return emojiByCategory[activeCategory] || emojiByCategory.smileys
}, [activeCategory, recentEmoji, searchValue])
const insertEmoji = (item) => {
if (!editor) {
return
}
editor.chain().focus().insertContent(item.emoji).run()
const updatedRecent = [
item,
...recentEmoji.filter((recentItem) => recentItem.hexcode !== item.hexcode)
].slice(0, RECENT_LIMIT)
setRecentEmoji(updatedRecent)
saveRecentEmoji(updatedRecent)
setIsOpen(false)
setSearchValue('')
}
return (
<div
ref={rootRef}
className={'atma-editor-toolbar-emoji' + (isOpen ? ' open' : '')}
>
<div
className={'qicon qemoji' + (isOpen ? ' active' : '')}
title='Эмодзи'
onMouseDown={(event) => {
event.preventDefault()
}}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
setIsOpen(!isOpen)
}}
/>
{isOpen && (
<div
className='atma-editor-emoji-popover'
onMouseDown={(event) => {
event.preventDefault()
event.stopPropagation()
}}
onClick={(event) => {
event.stopPropagation()
}}
>
<div className='atma-editor-emoji-popover-search'>
<input
value={searchValue}
type='text'
placeholder='Поиск эмодзи'
className='atma-editor-emoji-search'
onChange={(event) => {
setSearchValue(event.target.value)
}}
/>
</div>
<div className='atma-editor-emoji-popover-tabs'>
{tabs.map((category) => (
<button
key={category.id}
type='button'
title={category.title}
className={
'atma-editor-emoji-tab' +
(searchValue.trim().length === 0 && activeCategory === category.id
? ' active'
: '')
}
onClick={() => {
setSearchValue('')
setActiveCategory(category.id)
}}
>
<span>{category.icon}</span>
</button>
))}
</div>
<div className='atma-editor-emoji-popover-title'>
{searchValue.trim().length > 0
? 'Результаты поиска'
: ((tabs.find((category) => category.id === activeCategory) || {}).title)}
</div>
<div className='atma-editor-emoji-grid'>
{visibleEmoji.length > 0
? visibleEmoji.map((item) => (
<button
key={item.hexcode}
type='button'
title={item.label}
className='atma-editor-emoji-item'
onClick={() => insertEmoji(item)}
>
<span>{item.emoji}</span>
</button>
))
: (
<div className='atma-editor-emoji-empty'>
Ничего не найдено
</div>
)}
</div>
</div>
)}
</div>
)
}
export default EmojiPicker
...@@ -42,6 +42,7 @@ const toolsInit = { ...@@ -42,6 +42,7 @@ const toolsInit = {
{ {
type: 'g', type: 'g',
items: [ items: [
'emoji',
'link', 'link',
'file', 'file',
'image', 'image',
...@@ -193,6 +194,15 @@ const ToolBar = ({ editor, toolsLib = [], toolsOptions }) => { ...@@ -193,6 +194,15 @@ const ToolBar = ({ editor, toolsLib = [], toolsOptions }) => {
if(toolsLib[type]) { if(toolsLib[type]) {
item = toolsLib[type]; item = toolsLib[type];
if (item.render) {
return item.render({
key: idx,
type,
isActive: isActive(type),
isDisabled: isDisabled(type)
})
}
if (isTitle) { if (isTitle) {
return ( return (
<div <div
......
...@@ -396,6 +396,10 @@ body{ ...@@ -396,6 +396,10 @@ body{
} }
} }
&-emoji{
position: relative;
}
&:hover{ &:hover{
z-index: 100000; z-index: 100000;
} }
...@@ -909,6 +913,9 @@ body{ ...@@ -909,6 +913,9 @@ body{
.qicon{ .qicon{
&.qemoji{
background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='9' cy='9' r='7.25' stroke='%231D1D1F' stroke-width='1.5'/%3E%3Ccircle cx='6.1' cy='7' r='1' fill='%231D1D1F'/%3E%3Ccircle cx='11.9' cy='7' r='1' fill='%231D1D1F'/%3E%3Cpath d='M5.75 10.4C6.4 11.65 7.57 12.25 9 12.25C10.43 12.25 11.6 11.65 12.25 10.4' stroke='%231D1D1F' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
}
&.qglossary{ &.qglossary{
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNiAxNCIgZmlsbD0iYmxhY2siIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0xNS40Mjg2IDAuNzM0Mzc2SDExLjM0MjlDMTAuNDY2MSAwLjczNDM3NiA5LjYwODkzIDAuOTg2MTYxIDguODcxNDMgMS40NjExNkw4IDIuMDIwMDlMNy4xMjg1NyAxLjQ2MTE2QzYuMzkxODEgMC45ODYyNTIgNS41MzM3MSAwLjczMzkwNyA0LjY1NzE0IDAuNzM0Mzc2SDAuNTcxNDI5QzAuMjU1MzU3IDAuNzM0Mzc2IDAgMC45ODk3MzMgMCAxLjMwNThWMTEuNDQ4N0MwIDExLjc2NDcgMC4yNTUzNTcgMTIuMDIwMSAwLjU3MTQyOSAxMi4wMjAxSDQuNjU3MTRDNS41MzM5MyAxMi4wMjAxIDYuMzkxMDcgMTIuMjcxOSA3LjEyODU3IDEyLjc0NjlMNy45MjE0MyAxMy4yNTc2QzcuOTQ0NjQgMTMuMjcxOSA3Ljk3MTQzIDEzLjI4MDggNy45OTgyMSAxMy4yODA4QzguMDI1IDEzLjI4MDggOC4wNTE3OSAxMy4yNzM3IDguMDc1IDEzLjI1NzZMOC44Njc4NiAxMi43NDY5QzkuNjA3MTQgMTIuMjcxOSAxMC40NjYxIDEyLjAyMDEgMTEuMzQyOSAxMi4wMjAxSDE1LjQyODZDMTUuNzQ0NiAxMi4wMjAxIDE2IDExLjc2NDcgMTYgMTEuNDQ4N1YxLjMwNThDMTYgMC45ODk3MzMgMTUuNzQ0NiAwLjczNDM3NiAxNS40Mjg2IDAuNzM0Mzc2Wk00LjY1NzE0IDEwLjczNDRIMS4yODU3MVYyLjAyMDA5SDQuNjU3MTRDNS4yODkyOSAyLjAyMDA5IDUuOTAzNTcgMi4yMDA0NSA2LjQzMzkzIDIuNTQxNTJMNy4zMDUzNiAzLjEwMDQ1TDcuNDI4NTcgMy4xODA4VjExLjQzMDhDNi41Nzg1NyAxMC45NzM3IDUuNjI4NTcgMTAuNzM0NCA0LjY1NzE0IDEwLjczNDRaTTE0LjcxNDMgMTAuNzM0NEgxMS4zNDI5QzEwLjM3MTQgMTAuNzM0NCA5LjQyMTQzIDEwLjk3MzcgOC41NzE0MyAxMS40MzA4VjMuMTgwOEw4LjY5NDY0IDMuMTAwNDVMOS41NjYwNyAyLjU0MTUyQzEwLjA5NjQgMi4yMDA0NSAxMC43MTA3IDIuMDIwMDkgMTEuMzQyOSAyLjAyMDA5SDE0LjcxNDNWMTAuNzM0NFpNNS45NDQ2NCA0LjMwNThIMi42MjY3OUMyLjU1NzE0IDQuMzA1OCAyLjUgNC4zNjY1MiAyLjUgNC40Mzk3M1Y1LjI0MzNDMi41IDUuMzE2NTIgMi41NTcxNCA1LjM3NzIzIDIuNjI2NzkgNS4zNzcyM0g1Ljk0Mjg2QzYuMDEyNSA1LjM3NzIzIDYuMDY5NjQgNS4zMTY1MiA2LjA2OTY0IDUuMjQzM1Y0LjQzOTczQzYuMDcxNDMgNC4zNjY1MiA2LjAxNDI5IDQuMzA1OCA1Ljk0NDY0IDQuMzA1OFpNOS45Mjg1NyA0LjQzOTczVjUuMjQzM0M5LjkyODU3IDUuMzE2NTIgOS45ODU3MSA1LjM3NzIzIDEwLjA1NTQgNS4zNzcyM0gxMy4zNzE0QzEzLjQ0MTEgNS4zNzcyMyAxMy40OTgyIDUuMzE2NTIgMTMuNDk4MiA1LjI0MzNWNC40Mzk3M0MxMy40OTgyIDQuMzY2NTIgMTMuNDQxMSA0LjMwNTggMTMuMzcxNCA0LjMwNThIMTAuMDU1NEM5Ljk4NTcxIDQuMzA1OCA5LjkyODU3IDQuMzY2NTIgOS45Mjg1NyA0LjQzOTczWk01Ljk0NDY0IDYuODA1OEgyLjYyNjc5QzIuNTU3MTQgNi44MDU4IDIuNSA2Ljg2NjUyIDIuNSA2LjkzOTczVjcuNzQzM0MyLjUgNy44MTY1MiAyLjU1NzE0IDcuODc3MjMgMi42MjY3OSA3Ljg3NzIzSDUuOTQyODZDNi4wMTI1IDcuODc3MjMgNi4wNjk2NCA3LjgxNjUyIDYuMDY5NjQgNy43NDMzVjYuOTM5NzNDNi4wNzE0MyA2Ljg2NjUyIDYuMDE0MjkgNi44MDU4IDUuOTQ0NjQgNi44MDU4Wk0xMy4zNzMyIDYuODA1OEgxMC4wNTU0QzkuOTg1NzEgNi44MDU4IDkuOTI4NTcgNi44NjY1MiA5LjkyODU3IDYuOTM5NzNWNy43NDMzQzkuOTI4NTcgNy44MTY1MiA5Ljk4NTcxIDcuODc3MjMgMTAuMDU1NCA3Ljg3NzIzSDEzLjM3MTRDMTMuNDQxMSA3Ljg3NzIzIDEzLjQ5ODIgNy44MTY1MiAxMy40OTgyIDcuNzQzM1Y2LjkzOTczQzEzLjUgNi44NjY1MiAxMy40NDI5IDYuODA1OCAxMy4zNzMyIDYuODA1OFoiICBzdHlsZT0iZmlsbC1vcGFjaXR5OjE7Ii8+Cjwvc3ZnPgo='); background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNiAxNCIgZmlsbD0iYmxhY2siIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0xNS40Mjg2IDAuNzM0Mzc2SDExLjM0MjlDMTAuNDY2MSAwLjczNDM3NiA5LjYwODkzIDAuOTg2MTYxIDguODcxNDMgMS40NjExNkw4IDIuMDIwMDlMNy4xMjg1NyAxLjQ2MTE2QzYuMzkxODEgMC45ODYyNTIgNS41MzM3MSAwLjczMzkwNyA0LjY1NzE0IDAuNzM0Mzc2SDAuNTcxNDI5QzAuMjU1MzU3IDAuNzM0Mzc2IDAgMC45ODk3MzMgMCAxLjMwNThWMTEuNDQ4N0MwIDExLjc2NDcgMC4yNTUzNTcgMTIuMDIwMSAwLjU3MTQyOSAxMi4wMjAxSDQuNjU3MTRDNS41MzM5MyAxMi4wMjAxIDYuMzkxMDcgMTIuMjcxOSA3LjEyODU3IDEyLjc0NjlMNy45MjE0MyAxMy4yNTc2QzcuOTQ0NjQgMTMuMjcxOSA3Ljk3MTQzIDEzLjI4MDggNy45OTgyMSAxMy4yODA4QzguMDI1IDEzLjI4MDggOC4wNTE3OSAxMy4yNzM3IDguMDc1IDEzLjI1NzZMOC44Njc4NiAxMi43NDY5QzkuNjA3MTQgMTIuMjcxOSAxMC40NjYxIDEyLjAyMDEgMTEuMzQyOSAxMi4wMjAxSDE1LjQyODZDMTUuNzQ0NiAxMi4wMjAxIDE2IDExLjc2NDcgMTYgMTEuNDQ4N1YxLjMwNThDMTYgMC45ODk3MzMgMTUuNzQ0NiAwLjczNDM3NiAxNS40Mjg2IDAuNzM0Mzc2Wk00LjY1NzE0IDEwLjczNDRIMS4yODU3MVYyLjAyMDA5SDQuNjU3MTRDNS4yODkyOSAyLjAyMDA5IDUuOTAzNTcgMi4yMDA0NSA2LjQzMzkzIDIuNTQxNTJMNy4zMDUzNiAzLjEwMDQ1TDcuNDI4NTcgMy4xODA4VjExLjQzMDhDNi41Nzg1NyAxMC45NzM3IDUuNjI4NTcgMTAuNzM0NCA0LjY1NzE0IDEwLjczNDRaTTE0LjcxNDMgMTAuNzM0NEgxMS4zNDI5QzEwLjM3MTQgMTAuNzM0NCA5LjQyMTQzIDEwLjk3MzcgOC41NzE0MyAxMS40MzA4VjMuMTgwOEw4LjY5NDY0IDMuMTAwNDVMOS41NjYwNyAyLjU0MTUyQzEwLjA5NjQgMi4yMDA0NSAxMC43MTA3IDIuMDIwMDkgMTEuMzQyOSAyLjAyMDA5SDE0LjcxNDNWMTAuNzM0NFpNNS45NDQ2NCA0LjMwNThIMi42MjY3OUMyLjU1NzE0IDQuMzA1OCAyLjUgNC4zNjY1MiAyLjUgNC40Mzk3M1Y1LjI0MzNDMi41IDUuMzE2NTIgMi41NTcxNCA1LjM3NzIzIDIuNjI2NzkgNS4zNzcyM0g1Ljk0Mjg2QzYuMDEyNSA1LjM3NzIzIDYuMDY5NjQgNS4zMTY1MiA2LjA2OTY0IDUuMjQzM1Y0LjQzOTczQzYuMDcxNDMgNC4zNjY1MiA2LjAxNDI5IDQuMzA1OCA1Ljk0NDY0IDQuMzA1OFpNOS45Mjg1NyA0LjQzOTczVjUuMjQzM0M5LjkyODU3IDUuMzE2NTIgOS45ODU3MSA1LjM3NzIzIDEwLjA1NTQgNS4zNzcyM0gxMy4zNzE0QzEzLjQ0MTEgNS4zNzcyMyAxMy40OTgyIDUuMzE2NTIgMTMuNDk4MiA1LjI0MzNWNC40Mzk3M0MxMy40OTgyIDQuMzY2NTIgMTMuNDQxMSA0LjMwNTggMTMuMzcxNCA0LjMwNThIMTAuMDU1NEM5Ljk4NTcxIDQuMzA1OCA5LjkyODU3IDQuMzY2NTIgOS45Mjg1NyA0LjQzOTczWk01Ljk0NDY0IDYuODA1OEgyLjYyNjc5QzIuNTU3MTQgNi44MDU4IDIuNSA2Ljg2NjUyIDIuNSA2LjkzOTczVjcuNzQzM0MyLjUgNy44MTY1MiAyLjU1NzE0IDcuODc3MjMgMi42MjY3OSA3Ljg3NzIzSDUuOTQyODZDNi4wMTI1IDcuODc3MjMgNi4wNjk2NCA3LjgxNjUyIDYuMDY5NjQgNy43NDMzVjYuOTM5NzNDNi4wNzE0MyA2Ljg2NjUyIDYuMDE0MjkgNi44MDU4IDUuOTQ0NjQgNi44MDU4Wk0xMy4zNzMyIDYuODA1OEgxMC4wNTU0QzkuOTg1NzEgNi44MDU4IDkuOTI4NTcgNi44NjY1MiA5LjkyODU3IDYuOTM5NzNWNy43NDMzQzkuOTI4NTcgNy44MTY1MiA5Ljk4NTcxIDcuODc3MjMgMTAuMDU1NCA3Ljg3NzIzSDEzLjM3MTRDMTMuNDQxMSA3Ljg3NzIzIDEzLjQ5ODIgNy44MTY1MiAxMy40OTgyIDcuNzQzM1Y2LjkzOTczQzEzLjUgNi44NjY1MiAxMy40NDI5IDYuODA1OCAxMy4zNzMyIDYuODA1OFoiICBzdHlsZT0iZmlsbC1vcGFjaXR5OjE7Ii8+Cjwvc3ZnPgo=');
} }
...@@ -1263,3 +1270,120 @@ body{ ...@@ -1263,3 +1270,120 @@ body{
} }
} }
} }
.atma-editor-emoji-popover{
position: absolute;
top: calc(100% + 8px);
left: 0;
width: 344px;
padding: 12px;
border: 1px solid #d9d9d9;
border-radius: 14px;
background: #ffffff;
box-shadow: 0 18px 36px rgba(15, 23, 42, 0.16);
z-index: 30;
&-search{
margin-bottom: 10px;
}
&-tabs{
display: flex;
align-items: center;
gap: 4px;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
overflow-x: auto;
}
&-title{
font-size: 12px;
line-height: 16px;
color: #8c8c8c;
margin-bottom: 8px;
}
}
.atma-editor-emoji-search{
width: 100%;
height: 36px;
border: 1px solid #d9d9d9;
border-radius: 10px;
padding: 0 12px;
font-size: 14px;
outline: none;
&:focus{
border-color: #1790FF;
box-shadow: 0 0 0 2px rgba(23, 144, 255, 0.12);
}
}
.atma-editor-emoji-tab{
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 8px;
background: transparent;
padding: 0;
flex: 0 0 auto;
font-size: 17px;
&:hover{
cursor: pointer;
background: #f5f5f5;
}
&.active{
background: #edeef0;
}
}
.atma-editor-emoji-grid{
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 4px;
max-height: 280px;
overflow-y: auto;
padding-right: 2px;
}
.atma-editor-emoji-item{
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 40px;
border: none;
border-radius: 10px;
background: transparent;
padding: 0;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
font-size: 24px;
line-height: 1;
&:hover{
cursor: pointer;
background: #f5f5f5;
}
}
.atma-editor-emoji-empty{
grid-column: 1 / -1;
display: flex;
align-items: center;
justify-content: center;
min-height: 88px;
color: #8c8c8c;
font-size: 13px;
}
@media (max-width: 768px) {
.atma-editor-emoji-popover{
left: 0;
width: min(344px, calc(100vw - 32px));
}
}
...@@ -5606,6 +5606,11 @@ emoji-regex@^9.2.2: ...@@ -5606,6 +5606,11 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
   
emojibase-data@^16.0.3:
version "16.0.3"
resolved "https://registry.npmjs.org/emojibase-data/-/emojibase-data-16.0.3.tgz"
integrity sha512-MopInVCDZeXvqBMPJxnvYUyKw9ImJZqIDr2sABo6acVSPev5IDYX+mf+0tsu96JJyc3INNvgIf06Eso7bdTX2Q==
emojis-list@^3.0.0: emojis-list@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment