mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-12 18:35:45 +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}
|
||||
{node.text}
|
||||
{: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}
|
||||
<p class="p-inline contrast" class:overflow-label={preview} class:emojiOnly={checkEmoji(nodes)}>
|
||||
{#if nodes.length > 0}
|
||||
|
@ -18,13 +18,15 @@ import { Node, mergeAttributes } from '@tiptap/core'
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
emoji: {
|
||||
insertEmoji: (emoji: string) => ReturnType
|
||||
insertEmoji: (emoji: string, kind: 'unicode' | 'custom', url?: string) => ReturnType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface EmojiNodeOptions {
|
||||
emoji: string
|
||||
kind: 'unicode' | 'custom'
|
||||
url?: string
|
||||
}
|
||||
|
||||
export const EmojiNode = Node.create<EmojiNodeOptions>({
|
||||
@ -38,6 +40,12 @@ export const EmojiNode = Node.create<EmojiNodeOptions>({
|
||||
return {
|
||||
emoji: {
|
||||
default: ''
|
||||
},
|
||||
kind: {
|
||||
default: 'unicode'
|
||||
},
|
||||
url: {
|
||||
default: null
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -45,11 +53,11 @@ export const EmojiNode = Node.create<EmojiNodeOptions>({
|
||||
addCommands () {
|
||||
return {
|
||||
insertEmoji:
|
||||
(emoji: string) =>
|
||||
(emoji: string, kind: 'unicode' | 'custom', url?: string) =>
|
||||
({ commands }) => {
|
||||
return commands.insertContent({
|
||||
type: this.name,
|
||||
attrs: { emoji }
|
||||
attrs: { emoji, kind, url }
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -64,6 +72,28 @@ export const EmojiNode = Node.create<EmojiNodeOptions>({
|
||||
},
|
||||
|
||||
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 [
|
||||
'span',
|
||||
mergeAttributes(
|
||||
|
@ -1,14 +1,14 @@
|
||||
//
|
||||
// Copyright © 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
@ -108,7 +108,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: var(--theme-navpanel-color);
|
||||
|
||||
|
||||
&.vertical {
|
||||
flex-direction: column;
|
||||
min-width: var(--app-panel-width);
|
||||
@ -132,7 +132,7 @@
|
||||
display: flex;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
|
||||
|
||||
&.header { background-color: var(--theme-comp-header-color); }
|
||||
&.filled { background-color: var(--theme-bg-color); }
|
||||
&.filledNav { background-color: var(--theme-navpanel-color) !important; }
|
||||
@ -144,13 +144,13 @@
|
||||
min-width: 12.5rem;
|
||||
max-width: 22.5rem;
|
||||
width: 17.5rem;
|
||||
|
||||
|
||||
&:not(.second) { background-color: var(--theme-navpanel-color); }
|
||||
&.second.float {
|
||||
background-color: var(--theme-navpanel-color);
|
||||
filter: drop-shadow(2px 0 5px rgba(0, 0, 0, .2));
|
||||
z-index: 460;
|
||||
|
||||
|
||||
&:not(.inner) {
|
||||
position: fixed;
|
||||
top: calc(var(--status-bar-height) + 3.5rem + 1px);
|
||||
@ -301,7 +301,7 @@
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: .25rem;
|
||||
|
||||
|
||||
&.arrow {
|
||||
color: var(--theme-trans-color);
|
||||
|
||||
@ -446,7 +446,7 @@
|
||||
width: 15rem;
|
||||
padding-right: 1rem;
|
||||
color: var(--theme-caption-color);
|
||||
|
||||
|
||||
&.withDesciption {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -592,7 +592,7 @@
|
||||
height: 2.5rem;
|
||||
min-height: 2.5rem;
|
||||
border-bottom: 1px solid var(--theme-divider-color);
|
||||
|
||||
|
||||
&.high {
|
||||
padding-right: 1rem;
|
||||
height: 3.5rem;
|
||||
@ -613,7 +613,7 @@
|
||||
min-width: 0;
|
||||
font-size: 1rem;
|
||||
color: var(--caption-color);
|
||||
|
||||
|
||||
&:not(.short) { flex-grow: 1; }
|
||||
}
|
||||
&__header {
|
||||
@ -632,7 +632,7 @@
|
||||
}
|
||||
&__counter {
|
||||
color: var(--theme-darker-color);
|
||||
}
|
||||
}
|
||||
&__tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -662,7 +662,7 @@
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
|
||||
&:not(.none-appearance) {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -818,7 +818,7 @@
|
||||
}
|
||||
&:hover .caption.hasAttachments { margin-bottom: .5rem; }
|
||||
}
|
||||
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: .75rem;
|
||||
border-top-right-radius: .75rem;
|
||||
@ -854,3 +854,13 @@
|
||||
padding-top: 0;
|
||||
.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) => {
|
||||
editor?.insertText(text)
|
||||
},
|
||||
insertEmoji: (emoji) => {
|
||||
editor?.insertEmoji(emoji)
|
||||
insertEmoji: (text: string, url?: string) => {
|
||||
editor?.insertEmoji(text, url)
|
||||
},
|
||||
insertMarkup: (markup) => {
|
||||
editor?.insertMarkup(markup)
|
||||
|
@ -41,7 +41,11 @@
|
||||
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>
|
||||
{/if}
|
||||
|
||||
@ -74,6 +78,10 @@
|
||||
transform: translateY(1%);
|
||||
pointer-events: none;
|
||||
}
|
||||
span > img {
|
||||
height: 1em;
|
||||
width: auto;
|
||||
}
|
||||
&:enabled:hover {
|
||||
background-color: var(--theme-popup-hover);
|
||||
}
|
||||
|
@ -102,9 +102,8 @@
|
||||
selected = isCustomEmoji(emoji) ? emoji.shortcode : emoji.emoji
|
||||
addFrequentlyEmojis(emoji)
|
||||
dispatch('close', {
|
||||
// TODO: send ExtendedEmoji
|
||||
emoji: selected
|
||||
// codes: emoji.hexcode.split('-').map((hc) => parseInt(hc, 16))
|
||||
text: selected,
|
||||
url: isCustomEmoji(emoji) ? emoji.url : undefined
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,13 @@ export async function loadEmojis (lang?: string): Promise<EmojiWithGroup[]> {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -86,13 +93,15 @@ export function getEmojiByEmoticon (emoticon: string | undefined): string | unde
|
||||
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 === shortcode
|
||||
return e.shortcodes?.includes(shortcode.replaceAll(':', ''))
|
||||
if (isCustomEmoji(e)) return e.shortcode === pureShortcode
|
||||
return e.shortcodes?.includes(pureShortcode)
|
||||
}, skinTone)
|
||||
}
|
||||
|
||||
|
@ -178,8 +178,8 @@
|
||||
insertText: (text) => {
|
||||
editor?.commands.insertContent(text)
|
||||
},
|
||||
insertEmoji: (emoji) => {
|
||||
editor?.commands.insertEmoji(emoji)
|
||||
insertEmoji: (text: string, url?: string) => {
|
||||
editor?.commands.insertEmoji(text, url === undefined ? 'unicode' : 'custom')
|
||||
},
|
||||
insertMarkup: (markup) => {
|
||||
editor?.commands.insertContent(markupToJSON(markup))
|
||||
|
@ -84,8 +84,8 @@
|
||||
insertText: (text) => {
|
||||
editor?.insertText(text)
|
||||
},
|
||||
insertEmoji: (emoji) => {
|
||||
editor?.insertEmoji(emoji)
|
||||
insertEmoji: (text: string, url?: string) => {
|
||||
editor?.insertEmoji(text, url)
|
||||
},
|
||||
insertMarkup: (markup) => {
|
||||
editor?.insertMarkup(markup)
|
||||
|
@ -79,8 +79,8 @@
|
||||
insertText: (text) => {
|
||||
editor?.insertText(text)
|
||||
},
|
||||
insertEmoji: (emoji) => {
|
||||
editor?.insertEmoji(emoji)
|
||||
insertEmoji: (text: string, url?: string) => {
|
||||
editor?.insertEmoji(text, url)
|
||||
},
|
||||
insertMarkup: (markup) => {
|
||||
editor?.insertMarkup(markup)
|
||||
|
@ -96,8 +96,8 @@
|
||||
return editor
|
||||
}
|
||||
|
||||
export function insertEmoji (emoji: string): void {
|
||||
editor?.commands.insertEmoji(emoji)
|
||||
export function insertEmoji (text: string, url?: string): void {
|
||||
editor?.commands.insertEmoji(text, url === undefined ? 'unicode' : 'custom', url)
|
||||
}
|
||||
|
||||
export function insertText (text: string): void {
|
||||
|
@ -27,7 +27,7 @@ export const defaultRefActions: RefAction[] = [
|
||||
if (emoji === null || emoji === undefined) {
|
||||
return
|
||||
}
|
||||
editorHandler.insertEmoji(emoji.emoji)
|
||||
editorHandler.insertEmoji(emoji.text, emoji.url)
|
||||
editorHandler.focus()
|
||||
},
|
||||
() => {}
|
||||
|
@ -37,16 +37,28 @@ function handleEmoji (
|
||||
}
|
||||
const emoji = getEmojiFunction(match.pop())
|
||||
if (emoji === undefined) return
|
||||
// TODO: add custom emoji
|
||||
const unicodeEmoji = typeof emoji === 'string' ? emoji : (isCustomEmoji(emoji) ? emoji.shortcode : emoji.emoji)
|
||||
commands.insertContentAt(range, [
|
||||
{
|
||||
type: 'emoji',
|
||||
attrs: {
|
||||
emoji: unicodeEmoji
|
||||
if (typeof emoji === 'string' || !isCustomEmoji(emoji)) {
|
||||
commands.insertContentAt(range, [
|
||||
{
|
||||
type: 'emoji',
|
||||
attrs: {
|
||||
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>({
|
||||
|
@ -16,7 +16,7 @@ export type CollaboratorType = 'local' | 'cloud'
|
||||
*/
|
||||
export interface TextEditorHandler {
|
||||
insertText: (html: string) => void
|
||||
insertEmoji: (emoji: string) => void
|
||||
insertEmoji: (text: string, url?: string) => void
|
||||
insertMarkup: (markup: Markup) => void
|
||||
insertTemplate: (name: string, markup: string) => void
|
||||
insertTable: (options: { rows?: number, cols?: number, withHeaderRow?: boolean }) => void
|
||||
|
Loading…
Reference in New Issue
Block a user