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.

Revisions

  1. jaggy revised this gist Oct 16, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion suggestion_controller.js
    Original file line number Diff line number Diff line change
    @@ -58,7 +58,7 @@ export default class extends Controller {

    render () {
    this.listTarget.innerHTML = this.props.items.map((item) => `
    <button type="button" data-suggestions-id-param="${item}" class="suggestion">
    <button type="button" data-suggestions-id-param="${item}" data-action="suggestions#selectOnClick">
    ${item}
    </button>
    `).join('')
  2. jaggy created this gist Oct 16, 2021.
    70 changes: 70 additions & 0 deletions editor_controller.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,70 @@
    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()
    },
    }
    }
    },
    }),
    ],
    })
    }
    }
    6 changes: 6 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    <div data-controller="suggestions editor">
    <div data-editor-target="input" class="o-panel"></div>

    <div data-suggestions-target="list">
    </div>
    </div>
    99 changes: 99 additions & 0 deletions suggestion_controller.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,99 @@
    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}" class="suggestion">
    ${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]
    }
    }