Skip to content

Commit

Permalink
feat: make slash command works
Browse files Browse the repository at this point in the history
  • Loading branch information
phodal committed Nov 21, 2023
1 parent 8202680 commit b4e85f9
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 167 deletions.
21 changes: 14 additions & 7 deletions components/editor/live-editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { MenuBar } from './menu-bar'

import MarkdownIt from 'markdown-it'
import { AiBubbleMenu } from './ai-bubble-menu'
import { SlashCommands } from './slash-commands'
import { createSlash, SlashCommands } from './slash-commands'
import { SlashMenuContainer } from './slash-menu-view'

const md = new MarkdownIt()
Expand Down Expand Up @@ -40,10 +40,17 @@ const extensions = [
keepAttributes: false, // TODO : Making this as `false` becase marks are not preserved when I try to preserve attrs, awaiting a bit of help
},
}),
SlashCommands.configure({
items: [{
title: "continue"
}]
createSlash('ai-slash', {
items: [
{
title: '续写',
command: 'continue',
},
{
"title": '总结',
"command": 'summarize',
}
]
}),
Color.configure({ types: [TextStyle.name, ListItem.name] }),
// @ts-ignore
Expand Down Expand Up @@ -74,9 +81,9 @@ const LiveEditor = () => {

return (
<div>
{ editor && <MenuBar editor={editor}/> }
{editor && <MenuBar editor={editor}/>}
<EditorContent editor={editor}/>
{ editor && <AiBubbleMenu editor={editor}/> }
{editor && <AiBubbleMenu editor={editor}/>}
</div>
)
}
Expand Down
172 changes: 87 additions & 85 deletions components/editor/slash-commands.jsx
Original file line number Diff line number Diff line change
@@ -1,104 +1,106 @@
import { Extension, ReactRenderer } from '@tiptap/react'
import { ReactRenderer } from '@tiptap/react'
import { Node } from '@tiptap/core'
import { Suggestion } from '@tiptap/suggestion'
import tippy from 'tippy.js'
import { SlashMenuContainer } from './slash-menu-view'
import { useRef } from 'react'
import SlashMenuContainer from './slash-menu-view'

// or try SlashCommands: https://github.com/ueberdosis/tiptap/issues/1508
const extensionName = `ai-insert`

export const SlashCommands = Extension.create({
name: 'slash-command',
addOptions () {
return {
char: '/',
pluginKey: 'slash-/',
}
},
addProseMirrorPlugins () {
return [
Suggestion({
editor: this.editor,
char: this.options.char,
export const createSlash = (name, options) => {
return Node.create({
name: 'slash-command',
addOptions () {
return {
char: '/',
pluginKey: 'slash-/',
}
},
addProseMirrorPlugins () {
return [
Suggestion({
editor: this.editor,
char: this.options.char,

command: ({ editor, props }) => {
const { state, dispatch } = editor.view
const { $head, $from } = state.selection

const end = $from.pos
const from = $head?.nodeBefore?.text
? end -
$head.nodeBefore.text.substring(
$head.nodeBefore.text.indexOf('/')
).length
: $from.start()
command: ({ editor, props }) => {
const { state, dispatch } = editor.view
const { $head, $from } = state.selection

const tr = state.tr.deleteRange(from, end)
dispatch(tr)
props?.action?.(editor, props.user)
editor?.view?.focus()
},
items: ({ query }) => {
// todo: match fo query
return this.options.items
},
render: () => {
let component
let popup
let isEditable
const end = $from.pos
const from = $head?.nodeBefore?.text
? end -
$head.nodeBefore.text.substring(
$head.nodeBefore.text.indexOf('/')
).length
: $from.start()

return {
onStart: (props) => {
isEditable = props.editor.isEditable
if (!isEditable) return
const tr = state.tr.deleteRange(from, end)
dispatch(tr)
props?.action?.(editor, props.user)
editor?.view?.focus()
},
items: ({ query }) => {
// todo: match fo query
return options.items
},
render: () => {
let component
let popup
let isEditable

component = new ReactRenderer(SlashMenuContainer, {
props,
editor: props.editor,
})
return {
onStart: (props) => {
isEditable = props.editor.isEditable
if (!isEditable) return

console.log(component.element)
component = new ReactRenderer(SlashMenuContainer, {
props,
editor: props.editor,
})

popup = tippy('body', {
getReferenceClientRect: props.clientRect || (() => props.editor.storage[extensionName].rect),
appendTo: () => document.body,
content: component.element,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'bottom-start',
})
},
console.log(component.element)

onUpdate (props) {
if (!isEditable) return
popup = tippy('body', {
getReferenceClientRect: props.clientRect || (() => props.editor.storage[extensionName].rect),
appendTo: () => document.body,
content: component.element,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'bottom-start',
})
},

component.updateProps(props)
props.editor.storage[extensionName].rect = props.clientRect()
popup[0].setProps({
getReferenceClientRect: props.clientRect,
})
},
onUpdate (props) {
if (!isEditable) return

onKeyDown (props) {
if (!isEditable) return
component.updateProps(props)
props.editor.storage[extensionName].rect = props.clientRect()
popup[0].setProps({
getReferenceClientRect: props.clientRect,
})
},

if (props.event.key === 'Escape') {
popup[0].hide()
return true
}
return component.ref?.onKeyDown(props)
},
onKeyDown (props) {
if (!isEditable) return

onExit () {
if (!isEditable) return
popup && popup[0].destroy()
component.destroy()
},
}
},
}),
]
},
})
if (props.event.key === 'Escape') {
popup[0].hide()
return true
}
return component.ref?.onKeyDown(props)
},

onExit () {
if (!isEditable) return
popup && popup[0].destroy()
component.destroy()
},
}
},
}),
]
},
})
}
Loading

0 comments on commit b4e85f9

Please sign in to comment.