OverviewFilesCommitsIssuesDocsReleasesPeers

keet-deobfuscated / @holepunchto / keet-editor / src / extensions / emoji.js

155 lines · javascript ref:
import { Node as t, mergeAttributes as e, nodeInputRule as o } from "@tiptap/core"
import { Suggestion as s } from "@tiptap/suggestion"
const n = (t) => (t ? t.charAt(t.length - 1) : ""),
  i = (t) => t?.match(/([^\s]+)\s*$/)?.[1] || ""
export const Emoji = t.create({
  name: "emoji",
  content: "text*",
  group: "inline",
  inline: !0,
  selectable: !0,
  atom: !0,
  defining: !0,
  addStorage: () => ({ suggestionOpen: !1 }),
  addOptions() {
    return {
      HTMLAttributes: { class: "emoji" },
      suggestion: {
        char: ":",
        command: ({ editor: t, range: e, props: o }) => {
          const { nodeAfter: s } = t.view.state.selection.$to,
            n = s?.text?.startsWith(" ")
          n && (e.to += 1)
          const { emoji: i, shortCodes: a } = o
          ;(t
            .chain()
            .focus()
            .insertContentAt(e, [
              {
                type: this.name,
                tag: "span",
                attrs: { "data-shortcode": a[0], "data-emoji": i },
                text: i,
              },
              { type: "text", text: " " },
            ])
            .run(),
            window.getSelection()?.collapseToEnd())
        },
        allow: ({ state: t, range: e }) => {
          const o = t.doc.resolve(e.from),
            s = t.schema.nodes[this.name]
          return !!o.parent.type.contentMatch.matchType(s)
        },
      },
    }
  },
  addCommands() {
    return {
      setEmoji:
        ({ key: t, emoji: e }) =>
        ({ commands: o }) => {
          const s = this.options.emojiModule.getEmojiData(t)
          if (!s) return !1
          o.insertContent(" ")
          const { url: n, shortCodes: i } = s
          return (
            n
              ? o.insertContent(
                  `<span class="emoji" data-shortcode="${i?.[0]}" data-emoji=${e}>:${i?.[0]}:</span>`,
                )
              : o.insertContent(
                  `<span class="emoji" data-shortcode="${t}" data-emoji="${e}">${e}</span>`,
                ),
            !0
          )
        },
    }
  },
  addAttributes: () => ({
    "data-shortcode": { parseHTML: (t) => t.getAttribute("data-shortcode"), default: "" },
    "data-emoji": { parseHTML: (t) => t.getAttribute("data-emoji"), default: "" },
  }),
  addInputRules() {
    const { getEmojiData: t } = this.options.emojiModule,
      { emojisWithEmoticons: e } = this.options.suggestion,
      s = (t, e, o) => -1 !== t && e.slice(t) === o && (/\s/.test(e.slice(t - 1, t)) || 0 === t)
    return [
      o({
        find: (o) => {
          const a = ((t) => {
            const e = []
            return (
              t.descendants((t) => {
                "mention" === t.type.name && e.push(`@${t.attrs.label}`)
              }),
              e
            )
          })(this.editor.state.doc)
          for (const t of a) o = o.replaceAll(t, "")
          const r = n(o)
          if (" " !== r && "\n" !== r) return
          const c = ((o) => {
            for (const t of e)
              if (Array.isArray(t.emoticon))
                for (const e of t.emoticon) {
                  const n = o.indexOf(e)
                  if (s(n, o, e)) return { text: o.slice(n), emoji: t }
                }
              else {
                const e = o.indexOf(t.emoticon)
                if (s(e, o, t.emoticon)) return { text: o.slice(e), emoji: t }
              }
            const n = o.match(/:(.*):/)
            if (n) {
              const e = t(n[1])
              if (e) return { text: o.slice(n.index), emoji: e }
            }
            return null
          })(i(o))
          return c
            ? { data: c.emoji, text: o, replaceWith: "\n" === r ? `${c.text}\n` : c.text }
            : void 0
        },
        type: this.type,
        getAttributes: (t) => {
          if (t.data) {
            const { shortCodes: e, emoji: o } = t.data
            return { "data-shortcode": e?.[0], "data-emoji": o }
          }
        },
      }),
    ]
  },
  addProseMirrorPlugins() {
    return [s({ editor: this.editor, ...this.options.suggestion })]
  },
  parseHTML: () => [
    {
      tag: "span",
      getAttrs: (t) =>
        !(!t.hasAttribute("class") || !t.classList.contains("emoji")) && {
          "data-shortcode": t.getAttribute("data-shortcode"),
          "data-emoji": t.getAttribute("data-emoji"),
        },
    },
  ],
  renderHTML({ node: t, HTMLAttributes: o }) {
    const { getEmojiData: s } = this.options.emojiModule,
      n = o["data-shortcode"],
      { url: i } = s(n) || {}
    if (i)
      return [
        "span",
        e(this.options.HTMLAttributes, o, { contenteditable: "false" }),
        ["img", { class: "emoji-img", src: i }, 0],
      ]
    const { emoji: a } = s(n) || {}
    return [
      "span",
      e(this.options.HTMLAttributes, o, { contenteditable: "false" }),
      a || n || t.textContent || "",
    ]
  },
})