Remove files for Tiptap mention editor

The interactions on this were a huge pain in the ass
This commit is contained in:
Justin Edmund 2023-08-21 17:25:10 -07:00
parent 608f89c788
commit 3b32c5a730
3 changed files with 0 additions and 490 deletions

View file

@ -1,211 +0,0 @@
.wrapper {
border-radius: $input-corner;
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
.editor {
-webkit-font-smoothing: antialiased;
box-sizing: border-box;
color: var(--text-primary);
display: block;
flex-grow: 1;
font-size: $font-regular;
overflow: scroll;
padding: ($unit * 1.5) $unit-2x;
white-space: pre-wrap;
width: 100%;
&:focus {
// border: 2px solid $blue;
outline: none;
}
p {
line-height: 1.5;
}
p.empty:first-child::before {
color: var(--text-tertiary);
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
&.bound {
background-color: var(--input-bound-bg);
&:hover {
background-color: var(--input-bound-bg-hover);
}
}
.mention {
border-radius: $item-corner-small;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--null-shadow);
background: var(--null-bg);
display: inline-flex;
color: var(--text-primary);
font-weight: $medium;
font-size: 15px;
padding: 1px $unit-half;
margin: $unit-fourth;
transition: all 0.1s ease-out;
:global(.remove) {
display: none;
padding: 0 $unit-half;
}
&:hover {
background: var(--null-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--null-shadow-hover);
text-decoration: none;
cursor: pointer;
}
&:focus {
outline: 2px solid blue;
:global(.remove) {
display: block;
}
}
&[data-element='fire'] {
background: var(--fire-bg);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--fire-shadow);
color: var(--fire-text);
:global(.remove) {
color: var(--fire-text);
}
&:hover {
background: var(--fire-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--fire-shadow-hover);
color: var(--fire-text-hover);
}
&:focus {
outline: 2px solid var(--fire-shadow-hover);
}
}
&[data-element='water'] {
background: var(--water-bg);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--water-shadow);
color: var(--water-text);
:global(.remove) {
color: var(--water-text);
}
&:hover {
background: var(--water-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--water-shadow-hover);
color: var(--water-text-hover);
}
&:focus {
outline: 2px solid var(--water-shadow-hover);
}
}
&[data-element='earth'] {
background: var(--earth-bg);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--earth-shadow);
color: var(--earth-text);
:global(.remove) {
color: var(--earth-text);
}
&:hover {
background: var(--earth-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--earth-shadow-hover);
color: var(--earth-text-hover);
}
&:focus {
outline: 2px solid var(--earth-shadow-hover);
}
}
&[data-element='wind'] {
background: var(--wind-bg);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--wind-shadow);
color: var(--wind-text);
:global(.remove) {
color: var(--wind-text);
}
&:hover {
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--wind-shadow-hover);
color: var(--wind-text-hover);
}
&:focus {
outline: 2px solid var(--wind-shadow-hover);
}
}
&[data-element='dark'] {
background: var(--dark-bg);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--dark-shadow);
color: var(--dark-text);
:global(.remove) {
color: var(--dark-text);
}
&:hover {
background: var(--dark-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--dark-shadow-hover);
color: var(--dark-text-hover);
}
&:focus {
outline: 2px solid var(--dark-shadow-hover);
}
}
&[data-element='light'] {
background: var(--light-bg);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--light-shadow);
color: var(--light-text);
:global(.remove) {
color: var(--light-text);
}
&:hover {
background: var(--light-bg-hover);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25),
0 1px 0px var(--light-shadow-hover);
color: var(--light-text-hover);
}
&:focus {
outline: 2px solid var(--light-shadow-hover);
}
}
}
}
}

View file

