add commands experiment

This commit is contained in:
Philipp Kühn 2021-01-21 12:13:20 +01:00
parent 001e5b7579
commit a2d5eef6b2
8 changed files with 305 additions and 0 deletions

View File

@ -0,0 +1,112 @@
<template>
<div class="items">
<button
class="item"
:class="{ 'is-selected': index === selectedIndex }"
v-for="(item, index) in items"
:key="index"
@click="selectItem(index)"
>
{{ item.title }}
</button>
</div>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
command: {
type: Function,
required: true,
},
},
data() {
return {
selectedIndex: 0,
}
},
watch: {
items() {
this.selectedIndex = 0
},
},
methods: {
onKeyDown({ event }) {
if (event.key === 'ArrowUp') {
this.upHandler()
return true
}
if (event.key === 'ArrowDown') {
this.downHandler()
return true
}
if (event.key === 'Enter') {
this.enterHandler()
return true
}
return false
},
upHandler() {
this.selectedIndex = ((this.selectedIndex + this.items.length) - 1) % this.items.length
},
downHandler() {
this.selectedIndex = (this.selectedIndex + 1) % this.items.length
},
enterHandler() {
this.selectItem(this.selectedIndex)
},
selectItem(index) {
const item = this.items[index]
if (item) {
this.command(item)
}
},
},
}
</script>
<style lang="scss" scoped>
.items {
position: relative;
border-radius: 0.25rem;
background: white;
color: rgba(black, 0.8);
overflow: hidden;
font-size: 0.9rem;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.1),
0px 10px 20px rgba(0, 0, 0, 0.1),
;
}
.item {
display: block;
width: 100%;
text-align: left;
background: transparent;
border: none;
padding: 0.2rem 0.5rem;
&.is-selected,
&:hover {
color: #A975FF;
background: rgba(#A975FF, 0.1);
}
}
</style>

View File

@ -0,0 +1,25 @@
import { Extension } from '@tiptap/core'
import Suggestion from '@tiptap/suggestion'
export default Extension.create({
name: 'mention',
defaultOptions: {
suggestion: {
char: '/',
startOfLine: true,
command: ({ editor, range, attributes }) => {
attributes.command({ editor, range })
},
},
},
addProseMirrorPlugins() {
return [
Suggestion({
editor: this.editor,
...this.options.suggestion,
}),
]
},
})

View File

@ -0,0 +1,145 @@
<template>
<div v-if="editor">
<editor-content :editor="editor" />
</div>
</template>
<script>
import tippy from 'tippy.js'
import {
Editor, EditorContent, defaultExtensions, VueRenderer,
} from '@tiptap/vue-starter-kit'
import Commands from './commands'
import CommandsList from './CommandsList'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
...defaultExtensions(),
Commands.configure({
suggestion: {
items: query => {
return [
{
title: 'H1',
command: ({ editor, range }) => {
editor
.chain()
.focus()
.deleteRange(range)
.setNode('heading', { level: 1 })
.run()
},
},
{
title: 'H2',
command: ({ editor, range }) => {
editor
.chain()
.focus()
.deleteRange(range)
.setNode('heading', { level: 2 })
.run()
},
},
{
title: 'bold',
command: ({ editor, range }) => {
editor
.chain()
.focus()
.deleteRange(range)
.setMark('bold')
.run()
},
},
{
title: 'italic',
command: ({ editor, range }) => {
editor
.chain()
.focus()
.deleteRange(range)
.setMark('italic')
.run()
},
},
].filter(item => item.title.toLowerCase().startsWith(query.toLowerCase())).slice(0, 10)
},
render: () => {
let component
let popup
return {
onStart: props => {
component = new VueRenderer(CommandsList, {
parent: this,
propsData: props,
})
popup = tippy('body', {
getReferenceClientRect: props.clientRect,
appendTo: () => document.body,
content: component.element,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'bottom-start',
})
},
onUpdate(props) {
component.updateProps(props)
popup[0].setProps({
getReferenceClientRect: props.clientRect,
})
},
onKeyDown(props) {
return component.vm.onKeyDown(props)
},
onExit() {
popup[0].destroy()
component.destroy()
},
}
},
},
}),
],
content: `
<p>Text</p>
`,
})
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
.mention {
color: #A975FF;
background-color: rgba(#A975FF, 0.1);
border-radius: 0.3rem;
padding: 0.1rem 0.3rem;
}
</style>

View File

@ -175,6 +175,7 @@ Have a look at all of the core commands listed below. They should give you a goo
| Command | Description |
| --------------------- | --------------------------------------- |
| .blur() | Removes focus from the editor. |
| .deleteRange() | Delete a given range. |
| .deleteSelection() | Delete the selection, if there is one. |
| .focus() | Focus the editor at the given position. |
| .scrollIntoView() | Scroll the selection into view. |

View File

@ -6,3 +6,4 @@ Congratulations! Youve found our secret playground with a list of experiments
* [Comments](/experiments/comments)
* [CharacterLimit](/experiments/character-limit)
* [Color](/experiments/color)
* [Commands](/experiments/commands)

View File

@ -0,0 +1,5 @@
# Commands
⚠️ Experiment
<demo name="Experiments/Commands" highlight="" />

View File

@ -0,0 +1,14 @@
import { Command, Range } from '../types'
/**
* Delete a given range.
*/
export const deleteRange = (range: Range): Command => ({ tr, dispatch }) => {
const { from, to } = range
if (dispatch) {
tr.delete(from, to)
}
return true
}

View File

@ -4,6 +4,7 @@ import * as clearContent from '../commands/clearContent'
import * as clearNodes from '../commands/clearNodes'
import * as command from '../commands/command'
import * as createParagraphNear from '../commands/createParagraphNear'
import * as deleteRange from '../commands/deleteRange'
import * as deleteSelection from '../commands/deleteSelection'
import * as exitCode from '../commands/exitCode'
import * as extendMarkRange from '../commands/extendMarkRange'
@ -52,6 +53,7 @@ export const Commands = Extension.create({
...clearNodes,
...command,
...createParagraphNear,
...deleteRange,
...deleteSelection,
...exitCode,
...extendMarkRange,