Skip to content

Instantly share code, notes, and snippets.

@jaggy
Last active November 27, 2024 08:17
Show Gist options
  • Save jaggy/06c998758bc25b09f1b2b4beba8287eb to your computer and use it in GitHub Desktop.
Save jaggy/06c998758bc25b09f1b2b4beba8287eb to your computer and use it in GitHub Desktop.
import {Controller} from '@hotwired/stimulus'
import {Editor} from "@tiptap/core"
import {Mention} from "@tiptap/extension-mention";
import StarterKit from "@tiptap/starter-kit"
export default class extends Controller {
static targets = ['input', 'suggestions']
connect() {
this.editor = new Editor({
element: this.inputTarget,
extensions: [
StarterKit,
Mention.configure({
suggestion: {
allowSpaces: true,
items: query => {
return [
'Lea Thompson', 'Cyndi Lauper', 'Tom Cruise', 'Madonna', 'Jerry Hall', 'Joan Collins', 'Winona Ryder', 'Christina Applegate', 'Alyssa Milano', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey', 'Mickey Rourke', 'John Cusack', 'Matthew Broderick', 'Justine Bateman', 'Lisa Bonet',
].filter(item => item.toLowerCase().startsWith(query.toLowerCase())).slice(0, 5)
},
render: () => {
return {
onStart: props => {
this.element.suggestions.setProps(props)
this.element.suggestions.start()
this.element.suggestions.show()
this.element.suggestions.render()
},
onUpdate: async (props) => {
this.element.suggestions.setProps(props)
this.element.suggestions.render()
},
onKeyDown: (props) => {
if (props.event.key === 'Escape') {
return this.element.suggestions.hide()
}
if (props.event.key === 'Enter') {
return this.element.suggestions.select()
}
if (props.event.key === 'ArrowUp') {
return this.element.suggestions.up()
}
if (props.event.key === 'ArrowDown') {
return this.element.suggestions.down()
}
return false
},
onExit: () => {
this.element.suggestions.exit()
},
}
}
},
}),
],
})
}
}
<div data-controller="suggestions editor">
<div data-editor-target="input" class="o-panel"></div>
<div data-suggestions-target="list">
</div>
</div>
import { createPopper } from '@popperjs/core'
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ['list']
connect() {
this.element[this.identifier] = this
this.cursor = 0
this.listTarget.style.display = 'none'
}
setProps (props) {
this.props = props
}
start () {
this.popup = createPopper(this.props.decorationNode, this.listTarget, {
placement: 'bottom-start',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 10],
},
}
]
})
}
show () {
this.listTarget.style.display = 'block'
}
up () {
this.cursor = ((this.cursor + this.length) - 1) % this.length
this.highlight()
return true
}
down () {
this.cursor = (this.cursor === null) ? 0 : (this.cursor + 1) % this.length
this.highlight()
return true
}
highlight () {
Array.from(this.listTarget.children).forEach(
item => item.style.background = 'transparent'
)
if (! this.current) {
return
}
this.current.style.background = 'red'
}
render () {
this.listTarget.innerHTML = this.props.items.map((item) => `
<button type="button" data-suggestions-id-param="${item}" data-action="suggestions#selectOnClick">
${item}
</button>
`).join('')
this.cursor = 0
this.highlight()
}
hide () {
this.listTarget.style.display = 'none'
return true
}
exit () {
this.popup.destroy()
this.hide()
}
select () {
this.props.command({
id: this.current.dataset.suggestionsIdParam,
})
return true
}
selectOnClick ({ params }) {
this.props.command({ id: params.id })
}
get length () {
return this.listTarget.children.length
}
get current () {
return this.listTarget.children[this.cursor]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment