For sure matches Mention

This commit is contained in:
Eric Bailey
2024-09-09 21:24:32 -05:00
parent bea6627d59
commit 7ae1321608
2 changed files with 80 additions and 52 deletions
@@ -17,15 +17,18 @@ export interface MentionNodeAttrs {
* The identifier for the selected item that was mentioned, stored as a `data-id` * The identifier for the selected item that was mentioned, stored as a `data-id`
* attribute. * attribute.
*/ */
id: string | null; id: string | null
/** /**
* The label to be rendered by the editor as the displayed text for this mentioned * The label to be rendered by the editor as the displayed text for this mentioned
* item, if provided. Stored as a `data-label` attribute. See `renderLabel`. * item, if provided. Stored as a `data-label` attribute. See `renderLabel`.
*/ */
label?: string | null; label?: string | null
} }
export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, any> = MentionNodeAttrs> = { export type MentionOptions<
SuggestionItem = any,
Attrs extends Record<string, any> = MentionNodeAttrs,
> = {
/** /**
* The HTML attributes for a mention node. * The HTML attributes for a mention node.
* @default {} * @default {}
@@ -40,7 +43,10 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
* @returns The label * @returns The label
* @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}` * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
*/ */
renderLabel?: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string renderLabel?: (props: {
options: MentionOptions<SuggestionItem, Attrs>
node: ProseMirrorNode
}) => string
/** /**
* A function to render the text of a mention. * A function to render the text of a mention.
@@ -48,7 +54,10 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
* @returns The text * @returns The text
* @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}` * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
*/ */
renderText: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => string renderText: (props: {
options: MentionOptions<SuggestionItem, Attrs>
node: ProseMirrorNode
}) => string
/** /**
* A function to render the HTML of a mention. * A function to render the HTML of a mention.
@@ -56,7 +65,10 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
* @returns The HTML as a ProseMirror DOM Output Spec * @returns The HTML as a ProseMirror DOM Output Spec
* @example ({ options, node }) => ['span', { 'data-type': 'mention' }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`] * @example ({ options, node }) => ['span', { 'data-type': 'mention' }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]
*/ */
renderHTML: (props: { options: MentionOptions<SuggestionItem, Attrs>; node: ProseMirrorNode }) => DOMOutputSpec renderHTML: (props: {
options: MentionOptions<SuggestionItem, Attrs>
node: ProseMirrorNode
}) => DOMOutputSpec
/** /**
* Whether to delete the trigger character with backspace. * Whether to delete the trigger character with backspace.
@@ -133,15 +145,9 @@ export const Tags = Node.create<MentionOptions>({
const $from = state.doc.resolve(range.from) const $from = state.doc.resolve(range.from)
const type = state.schema.nodes[this.name] const type = state.schema.nodes[this.name]
const allow = !!$from.parent.type.contentMatch.matchType(type) const allow = !!$from.parent.type.contentMatch.matchType(type)
console.log({
allow,
$from,
state,
})
return allow return allow
}, },
findSuggestionMatch: findSuggestionMatch findSuggestionMatch: findSuggestionMatch,
}, },
} }
}, },
@@ -196,10 +202,16 @@ export const Tags = Node.create<MentionOptions>({
renderHTML({node, HTMLAttributes}) { renderHTML({node, HTMLAttributes}) {
if (this.options.renderLabel !== undefined) { if (this.options.renderLabel !== undefined) {
console.warn('renderLabel is deprecated use renderText and renderHTML instead') console.warn(
'renderLabel is deprecated use renderText and renderHTML instead',
)
return [ return [
'span', 'span',
mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes), mergeAttributes(
{'data-type': this.name},
this.options.HTMLAttributes,
HTMLAttributes,
),
this.options.renderLabel({ this.options.renderLabel({
options: this.options, options: this.options,
node, node,
@@ -208,7 +220,11 @@ export const Tags = Node.create<MentionOptions>({
} }
const mergedOptions = {...this.options} const mergedOptions = {...this.options}
mergedOptions.HTMLAttributes = mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes) mergedOptions.HTMLAttributes = mergeAttributes(
{'data-type': this.name},
this.options.HTMLAttributes,
HTMLAttributes,
)
const html = this.options.renderHTML({ const html = this.options.renderHTML({
options: mergedOptions, options: mergedOptions,
node, node,
@@ -217,7 +233,11 @@ export const Tags = Node.create<MentionOptions>({
if (typeof html === 'string') { if (typeof html === 'string') {
return [ return [
'span', 'span',
mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes), mergeAttributes(
{'data-type': this.name},
this.options.HTMLAttributes,
HTMLAttributes,
),
html, html,
] ]
} }
@@ -226,7 +246,9 @@ export const Tags = Node.create<MentionOptions>({
renderText({node}) { renderText({node}) {
if (this.options.renderLabel !== undefined) { if (this.options.renderLabel !== undefined) {
console.warn('renderLabel is deprecated use renderText and renderHTML instead') console.warn(
'renderLabel is deprecated use renderText and renderHTML instead',
)
return this.options.renderLabel({ return this.options.renderLabel({
options: this.options, options: this.options,
node, node,
@@ -240,7 +262,8 @@ export const Tags = Node.create<MentionOptions>({
addKeyboardShortcuts() { addKeyboardShortcuts() {
return { return {
Backspace: () => this.editor.commands.command(({ tr, state }) => { Backspace: () =>
this.editor.commands.command(({tr, state}) => {
let isMention = false let isMention = false
const {selection} = state const {selection} = state
const {empty, anchor} = selection const {empty, anchor} = selection
@@ -253,7 +276,9 @@ export const Tags = Node.create<MentionOptions>({
if (node.type.name === this.name) { if (node.type.name === this.name) {
isMention = true isMention = true
tr.insertText( tr.insertText(
this.options.deleteTriggerWithBackspace ? '' : this.options.suggestion.char || '', this.options.deleteTriggerWithBackspace
? ''
: this.options.suggestion.char || '',
pos, pos,
pos + node.nodeSize, pos + node.nodeSize,
) )
@@ -42,9 +42,12 @@ export function findSuggestionMatch({
if (!tag || tag.length === 0 || tag.length > 64) return null if (!tag || tag.length === 0 || tag.length > 64) return null
const leadingSpaceOffset = fullMatch.startsWith(' ') ? 1 : 0
const hashtagOffset = 1
// The absolute position of the match in the document // The absolute position of the match in the document
const from = textFrom + fullMatch.indexOf(tag) const from = textFrom + match.index + leadingSpaceOffset
const to = from + tag.length + 1 const to = from + tag.length + hashtagOffset
// If the $position is located within the matched substring, return that range // If the $position is located within the matched substring, return that range
if (from < $position.pos && to >= $position.pos) { if (from < $position.pos && to >= $position.pos) {
@@ -54,7 +57,7 @@ export function findSuggestionMatch({
to, to,
}, },
query: tag.replace(TRAILING_PUNCTUATION_REGEX, ''), query: tag.replace(TRAILING_PUNCTUATION_REGEX, ''),
text: fullMatch, text: fullMatch.replace(/^\s{1}/, ''),
} }
} }