parent
8a8abd3652
commit
df8ee62112
|
@ -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() &&
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -117,7 +117,10 @@ const SimplifiedEditor = (props: Props) => {
|
|||
Paragraph,
|
||||
Bold,
|
||||
Italic,
|
||||
Link.configure({
|
||||
Link.extend({
|
||||
inclusive: false,
|
||||
}).configure({
|
||||
autolink: true,
|
||||
openOnClick: false,
|
||||
}),
|
||||
CharacterCount.configure({
|
||||
|
|
|
@ -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(),
|
||||
})}
|
||||
|
|
31
src/components/Editor/extensions/Span.ts
Normal file
31
src/components/Editor/extensions/Span.ts
Normal 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,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
50
src/components/Editor/extensions/ToggleTextWrap.ts
Normal file
50
src/components/Editor/extensions/ToggleTextWrap.ts
Normal 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
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
Loading…
Reference in New Issue
Block a user