mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-13 02:41:11 +00:00
Add custom emojis presentation
Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
This commit is contained in:
parent
c8f61385ec
commit
14f5252d7f
@ -80,7 +80,15 @@
|
|||||||
{:else if node.type === MarkupNodeType.text}
|
{:else if node.type === MarkupNodeType.text}
|
||||||
{node.text}
|
{node.text}
|
||||||
{:else if node.type === MarkupNodeType.emoji}
|
{:else if node.type === MarkupNodeType.emoji}
|
||||||
<span class="emoji" class:emojiOnly={single}>{node.attrs?.emoji}</span>
|
<span class="emoji" class:emojiOnly={single}>
|
||||||
|
{#if node.attrs?.kind === 'custom'}
|
||||||
|
{@const src = toString(attrs.url)}
|
||||||
|
{@const alt = toString(attrs.emoji)}
|
||||||
|
<img {src} {alt}/>
|
||||||
|
{:else}
|
||||||
|
{node.attrs?.emoji}
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
{:else if node.type === MarkupNodeType.paragraph}
|
{:else if node.type === MarkupNodeType.paragraph}
|
||||||
<p class="p-inline contrast" class:overflow-label={preview} class:emojiOnly={checkEmoji(nodes)}>
|
<p class="p-inline contrast" class:overflow-label={preview} class:emojiOnly={checkEmoji(nodes)}>
|
||||||
{#if nodes.length > 0}
|
{#if nodes.length > 0}
|
||||||
|
@ -18,13 +18,15 @@ import { Node, mergeAttributes } from '@tiptap/core'
|
|||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
emoji: {
|
emoji: {
|
||||||
insertEmoji: (emoji: string) => ReturnType
|
insertEmoji: (emoji: string, kind: 'unicode' | 'custom', url?: string) => ReturnType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiNodeOptions {
|
export interface EmojiNodeOptions {
|
||||||
emoji: string
|
emoji: string
|
||||||
|
kind: 'unicode' | 'custom'
|
||||||
|
url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmojiNode = Node.create<EmojiNodeOptions>({
|
export const EmojiNode = Node.create<EmojiNodeOptions>({
|
||||||
@ -38,6 +40,12 @@ export const EmojiNode = Node.create<EmojiNodeOptions>({
|
|||||||
return {
|
return {
|
||||||
emoji: {
|
emoji: {
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
kind: {
|
||||||
|
default: 'unicode'
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -45,11 +53,11 @@ export const EmojiNode = Node.create<EmojiNodeOptions>({
|
|||||||
addCommands () {
|
addCommands () {
|
||||||
return {
|
return {
|
||||||
insertEmoji:
|
insertEmoji:
|
||||||
(emoji: string) =>
|
(emoji: string, kind: 'unicode' | 'custom', url?: string) =>
|
||||||
({ commands }) => {
|
({ commands }) => {
|
||||||
return commands.insertContent({
|
return commands.insertContent({
|
||||||
type: this.name,
|
type: this.name,
|
||||||
attrs: { emoji }
|
attrs: { emoji, kind, url }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,6 +72,28 @@ export const EmojiNode = Node.create<EmojiNodeOptions>({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderHTML ({ node, HTMLAttributes }) {
|
renderHTML ({ node, HTMLAttributes }) {
|
||||||
|
if (node.attrs.kind === 'custom') {
|
||||||
|
return [
|
||||||
|
'span',
|
||||||
|
mergeAttributes(
|
||||||
|
{
|
||||||
|
'data-type': this.name,
|
||||||
|
class: 'emoji'
|
||||||
|
},
|
||||||
|
HTMLAttributes
|
||||||
|
),
|
||||||
|
[
|
||||||
|
'img',
|
||||||
|
mergeAttributes(
|
||||||
|
{
|
||||||
|
'data-type': this.name,
|
||||||
|
src: node.attrs.url,
|
||||||
|
alt: node.attrs.emoji
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
'span',
|
'span',
|
||||||
mergeAttributes(
|
mergeAttributes(
|
||||||
|
@ -854,3 +854,13 @@
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
.message.outcoming { border-radius: 0.75rem 0.125rem 0.125rem 0.75rem; }
|
.message.outcoming { border-radius: 0.75rem 0.125rem 0.125rem 0.75rem; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emoji > img {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: sub;
|
||||||
|
height: 1.3em;
|
||||||
|
width: auto;
|
||||||
|
margin: 0 0.05em 0 0.1em;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
@ -77,8 +77,8 @@
|
|||||||
insertText: (text) => {
|
insertText: (text) => {
|
||||||
editor?.insertText(text)
|
editor?.insertText(text)
|
||||||
},
|
},
|
||||||
insertEmoji: (emoji) => {
|
insertEmoji: (text: string, url?: string) => {
|
||||||
editor?.insertEmoji(emoji)
|
editor?.insertEmoji(text, url)
|
||||||
},
|
},
|
||||||
insertMarkup: (markup) => {
|
insertMarkup: (markup) => {
|
||||||
editor?.insertMarkup(markup)
|
editor?.insertMarkup(markup)
|
||||||
|
@ -41,7 +41,11 @@
|
|||||||
dispatch('select', displayedEmoji)
|
dispatch('select', displayedEmoji)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>{isCustomEmoji(displayedEmoji) ? displayedEmoji.shortcode : displayedEmoji.emoji}</span>
|
{#if isCustomEmoji(displayedEmoji)}
|
||||||
|
<span><img src="{displayedEmoji.url}" alt="{displayedEmoji.shortcode}"></span>
|
||||||
|
{:else}
|
||||||
|
<span>{displayedEmoji.emoji}</span>
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -74,6 +78,10 @@
|
|||||||
transform: translateY(1%);
|
transform: translateY(1%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
span > img {
|
||||||
|
height: 1em;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
&:enabled:hover {
|
&:enabled:hover {
|
||||||
background-color: var(--theme-popup-hover);
|
background-color: var(--theme-popup-hover);
|
||||||
}
|
}
|
||||||
|
@ -102,9 +102,8 @@
|
|||||||
selected = isCustomEmoji(emoji) ? emoji.shortcode : emoji.emoji
|
selected = isCustomEmoji(emoji) ? emoji.shortcode : emoji.emoji
|
||||||
addFrequentlyEmojis(emoji)
|
addFrequentlyEmojis(emoji)
|
||||||
dispatch('close', {
|
dispatch('close', {
|
||||||
// TODO: send ExtendedEmoji
|
text: selected,
|
||||||
emoji: selected
|
url: isCustomEmoji(emoji) ? emoji.url : undefined
|
||||||
// codes: emoji.hexcode.split('-').map((hc) => parseInt(hc, 16))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +69,13 @@ export async function loadEmojis (lang?: string): Promise<EmojiWithGroup[]> {
|
|||||||
|
|
||||||
export async function updateEmojis (lang?: string): Promise<void> {
|
export async function updateEmojis (lang?: string): Promise<void> {
|
||||||
const emojis = await loadEmojis(lang)
|
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)
|
emojiStore.set(emojis)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,13 +93,15 @@ export function getEmojiByEmoticon (emoticon: string | undefined): string | unde
|
|||||||
export function getUnicodeEmojiByShortCode (shortcode: string | undefined, skinTone?: number): Emoji | undefined {
|
export function getUnicodeEmojiByShortCode (shortcode: string | undefined, skinTone?: number): Emoji | undefined {
|
||||||
const emoji = getEmojiByShortCode(shortcode, skinTone)
|
const emoji = getEmojiByShortCode(shortcode, skinTone)
|
||||||
if (emoji === undefined || isCustomEmoji(emoji)) return undefined
|
if (emoji === undefined || isCustomEmoji(emoji)) return undefined
|
||||||
|
return emoji
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEmojiByShortCode (shortcode: string | undefined, skinTone?: number): ExtendedEmoji | undefined {
|
export function getEmojiByShortCode (shortcode: string | undefined, skinTone?: number): ExtendedEmoji | undefined {
|
||||||
if (shortcode === undefined) return undefined
|
if (shortcode === undefined) return undefined
|
||||||
|
const pureShortcode = shortcode.replaceAll(':', '')
|
||||||
return findEmoji((e) => {
|
return findEmoji((e) => {
|
||||||
if (isCustomEmoji(e)) return e.shortcode === shortcode
|
if (isCustomEmoji(e)) return e.shortcode === pureShortcode
|
||||||
return e.shortcodes?.includes(shortcode.replaceAll(':', ''))
|
return e.shortcodes?.includes(pureShortcode)
|
||||||
}, skinTone)
|
}, skinTone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,8 +178,8 @@
|
|||||||
insertText: (text) => {
|
insertText: (text) => {
|
||||||
editor?.commands.insertContent(text)
|
editor?.commands.insertContent(text)
|
||||||
},
|
},
|
||||||
insertEmoji: (emoji) => {
|
insertEmoji: (text: string, url?: string) => {
|
||||||
editor?.commands.insertEmoji(emoji)
|
editor?.commands.insertEmoji(text, url === undefined ? 'unicode' : 'custom')
|
||||||
},
|
},
|
||||||
insertMarkup: (markup) => {
|
insertMarkup: (markup) => {
|
||||||
editor?.commands.insertContent(markupToJSON(markup))
|
editor?.commands.insertContent(markupToJSON(markup))
|
||||||
|
@ -84,8 +84,8 @@
|
|||||||
insertText: (text) => {
|
insertText: (text) => {
|
||||||
editor?.insertText(text)
|
editor?.insertText(text)
|
||||||
},
|
},
|
||||||
insertEmoji: (emoji) => {
|
insertEmoji: (text: string, url?: string) => {
|
||||||
editor?.insertEmoji(emoji)
|
editor?.insertEmoji(text, url)
|
||||||
},
|
},
|
||||||
insertMarkup: (markup) => {
|
insertMarkup: (markup) => {
|
||||||
editor?.insertMarkup(markup)
|
editor?.insertMarkup(markup)
|
||||||
|
@ -79,8 +79,8 @@
|
|||||||
insertText: (text) => {
|
insertText: (text) => {
|
||||||
editor?.insertText(text)
|
editor?.insertText(text)
|
||||||
},
|
},
|
||||||
insertEmoji: (emoji) => {
|
insertEmoji: (text: string, url?: string) => {
|
||||||
editor?.insertEmoji(emoji)
|
editor?.insertEmoji(text, url)
|
||||||
},
|
},
|
||||||
insertMarkup: (markup) => {
|
insertMarkup: (markup) => {
|
||||||
editor?.insertMarkup(markup)
|
editor?.insertMarkup(markup)
|
||||||
|
@ -96,8 +96,8 @@
|
|||||||
return editor
|
return editor
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertEmoji (emoji: string): void {
|
export function insertEmoji (text: string, url?: string): void {
|
||||||
editor?.commands.insertEmoji(emoji)
|
editor?.commands.insertEmoji(text, url === undefined ? 'unicode' : 'custom', url)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertText (text: string): void {
|
export function insertText (text: string): void {
|
||||||
|
@ -27,7 +27,7 @@ export const defaultRefActions: RefAction[] = [
|
|||||||
if (emoji === null || emoji === undefined) {
|
if (emoji === null || emoji === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
editorHandler.insertEmoji(emoji.emoji)
|
editorHandler.insertEmoji(emoji.text, emoji.url)
|
||||||
editorHandler.focus()
|
editorHandler.focus()
|
||||||
},
|
},
|
||||||
() => {}
|
() => {}
|
||||||
|
@ -37,16 +37,28 @@ function handleEmoji (
|
|||||||
}
|
}
|
||||||
const emoji = getEmojiFunction(match.pop())
|
const emoji = getEmojiFunction(match.pop())
|
||||||
if (emoji === undefined) return
|
if (emoji === undefined) return
|
||||||
// TODO: add custom emoji
|
if (typeof emoji === 'string' || !isCustomEmoji(emoji)) {
|
||||||
const unicodeEmoji = typeof emoji === 'string' ? emoji : (isCustomEmoji(emoji) ? emoji.shortcode : emoji.emoji)
|
|
||||||
commands.insertContentAt(range, [
|
commands.insertContentAt(range, [
|
||||||
{
|
{
|
||||||
type: 'emoji',
|
type: 'emoji',
|
||||||
attrs: {
|
attrs: {
|
||||||
emoji: unicodeEmoji
|
emoji: typeof emoji === 'string' ? emoji : emoji.emoji,
|
||||||
|
kind: 'unicode'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
} else {
|
||||||
|
commands.insertContentAt(range, [
|
||||||
|
{
|
||||||
|
type: 'emoji',
|
||||||
|
attrs: {
|
||||||
|
emoji: emoji.shortcode,
|
||||||
|
kind: 'custom',
|
||||||
|
url: emoji.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmojiExtension = EmojiNode.extend<EmojiNodeOptions>({
|
export const EmojiExtension = EmojiNode.extend<EmojiNodeOptions>({
|
||||||
|
@ -16,7 +16,7 @@ export type CollaboratorType = 'local' | 'cloud'
|
|||||||
*/
|
*/
|
||||||
export interface TextEditorHandler {
|
export interface TextEditorHandler {
|
||||||
insertText: (html: string) => void
|
insertText: (html: string) => void
|
||||||
insertEmoji: (emoji: string) => void
|
insertEmoji: (text: string, url?: string) => void
|
||||||
insertMarkup: (markup: Markup) => void
|
insertMarkup: (markup: Markup) => void
|
||||||
insertTemplate: (name: string, markup: string) => void
|
insertTemplate: (name: string, markup: string) => void
|
||||||
insertTable: (options: { rows?: number, cols?: number, withHeaderRow?: boolean }) => void
|
insertTable: (options: { rows?: number, cols?: number, withHeaderRow?: boolean }) => void
|
||||||
|
Loading…
Reference in New Issue
Block a user