UBERF-17: Missing smiles auto-conversion in rich texts :) (#3771)

Signed-off-by: Maxim Karmatskikh <mkarmatskih@gmail.com>
This commit is contained in:
Maksim Karmatskikh 2023-10-02 13:22:40 +06:00 committed by GitHub
parent 3c42b948ac
commit 56f0dc573d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 17 deletions

View File

@ -34,6 +34,7 @@
import { RefAction, RefInputActionItem, TextEditorHandler, TextFormatCategory } from '../types' import { RefAction, RefInputActionItem, TextEditorHandler, TextFormatCategory } from '../types'
import TextEditor from './TextEditor.svelte' import TextEditor from './TextEditor.svelte'
import { completionConfig } from './extensions' import { completionConfig } from './extensions'
import { EmojiExtension } from './extension/emoji'
import Attach from './icons/Attach.svelte' import Attach from './icons/Attach.svelte'
import RIMention from './icons/RIMention.svelte' import RIMention from './icons/RIMention.svelte'
import Send from './icons/Send.svelte' import Send from './icons/Send.svelte'
@ -177,7 +178,7 @@
updateFocus() updateFocus()
dispatch('focus', focused) dispatch('focus', focused)
}} }}
extensions={[completionPlugin]} extensions={[completionPlugin, EmojiExtension.configure()]}
on:update on:update
placeholder={placeholder ?? textEditorPlugin.string.EditorPlaceholder} placeholder={placeholder ?? textEditorPlugin.string.EditorPlaceholder}
textFormatCategories={[ textFormatCategories={[

View File

@ -13,10 +13,15 @@
resizeObserver resizeObserver
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import type { AnyExtension } from '@tiptap/core'
import { Completion } from '../Completion' import { Completion } from '../Completion'
import textEditorPlugin from '../plugin' import textEditorPlugin from '../plugin'
import StyledTextEditor from './StyledTextEditor.svelte' import StyledTextEditor from './StyledTextEditor.svelte'
import { completionConfig } from './extensions' import { completionConfig } from './extensions'
import { EmojiExtension } from './extension/emoji'
import { ImageRef, FileAttachFunction } from './imageExt' import { ImageRef, FileAttachFunction } from './imageExt'
import { Node as ProseMirrorNode } from '@tiptap/pm/model' import { Node as ProseMirrorNode } from '@tiptap/pm/model'
@ -38,6 +43,7 @@
export let enableFormatting = false export let enableFormatting = false
export let autofocus = false export let autofocus = false
export let enableBackReferences: boolean = false export let enableBackReferences: boolean = false
export let enableEmojiReplace: boolean = true
export let isScrollable: boolean = true export let isScrollable: boolean = true
export let attachFile: FileAttachFunction | undefined = undefined export let attachFile: FileAttachFunction | undefined = undefined
@ -136,24 +142,9 @@
focusManager?.setFocus(idx) focusManager?.setFocus(idx)
} }
} }
const completionPlugin = Completion.configure({
...completionConfig,
showDoc (event: MouseEvent, _id: string, _class: string) {
dispatch('open-document', { event, _id, _class })
}
})
const attachments = new Map<string, ProseMirrorNode>() const attachments = new Map<string, ProseMirrorNode>()
const imagePlugin = ImageRef.configure({
inline: true,
HTMLAttributes: {},
attachFile,
reportNode: (id, node) => {
attachments.set(id, node)
}
})
/** /**
* @public * @public
*/ */
@ -163,6 +154,36 @@
textEditor.removeNode(nde) textEditor.removeNode(nde)
} }
} }
function configureExtensions () {
const imagePlugin = ImageRef.configure({
inline: true,
HTMLAttributes: {},
attachFile,
reportNode: (id, node) => {
attachments.set(id, node)
}
})
const completionPlugin = Completion.configure({
...completionConfig,
showDoc (event: MouseEvent, _id: string, _class: string) {
dispatch('open-document', { event, _id, _class })
}
})
const extensions: AnyExtension[] = [imagePlugin]
if (enableBackReferences) {
extensions.unshift(completionPlugin)
}
if (enableEmojiReplace) {
extensions.push(EmojiExtension.configure())
}
return extensions
}
const extensions = configureExtensions()
</script> </script>
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
@ -195,7 +216,7 @@
{enableFormatting} {enableFormatting}
{autofocus} {autofocus}
{isScrollable} {isScrollable}
extensions={enableBackReferences ? [completionPlugin, imagePlugin] : [imagePlugin]} {extensions}
bind:content={rawValue} bind:content={rawValue}
bind:this={textEditor} bind:this={textEditor}
on:attach on:attach

View File

@ -0,0 +1,80 @@
import { Extension } from '@tiptap/core'
const emojiReplaceDict = {
'0:)': '😇',
'0:-)': '😇',
'0:-3': '😇',
'0:3': '😇',
'0;^)': '😇',
'O:-)': '😇',
'3:)': '😈',
'3:-)': '😈',
'}:)': '😈',
'}:-)': '😈',
'>:)': '😈',
'>:-)': '😈',
'>;)': '😈',
':-D': '😁',
":')": '😂',
":'-)": '😂',
':)': '😊',
':-)': '😄',
':]': '😄',
':^)': '😄',
':o)': '😄',
':}': '😄',
'*-)': '😉',
':-,': '😉',
';)': '😉',
';-)': '😉',
';-]': '😉',
';]': '😉',
';^)': '😉',
':-|': '😐',
':|': '😐',
':(': '😞',
':-(': '😒',
':-<': '😒',
':-[': '😒',
':-c': '😒',
':<': '😒',
':[': '😒',
':{': '😒',
'%)': '😖',
'%-)': '😖',
':-P': '😜',
':-p': '😜',
';(': '😜',
':-||': '😠',
':-.': '😡',
':-/': '😡',
':/': '😐',
":'(": '😢',
":'-(": '😢',
':-O': '😲',
':-o': '😲',
':-&': '😶',
':-X': '😶'
}
function escapeRegExp (text: string): string {
return text.replace(/[[\]{}()*+?.\\^$|#]/g, '\\$&')
}
export const EmojiExtension = Extension.create({
addInputRules () {
return Object.keys(emojiReplaceDict).map((pattern) => {
return {
find: new RegExp(escapeRegExp(pattern)),
handler: ({ range, match, commands }) => {
commands.insertContentAt(range, [
{
type: 'text',
text: emojiReplaceDict[pattern as keyof typeof emojiReplaceDict]
}
])
}
}
})
}
})