platform/packages/text-editor/src/components/EmojiPopup.svelte
Alexander Platov 63b0e63866
Fix Popup for @ document referencing and Emoji popup (#2398)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
2022-11-29 13:44:40 +07:00

211 lines
6.1 KiB
Svelte

<script lang="ts">
import emojiRegex from 'emoji-regex'
import { getContext } from 'svelte'
import { createEventDispatcher } from 'svelte'
import { IntlString } from '@hcengineering/platform'
import { Label, tooltip, Scroller, emojiSP } from '@hcengineering/ui'
import type { AnySvelteComponent } from '@hcengineering/ui'
import Emoji from './icons/Emoji.svelte'
import Food from './icons/Food.svelte'
import Nature from './icons/Nature.svelte'
import Objects from './icons/Objects.svelte'
import Places from './icons/Places.svelte'
import Symbols from './icons/Symbols.svelte'
import Work from './icons/Work.svelte'
import plugin from '../plugin'
let div: HTMLDivElement
const regex = emojiRegex()
const { currentFontSize } = getContext('fontsize') as { currentFontSize: string }
function getEmojis (startCode: number, endCode: number, postfix?: number[]): (string | undefined)[] {
return [...Array(endCode - startCode + 1).keys()].map((v) => {
const str = postfix ? String.fromCodePoint(v + startCode, ...postfix) : String.fromCodePoint(v + startCode)
if ([...str.matchAll(regex)].length > 0) return str
return undefined
})
}
interface Category {
id: string
label: IntlString
emojis: (string | undefined)[]
icon: AnySvelteComponent
}
const categories: Category[] = [
{
id: 'work',
label: plugin.string.GettingWorkDone,
emojis: [
String.fromCodePoint(0x1f440),
String.fromCodePoint(0x2705),
String.fromCodePoint(0x274c),
String.fromCodePoint(0x2795),
String.fromCodePoint(0x2796),
String.fromCodePoint(0x2757),
String.fromCodePoint(0x0031, 0xfe0f, 0x20e3),
String.fromCodePoint(0x0032, 0xfe0f, 0x20e3),
String.fromCodePoint(0x0033, 0xfe0f, 0x20e3),
String.fromCodePoint(0x1f44b, 0x1f3fc),
String.fromCodePoint(0x1f44d, 0x1f3fc),
String.fromCodePoint(0x1f44c, 0x1f3fc)
],
icon: Work
},
{
id: 'smileys',
label: plugin.string.Smileys,
emojis: [...getEmojis(0x1f600, 0x1f64f), ...getEmojis(0x1f90c, 0x1f92f)],
icon: Emoji
},
{
id: 'nature',
label: plugin.string.Nature,
emojis: [
...getEmojis(0x1f408, 0x1f43e),
...getEmojis(0x1f980, 0x1f9ae),
...getEmojis(0x1f330, 0x1f343),
...getEmojis(0x1f300, 0x1f320),
...getEmojis(0x1f324, 0x1f32c, [0xfe0f]),
...getEmojis(0x2600, 0x2604, [0xfe0f])
],
icon: Nature
},
{
id: 'travels',
label: plugin.string.TravelAndPlaces,
emojis: [...getEmojis(0x1f5fb, 0x1f5ff), ...getEmojis(0x1f3e0, 0x1f3f0), ...getEmojis(0x1f680, 0x1f6a3)],
icon: Places
},
{
id: 'food',
label: plugin.string.Food,
emojis: [...getEmojis(0x1f345, 0x1f37f), ...getEmojis(0x1f32d, 0x1f32f)],
icon: Food
},
{
id: 'objects',
label: plugin.string.Objects,
emojis: [...getEmojis(0x1f4b6, 0x1f4fc)],
icon: Objects
},
{
id: 'symbols',
label: plugin.string.Symbols,
emojis: [
...getEmojis(0x00a9, 0x25fc, [0xfe0f]),
...getEmojis(0x2764, 0x2b07, [0xfe0f]),
...getEmojis(0x0023, 0x0039, [0xfe0f, 0x20e3]),
...getEmojis(0x1f532, 0x1f53d)
],
icon: Symbols
}
]
const dispatch = createEventDispatcher()
function handleScrollToCategory (categoryId: string) {
const el = document.getElementById(categoryId)
if (el) {
const next = el.nextElementSibling as HTMLElement
div.scroll(0, next.offsetTop - (currentFontSize === 'small-font' ? 14 : 16) * 1.75)
}
}
let currentCategory = categories[0]
function handleScrolled (catId?: CustomEvent) {
if (catId) {
const curCat = categories.find((it) => it.id === catId.detail[catId.detail.length - 1])
if (curCat) currentCategory = curCat
}
}
</script>
<div class="antiPopup antiPopup-withHeader popup">
<div class="flex-row-center popup-header">
{#each categories as category}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
use:tooltip={{ label: category.label }}
class="element m-0-5"
class:selected={currentCategory === category}
on:click={() => handleScrollToCategory(category.id)}
>
<svelte:component this={category.icon} size={'large'} opacity={currentCategory === category ? '1' : '0.3'} />
</div>
{/each}
</div>
<div class="scrolling">
<Scroller bind:divScroll={div} on:scrolledCategories={handleScrolled} fade={emojiSP} noStretch>
{#each categories as category}
<div id={category.id} class="scroll-header categoryHeader">
<Label label={category.label} />
</div>
<div class="palette ml-3">
{#each category.emojis as emoji}
{#if emoji !== undefined}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="element m-0-5" on:click={() => dispatch('close', emoji)}>{emoji}</div>
{/if}
{/each}
</div>
{/each}
</Scroller>
</div>
<div class="ap-space x2" />
</div>
<style lang="scss">
.popup {
height: 21.25rem;
}
.scrolling {
min-height: 0;
height: 16.5rem;
max-height: 16.5rem;
}
.popup-header {
margin: 0.75rem 0.75rem 0.5rem;
}
.scroll-header {
position: sticky;
top: 0;
margin: 0.75rem 0.75rem 0.25rem;
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
color: var(--dark-color);
background-color: var(--board-bg-color);
border-radius: 0.25rem;
&:first-child {
margin-top: 0;
}
}
.palette {
display: flex;
flex-wrap: wrap;
width: 19.25rem;
font-size: 1.75rem;
}
.element {
display: flex;
justify-content: center;
align-items: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 0.25rem;
color: var(--caption-color);
cursor: pointer;
&:hover {
background-color: var(--popup-bg-hover);
}
&.selected {
background-color: var(--popup-bg-hover);
}
}
</style>