Skip to content

Commit

Permalink
feature: slash menu in tiptap
Browse files Browse the repository at this point in the history
  • Loading branch information
sereneinserenade committed May 15, 2022
1 parent 7296a68 commit 9bb7b0a
Show file tree
Hide file tree
Showing 12 changed files with 448 additions and 5 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"editor.formatOnSave": true,
"cSpell.words": [
"fuzzysort",
"lowlight",
"smilie"
]
Expand Down
39 changes: 39 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@
"@tiptap/extension-underline": "^2.0.0-beta.23",
"@tiptap/react": "^2.0.0-beta.109",
"@tiptap/starter-kit": "^2.0.0-beta.184",
"@tiptap/suggestion": "^2.0.0-beta.91",
"date-fns": "^2.28.0",
"file-saver": "^2.0.5",
"fuzzysort": "^1.9.0",
"lodash": "^4.17.21",
"lowlight": "^2.6.1",
"react": "^17.0.2",
Expand Down
5 changes: 3 additions & 2 deletions src/pages/Newtab/components/editor/Tiptap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Typography from '@tiptap/extension-typography';

import './Tiptap.scss'
import Menubar from './Menubar'
import { SearchAndReplace, SmilieReplacer } from './extensions'
import { suggestions, Commands, SearchAndReplace, SmilieReplacer } from './extensions'
import Table from '@tiptap/extension-table';
import TableCell from '@tiptap/extension-table-cell';
import TableHeader from '@tiptap/extension-table-header';
Expand Down Expand Up @@ -94,7 +94,8 @@ const Tiptap = ({ onUpdate, content, isNoteInBin }: TiptapProps) => {
linkOnPaste: true,
autolink: true,
}),
SmilieReplacer
SmilieReplacer,
Commands.configure({ suggestions })
],
content: content,
onUpdate: ({ editor }) => onUpdate(editor.getHTML(), editor.getText()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ function parseNodes(nodes: any[], className: string[] = []): { text: string, cla
: [],
]

if (node.children) {
return parseNodes(node.children, classes)
}
if (node.children) return parseNodes(node.children, classes)

return {
text: node.value,
Expand Down
1 change: 1 addition & 0 deletions src/pages/Newtab/components/editor/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './searchAndReplace'
export * from './smilieReplacer'
export * from './slash-menu'
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useEffect, useState } from 'react'
import { stopPrevent } from '../../../../utils'

import './styles/CommandList.scss'

interface CommandListProps {
items: any[],
command: Function,
event: any
}

export const CommandList: React.FC<CommandListProps> = ({ items, command, event }) => {
const [selectedIndex, setSelectedIndex] = useState(0)

useEffect(() => setSelectedIndex(0), [items])

const onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'ArrowUp') {
stopPrevent(event)
upHandler()
return true
}

if (event.key === 'ArrowDown') {
stopPrevent(event)
downHandler()
return true
}

if (event.key === 'Enter') {
stopPrevent(event)
enterHandler()
return true
}

return false
}

useEffect(() => { onKeyDown(event) }, [event])

const upHandler = () => {
setSelectedIndex(((selectedIndex + items.length) - 1) % items.length)
}

const downHandler = () => {
setSelectedIndex((selectedIndex + 1) % items.length)
}

const enterHandler = () => {
selectItem(selectedIndex)
}

const selectItem = (index: number) => {
const item = items[index]

if (item) setTimeout(() => command(item))
}

return (
<div className="items">
{
items.length
? (
<>
{
items.map((item, index) => {
return (
<article
className={`item flex ${index === selectedIndex ? 'is-selected' : ''}`}
key={index}
onClick={() => selectItem(index)}
>
<span className='flex align-center gap-8px'>
{item.icon()} <span dangerouslySetInnerHTML={{ __html: item.highlightedTitle || item.title }} />
</span>
{item.shortcut && <code>{item.shortcut}</code>}
</article>
)
})
}
</>
) : <div className="item"> No result </div>
}
</div >
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Extension } from '@tiptap/core'
import Suggestion from '@tiptap/suggestion'

export const Commands = Extension.create({
name: 'commands',

addOptions() {
return {
suggestions: {
char: '/',
command: ({ editor, range, props } : any) => props.command({ editor, range }),
},
}
},

addProseMirrorPlugins() {
return [
Suggestion({
editor: this.editor,
...this.options.suggestions,
}),
]
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './CommandList'
export * from './command'
export * from './suggestions'
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.items {
position: relative;
border-radius: var(--nextui-radii-sm);
background: var(--nextui-colors-background);
color: var(--nextui-colors-text);
box-shadow: var(--nextui-shadows-md);
padding: 6px;
box-sizing: border-box;
}

.item {
width: 100%;
border-radius: 0.4rem;
align-items: center;
gap: 8px;
padding: 6px 8px;
box-sizing: border-box;
justify-content: space-between;

svg {
display: inline-flex;
justify-content: center;
align-items: center;
}

&.is-selected {
background-color: var(--nextui-colors-selection);
}
}
Loading

0 comments on commit 9bb7b0a

Please sign in to comment.