Add emoji text node

Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
This commit is contained in:
Anton Alexeyev 2025-04-29 15:29:03 +07:00
parent 76a425ba98
commit 4d1b2a436d
15 changed files with 111 additions and 7 deletions

View File

@ -19,6 +19,7 @@
import NodeContent from './NodeContent.svelte'
export let node: MarkupNode
export let single = true
export let preview = false
</script>
@ -27,9 +28,9 @@
{#if marks.length > 0}
<NodeMarks {marks}>
<NodeContent {node} {preview} />
<NodeContent {node} {single} {preview} />
</NodeMarks>
{:else}
<NodeContent {node} {preview} />
<NodeContent {node} {single} {preview} />
{/if}
{/if}

View File

@ -22,6 +22,7 @@
import Node from './Node.svelte'
export let node: MarkupNode
export let single = true
export let preview = false
function toRef (objectId: string): Ref<Doc> {
@ -78,11 +79,13 @@
{/if}
{:else if node.type === MarkupNodeType.text}
{node.text}
{:else if node.type === MarkupNodeType.emoji}
<span class="emoji" class:emojiOnly={single}>{node.attrs?.emoji}</span>
{:else if node.type === MarkupNodeType.paragraph}
<p class="p-inline contrast" class:overflow-label={preview} class:emojiOnly={checkEmoji(nodes)}>
{#if nodes.length > 0}
{#each nodes as node}
<Node {node} {preview} />
<Node {node} {preview} single={nodes.length === 1} />
{/each}
{/if}
</p>

View File

@ -25,6 +25,7 @@ export enum MarkupNodeType {
image = 'image',
file = 'file',
reference = 'reference',
emoji = 'emoji',
hard_break = 'hardBreak',
ordered_list = 'orderedList',
bullet_list = 'bulletList',

View File

@ -91,6 +91,7 @@ const nonEmptyNodes = [
MarkupNodeType.horizontal_rule,
MarkupNodeType.image,
MarkupNodeType.reference,
MarkupNodeType.emoji,
MarkupNodeType.subLink,
MarkupNodeType.table
]

View File

@ -37,6 +37,7 @@ import { ImageNode, ImageOptions } from '../nodes/image'
import { MarkdownNode } from '../nodes/markdown'
import { MermaidExtension, mermaidOptions } from '../nodes/mermaid'
import { ReferenceNode } from '../nodes/reference'
import { EmojiNode } from '../nodes/emoji'
import { TodoItemNode, TodoListNode } from '../nodes/todo'
import { DefaultKit, DefaultKitOptions } from './default-kit'
@ -103,6 +104,7 @@ export const ServerKit = Extension.create<ServerKitOptions>({
TodoItemNode,
TodoListNode,
ReferenceNode,
EmojiNode,
CommentNode,
MarkdownNode,
NodeUuid,

View File

@ -0,0 +1,79 @@
//
// 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 { Node, mergeAttributes } from '@tiptap/core'
declare module '@tiptap/core' {
interface Commands<ReturnType> {
emoji: {
insertEmoji: (emoji: string) => ReturnType
}
}
}
export interface EmojiNodeOptions {
emoji: string
}
export const EmojiNode = Node.create<EmojiNodeOptions>({
name: 'emoji',
group: 'inline',
inline: true,
atom: true,
selectable: false,
addAttributes () {
return {
emoji: {
default: ''
}
}
},
addCommands () {
return {
insertEmoji:
(emoji: string) =>
({ commands }) => {
return commands.insertContent({
type: this.name,
attrs: { emoji }
})
}
}
},
parseHTML () {
return [
{
tag: `span[data-type="${this.name}"]`
}
]
},
renderHTML ({ node, HTMLAttributes }) {
return [
'span',
mergeAttributes(
{
'data-type': this.name,
class: 'emoji'
},
HTMLAttributes
),
node.attrs.emoji
]
}
})

View File

@ -15,6 +15,7 @@
export * from './image'
export * from './reference'
export * from './emoji'
export * from './todo'
export * from './file'
export * from './codeblock'

View File

@ -77,6 +77,9 @@
insertText: (text) => {
editor?.insertText(text)
},
insertEmoji: (emoji) => {
editor?.insertEmoji(emoji)
},
insertMarkup: (markup) => {
editor?.insertMarkup(markup)
},

View File

@ -178,6 +178,9 @@
insertText: (text) => {
editor?.commands.insertContent(text)
},
insertEmoji: (emoji) => {
editor?.commands.insertEmoji(emoji)
},
insertMarkup: (markup) => {
editor?.commands.insertContent(markupToJSON(markup))
},

View File

@ -84,6 +84,9 @@
insertText: (text) => {
editor?.insertText(text)
},
insertEmoji: (emoji) => {
editor?.insertEmoji(emoji)
},
insertMarkup: (markup) => {
editor?.insertMarkup(markup)
},

View File

@ -79,6 +79,9 @@
insertText: (text) => {
editor?.insertText(text)
},
insertEmoji: (emoji) => {
editor?.insertEmoji(emoji)
},
insertMarkup: (markup) => {
editor?.insertMarkup(markup)
},

View File

@ -96,6 +96,10 @@
return editor
}
export function insertEmoji (emoji: string): void {
editor?.commands.insertEmoji(emoji)
}
export function insertText (text: string): void {
editor?.commands.insertContent(text)
}

View File

@ -27,8 +27,7 @@ export const defaultRefActions: RefAction[] = [
if (emoji === null || emoji === undefined) {
return
}
editorHandler.insertText(emoji.emoji)
editorHandler.insertEmoji(emoji.emoji)
editorHandler.focus()
},
() => {}

View File

@ -1,4 +1,4 @@
import { Extension } from '@tiptap/core'
import { EmojiNode, type EmojiNodeOptions } from '@hcengineering/text'
import { type ResolvedPos } from '@tiptap/pm/model'
const emojiReplaceDict = {
@ -63,7 +63,7 @@ function isValidEmojiPosition ($pos: ResolvedPos): boolean {
return true
}
export const EmojiExtension = Extension.create({
export const EmojiExtension = EmojiNode.extend<EmojiNodeOptions>({
addInputRules () {
return Object.keys(emojiReplaceDict).map((pattern) => {
return {

View File

@ -16,6 +16,7 @@ export type CollaboratorType = 'local' | 'cloud'
*/
export interface TextEditorHandler {
insertText: (html: string) => void
insertEmoji: (emoji: string) => void
insertMarkup: (markup: Markup) => void
insertTemplate: (name: string, markup: string) => void
insertTable: (options: { rows?: number, cols?: number, withHeaderRow?: boolean }) => void