mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-10 17:30:51 +00:00
Remove emoji from packages
Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
This commit is contained in:
parent
e34e6250a1
commit
cc32979f8a
@ -12,4 +12,3 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
|
@ -84,7 +84,7 @@
|
||||
{#if node.attrs?.kind === 'custom'}
|
||||
{@const src = toString(attrs.url)}
|
||||
{@const alt = toString(attrs.emoji)}
|
||||
<img {src} {alt}/>
|
||||
<img {src} {alt} />
|
||||
{:else}
|
||||
{node.attrs?.emoji}
|
||||
{/if}
|
||||
|
@ -84,13 +84,11 @@ export const EmojiNode = Node.create<EmojiNodeOptions>({
|
||||
),
|
||||
[
|
||||
'img',
|
||||
mergeAttributes(
|
||||
{
|
||||
'data-type': this.name,
|
||||
src: node.attrs.url,
|
||||
alt: node.attrs.emoji
|
||||
}
|
||||
)
|
||||
mergeAttributes({
|
||||
'data-type': this.name,
|
||||
src: node.attrs.url,
|
||||
alt: node.attrs.emoji
|
||||
})
|
||||
]
|
||||
]
|
||||
}
|
||||
|
@ -1,116 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Emoji } from 'emojibase'
|
||||
import { ExtendedEmoji, getEmojiByHexcode, EmojiButton, getSkinTone, emojiStore, getEmojiSkins } from '.'
|
||||
import { Label, IconDelete, closeTooltip, ButtonBase } from '../..'
|
||||
import plugin from '../../plugin'
|
||||
import SkinToneTooltip from './SkinToneTooltip.svelte'
|
||||
|
||||
export let emoji: ExtendedEmoji
|
||||
export let remove: boolean = false
|
||||
export let skinTone: number = getSkinTone()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
closeTooltip()
|
||||
|
||||
const skins = getEmojiSkins(emoji)
|
||||
const haveSkins = Array.isArray(skins)
|
||||
const combinedEmoji = haveSkins && (skins?.length ?? 0) > 5
|
||||
|
||||
const clickRemove = (): void => {
|
||||
dispatch('close', 'remove')
|
||||
}
|
||||
const getEmojiParts = (): ExtendedEmoji[] => {
|
||||
const def = $emojiStore[168]
|
||||
const temp = skins?.find((skin) => Array.isArray(skin.tone) && skin.tone.length > 1)?.hexcode.split('-200D-')
|
||||
if (temp === undefined || temp.length < 2) return [def, def]
|
||||
const firstEmoji = getEmojiByHexcode(temp[0].slice(0, -6)) ?? def
|
||||
const secondEmoji = getEmojiByHexcode(temp[temp.length - 1].slice(0, -6)) ?? def
|
||||
return [firstEmoji, secondEmoji]
|
||||
}
|
||||
const emojiParts = getEmojiParts()
|
||||
const combinedTones: number[] = [skinTone, skinTone]
|
||||
|
||||
const updateSkinTone = (result: CustomEvent<{ detail: number }>, index: number): void => {
|
||||
const res = result.detail
|
||||
if (typeof res === 'number') {
|
||||
combinedTones[index] = res
|
||||
const nextIndex = index === 1 ? 0 : 1
|
||||
if (res === 0 && combinedTones[nextIndex] !== 0) combinedTones[nextIndex] = 0
|
||||
else if (res !== 0 && combinedTones[nextIndex] === 0) combinedTones[nextIndex] = res
|
||||
}
|
||||
}
|
||||
|
||||
const getEmojiByTone = (e: ExtendedEmoji, [a, b]: number[]): Emoji | undefined => {
|
||||
const equal = a === b
|
||||
const noTone = a === 0
|
||||
return equal && noTone
|
||||
? e as Emoji
|
||||
: getEmojiSkins(e)?.find((skin) =>
|
||||
equal ? skin.tone === a : Array.isArray(skin.tone) && skin.tone[0] === a && skin.tone[1] === b
|
||||
)
|
||||
}
|
||||
const getEmojiStringByTone = (e: ExtendedEmoji, [a, b]: number[]): string | undefined => {
|
||||
return getEmojiByTone(e, [a, b])?.emoji
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyPopup-container noPadding autoWidth">
|
||||
{#if haveSkins}
|
||||
{#if combinedEmoji && emojiParts?.length === 2}
|
||||
<div class="hulyPopup-row disabled skins-row">
|
||||
{#each new Array(2) as _, index}
|
||||
<EmojiButton
|
||||
emoji={emojiParts[index]}
|
||||
skinTone={combinedTones[index]}
|
||||
preview
|
||||
showTooltip={{
|
||||
component: SkinToneTooltip,
|
||||
props: { emoji: emojiParts[index], selected: combinedTones[index] },
|
||||
onUpdate: (result) => {
|
||||
updateSkinTone(result, index)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{#if index === 0}
|
||||
<ButtonBase
|
||||
type={'type-button-icon'}
|
||||
kind={'tertiary'}
|
||||
size={'large'}
|
||||
on:click={() => {
|
||||
dispatch('close', getEmojiByTone(emoji, combinedTones))
|
||||
}}
|
||||
>
|
||||
<span style:font-size={'2.5rem'}>{getEmojiStringByTone(emoji, combinedTones)}</span>
|
||||
</ButtonBase>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="hulyPopup-row disabled skins-row">
|
||||
{#each new Array((skins?.length ?? 5) + 1) as _, skin}
|
||||
<EmojiButton {emoji} skinTone={skin} preview on:select={(result) => dispatch('close', result.detail)} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if remove}
|
||||
{#if haveSkins}<div class="hulyPopup-divider" />{/if}
|
||||
<div class="hulyPopup-group">
|
||||
<button class="hulyPopup-row" on:click={clickRemove}>
|
||||
<div class="hulyPopup-row__icon red-color"><IconDelete size={'small'} /></div>
|
||||
<span class="hulyPopup-row__label red-color"><Label label={plugin.string.Remove} /></span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.skins-row {
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
@ -1,150 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { tooltip, capitalizeFirstLetter, type LabelAndProps, type ExtendedEmoji, getEmojiSkins } from '../../'
|
||||
import { isCustomEmoji } from './types'
|
||||
|
||||
export let emoji: ExtendedEmoji
|
||||
export let selected: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let preview: boolean = false
|
||||
export let skinTone: number = 0
|
||||
export let showTooltip: LabelAndProps | undefined = undefined
|
||||
|
||||
const skins = getEmojiSkins(emoji)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let displayedEmoji: ExtendedEmoji
|
||||
$: skinIndex = skins?.findIndex((skin) => skin.tone === skinTone) ?? -1
|
||||
$: displayedEmoji = skinTone > 0 && skins !== undefined && skinIndex > -1 ? skins[skinIndex] : emoji
|
||||
</script>
|
||||
|
||||
{#if emoji}
|
||||
<button
|
||||
use:tooltip={showTooltip ?? { label: getEmbeddedLabel(capitalizeFirstLetter(displayedEmoji?.label ?? '')) }}
|
||||
class="hulyPopupEmoji-button"
|
||||
class:preview
|
||||
class:selected
|
||||
class:skins={skins !== undefined && skins.length === 5}
|
||||
class:constructor={skins !== undefined && skins.length > 5}
|
||||
data-skins={skins?.length}
|
||||
{disabled}
|
||||
on:touchstart
|
||||
on:contextmenu
|
||||
on:click={() => {
|
||||
if (disabled) return
|
||||
dispatch('select', displayedEmoji)
|
||||
}}
|
||||
>
|
||||
{#if isCustomEmoji(displayedEmoji)}
|
||||
<span><img src="{displayedEmoji.url}" alt="{displayedEmoji.shortcode}"></span>
|
||||
{:else}
|
||||
<span>{displayedEmoji.emoji}</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.hulyPopupEmoji-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
line-height: 150%;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:not(.preview) {
|
||||
margin: 0.125rem;
|
||||
padding: 0.25rem;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
font-size: 2rem;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
&.preview {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
font-size: 1.75rem;
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
span {
|
||||
transform: translateY(1%);
|
||||
pointer-events: none;
|
||||
}
|
||||
span > img {
|
||||
height: 1em;
|
||||
width: auto;
|
||||
}
|
||||
&:enabled:hover {
|
||||
background-color: var(--theme-popup-hover);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: var(--button-primary-BorderColor);
|
||||
background-color: var(--button-primary-BackgroundColor);
|
||||
|
||||
&:not(.disabled, :disabled):hover {
|
||||
background-color: var(--button-primary-hover-BackgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.mobile-theme) & {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-size: 1.5rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
&.skins:not(.preview) {
|
||||
position: relative;
|
||||
border: 1px dashed var(--theme-button-border);
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -0.375rem;
|
||||
right: -0.375rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!-- Generator: Adobe Illustrator 28.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 8 8' style='enable-background:new 0 0 8 8;' xml:space='preserve'%3E%3Cg%3E%3Ccircle fill='%23FFC92C' cx='5.3' cy='1.3' r='1.3' /%3E%3Ccircle fill='%23BF8F68' cx='2.7' cy='6' r='1.3' /%3E%3Ccircle fill='%23E0BB95' cx='5.3' cy='6' r='1.3' /%3E%3Ccircle fill='%239B643D' cx='1.3' cy='3.6' r='1.3' /%3E%3Ccircle fill='%23594539' cx='2.7' cy='1.3' r='1.3' /%3E%3Ccircle fill='%23FADCBC' cx='6.7' cy='3.6' r='1.3' /%3E%3C/g%3E%3C/svg%3E%0A");
|
||||
}
|
||||
}
|
||||
}
|
||||
&.constructor:not(.preview) {
|
||||
position: relative;
|
||||
border: 1px dashed var(--theme-button-border);
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -0.25rem;
|
||||
right: -0.25rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
background-color: var(--global-focus-BorderColor);
|
||||
}
|
||||
&::after {
|
||||
content: attr(data-skins);
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
right: 0.25rem;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,105 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Label, Lazy } from '../../index'
|
||||
import EmojiGroupPalette from './EmojiGroupPalette.svelte'
|
||||
import { resultEmojis, EmojiWithGroup, EmojiCategory } from '.'
|
||||
|
||||
import plugin from '../../plugin'
|
||||
|
||||
export let group: EmojiCategory
|
||||
export let lazy: boolean = true
|
||||
export let searching: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let selected: string | undefined = undefined
|
||||
export let skinTone: number = 0
|
||||
export let kind: 'default' | 'fade' = 'fade'
|
||||
|
||||
let emojis: EmojiWithGroup[] = []
|
||||
$: emojis = searching
|
||||
? $resultEmojis
|
||||
: Array.isArray(group.emojis)
|
||||
? group.emojis
|
||||
: $resultEmojis.filter((re) => re.key === group.id)
|
||||
</script>
|
||||
|
||||
<div class="hulyPopupEmoji-group kind-{kind}" class:lazy>
|
||||
<div id={group.id} class="hulyPopupEmoji-group__header categoryHeader">
|
||||
<Label label={searching && $resultEmojis.length === 0 ? plugin.string.NoResults : group.label} />
|
||||
</div>
|
||||
{#if lazy}
|
||||
<Lazy>
|
||||
<EmojiGroupPalette {emojis} {disabled} {selected} {skinTone} on:select on:touchstart on:contextmenu />
|
||||
</Lazy>
|
||||
{:else}
|
||||
<EmojiGroupPalette {emojis} {disabled} {selected} {skinTone} on:select on:touchstart on:contextmenu />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.hulyPopupEmoji-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
&.lazy {
|
||||
min-height: 10rem;
|
||||
}
|
||||
|
||||
&__header {
|
||||
position: sticky;
|
||||
flex-shrink: 0;
|
||||
margin: 0.75rem 0.75rem 0.25rem;
|
||||
padding: 0.25rem 0.375rem;
|
||||
top: 0;
|
||||
height: 1.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 0 0.25rem var(--theme-popup-color);
|
||||
color: var(--theme-caption-color);
|
||||
border-radius: 0.25rem;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 150%;
|
||||
background: var(--theme-popup-trans-gradient);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
&.kind-default {
|
||||
.hulyPopupEmoji-group__header {
|
||||
background: var(--theme-popup-header);
|
||||
|
||||
&::before {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,59 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { ExtendedEmoji } from '.'
|
||||
|
||||
import EmojiButton from './EmojiButton.svelte'
|
||||
import { isCustomEmoji } from './types'
|
||||
|
||||
export let emojis: ExtendedEmoji[]
|
||||
export let selected: string | undefined
|
||||
export let disabled: boolean = false
|
||||
export let skinTone: number = 0
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="hulyPopupEmoji-group__palette">
|
||||
{#each emojis as emoji}
|
||||
<EmojiButton
|
||||
{emoji}
|
||||
selected={isCustomEmoji(emoji) ? emoji.shortcode === selected : emoji.emoji === selected}
|
||||
{disabled}
|
||||
{skinTone}
|
||||
on:select
|
||||
on:touchstart={(event) => {
|
||||
dispatch('touchstart', { event, emoji })
|
||||
}}
|
||||
on:contextmenu={(event) => {
|
||||
dispatch('contextmenu', { event, emoji })
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<div class="clear-mins flex-grow" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.hulyPopupEmoji-group__palette {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
flex-shrink: 0;
|
||||
margin-inline: 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
</style>
|
@ -1,428 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
import { createEventDispatcher, onMount, onDestroy } from 'svelte'
|
||||
import {
|
||||
Scroller,
|
||||
SearchInput,
|
||||
tooltip,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
showPopup,
|
||||
eventToHTMLElement,
|
||||
ButtonBase,
|
||||
closeTooltip, getEmojiByShortCode, getEmojiSkins, getUnicodeEmojiByShortCode
|
||||
} from '../../'
|
||||
import plugin from '../../plugin'
|
||||
import {
|
||||
searchEmoji,
|
||||
emojiStore,
|
||||
emojiCategories,
|
||||
getFrequentlyEmojis,
|
||||
addFrequentlyEmojis,
|
||||
removeFrequentlyEmojis,
|
||||
getSkinTone,
|
||||
setSkinTone
|
||||
} from '.'
|
||||
import type { EmojiWithGroup, EmojiCategory } from '.'
|
||||
import ActionsPopup from './ActionsPopup.svelte'
|
||||
import SkinTonePopup from './SkinTonePopup.svelte'
|
||||
import IconSearch from './icons/Search.svelte'
|
||||
import EmojiGroup from './EmojiGroup.svelte'
|
||||
import { isCustomEmoji } from './types'
|
||||
|
||||
export let embedded = false
|
||||
export let selected: string | undefined
|
||||
export let disabled: boolean = false
|
||||
export let kind: 'default' | 'fade' = 'fade'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let scrollElement: HTMLDivElement
|
||||
const touchEvents = ['touchend', 'touchcancel', 'touchmove']
|
||||
let skinTone: number = getSkinTone()
|
||||
let shownSTM: boolean = false
|
||||
let shownContext: boolean = false
|
||||
$: emojiRowHeightPx = ($deviceInfo.fontSize ?? 16) * 2
|
||||
$searchEmoji = ''
|
||||
$: searching = $searchEmoji !== ''
|
||||
const searchCategory: EmojiCategory[] = [
|
||||
{
|
||||
id: 'search-category',
|
||||
label: plugin.string.SearchResults,
|
||||
icon: IconSearch
|
||||
}
|
||||
]
|
||||
let timer: any = null
|
||||
const isMobile = $deviceInfo.isMobile
|
||||
|
||||
let emojisCat = emojiCategories
|
||||
let currentCategory = emojisCat[0]
|
||||
$: emojiTabs = emojisCat
|
||||
.map((ec) => {
|
||||
if (ec.categories !== undefined || ec.emojisString !== undefined || ec.emojis !== undefined) {
|
||||
return { id: ec.id, label: ec.label, icon: ec.icon }
|
||||
} else return undefined
|
||||
})
|
||||
.filter((f) => f !== undefined) as EmojiCategory[]
|
||||
$: categoryTabs = searching ? ([...searchCategory, ...emojiTabs] as EmojiCategory[]) : emojiTabs
|
||||
|
||||
function handleScrollToCategory (categoryId: string): void {
|
||||
if (searching && categoryId !== searchCategory[0].id) $searchEmoji = ''
|
||||
if (isMobile) {
|
||||
const tempCat = emojiTabs.find((ct) => ct?.id === categoryId)
|
||||
if (tempCat === undefined) return
|
||||
currentCategory = tempCat
|
||||
outputGroups = updateGroups(searching, emojisCat)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
const labelElement = document.getElementById(categoryId)
|
||||
if (labelElement) {
|
||||
const emojisElement = labelElement.nextElementSibling as HTMLElement
|
||||
scrollElement.scroll(0, emojisElement.offsetTop - $deviceInfo.fontSize * 1.75)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleCategoryScrolled (): void {
|
||||
if (isMobile) return
|
||||
const selectedCategory = emojisCat.find((category) => {
|
||||
const labelElement = document.getElementById(category.id)
|
||||
if (labelElement == null) return false
|
||||
const emojisElement = labelElement.nextElementSibling as HTMLElement
|
||||
|
||||
return emojisElement.offsetTop + emojisElement.offsetHeight - emojiRowHeightPx > scrollElement.scrollTop
|
||||
})
|
||||
if (selectedCategory !== undefined) currentCategory = selectedCategory
|
||||
}
|
||||
|
||||
const sendEmoji = (emoji: EmojiWithGroup): void => {
|
||||
selected = isCustomEmoji(emoji) ? emoji.shortcode : emoji.emoji
|
||||
addFrequentlyEmojis(emoji)
|
||||
dispatch('close', {
|
||||
text: selected,
|
||||
url: isCustomEmoji(emoji) ? emoji.url : undefined
|
||||
})
|
||||
}
|
||||
|
||||
const selectedEmoji = (event: CustomEvent<EmojiWithGroup>): void => {
|
||||
if (event.detail === undefined || typeof event.detail !== 'object') return
|
||||
sendEmoji(event.detail)
|
||||
}
|
||||
|
||||
function openContextMenu (event: TouchEvent | MouseEvent, _emoji: EmojiWithGroup, remove: boolean): void {
|
||||
event.preventDefault()
|
||||
const emoji = _emoji
|
||||
if (emoji === undefined) return
|
||||
|
||||
clearTimer()
|
||||
shownContext = true
|
||||
showPopup(
|
||||
ActionsPopup,
|
||||
{ emoji, remove },
|
||||
eventToHTMLElement(event),
|
||||
(result: 'remove' | EmojiWithGroup) => {
|
||||
if (result === 'remove') {
|
||||
removeFrequentlyEmojis(emoji)
|
||||
const index = emojisCat.findIndex((ec) => ec.id === 'frequently-used')
|
||||
if (index > -1) emojisCat[index].emojis = getFrequentlyEmojis()
|
||||
emojisCat = emojisCat.filter(
|
||||
(em) => em.categories !== undefined || (Array.isArray(em.emojis) && em.emojis.length > 0)
|
||||
)
|
||||
if (currentCategory.emojis?.length === 0) {
|
||||
currentCategory = emojisCat.find((ec) => ec.emojis !== undefined && ec.emojis.length > 0) ?? emojisCat[0]
|
||||
}
|
||||
} else if (result !== undefined) {
|
||||
sendEmoji(result)
|
||||
}
|
||||
shownContext = false
|
||||
}
|
||||
)
|
||||
}
|
||||
function handleContextMenu (event: MouseEvent, emoji: EmojiWithGroup, remove: boolean): void {
|
||||
event.preventDefault()
|
||||
const skins = getEmojiSkins(emoji)
|
||||
if (Array.isArray(skins) || remove) openContextMenu(event, emoji, remove)
|
||||
}
|
||||
const clearTimer = (): void => {
|
||||
clearTimeout(timer)
|
||||
touchObserver(true)
|
||||
}
|
||||
const touchObserver = (remove: boolean = false): void => {
|
||||
touchEvents.forEach((event) => {
|
||||
if (remove) window.removeEventListener(event, clearTimer)
|
||||
else window.addEventListener(event, clearTimer)
|
||||
})
|
||||
}
|
||||
function clampedContextMenu (event: TouchEvent, emoji: EmojiWithGroup, remove: boolean): void {
|
||||
const skins = getEmojiSkins(emoji)
|
||||
if (timer == null && (Array.isArray(skins) || remove)) {
|
||||
touchObserver()
|
||||
timer = setTimeout(function () {
|
||||
if (!shownContext) openContextMenu(event, emoji, remove)
|
||||
clearTimer()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
const showSkinMenu = (event: MouseEvent): void => {
|
||||
shownSTM = true
|
||||
showPopup(SkinTonePopup, { emoji: getEmojiByShortCode(':hand:', 0), selected: skinTone }, eventToHTMLElement(event), (result) => {
|
||||
if (typeof result === 'number') {
|
||||
skinTone = result
|
||||
setSkinTone(skinTone)
|
||||
}
|
||||
shownSTM = false
|
||||
})
|
||||
}
|
||||
|
||||
let hidden: boolean = true
|
||||
const checkScroll = (event: Event): void => {
|
||||
if (timer != null) clearTimer()
|
||||
const target = event.target as HTMLElement
|
||||
if (target == null) return
|
||||
hidden = target.scrollHeight - target.scrollTop - target.clientHeight < 5
|
||||
}
|
||||
|
||||
const initEmoji = (): void => {
|
||||
emojiCategories.forEach((em, index) => {
|
||||
if (em.id === 'frequently-used') {
|
||||
emojiCategories[index].emojis = getFrequentlyEmojis()
|
||||
}
|
||||
if (em.emojisString !== undefined && Array.isArray(em.emojisString) && em.emojis === undefined) {
|
||||
const tempEmojis: string[] = em.emojisString
|
||||
const emojis: EmojiWithGroup[] = []
|
||||
tempEmojis.forEach((te) => {
|
||||
const e = $emojiStore.find((es) => isCustomEmoji(es) ? es.shortcode === te : es.hexcode === te)
|
||||
if (e !== undefined) emojis.push(e)
|
||||
})
|
||||
emojiCategories[index].emojis = emojis
|
||||
}
|
||||
})
|
||||
emojisCat = emojiCategories.filter(
|
||||
(em) => em.categories !== undefined || (Array.isArray(em.emojis) && em.emojis.length > 0)
|
||||
)
|
||||
currentCategory = emojisCat.find((ec) => ec.emojis !== undefined) ?? emojisCat[0]
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (scrollElement !== undefined) scrollElement.addEventListener('scroll', checkScroll)
|
||||
closeTooltip()
|
||||
setTimeout(initEmoji)
|
||||
})
|
||||
onDestroy(() => {
|
||||
if (scrollElement !== undefined) scrollElement.removeEventListener('scroll', checkScroll)
|
||||
})
|
||||
|
||||
const updateGroups = (s: boolean, ec: EmojiCategory[]): EmojiCategory[] => {
|
||||
return s ? searchCategory : isMobile ? ec.filter((e) => e.id === currentCategory.id) : ec
|
||||
}
|
||||
$: outputGroups = updateGroups(searching, emojisCat)
|
||||
</script>
|
||||
|
||||
<div class="hulyPopupEmoji-container kind-{kind}" class:embedded>
|
||||
<div class="hulyPopupEmoji-header__tabs-wrapper">
|
||||
<div class="hulyPopupEmoji-header__tabs">
|
||||
{#each categoryTabs as category (category.id)}
|
||||
<button
|
||||
class="hulyPopupEmoji-header__tab"
|
||||
class:selected={(searching && searchCategory[0].id === category.id) ||
|
||||
(!searching && currentCategory.id === category.id)}
|
||||
data-id={category.id}
|
||||
use:tooltip={{ label: category.label }}
|
||||
on:click={() => {
|
||||
handleScrollToCategory(category.id)
|
||||
}}
|
||||
>
|
||||
<svelte:component this={category.icon} size={isMobile ? 'large' : 'x-large'} />
|
||||
</button>
|
||||
{/each}
|
||||
<div
|
||||
style:left={`${
|
||||
(searching ? 0 : emojisCat.findIndex((ec) => ec.id === currentCategory.id)) * (isMobile ? 1.875 : 2.125)
|
||||
}rem`}
|
||||
class="hulyPopupEmoji-header__tab-cursor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hulyPopupEmoji-header__tools">
|
||||
<SearchInput
|
||||
value={$searchEmoji}
|
||||
placeholder={plugin.string.SearchDots}
|
||||
width={'100%'}
|
||||
autoFocus
|
||||
delay={50}
|
||||
on:change={(result) => {
|
||||
if (result.detail !== undefined) $searchEmoji = result.detail
|
||||
else if (result.detail !== '') currentCategory = searchCategory[0]
|
||||
}}
|
||||
/>
|
||||
<ButtonBase
|
||||
type={'type-button-icon'}
|
||||
hasMenu
|
||||
pressed={shownSTM}
|
||||
kind={'tertiary'}
|
||||
size={'small'}
|
||||
tooltip={{ label: plugin.string.DefaultSkinTone }}
|
||||
on:click={showSkinMenu}
|
||||
>
|
||||
<span style:font-size={'1.5rem'}>{getUnicodeEmojiByShortCode(':hand:', skinTone)?.emoji}</span>
|
||||
</ButtonBase>
|
||||
</div>
|
||||
<Scroller
|
||||
bind:divScroll={scrollElement}
|
||||
gap="0.5rem"
|
||||
checkForHeaders
|
||||
noStretch
|
||||
on:scrolledCategories={handleCategoryScrolled}
|
||||
>
|
||||
{#each outputGroups as group (group.id)}
|
||||
{@const canRemove = group.id === 'frequently-used'}
|
||||
<EmojiGroup
|
||||
{group}
|
||||
{searching}
|
||||
{disabled}
|
||||
{selected}
|
||||
{skinTone}
|
||||
{kind}
|
||||
lazy={group.id !== 'frequently-used'}
|
||||
on:select={selectedEmoji}
|
||||
on:touchstart={(ev) => {
|
||||
const { event, emoji } = ev.detail
|
||||
clampedContextMenu(event, emoji, canRemove)
|
||||
}}
|
||||
on:contextmenu={(ev) => {
|
||||
const { event, emoji } = ev.detail
|
||||
handleContextMenu(event, emoji, canRemove)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</Scroller>
|
||||
{#if !hidden && kind === 'fade'}<div class="hulyPopupEmoji-footer" />{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.hulyPopupEmoji-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 28.5rem;
|
||||
max-height: 28.5rem;
|
||||
user-select: none;
|
||||
|
||||
:global(.mobile-theme) & {
|
||||
min-width: 0;
|
||||
max-width: calc(100vw - 2rem);
|
||||
min-height: 0;
|
||||
max-height: calc(100% - 4rem);
|
||||
}
|
||||
|
||||
&:not(.embedded) {
|
||||
min-width: 25.5rem;
|
||||
max-width: 25.5rem;
|
||||
background: var(--theme-popup-color); // var(--global-popover-BackgroundColor);
|
||||
border: 1px solid var(--theme-popup-divider); // var(--global-popover-BorderColor);
|
||||
border-radius: var(--small-BorderRadius);
|
||||
box-shadow: var(--global-popover-ShadowX) var(--global-popover-ShadowY) var(--global-popover-ShadowBlur)
|
||||
var(--global-popover-ShadowSpread) var(--global-popover-ShadowColor);
|
||||
|
||||
:global(.mobile-theme) & {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.hulyPopupEmoji-header__tools,
|
||||
.hulyPopupEmoji-header__tabs-wrapper,
|
||||
.hulyPopupEmoji-header__tabs,
|
||||
.hulyPopupEmoji-header__tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.hulyPopupEmoji-header__tabs-wrapper {
|
||||
overflow-x: auto;
|
||||
padding: 0.25rem 0.75rem;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--theme-popup-divider);
|
||||
}
|
||||
.hulyPopupEmoji-header__tabs {
|
||||
position: relative;
|
||||
gap: 0.125rem;
|
||||
width: 100%;
|
||||
}
|
||||
.hulyPopupEmoji-header__tab {
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: var(--theme-halfcontent-color);
|
||||
transition: color 0.15s ease-in;
|
||||
transition: left 0.15s ease-in;
|
||||
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
color: var(--theme-darker-color);
|
||||
}
|
||||
:global(.mobile-theme) & {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
}
|
||||
:global(svg) {
|
||||
transform: scale(0.8);
|
||||
transition: transform 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
&:not(:disabled, .disabled) {
|
||||
&.selected {
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
:global(svg) {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
color: var(--theme-content-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hulyPopupEmoji-header__tab-cursor {
|
||||
position: absolute;
|
||||
bottom: -0.25rem;
|
||||
width: 2rem;
|
||||
height: 0.125rem;
|
||||
background-color: var(--theme-tablist-plain-color);
|
||||
transition: left 0.15s ease-in;
|
||||
|
||||
:global(.mobile-theme) & {
|
||||
width: 1.75rem;
|
||||
}
|
||||
}
|
||||
.hulyPopupEmoji-header__tools {
|
||||
gap: 0.5rem;
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
|
||||
.hulyPopupEmoji-footer {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0.75rem;
|
||||
width: calc(100% - 1.5rem);
|
||||
height: 1rem;
|
||||
background: var(--theme-popup-trans-gradient);
|
||||
transform-origin: center;
|
||||
transform: translateX(-50%) rotate(180deg);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,40 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { Emoji } from 'emojibase'
|
||||
import { Label, closeTooltip, ModernCheckbox, getEmojiSkins } from '../../'
|
||||
import { skinTones } from '.'
|
||||
|
||||
export let emoji: Emoji
|
||||
export let selected: number
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
closeTooltip()
|
||||
|
||||
const emojiSkins = getEmojiSkins(emoji)
|
||||
const skins: Emoji[] = emojiSkins !== undefined ? [emoji, ...emojiSkins] : []
|
||||
</script>
|
||||
|
||||
<div class="hulyPopup-container noPadding">
|
||||
<div class="hulyPopup-group">
|
||||
{#each skins as skin, index}
|
||||
{@const disabled = selected === index}
|
||||
{@const label = skinTones.get(index)}
|
||||
<button
|
||||
class="hulyPopup-row withKeys"
|
||||
class:noHover={disabled}
|
||||
on:click={() => {
|
||||
if (disabled) return undefined
|
||||
dispatch('close', index)
|
||||
}}
|
||||
>
|
||||
<span style:font-size={'1.5rem'}>{skin.emoji}</span>
|
||||
{#if label}<span class="hulyPopup-row__label"><Label {label} /></span>{/if}
|
||||
{#if disabled}<span class="hulyPopup-row__keys"><ModernCheckbox checked disabled /></span>{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
@ -1,37 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getEmojiSkins } from '.'
|
||||
import { ButtonBase, closeTooltip } from '../..'
|
||||
import { Emoji } from 'emojibase'
|
||||
|
||||
export let emoji: Emoji
|
||||
export let selected: number
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const emojiSkins = getEmojiSkins(emoji)
|
||||
const skins: Emoji[] = emojiSkins !== undefined ? [emoji, ...emojiSkins] : []
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-gap-1">
|
||||
{#each skins as skin, index}
|
||||
{@const disabled = selected === index}
|
||||
<ButtonBase
|
||||
type={'type-button-icon'}
|
||||
{disabled}
|
||||
kind={disabled ? 'secondary' : 'tertiary'}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
if (disabled) return undefined
|
||||
dispatch('update', index)
|
||||
closeTooltip()
|
||||
}}
|
||||
>
|
||||
<span style:font-size={'1.5rem'}>{skin.emoji}</span>
|
||||
</ButtonBase>
|
||||
{/each}
|
||||
</div>
|
@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12,7.8c0.4,0,0.8-0.3,0.8-0.8S12.4,6.2,12,6.2c-3.2,0-5.8,2.6-5.8,5.8c0,3.2,2.6,5.8,5.8,5.8c3.2,0,5.8-2.6,5.8-5.8c0-0.4-0.3-0.8-0.8-0.8s-0.8,0.3-0.8,0.8c0,2.3-1.9,4.2-4.2,4.2c-2.3,0-4.2-1.9-4.2-4.2C7.8,9.7,9.7,7.8,12,7.8z"
|
||||
/>
|
||||
<path
|
||||
d="M21.7,10.9c0-0.4-0.4-0.7-0.8-0.7c-0.4,0-0.7,0.4-0.7,0.8c0.1,0.6,0.1,1.2,0,1.8c-0.2,2.2-1.3,4.2-3.1,5.5c-1.7,1.4-3.9,2-6.1,1.8c-2.2-0.2-4.2-1.3-5.5-3.1c-1.4-1.7-2-3.9-1.8-6.1C4,8.9,5.1,6.9,6.8,5.6s3.9-2,6.1-1.8c0.4,0.1,0.8-0.2,0.8-0.7c0-0.4-0.2-0.8-0.7-0.8C10.5,2,7.9,2.8,5.9,4.4c-2,1.6-3.3,3.9-3.6,6.5C2,13.5,2.8,16,4.4,18.1c1.6,2,3.9,3.3,6.5,3.6c0.4,0,0.7,0.1,1.1,0.1c2.2,0,4.3-0.7,6.1-2.1c2-1.6,3.3-3.9,3.6-6.5C21.8,12.4,21.8,11.6,21.7,10.9z"
|
||||
/>
|
||||
<path
|
||||
d="M12.6,10.4c-0.2-0.1-0.4-0.1-0.6-0.1c-1,0-1.8,0.8-1.8,1.8s0.8,1.8,1.8,1.8s1.8-0.8,1.8-1.8c0-0.2,0-0.4-0.1-0.6l1.7-1.7H18c0.2,0,0.4-0.1,0.5-0.2l3-3c0.2-0.2,0.3-0.5,0.2-0.8c-0.1-0.3-0.4-0.5-0.7-0.5h-2.2V3c0-0.3-0.2-0.6-0.5-0.7c-0.3-0.1-0.6-0.1-0.8,0.2l-3,3c-0.1,0.1-0.2,0.3-0.2,0.5v2.7L12.6,10.4z M15.8,6.3l1.5-1.5V6c0,0.4,0.3,0.8,0.8,0.8h1.2l-1.5,1.5h-1.9V6.3z"
|
||||
/>
|
||||
</svg>
|
@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10,9.8c-0.7,0-1.2,0.6-1.2,1.3s0.6,1.2,1.2,1.2s1.2-0.6,1.2-1.2v0C11.2,10.3,10.7,9.8,10,9.8z" />
|
||||
<path d="M14,9.8c-0.7,0-1.2,0.6-1.2,1.3s0.6,1.2,1.2,1.2s1.2-0.6,1.2-1.2v0C15.2,10.3,14.7,9.8,14,9.8z" />
|
||||
<path
|
||||
d="M21.7,10.6l-2-6.8c-0.1-0.4-0.5-0.6-0.8-0.5l-6,1c0,0,0,0,0,0h-1.7c0,0,0,0,0,0l-6-1C4.7,3.2,4.4,3.4,4.3,3.8l-2,6.9c-0.1,0.6,0.1,1.3,0.6,1.7c0.3,0.3,0.7,0.4,1.1,0.4c0.1,0,0.2,0,0.3,0c0.8,5.3,2.7,8,5.7,8h4c3,0,4.9-2.7,5.7-8c0.1,0,0.2,0,0.3,0c0.4,0,0.8-0.1,1.1-0.4C21.6,11.9,21.9,11.3,21.7,10.6z M3.8,11.2c-0.1,0-0.1-0.1-0.1-0.2l1.8-6.2l4,0.6l-5.3,5.7C4.1,11.3,3.9,11.3,3.8,11.2z M14,19.2h-1.2V18c0-0.1,0-0.2-0.1-0.4c0.7-0.2,1.1-0.8,1.1-1.6c0-0.4-0.3-0.8-0.8-0.8h-2c-0.4,0-0.8,0.3-0.8,0.8c0,0.8,0.4,1.4,1.1,1.6c-0.1,0.1-0.1,0.2-0.1,0.4v1.2H10c-2.2,0-3.6-2.5-4.3-7.3c0-0.1,0-0.1,0-0.1l5.6-6h1.4l5.6,6c0,0,0,0.1,0,0.1C17.6,16.8,16.2,19.2,14,19.2z M20.2,11.2c-0.1,0.1-0.3,0.1-0.4,0l-5.3-5.7l4-0.6l1.8,6.1C20.3,11.1,20.2,11.1,20.2,11.2z"
|
||||
/>
|
||||
</svg>
|
@ -1,14 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M19.3,4.3c-0.3-0.1-0.6-0.1-0.8,0.2c-1.6,1.6-4.3,1.6-5.9,0c-2.2-2.2-5.8-2.2-8,0C4.3,4.6,4.2,4.8,4.2,5v9v7c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8v-6.6c1.7-1.4,4.2-1.4,5.7,0.2c1.1,1.1,2.6,1.6,4,1.6s2.9-0.5,4-1.6c0.1-0.1,0.2-0.3,0.2-0.5V5C19.8,4.7,19.6,4.4,19.3,4.3z M18.2,13.7c-1.7,1.4-4.2,1.3-5.7-0.2c-1.1-1.1-2.6-1.6-4-1.6c-0.9,0-1.9,0.2-2.8,0.7V5.3C7.4,3.9,9.9,4,11.5,5.5c1.8,1.8,4.6,2.1,6.8,0.9V13.7z"
|
||||
/>
|
||||
</svg>
|
@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7.6,7.6C7.7,7.7,7.8,7.8,8,7.8c0.2,0,0.5-0.1,0.6-0.3c0.2-0.3,0.2-0.8-0.2-1C8,6.1,7.7,5.6,7.8,5c0-0.5,0.2-1.1,0.7-1.4c0.3-0.2,0.4-0.7,0.2-1c-0.2-0.3-0.7-0.4-1-0.2C6.7,3,6.2,4,6.2,5C6.2,6,6.7,7,7.6,7.6z"
|
||||
/>
|
||||
<path
|
||||
d="M11.6,7.6c0.1,0.1,0.3,0.1,0.4,0.1c0.2,0,0.5-0.1,0.6-0.3c0.2-0.3,0.2-0.8-0.2-1C12,6.1,11.7,5.6,11.8,5c0-0.5,0.2-1.1,0.7-1.4c0.3-0.2,0.4-0.7,0.2-1c-0.2-0.3-0.7-0.4-1-0.2C10.7,3,10.2,4,10.2,5C10.2,6,10.7,7,11.6,7.6z"
|
||||
/>
|
||||
<path
|
||||
d="M19.6,10.6c-0.6-0.3-1.2-0.4-1.8-0.3V10c0-0.4-0.3-0.8-0.8-0.8H3c-0.4,0-0.8,0.3-0.8,0.8v5c0,3.7,3,6.8,6.8,6.8h2c2.8,0,5.2-1.7,6.2-4.1c0.3,0.1,0.5,0.1,0.8,0.1c0.4,0,0.9-0.1,1.3-0.2c0.9-0.3,1.7-1,2.1-2C22.3,13.7,21.4,11.5,19.6,10.6zM16.2,10.8v0.4c0,0,0,0,0,0v1.6c-0.8-0.3-1.7-0.5-2.7-0.5c-1.5,0-3,0.4-4,1.2c-0.7,0.5-1.8,0.8-3.1,0.8c-1.1,0-2-0.2-2.7-0.7v-2.8H16.2z M11,20.2H9c-2.8,0-5.1-2.2-5.2-5c0.8,0.3,1.7,0.5,2.6,0.5c0.1,0,0.1,0,0.2,0c1.5,0,2.9-0.4,3.9-1.2c0.7-0.5,1.8-0.8,3-0.8c0.1,0,0.1,0,0.2,0c1,0,2,0.2,2.7,0.7V15C16.2,17.9,13.9,20.2,11,20.2z M20,14.9c-0.2,0.5-0.7,1-1.3,1.2c-0.4,0.1-0.8,0.2-1.1,0.1c0.1-0.4,0.1-0.8,0.1-1.2v-3.2c0.4,0,0.8,0,1.2,0.2C20.1,12.5,20.6,13.8,20,14.9z"
|
||||
/>
|
||||
</svg>
|
@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12,7.2c-0.4,0-0.8,0.3-0.8,0.8v4c0,0.2,0.1,0.4,0.2,0.5l2,2c0.1,0.1,0.3,0.2,0.5,0.2s0.4-0.1,0.5-0.2c0.3-0.3,0.3-0.8,0-1.1l-1.8-1.8V8C12.8,7.6,12.4,7.2,12,7.2z"
|
||||
/>
|
||||
<path
|
||||
d="M19.6,5.8c-1.6-2-4-3.3-6.6-3.5C7.6,1.7,2.8,5.6,2.3,10.9c0,0.4,0.3,0.8,0.7,0.8c0.4,0,0.8-0.3,0.8-0.7c0.5-4.5,4.5-7.8,9-7.4C15,3.9,17,5,18.4,6.7c1.4,1.7,2,3.9,1.8,6c-0.2,2.2-1.3,4.2-3,5.6c-1.7,1.4-3.9,2-6,1.8c-2.8-0.3-5.2-2-6.5-4.4H8c0.4,0,0.8-0.3,0.8-0.8S8.5,14.2,8,14.2H3.6c0,0,0,0,0,0H3c-0.4,0-0.8,0.3-0.8,0.8v5c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8v-2.8c1.6,2.5,4.2,4.1,7.2,4.4c0.3,0,0.7,0.1,1,0.1c2.2,0,4.4-0.8,6.1-2.2c2-1.6,3.3-4,3.5-6.6C22,10.3,21.2,7.8,19.6,5.8z"
|
||||
/>
|
||||
</svg>
|
@ -1,14 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M11,21.8c-0.1,0-0.2,0-0.2,0c-0.3-0.1-0.5-0.4-0.5-0.7v-6.2H5c-0.3,0-0.5-0.2-0.7-0.4c-0.1-0.3-0.1-0.6,0.1-0.8l8-11c0.2-0.3,0.5-0.4,0.8-0.3c0.3,0.1,0.5,0.4,0.5,0.7v6.2H19c0.3,0,0.5,0.2,0.7,0.4s0.1,0.6-0.1,0.8l-8,11C11.5,21.6,11.2,21.8,11,21.8z M6.5,13.2H11c0.4,0,0.8,0.3,0.8,0.8v4.7l5.8-7.9H13c-0.4,0-0.8-0.3-0.8-0.8V5.3L6.5,13.2z"
|
||||
/>
|
||||
</svg>
|
@ -1,15 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M20.7,6.9l-3.6-3.6c-1.4-1.4-3.7-1.4-5.1,0L9.3,6C8.6,6.6,8.2,7.6,8.2,8.5c0,0.9,0.3,1.7,0.8,2.3l-1.6,1.6c-0.1,0.1-0.2,0.3-0.2,0.5v1.2H6c-0.4,0-0.8,0.3-0.8,0.8v1.2H4c-0.2,0-0.4,0.1-0.5,0.2l-0.6,0.6c-0.4,0.5-0.6,1.1-0.6,1.8l0,1.2l0,0.2c0.1,0.9,0.9,1.5,1.7,1.5l1.2,0l0.2,0c0.6-0.1,1.2-0.3,1.7-0.8l6-6c1.4,1.2,3.6,1.1,4.9-0.2l2.6-2.6C22.1,10.6,22.1,8.3,20.7,6.9z M19.6,11L17,13.6c-0.8,0.8-2.2,0.8-3,0l-0.3-0.3c-0.3-0.3-0.8-0.3-1.1,0l-6.6,6.6c-0.2,0.2-0.5,0.3-0.7,0.4l-1.3,0c-0.1,0-0.2-0.1-0.2-0.2l0-1.2c0-0.3,0.1-0.6,0.3-0.8l0.3-0.3H6c0.4,0,0.8-0.3,0.8-0.8v-1.2H8c0.4,0,0.8-0.3,0.8-0.8v-1.7l1.9-1.9c0.1-0.1,0.2-0.3,0.2-0.5c0-0.2-0.1-0.4-0.2-0.5L10.4,10c-0.8-0.8-0.8-2.2,0-3L13,4.4c0.8-0.8,2.2-0.8,3,0L19.6,8C20.5,8.8,20.5,10.2,19.6,11z"
|
||||
/>
|
||||
<path d="M15,7.8L15,7.8c-0.7,0-1.3,0.6-1.3,1.2s0.6,1.2,1.3,1.2s1.2-0.6,1.2-1.2S15.7,7.8,15,7.8z" />
|
||||
</svg>
|
@ -1,14 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M21.5,20.5L16,14.9c1.1-1.3,1.8-3.1,1.8-4.9c0-4.3-3.5-7.8-7.8-7.8S2.2,5.7,2.2,10s3.5,7.8,7.8,7.8c1.9,0,3.6-0.7,4.9-1.8l5.5,5.5c0.1,0.1,0.3,0.2,0.5,0.2s0.4-0.1,0.5-0.2C21.8,21.2,21.8,20.8,21.5,20.5z M3.8,10c0-3.4,2.8-6.2,6.2-6.2s6.2,2.8,6.2,6.2s-2.8,6.2-6.2,6.2S3.8,13.4,3.8,10z"
|
||||
/>
|
||||
</svg>
|
@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12,2.2c-5.4,0-9.8,4.4-9.8,9.8s4.4,9.8,9.8,9.8s9.8-4.4,9.8-9.8S17.4,2.2,12,2.2z M12,20.2c-4.5,0-8.2-3.7-8.2-8.2S7.5,3.8,12,3.8s8.2,3.7,8.2,8.2S16.5,20.2,12,20.2z"
|
||||
/>
|
||||
<path
|
||||
d="M7.8,9.5c0,0.1,0.1,0.1,0.1,0.2c0,0.1,0.1,0.1,0.2,0.2c0.2,0.2,0.6,0.4,0.9,0.4c0.1,0,0.2,0,0.2,0c0.1,0,0.1,0,0.2-0.1c0.1,0,0.1-0.1,0.2-0.1c0.1,0,0.1-0.1,0.2-0.2C9.9,9.8,10,9.8,10,9.7c0-0.1,0.1-0.1,0.1-0.2c0-0.1,0.1-0.2,0.1-0.2c0-0.1,0-0.2,0-0.2c0-0.3-0.1-0.7-0.4-0.9C9.8,8.1,9.8,8,9.7,8c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.2-0.1-0.2-0.1c-0.4-0.1-0.8,0-1.1,0.3C7.9,8.3,7.8,8.7,7.8,9c0,0.1,0,0.2,0,0.2C7.8,9.3,7.8,9.4,7.8,9.5z"
|
||||
/>
|
||||
<path
|
||||
d="M14.3,10c0.1,0,0.1,0.1,0.2,0.1c0.1,0,0.2,0.1,0.2,0.1c0.1,0,0.2,0,0.2,0s0.2,0,0.2,0c0.1,0,0.2,0,0.2-0.1c0.1,0,0.2-0.1,0.2-0.1c0.1-0.1,0.1-0.1,0.2-0.2c0.2-0.2,0.4-0.6,0.4-0.9c0-0.3-0.1-0.7-0.4-0.9c0-0.1-0.1-0.1-0.2-0.2c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.2-0.1-0.2-0.1c-0.2,0-0.3,0-0.5,0c-0.1,0-0.2,0-0.2,0.1c-0.1,0-0.1,0.1-0.2,0.1c-0.1,0-0.1,0.1-0.2,0.2c-0.2,0.2-0.4,0.6-0.4,0.9c0,0.3,0.1,0.6,0.4,0.9C14.2,9.9,14.2,10,14.3,10z"
|
||||
/>
|
||||
<path
|
||||
d="M16,12.2H8c-0.4,0-0.8,0.3-0.8,0.8c0,2.6,2.1,4.8,4.8,4.8s4.8-2.1,4.8-4.8C16.8,12.6,16.4,12.2,16,12.2z M12,16.2c-1.5,0-2.8-1.1-3.2-2.5h6.3C14.8,15.2,13.5,16.2,12,16.2z"
|
||||
/>
|
||||
</svg>
|
@ -1,23 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M6.5,10.8c2.3,0,4.2-1.9,4.2-4.2S8.8,2.2,6.5,2.2S2.2,4.2,2.2,6.5S4.2,10.8,6.5,10.8z M6.5,3.8C8,3.8,9.2,5,9.2,6.5S8,9.2,6.5,9.2S3.8,8,3.8,6.5S5,3.8,6.5,3.8z"
|
||||
/>
|
||||
<path
|
||||
d="M7.2,13.6c-0.3-0.5-1-0.5-1.3,0l-4,7c-0.1,0.2-0.1,0.5,0,0.7c0.1,0.2,0.4,0.4,0.6,0.4h8c0.3,0,0.5-0.1,0.6-0.4c0.1-0.2,0.1-0.5,0-0.7L7.2,13.6z M3.8,20.2l2.7-4.7l2.7,4.7H3.8z"
|
||||
/>
|
||||
<path
|
||||
d="M18.6,6.5l3-3c0.3-0.3,0.3-0.8,0-1.1s-0.8-0.3-1.1,0l-3,3l-3-3c-0.3-0.3-0.8-0.3-1.1,0s-0.3,0.8,0,1.1l3,3l-3,3c-0.3,0.3-0.3,0.8,0,1.1c0.1,0.1,0.3,0.2,0.5,0.2s0.4-0.1,0.5-0.2l3-3l3,3c0.1,0.1,0.3,0.2,0.5,0.2s0.4-0.1,0.5-0.2c0.3-0.3,0.3-0.8,0-1.1L18.6,6.5z"
|
||||
/>
|
||||
<path
|
||||
d="M21,13.2h-7c-0.4,0-0.8,0.3-0.8,0.8v7c0,0.4,0.3,0.8,0.8,0.8h7c0.4,0,0.8-0.3,0.8-0.8v-7C21.8,13.6,21.4,13.2,21,13.2zM20.2,20.2h-5.5v-5.5h5.5V20.2z"
|
||||
/>
|
||||
</svg>
|
@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
//
|
||||
// © 2025 Hardcore Engineering, Inc. All Rights Reserved.
|
||||
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
|
||||
//
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" viewBox="0 0 24 24" {fill} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M21.5,11.5l-9-9c-0.3-0.3-0.8-0.3-1.1,0l-9,9c-0.2,0.2-0.3,0.5-0.2,0.8s0.4,0.5,0.7,0.5h1.2V19c0,1.5,1.2,2.8,2.8,2.8h10c1.5,0,2.8-1.2,2.8-2.8v-6.2H21c0.3,0,0.6-0.2,0.7-0.5C21.8,12,21.7,11.7,21.5,11.5z M18.2,19c0,0.7-0.6,1.2-1.2,1.2H7c-0.7,0-1.2-0.6-1.2-1.2v-7c0-0.4-0.3-0.8-0.8-0.8H4.8L12,4.1l7.2,7.2H19c-0.4,0-0.8,0.3-0.8,0.8V19z"
|
||||
/>
|
||||
<path
|
||||
d="M14,11.2h-4c-0.4,0-0.8,0.3-0.8,0.8v4c0,0.4,0.3,0.8,0.8,0.8h4c0.4,0,0.8-0.3,0.8-0.8v-4C14.8,11.6,14.4,11.2,14,11.2zM13.2,15.2h-2.5v-2.5h2.5V15.2z"
|
||||
/>
|
||||
</svg>
|
@ -1,97 +0,0 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import IconFrequentlyUsed from './icons/FrequentlyUsed.svelte'
|
||||
import IconGettingWorkDone from './icons/GettingWorkDone.svelte'
|
||||
import IconSmileysAndPeople from './icons/SmileysAndPeople.svelte'
|
||||
import IconAnimalsAndNature from './icons/AnimalsAndNature.svelte'
|
||||
import IconFoodAndDrink from './icons/FoodAndDrink.svelte'
|
||||
import IconTravelAndPlaces from './icons/TravelAndPlaces.svelte'
|
||||
import IconActivities from './icons/Activities.svelte'
|
||||
import IconObjects from './icons/Objects.svelte'
|
||||
import IconSymbols from './icons/Symbols.svelte'
|
||||
import IconFlags from './icons/Flags.svelte'
|
||||
|
||||
import plugin from '../../plugin'
|
||||
import type { EmojiCategory } from './types'
|
||||
|
||||
export * from './types'
|
||||
export * from './store'
|
||||
export * from './utils'
|
||||
|
||||
export { default as EmojiPopup } from './EmojiPopup.svelte'
|
||||
export { default as EmojiButton } from './EmojiButton.svelte'
|
||||
|
||||
export const emojiCategories: EmojiCategory[] = [
|
||||
{ id: 'frequently-used', label: plugin.string.FrequentlyUsed, icon: IconFrequentlyUsed },
|
||||
{
|
||||
id: 'getting-work-done',
|
||||
label: plugin.string.GettingWorkDone,
|
||||
icon: IconGettingWorkDone,
|
||||
emojisString: [
|
||||
'2705',
|
||||
'1F440',
|
||||
'1F64C',
|
||||
'1F64F',
|
||||
'2795',
|
||||
'2796',
|
||||
'1F44F',
|
||||
'1F4A1',
|
||||
'1F3AF',
|
||||
'1F44B',
|
||||
'1F44D',
|
||||
'1F389',
|
||||
'0031-FE0F-20E3',
|
||||
'0032-FE0F-20E3',
|
||||
'0033-FE0F-20E3',
|
||||
'1F4E3',
|
||||
'26AA',
|
||||
'1F535',
|
||||
'1F534',
|
||||
'1F3CE'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'smileys-people',
|
||||
label: plugin.string.SmileysAndPeople,
|
||||
icon: IconSmileysAndPeople,
|
||||
categories: ['smileys-emotion', 'people-body']
|
||||
},
|
||||
{
|
||||
id: 'animals-nature',
|
||||
label: plugin.string.AnimalsAndNature,
|
||||
icon: IconAnimalsAndNature,
|
||||
categories: 'animals-nature'
|
||||
},
|
||||
{ id: 'food-drink', label: plugin.string.FoodAndDrink, icon: IconFoodAndDrink, categories: 'food-drink' },
|
||||
{ id: 'travel-places', label: plugin.string.TravelAndPlaces, icon: IconTravelAndPlaces, categories: 'travel-places' },
|
||||
{ id: 'activities', label: plugin.string.Activities, icon: IconActivities, categories: 'activities' },
|
||||
{ id: 'objects', label: plugin.string.Objects, icon: IconObjects, categories: 'objects' },
|
||||
{ id: 'symbols', label: plugin.string.Symbols, icon: IconSymbols, categories: 'symbols' },
|
||||
{ id: 'flags', label: plugin.string.Flags, icon: IconFlags, categories: 'flags' }
|
||||
]
|
||||
|
||||
export const skinTonesCodes = [0x1f3fb, 0x1f3fc, 0x1f3fd, 0x1f3fe, 0x1f3ff]
|
||||
|
||||
export const skinTones: Map<number, IntlString> = new Map<number, IntlString>(
|
||||
[
|
||||
plugin.string.NoTone,
|
||||
plugin.string.Light,
|
||||
plugin.string.MediumLight,
|
||||
plugin.string.Medium,
|
||||
plugin.string.MediumDark,
|
||||
plugin.string.Dark
|
||||
].map((label, index) => [index, label])
|
||||
)
|
@ -1,29 +0,0 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { writable, derived } from 'svelte/store'
|
||||
import type { EmojiWithGroup } from '.'
|
||||
|
||||
export const emojiStore = writable<EmojiWithGroup[]>([])
|
||||
export const searchEmoji = writable<string>('')
|
||||
|
||||
export const resultEmojis = derived([emojiStore, searchEmoji], ([emojis, search]) => {
|
||||
return search !== ''
|
||||
? emojis.filter(
|
||||
(emoji) =>
|
||||
(emoji.tags?.some((tag: string) => tag.toLowerCase().startsWith(search.toLowerCase())) ?? false) ||
|
||||
emoji.label.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
: emojis
|
||||
})
|
@ -1,44 +0,0 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import type { AnySvelteComponent } from '../..'
|
||||
import type { Emoji } from 'emojibase'
|
||||
|
||||
export type ExtendedEmoji = Emoji | CustomEmoji
|
||||
export type EmojiWithGroup = ExtendedEmoji & { key: string }
|
||||
|
||||
export interface CustomEmoji {
|
||||
shortcode: string
|
||||
label: string
|
||||
url: string
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
export interface EmojiCategory {
|
||||
id: string
|
||||
label: IntlString
|
||||
icon: AnySvelteComponent
|
||||
categories?: string[] | string
|
||||
emojisString?: string[]
|
||||
emojis?: EmojiWithGroup[]
|
||||
}
|
||||
|
||||
export function isCustomEmoji (emoji: ExtendedEmoji): emoji is CustomEmoji {
|
||||
return 'url' in emoji
|
||||
}
|
||||
|
||||
/* export function isCustomEmoji (emoji: ExtendedEmoji): boolean {
|
||||
return false
|
||||
} */
|
@ -1,201 +0,0 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { get } from 'svelte/store'
|
||||
import type { Emoji, Locale } from 'emojibase'
|
||||
import { fetchEmojis, fetchMessages } from 'emojibase'
|
||||
import EMOJI_REGEX from 'emojibase-regex'
|
||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon'
|
||||
import SHORTCODE_REGEX from 'emojibase-regex/shortcode'
|
||||
import { getCurrentAccount } from '@hcengineering/core'
|
||||
import { deviceOptionsStore as deviceInfo, type ExtendedEmoji, isCustomEmoji } from '../..'
|
||||
import type { EmojiWithGroup } from '.'
|
||||
import { emojiCategories, emojiStore } from '.'
|
||||
|
||||
export const emojiRegex = EMOJI_REGEX
|
||||
export const emojiGlobalRegex = new RegExp(EMOJI_REGEX.source, EMOJI_REGEX.flags + 'g')
|
||||
|
||||
export const emoticonRegex = new RegExp(`(?:^|\\s)(${EMOTICON_REGEX.source})$`)
|
||||
export const emoticonGlobalRegex = new RegExp(`(?<!\\S)${EMOTICON_REGEX.source}(?!\\S)`, EMOTICON_REGEX.flags + 'g')
|
||||
|
||||
export const shortcodeRegex = new RegExp(`(?:^|\\s)(${SHORTCODE_REGEX.source})$`)
|
||||
export const shortcodeGlobalRegex = new RegExp(`(?<!\\S)${SHORTCODE_REGEX.source}(?!\\S)`, SHORTCODE_REGEX.flags + 'g')
|
||||
|
||||
export async function loadEmojis (lang?: string): Promise<EmojiWithGroup[]> {
|
||||
const local = lang ?? get(deviceInfo).language ?? 'en'
|
||||
const englishEmojis =
|
||||
local === 'en'
|
||||
? await fetchEmojis('en', { version: '15.0', shortcodes: ['iamcal'] })
|
||||
: await fetchEmojis('en', { compact: true, version: '15.0', shortcodes: ['iamcal'] })
|
||||
const languageEmojis = local === 'en' ? null : await fetchEmojis(local as Locale, { version: '15.0' })
|
||||
const messages = await fetchMessages(local as Locale)
|
||||
const groups = messages.groups
|
||||
const groupKeys = new Map<number, string>(groups.map((group, index) => [index, group.key]))
|
||||
|
||||
const categories = new Map<string, string>()
|
||||
emojiCategories.forEach((cat) => {
|
||||
if (Array.isArray(cat.categories)) cat.categories.forEach((c) => categories.set(c, cat.id))
|
||||
else if (typeof cat.categories === 'string') categories.set(cat.categories, cat.id)
|
||||
})
|
||||
|
||||
const emojis =
|
||||
languageEmojis !== null
|
||||
? languageEmojis.map((langEmoji, index) => {
|
||||
return {
|
||||
...langEmoji,
|
||||
tags: [...(englishEmojis[index]?.tags ?? []), ...(langEmoji?.tags ?? [])],
|
||||
shortcodes: [...(englishEmojis[index]?.shortcodes ?? []), ...(langEmoji?.shortcodes ?? [])]
|
||||
}
|
||||
})
|
||||
: (englishEmojis as Emoji[])
|
||||
|
||||
return emojis
|
||||
.filter((e) => e.group !== 2 && e.group !== undefined)
|
||||
.map((e) => {
|
||||
return { ...e, key: categories.get(groupKeys.get(e?.group ?? 0) ?? '') ?? '' }
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateEmojis (lang?: string): Promise<void> {
|
||||
const emojis = await loadEmojis(lang)
|
||||
emojis.push({
|
||||
shortcode: 'huly',
|
||||
label: 'huly',
|
||||
url: 'https://fonts.gstatic.com/s/e/notoemoji/latest/1f979/512.gif',
|
||||
tags: ['huly'],
|
||||
key: 'flags'
|
||||
})
|
||||
emojiStore.set(emojis)
|
||||
}
|
||||
|
||||
export function getEmojiByHexcode (hexcode: string): EmojiWithGroup | undefined {
|
||||
return get(emojiStore).find((e) => !isCustomEmoji(e) && e.hexcode === hexcode)
|
||||
}
|
||||
|
||||
export function getEmojiByEmoticon (emoticon: string | undefined): string | undefined {
|
||||
if (emoticon === undefined) return undefined
|
||||
const matchEmoji = findEmoji(e => !isCustomEmoji(e) && (Array.isArray(e.emoticon) ? e.emoticon.includes(emoticon) : e.emoticon === emoticon))
|
||||
if (matchEmoji === undefined) return undefined
|
||||
return !isCustomEmoji(matchEmoji) ? matchEmoji.emoji : undefined
|
||||
}
|
||||
|
||||
export function getUnicodeEmojiByShortCode (shortcode: string | undefined, skinTone?: number): Emoji | undefined {
|
||||
const emoji = getEmojiByShortCode(shortcode, skinTone)
|
||||
if (emoji === undefined || isCustomEmoji(emoji)) return undefined
|
||||
return emoji
|
||||
}
|
||||
|
||||
export function getEmojiByShortCode (shortcode: string | undefined, skinTone?: number): ExtendedEmoji | undefined {
|
||||
if (shortcode === undefined) return undefined
|
||||
const pureShortcode = shortcode.replaceAll(':', '')
|
||||
return findEmoji((e) => {
|
||||
if (isCustomEmoji(e)) return e.shortcode === pureShortcode
|
||||
return e.shortcodes?.includes(pureShortcode)
|
||||
}, skinTone)
|
||||
}
|
||||
|
||||
function findEmoji (predicate: (e: EmojiWithGroup) => boolean | undefined, skinTone?: number): ExtendedEmoji | undefined {
|
||||
const emojis = get(emojiStore)
|
||||
const matchEmoji = emojis.find(predicate)
|
||||
if (matchEmoji === undefined) return undefined
|
||||
if (isCustomEmoji(matchEmoji)) return matchEmoji
|
||||
if (skinTone === undefined) skinTone = getSkinTone()
|
||||
if (skinTone === 0 || matchEmoji.skins === undefined) return matchEmoji
|
||||
return matchEmoji.skins[skinTone - 1]
|
||||
}
|
||||
|
||||
export const removeFrequentlyEmojis = (emoji: EmojiWithGroup): void => {
|
||||
const hexcode = isCustomEmoji(emoji) ? emoji.shortcode : emoji.hexcode
|
||||
if (hexcode === undefined) return
|
||||
|
||||
const frequentlyEmojisKey = getEmojisLocalStorageKey()
|
||||
const frequentlyEmojis = window.localStorage.getItem(frequentlyEmojisKey)
|
||||
if (frequentlyEmojis != null) {
|
||||
const parsedEmojis = JSON.parse(frequentlyEmojis)
|
||||
if (Array.isArray(parsedEmojis)) {
|
||||
window.localStorage.setItem(
|
||||
frequentlyEmojisKey,
|
||||
JSON.stringify(parsedEmojis.filter((pe) => pe.hexcode !== hexcode))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
export const addFrequentlyEmojis = (emoji: EmojiWithGroup): void => {
|
||||
if (emoji === undefined) return
|
||||
const hexcode = isCustomEmoji(emoji) ? emoji.shortcode : emoji.hexcode
|
||||
|
||||
const frequentlyEmojisKey = getEmojisLocalStorageKey()
|
||||
const frequentlyEmojis = window.localStorage.getItem(frequentlyEmojisKey)
|
||||
const empty = frequentlyEmojis == null
|
||||
|
||||
if (!empty) {
|
||||
const parsedEmojis = JSON.parse(frequentlyEmojis)
|
||||
if (Array.isArray(parsedEmojis)) {
|
||||
const index = parsedEmojis.findIndex((pe) => pe.hexcode === hexcode)
|
||||
if (index === -1) parsedEmojis.push({ hexcode, count: 1 })
|
||||
else parsedEmojis[index].count++
|
||||
parsedEmojis.sort((a, b) => b.count - a.count)
|
||||
window.localStorage.setItem(frequentlyEmojisKey, JSON.stringify(parsedEmojis))
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
window.localStorage.setItem(frequentlyEmojisKey, JSON.stringify([{ hexcode, count: 1 }]))
|
||||
}
|
||||
export const getFrequentlyEmojis = (): EmojiWithGroup[] | undefined => {
|
||||
const frequentlyEmojisKey = getEmojisLocalStorageKey()
|
||||
const frequentlyEmojis = window.localStorage.getItem(frequentlyEmojisKey)
|
||||
if (frequentlyEmojis == null) return undefined
|
||||
|
||||
try {
|
||||
const parsedEmojis = JSON.parse(frequentlyEmojis)
|
||||
if (!Array.isArray(parsedEmojis)) return undefined
|
||||
const emojis = get(emojiStore)
|
||||
return emojis.filter((e) => {
|
||||
if (isCustomEmoji(e)) {
|
||||
return parsedEmojis.find(pe => pe.hexcode === e.shortcode) !== undefined
|
||||
}
|
||||
return parsedEmojis.find(pe => pe.hexcode === e.hexcode || e.skins?.find(s => s.hexcode === pe.hexcode) !== undefined) !== undefined
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function getEmojiSkins (emoji: ExtendedEmoji): Emoji[] | undefined {
|
||||
if (isCustomEmoji(emoji)) return undefined
|
||||
return emoji.skins
|
||||
}
|
||||
|
||||
export const setSkinTone = (skinTone: number): void => {
|
||||
const skinToneKey = getEmojisLocalStorageKey('skinTone')
|
||||
window.localStorage.setItem(skinToneKey, JSON.stringify(skinTone))
|
||||
}
|
||||
export const getSkinTone = (): number => {
|
||||
const skinToneKey = getEmojisLocalStorageKey('skinTone')
|
||||
const skinTone = window.localStorage.getItem(skinToneKey)
|
||||
if (skinTone == null) return 0
|
||||
|
||||
try {
|
||||
return JSON.parse(skinTone)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function getEmojisLocalStorageKey (suffix: string = 'frequently'): string {
|
||||
const me = getCurrentAccount()
|
||||
return `emojis.${suffix}.${me.uuid}`
|
||||
}
|
@ -11,8 +11,7 @@
|
||||
checkMobile,
|
||||
deviceOptionsStore as deviceInfo,
|
||||
checkAdaptiveMatching,
|
||||
getLocalWeekStart,
|
||||
updateEmojis
|
||||
getLocalWeekStart
|
||||
} from '../../'
|
||||
import { desktopPlatform, getCurrentLocation, location, locationStorageKeyId, navigate } from '../../location'
|
||||
import uiPlugin from '../../plugin'
|
||||
@ -93,8 +92,6 @@
|
||||
|
||||
$: document.documentElement.style.setProperty('--app-height', `${docHeight}px`)
|
||||
|
||||
$: void updateEmojis($themeStore.language)
|
||||
|
||||
let doubleTouchStartTimestamp = 0
|
||||
document.addEventListener('touchstart', (event) => {
|
||||
const now = +new Date()
|
||||
|
@ -302,7 +302,6 @@ export * from './colors'
|
||||
export * from './focus'
|
||||
export * from './resize'
|
||||
export * from './lazy'
|
||||
export * from './components/emoji'
|
||||
|
||||
export function createApp (target: HTMLElement): SvelteComponent {
|
||||
return new Root({ target })
|
||||
|
@ -6,8 +6,7 @@ import {
|
||||
getCurrentResolvedLocation,
|
||||
getEventPositionElement,
|
||||
showPopup,
|
||||
type Location,
|
||||
type EmojiWithGroup, isCustomEmoji
|
||||
type Location
|
||||
} from '@hcengineering/ui'
|
||||
import { type AttributeModel } from '@hcengineering/view'
|
||||
import emojiPlugin from '@hcengineering/emoji'
|
||||
|
@ -5,7 +5,7 @@
|
||||
//
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import emojiPlugin, { ExtendedEmoji, Emoji } from '@hcengineering/emoji'
|
||||
import { getEmojiByHexcode, getEmojiSkins } from '../utils'
|
||||
import { getEmojiByHexcode, getEmojiSkins } from '../utils'
|
||||
import EmojiButton from './EmojiButton.svelte'
|
||||
import { getSkinTone, emojiStore } from '../store'
|
||||
import { Label, IconDelete, closeTooltip, ButtonBase } from '@hcengineering/ui'
|
||||
@ -50,7 +50,7 @@
|
||||
const equal = a === b
|
||||
const noTone = a === 0
|
||||
return equal && noTone
|
||||
? e as Emoji.Emoji
|
||||
? (e as Emoji.Emoji)
|
||||
: getEmojiSkins(e)?.find((skin) =>
|
||||
equal ? skin.tone === a : Array.isArray(skin.tone) && skin.tone[0] === a && skin.tone[1] === b
|
||||
)
|
||||
|
@ -5,8 +5,9 @@
|
||||
//
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { tooltip, capitalizeFirstLetter, type LabelAndProps, type ExtendedEmoji, getEmojiSkins } from '@hcengineering/ui'
|
||||
import { isCustomEmoji } from '@hcengineering/emoji'
|
||||
import { tooltip, capitalizeFirstLetter, type LabelAndProps } from '@hcengineering/ui'
|
||||
import { isCustomEmoji, type ExtendedEmoji } from '@hcengineering/emoji'
|
||||
import { getEmojiSkins } from '../utils'
|
||||
|
||||
export let emoji: ExtendedEmoji
|
||||
export let selected: boolean = false
|
||||
@ -42,7 +43,7 @@
|
||||
}}
|
||||
>
|
||||
{#if isCustomEmoji(displayedEmoji)}
|
||||
<span><img src="{displayedEmoji.url}" alt="{displayedEmoji.shortcode}"></span>
|
||||
<span><img src={displayedEmoji.url} alt={displayedEmoji.shortcode} /></span>
|
||||
{:else}
|
||||
<span>{displayedEmoji.emoji}</span>
|
||||
{/if}
|
||||
|
@ -12,8 +12,10 @@
|
||||
showPopup,
|
||||
eventToHTMLElement,
|
||||
ButtonBase,
|
||||
closeTooltip, getEmojiByShortCode, getEmojiSkins, getUnicodeEmojiByShortCode, Icon, icon
|
||||
closeTooltip,
|
||||
Icon
|
||||
} from '@hcengineering/ui'
|
||||
import { getEmojiByShortCode, getEmojiSkins, getUnicodeEmojiByShortCode } from '../utils'
|
||||
import {
|
||||
searchEmoji,
|
||||
emojiStore,
|
||||
@ -29,7 +31,6 @@
|
||||
import EmojiGroup from './EmojiGroup.svelte'
|
||||
import emojiPlugin, { isCustomEmoji } from '@hcengineering/emoji'
|
||||
import { emojiCategories, EmojiCategory } from '../types'
|
||||
import { Asset, getMetadata } from '@hcengineering/platform'
|
||||
|
||||
export let embedded = false
|
||||
export let selected: string | undefined
|
||||
@ -119,27 +120,22 @@
|
||||
|
||||
clearTimer()
|
||||
shownContext = true
|
||||
showPopup(
|
||||
ActionsPopup,
|
||||
{ emoji, remove },
|
||||
eventToHTMLElement(event),
|
||||
(result: 'remove' | EmojiWithGroup) => {
|
||||
if (result === 'remove') {
|
||||
removeFrequentlyEmojis(emoji)
|
||||
const index = emojisCat.findIndex((ec) => ec.id === 'frequently-used')
|
||||
if (index > -1) emojisCat[index].emojis = getFrequentlyEmojis()
|
||||
emojisCat = emojisCat.filter(
|
||||
(em) => em.categories !== undefined || (Array.isArray(em.emojis) && em.emojis.length > 0)
|
||||
)
|
||||
if (currentCategory.emojis?.length === 0) {
|
||||
currentCategory = emojisCat.find((ec) => ec.emojis !== undefined && ec.emojis.length > 0) ?? emojisCat[0]
|
||||
}
|
||||
} else if (result !== undefined) {
|
||||
sendEmoji(result)
|
||||
showPopup(ActionsPopup, { emoji, remove }, eventToHTMLElement(event), (result: 'remove' | EmojiWithGroup) => {
|
||||
if (result === 'remove') {
|
||||
removeFrequentlyEmojis(emoji)
|
||||
const index = emojisCat.findIndex((ec) => ec.id === 'frequently-used')
|
||||
if (index > -1) emojisCat[index].emojis = getFrequentlyEmojis()
|
||||
emojisCat = emojisCat.filter(
|
||||
(em) => em.categories !== undefined || (Array.isArray(em.emojis) && em.emojis.length > 0)
|
||||
)
|
||||
if (currentCategory.emojis?.length === 0) {
|
||||
currentCategory = emojisCat.find((ec) => ec.emojis !== undefined && ec.emojis.length > 0) ?? emojisCat[0]
|
||||
}
|
||||
shownContext = false
|
||||
} else if (result !== undefined) {
|
||||
sendEmoji(result)
|
||||
}
|
||||
)
|
||||
shownContext = false
|
||||
})
|
||||
}
|
||||
function handleContextMenu (event: MouseEvent, emoji: EmojiWithGroup, remove: boolean): void {
|
||||
event.preventDefault()
|
||||
@ -169,13 +165,18 @@
|
||||
|
||||
const showSkinMenu = (event: MouseEvent): void => {
|
||||
shownSTM = true
|
||||
showPopup(SkinTonePopup, { emoji: getEmojiByShortCode(':hand:', 0), selected: skinTone }, eventToHTMLElement(event), (result) => {
|
||||
if (typeof result === 'number') {
|
||||
skinTone = result
|
||||
setSkinTone(skinTone)
|
||||
showPopup(
|
||||
SkinTonePopup,
|
||||
{ emoji: getEmojiByShortCode(':hand:', 0), selected: skinTone },
|
||||
eventToHTMLElement(event),
|
||||
(result) => {
|
||||
if (typeof result === 'number') {
|
||||
skinTone = result
|
||||
setSkinTone(skinTone)
|
||||
}
|
||||
shownSTM = false
|
||||
}
|
||||
shownSTM = false
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
let hidden: boolean = true
|
||||
@ -195,7 +196,7 @@
|
||||
const tempEmojis: string[] = em.emojisString
|
||||
const emojis: EmojiWithGroup[] = []
|
||||
tempEmojis.forEach((te) => {
|
||||
const e = $emojiStore.find((es) => isCustomEmoji(es) ? es.shortcode === te : es.hexcode === te)
|
||||
const e = $emojiStore.find((es) => (isCustomEmoji(es) ? es.shortcode === te : es.hexcode === te))
|
||||
if (e !== undefined) emojis.push(e)
|
||||
})
|
||||
emojiCategories[index].emojis = emojis
|
||||
@ -236,7 +237,7 @@
|
||||
handleScrollToCategory(category.id)
|
||||
}}
|
||||
>
|
||||
<Icon icon={category.icon} size={isMobile ? 'large' : 'x-large'}/>
|
||||
<Icon icon={category.icon} size={isMobile ? 'large' : 'x-large'} />
|
||||
<!--<svelte:component this={category.icon} size={isMobile ? 'large' : 'x-large'} />-->
|
||||
</button>
|
||||
{/each}
|
||||
|
@ -2,6 +2,8 @@ import type { Resources } from '@hcengineering/platform'
|
||||
import EmojiPopup from './components/EmojiPopup.svelte'
|
||||
import WorkbenchExtension from './components/WorkbenchExtension.svelte'
|
||||
|
||||
export * from './utils'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
EmojiPopup,
|
||||
|
@ -10,7 +10,7 @@ export const resultEmojis = derived([emojiStore, searchEmoji], ([emojis, search]
|
||||
? emojis.filter(
|
||||
(emoji) =>
|
||||
(emoji.tags?.some((tag: string) => tag.toLowerCase().startsWith(search.toLowerCase())) ?? false) ||
|
||||
emoji.label.toLowerCase().includes(search.toLowerCase())
|
||||
emoji.label.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
: emojis
|
||||
})
|
||||
@ -81,14 +81,14 @@ export const getFrequentlyEmojis = (): EmojiWithGroup[] | undefined => {
|
||||
const result: EmojiWithGroup[] = []
|
||||
emojis.forEach((emoji: EmojiWithGroup) => {
|
||||
if (isCustomEmoji(emoji)) {
|
||||
if (parsedEmojis.find(pe => pe.hexcode === emoji.shortcode) !== undefined) result.push(emoji)
|
||||
if (parsedEmojis.find((pe) => pe.hexcode === emoji.shortcode) !== undefined) result.push(emoji)
|
||||
} else {
|
||||
parsedEmojis.forEach((parsedEmoji: any) => {
|
||||
if (parsedEmoji.hexcode === emoji.hexcode) {
|
||||
result.push(emoji)
|
||||
return
|
||||
}
|
||||
const skinEmoji = emoji.skins?.find(s => s.hexcode === parsedEmoji.hexcode)
|
||||
const skinEmoji = emoji.skins?.find((s) => s.hexcode === parsedEmoji.hexcode)
|
||||
if (skinEmoji === undefined) return
|
||||
result.push({ ...skinEmoji, key: '' })
|
||||
})
|
||||
|
@ -52,9 +52,24 @@ export const emojiCategories: EmojiCategory[] = [
|
||||
icon: emojiPlugin.icon.AnimalsAndNature,
|
||||
categories: 'animals-nature'
|
||||
},
|
||||
{ id: 'food-drink', label: emojiPlugin.string.FoodAndDrink, icon: emojiPlugin.icon.FoodAndDrink, categories: 'food-drink' },
|
||||
{ id: 'travel-places', label: emojiPlugin.string.TravelAndPlaces, icon: emojiPlugin.icon.TravelAndPlaces, categories: 'travel-places' },
|
||||
{ id: 'activities', label: emojiPlugin.string.Activities, icon: emojiPlugin.icon.Activities, categories: 'activities' },
|
||||
{
|
||||
id: 'food-drink',
|
||||
label: emojiPlugin.string.FoodAndDrink,
|
||||
icon: emojiPlugin.icon.FoodAndDrink,
|
||||
categories: 'food-drink'
|
||||
},
|
||||
{
|
||||
id: 'travel-places',
|
||||
label: emojiPlugin.string.TravelAndPlaces,
|
||||
icon: emojiPlugin.icon.TravelAndPlaces,
|
||||
categories: 'travel-places'
|
||||
},
|
||||
{
|
||||
id: 'activities',
|
||||
label: emojiPlugin.string.Activities,
|
||||
icon: emojiPlugin.icon.Activities,
|
||||
categories: 'activities'
|
||||
},
|
||||
{ id: 'objects', label: emojiPlugin.string.Objects, icon: emojiPlugin.icon.Objects, categories: 'objects' },
|
||||
{ id: 'symbols', label: emojiPlugin.string.Symbols, icon: emojiPlugin.icon.Symbols, categories: 'symbols' },
|
||||
{ id: 'flags', label: emojiPlugin.string.Flags, icon: emojiPlugin.icon.Flags, categories: 'flags' }
|
||||
|
@ -51,7 +51,9 @@ export function getEmojiByHexcode (hexcode: string): EmojiWithGroup | undefined
|
||||
|
||||
export function getEmojiByEmoticon (emoticon: string | undefined): string | undefined {
|
||||
if (emoticon === undefined) return undefined
|
||||
const matchEmoji = findEmoji(e => !isCustomEmoji(e) && (Array.isArray(e.emoticon) ? e.emoticon.includes(emoticon) : e.emoticon === emoticon))
|
||||
const matchEmoji = findEmoji(
|
||||
(e) => !isCustomEmoji(e) && (Array.isArray(e.emoticon) ? e.emoticon.includes(emoticon) : e.emoticon === emoticon)
|
||||
)
|
||||
if (matchEmoji === undefined) return undefined
|
||||
return !isCustomEmoji(matchEmoji) ? matchEmoji.emoji : undefined
|
||||
}
|
||||
@ -71,7 +73,10 @@ export function getEmojiByShortCode (shortcode: string | undefined, skinTone?: n
|
||||
}, skinTone)
|
||||
}
|
||||
|
||||
function findEmoji (predicate: (e: EmojiWithGroup) => boolean | undefined, skinTone?: number): ExtendedEmoji | undefined {
|
||||
function findEmoji (
|
||||
predicate: (e: EmojiWithGroup) => boolean | undefined,
|
||||
skinTone?: number
|
||||
): ExtendedEmoji | undefined {
|
||||
const emojis = get(emojiStore)
|
||||
const matchEmoji = emojis.find(predicate)
|
||||
if (matchEmoji === undefined) return undefined
|
||||
|
@ -433,7 +433,8 @@
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<Component is={emojiPlugin.component.EmojiPopup}
|
||||
<Component
|
||||
is={emojiPlugin.component.EmojiPopup}
|
||||
props={{
|
||||
selected: Array.isArray(color) ? fromCodePoint(...color) : color ? fromCodePoint(color) : undefined,
|
||||
disabled: readonly,
|
||||
|
@ -54,6 +54,7 @@
|
||||
"@hcengineering/presence": "^0.6.0",
|
||||
"@hcengineering/text-markdown": "^0.6.0",
|
||||
"@hcengineering/emoji": "^0.6.0",
|
||||
"@hcengineering/emoji-resources": "^0.6.0",
|
||||
"@tiptap/core": "^2.11.7",
|
||||
"@tiptap/pm": "^2.11.7",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.11.7",
|
||||
|
@ -4,12 +4,12 @@ import {
|
||||
emoticonGlobalRegex,
|
||||
shortcodeRegex,
|
||||
shortcodeGlobalRegex,
|
||||
getEmojiByEmoticon,
|
||||
getEmojiByShortCode,
|
||||
emojiRegex,
|
||||
emojiGlobalRegex,
|
||||
type ExtendedEmoji, isCustomEmoji
|
||||
} from '@hcengineering/ui'
|
||||
type ExtendedEmoji,
|
||||
isCustomEmoji
|
||||
} from '@hcengineering/emoji'
|
||||
import { getEmojiByEmoticon, getEmojiByShortCode } from '@hcengineering/emoji-resources'
|
||||
import { type ResolvedPos } from '@tiptap/pm/model'
|
||||
import { type ExtendedRegExpMatchArray, type SingleCommands, type Range, InputRule, PasteRule } from '@tiptap/core'
|
||||
import { type EditorState } from '@tiptap/pm/state'
|
||||
|
@ -22,7 +22,8 @@
|
||||
getPlatformColor,
|
||||
getPlatformColorDef,
|
||||
themeStore,
|
||||
Label, Component
|
||||
Label,
|
||||
Component
|
||||
} from '@hcengineering/ui'
|
||||
import emojiPlugin from '@hcengineering/emoji'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
@ -96,7 +97,8 @@
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<Component is={emojiPlugin.component.EmojiPopup}
|
||||
<Component
|
||||
is={emojiPlugin.component.EmojiPopup}
|
||||
props={{
|
||||
selected: Array.isArray(color) ? fromCodePoint(...color) : color ? fromCodePoint(color) : undefined
|
||||
}}
|
||||
|
Loading…
Reference in New Issue
Block a user