Update tiptap link insert (#368)

Update tiptap link insert
This commit is contained in:
Ilya Y 2024-01-22 13:45:46 +03:00 committed by GitHub
parent 8a8abd3652
commit df8ee62112
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 116 additions and 4 deletions

View File

@ -46,6 +46,8 @@ import { Figcaption } from './extensions/Figcaption'
import { Figure } from './extensions/Figure'
import { Footnote } from './extensions/Footnote'
import { Iframe } from './extensions/Iframe'
import { Span } from './extensions/Span'
import { ToggleTextWrap } from './extensions/ToggleTextWrap'
import { TrailingNode } from './extensions/TrailingNode'
import { TextBubbleMenu } from './TextBubbleMenu'
@ -201,6 +203,8 @@ export const Editor = (props: Props) => {
CustomBlockquote,
Bold,
Italic,
Span,
ToggleTextWrap,
Strike,
HorizontalRule.configure({
HTMLAttributes: {
@ -208,7 +212,10 @@ export const Editor = (props: Props) => {
},
}),
Underline,
Link.configure({
Link.extend({
inclusive: false,
}).configure({
autolink: true,
openOnClick: false,
}),
Heading.configure({
@ -244,6 +251,7 @@ export const Editor = (props: Props) => {
Figure,
Figcaption,
Footnote,
ToggleTextWrap,
CharacterCount.configure(), // https://github.com/ueberdosis/tiptap/issues/2589#issuecomment-1093084689
BubbleMenu.configure({
pluginKey: 'textBubbleMenu',
@ -252,6 +260,9 @@ export const Editor = (props: Props) => {
const { doc, selection } = state
const { empty } = selection
const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && isTextSelection(selection)
if (isEmptyTextBlock) {
e.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run()
}
setIsCommonMarkup(e.isActive('figcaption'))
const result =
(view.hasFocus() &&

View File

@ -311,3 +311,10 @@ footnote {
background-color: unset;
}
}
.highlight-fake-selection {
background: var(--selection-background);
color: var(--selection-color);
border: solid var(--selection-background);
border-width: 5px 0;
}

View File

@ -117,7 +117,10 @@ const SimplifiedEditor = (props: Props) => {
Paragraph,
Bold,
Italic,
Link.configure({
Link.extend({
inclusive: false,
}).configure({
autolink: true,
openOnClick: false,
}),
CharacterCount.configure({

View File

@ -129,11 +129,21 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
})
})
const handleOpenLinkForm = () => {
props.editor.chain().focus().addTextWrap({ class: 'highlight-fake-selection' }).run()
setLinkEditorOpen(true)
}
const handleCloseLinkForm = () => {
setLinkEditorOpen(false)
props.editor.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run()
}
return (
<div ref={props.ref} class={clsx(styles.TextBubbleMenu, { [styles.growWidth]: footnoteEditorOpen() })}>
<Switch>
<Match when={linkEditorOpen()}>
<InsertLinkForm editor={props.editor} onClose={() => setLinkEditorOpen(false)} />
<InsertLinkForm editor={props.editor} onClose={handleCloseLinkForm} />
</Match>
<Match when={footnoteEditorOpen()}>
<SimplifiedEditor
@ -329,7 +339,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
<button
ref={triggerRef}
type="button"
onClick={() => setLinkEditorOpen(true)}
onClick={handleOpenLinkForm}
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isLink(),
})}

View File

@ -0,0 +1,31 @@
import { Mark, mergeAttributes } from '@tiptap/core'
export const Span = Mark.create({
name: 'span',
parseHTML() {
return [
{
tag: 'span[class]',
getAttrs: (dom) => {
if (dom instanceof HTMLElement) {
return { class: dom.getAttribute('class') }
}
return false
},
},
]
},
renderHTML({ HTMLAttributes }) {
return ['span', mergeAttributes(HTMLAttributes), 0]
},
addAttributes() {
return {
class: {
default: null,
},
}
},
})

View File

@ -0,0 +1,50 @@
import { Extension } from '@tiptap/core'
declare module '@tiptap/core' {
interface Commands<ReturnType> {
toggleSpanWrap: {
addTextWrap: (attributes: { class: string }) => ReturnType
removeTextWrap: (attributes: { class: string }) => ReturnType
}
}
}
export const ToggleTextWrap = Extension.create({
name: 'toggleTextWrap',
addCommands() {
return {
addTextWrap:
(attributes) =>
({ commands, state }) => {
return commands.setMark('span', attributes)
},
removeTextWrap:
(attributes) =>
({ state, dispatch }) => {
let tr = state.tr
let changesApplied = false
state.doc.descendants((node, pos) => {
if (node.isInline) {
node.marks.forEach((mark) => {
if (mark.type.name === 'span' && mark.attrs.class === attributes.class) {
const end = pos + node.nodeSize
tr = tr.removeMark(pos, end, mark.type)
changesApplied = true
}
})
}
})
if (changesApplied) {
dispatch(tr)
return true
} else {
return false
}
},
}
},
})