From 3b32c5a7301ddf93ceedbd958458a847bf7a1079 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 21 Aug 2023 17:25:10 -0700 Subject: [PATCH] Remove files for Tiptap mention editor The interactions on this were a huge pain in the ass --- .../common/MentionEditor/index.module.scss | 211 ------------------ components/common/MentionEditor/index.tsx | 88 -------- extensions/FilterMention/index.tsx | 191 ---------------- 3 files changed, 490 deletions(-) delete mode 100644 components/common/MentionEditor/index.module.scss delete mode 100644 components/common/MentionEditor/index.tsx delete mode 100644 extensions/FilterMention/index.tsx diff --git a/components/common/MentionEditor/index.module.scss b/components/common/MentionEditor/index.module.scss deleted file mode 100644 index 4aa11680..00000000 --- a/components/common/MentionEditor/index.module.scss +++ /dev/null @@ -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); - } - } - } - } -} diff --git a/components/common/MentionEditor/index.tsx b/components/common/MentionEditor/index.tsx deleted file mode 100644 index 6bb0358e..00000000 --- a/components/common/MentionEditor/index.tsx +++ /dev/null @@ -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 ( -
- -
- ) -} - -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 diff --git a/extensions/FilterMention/index.tsx b/extensions/FilterMention/index.tsx deleted file mode 100644 index 72f5f719..00000000 --- a/extensions/FilterMention/index.tsx +++ /dev/null @@ -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 - renderLabel: (props: { - options: MentionOptions - node: ProseMirrorNode - }) => string - suggestion: Omit -} - -export const MentionPluginKey = new PluginKey('mention') - -export const FilterMention = Node.create({ - 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