diff --git a/packages/presentation/src/components/markup/NodeContent.svelte b/packages/presentation/src/components/markup/NodeContent.svelte index ca46d66037..4cb53b3628 100644 --- a/packages/presentation/src/components/markup/NodeContent.svelte +++ b/packages/presentation/src/components/markup/NodeContent.svelte @@ -80,7 +80,15 @@ {:else if node.type === MarkupNodeType.text} {node.text} {:else if node.type === MarkupNodeType.emoji} - {node.attrs?.emoji} + + {#if node.attrs?.kind === 'custom'} + {@const src = toString(attrs.url)} + {@const alt = toString(attrs.emoji)} + + {:else} + {node.attrs?.emoji} + {/if} + {:else if node.type === MarkupNodeType.paragraph}

{#if nodes.length > 0} diff --git a/packages/text/src/nodes/emoji.ts b/packages/text/src/nodes/emoji.ts index 2e11bc4464..6a6c062aee 100644 --- a/packages/text/src/nodes/emoji.ts +++ b/packages/text/src/nodes/emoji.ts @@ -18,13 +18,15 @@ import { Node, mergeAttributes } from '@tiptap/core' declare module '@tiptap/core' { interface Commands { 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({ @@ -38,6 +40,12 @@ export const EmojiNode = Node.create({ return { emoji: { default: '' + }, + kind: { + default: 'unicode' + }, + url: { + default: null } } }, @@ -45,11 +53,11 @@ export const EmojiNode = Node.create({ 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({ }, 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( diff --git a/packages/theme/styles/common.scss b/packages/theme/styles/common.scss index 7757fccba5..610ee03613 100644 --- a/packages/theme/styles/common.scss +++ b/packages/theme/styles/common.scss @@ -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; +} diff --git a/packages/ui-next/src/components/TextInput.svelte b/packages/ui-next/src/components/TextInput.svelte index 7b4dfebf72..fab2e436d4 100644 --- a/packages/ui-next/src/components/TextInput.svelte +++ b/packages/ui-next/src/components/TextInput.svelte @@ -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) diff --git a/packages/ui/src/components/emoji/EmojiButton.svelte b/packages/ui/src/components/emoji/EmojiButton.svelte index daf35fb068..ba4e90b8f8 100644 --- a/packages/ui/src/components/emoji/EmojiButton.svelte +++ b/packages/ui/src/components/emoji/EmojiButton.svelte @@ -41,7 +41,11 @@ dispatch('select', displayedEmoji) }} > - {isCustomEmoji(displayedEmoji) ? displayedEmoji.shortcode : displayedEmoji.emoji} + {#if isCustomEmoji(displayedEmoji)} + {displayedEmoji.shortcode} + {:else} + {displayedEmoji.emoji} + {/if} {/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); } diff --git a/packages/ui/src/components/emoji/EmojiPopup.svelte b/packages/ui/src/components/emoji/EmojiPopup.svelte index 26a7dba808..b5995324cc 100644 --- a/packages/ui/src/components/emoji/EmojiPopup.svelte +++ b/packages/ui/src/components/emoji/EmojiPopup.svelte @@ -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 }) } diff --git a/packages/ui/src/components/emoji/utils.ts b/packages/ui/src/components/emoji/utils.ts index 0b393d470d..eb2b16f99b 100644 --- a/packages/ui/src/components/emoji/utils.ts +++ b/packages/ui/src/components/emoji/utils.ts @@ -69,6 +69,13 @@ export async function loadEmojis (lang?: string): Promise { export async function updateEmojis (lang?: string): Promise { 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) } diff --git a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte index 2576c0e3e7..daa9b0930e 100644 --- a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte +++ b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte @@ -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)) diff --git a/plugins/text-editor-resources/src/components/ReferenceInput.svelte b/plugins/text-editor-resources/src/components/ReferenceInput.svelte index e73bd8003e..1a73097f58 100644 --- a/plugins/text-editor-resources/src/components/ReferenceInput.svelte +++ b/plugins/text-editor-resources/src/components/ReferenceInput.svelte @@ -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) diff --git a/plugins/text-editor-resources/src/components/StyledTextEditor.svelte b/plugins/text-editor-resources/src/components/StyledTextEditor.svelte index 7831e59cc2..e413d40dac 100644 --- a/plugins/text-editor-resources/src/components/StyledTextEditor.svelte +++ b/plugins/text-editor-resources/src/components/StyledTextEditor.svelte @@ -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) diff --git a/plugins/text-editor-resources/src/components/TextEditor.svelte b/plugins/text-editor-resources/src/components/TextEditor.svelte index 9e8984851b..d1ec7c4ef8 100644 --- a/plugins/text-editor-resources/src/components/TextEditor.svelte +++ b/plugins/text-editor-resources/src/components/TextEditor.svelte @@ -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 { diff --git a/plugins/text-editor-resources/src/components/editor/actions.ts b/plugins/text-editor-resources/src/components/editor/actions.ts index e76b17e1da..fc8819dc73 100644 --- a/plugins/text-editor-resources/src/components/editor/actions.ts +++ b/plugins/text-editor-resources/src/components/editor/actions.ts @@ -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() }, () => {} diff --git a/plugins/text-editor-resources/src/components/extension/emoji.ts b/plugins/text-editor-resources/src/components/extension/emoji.ts index eeba837094..977279696f 100644 --- a/plugins/text-editor-resources/src/components/extension/emoji.ts +++ b/plugins/text-editor-resources/src/components/extension/emoji.ts @@ -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({ diff --git a/plugins/text-editor/src/types.ts b/plugins/text-editor/src/types.ts index 43074d6298..292345ff3b 100644 --- a/plugins/text-editor/src/types.ts +++ b/plugins/text-editor/src/types.ts @@ -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