@ -1,88 +0,0 @@
import { ComponentProps, useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'
import type { JSONContent } from '@tiptap/core'
import { useEditor, EditorContent } from '@tiptap/react'
import { useTranslation } from 'next-i18next'
import Placeholder from '@tiptap/extension-placeholder'
import FilterMention from '~extensions/FilterMention'
import NoNewLine from '~extensions/NoNewLine'
import classNames from 'classnames'
import { mentionSuggestionOptions } from '~utils/mentionSuggestions'
import styles from './index.module.scss'
import StarterKit from '@tiptap/starter-kit'
interface Props extends ComponentProps<'div'> {
bound?: boolean
placeholder?: string
onUpdate?: (content: string[]) => void
}
const MentionEditor = ({ bound, placeholder, onUpdate, ...props }: Props) => {
const locale = useRouter().locale || 'en'
const { t } = useTranslation('common')
// Setup: Editor
const editor = useEditor({
content: '',
editable: true,
editorProps: {
attributes: {
class: classNames({
[styles.editor]: true,
[styles.bound]: bound,
}),
},
},
extensions: [
StarterKit,
Placeholder.configure({
emptyEditorClass: styles.empty,
placeholder: placeholder,
}),
NoNewLine,
FilterMention.configure({
renderLabel({ options, node }) {
return `${node.attrs.id.name[locale] ?? node.attrs.id.granblue_en}`
},
suggestion: mentionSuggestionOptions,
HTMLAttributes: {
class: classNames({
[styles.mention]: true,
}),
},
}),
],
onFocus: ({ editor }) => {
console.log('Editor reporting that is focused')
},
onUpdate: ({ editor }) => {
const mentions = parseMentions(editor.getJSON())
if (onUpdate) onUpdate(mentions)
},
})
return (
<div className={styles.wrapper}>
<EditorContent editor={editor} />
</div>
)
}
function parseMentions(data: JSONContent) {
const mentions: string[] = (data.content || []).flatMap(parseMentions)
if (data.type === 'mention') {
const granblueId = data.attrs?.id.granblue_id
mentions.push(granblueId)
}
return [...new Set(mentions)]
}
MentionEditor.defaultProps = {
bound: false,
}
export default MentionEditor

View file

@ -1,191 +0,0 @@
import { mergeAttributes, Node } from '@tiptap/core'
import {
CustomSuggestion,
SuggestionOptions,
} from '~extensions/CustomSuggestion'
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { PluginKey } from '@tiptap/pm/state'
export type MentionOptions = {
HTMLAttributes: Record<string, any>
renderLabel: (props: {
options: MentionOptions
node: ProseMirrorNode
}) => string
suggestion: Omit<SuggestionOptions, 'editor'>
}
export const MentionPluginKey = new PluginKey('mention')
export const FilterMention = Node.create<MentionOptions>({
name: 'mention',
addOptions() {
return {
HTMLAttributes: {},
renderLabel({ options, node }) {
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
},
suggestion: {
char: '@',
pluginKey: MentionPluginKey,
command: ({ editor, range, props }) => {
// increase range.to by one when the next node is of type "text"
// and starts with a space character
const nodeAfter = editor.view.state.selection.$to.nodeAfter
const overrideSpace = nodeAfter?.text?.startsWith(' ')
if (overrideSpace) {
range.to += 1
}
editor
.chain()
.focus()
.insertContentAt(range, [
{
type: this.name,
attrs: props,
},
{
type: 'text',
text: ' ',
},
])
.run()
window.getSelection()?.collapseToEnd()
},
allow: ({ state, range }) => {
const $from = state.doc.resolve(range.from)
const type = state.schema.nodes[this.name]
const allow = !!$from.parent.type.contentMatch.matchType(type)
return allow
},
},
}
},
group: 'inline',
inline: true,
selectable: false,
atom: true,
addAttributes() {
return {
id: {
default: null,
parseHTML: (element) => element.getAttribute('data-id'),
renderHTML: (attributes) => {
if (!attributes.id) {
return {}
}
return {
'data-id': attributes.id,
}
},
},
label: {
default: null,
parseHTML: (element) => element.getAttribute('data-label'),
renderHTML: (attributes) => {
if (!attributes.label) {
return {}
}
return {
'data-label': attributes.label,
}
},
},
}
},
parseHTML() {
return [
{
tag: `span[data-type="${this.name}"]`,
},
]
},
renderHTML({ node, HTMLAttributes }) {
const removeButton = [
'span',
{
class: 'remove',
onclick: () => {
// Add functionality for the button click here
},
},
'\u00D7', // Unicode for the multiplication symbol
]
return [
'div',
mergeAttributes(
{ 'data-type': this.name },
{ 'data-element': node.attrs.id.element.slug },
{ tabindex: -1 },
this.options.HTMLAttributes,
HTMLAttributes
),
this.options.renderLabel({
options: this.options,
node,
}),
removeButton,
]
},
renderText({ node }) {
return this.options.renderLabel({
options: this.options,
node,
})
},
addKeyboardShortcuts() {
return {
Backspace: () =>
this.editor.commands.command(({ tr, state }) => {
let isMention = false
const { selection } = state
const { empty, anchor } = selection
if (!empty) {
return false
}
state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {
if (node.type.name === this.name) {
isMention = true
tr.insertText('', pos, pos + node.nodeSize)
return false
}
})
return isMention
}),
}
},
addProseMirrorPlugins() {
return [
CustomSuggestion({
editor: this.editor,
...this.options.suggestion,
}),
]
},
})
export default FilterMention