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`
* attribute.
*/
id: string | null;
id: string | null
/**
* 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`.
*/
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.
* @default {}
@@ -40,7 +43,10 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
* @returns The label
* @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.
@@ -48,7 +54,10 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
* @returns The text
* @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.
@@ -56,7 +65,10 @@ export type MentionOptions<SuggestionItem = any, Attrs extends Record<string, an
* @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}`]
*/
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.
@@ -133,15 +145,9 @@ export const Tags = Node.create<MentionOptions>({
const $from = state.doc.resolve(range.from)
const type = state.schema.nodes[this.name]
const allow = !!$from.parent.type.contentMatch.matchType(type)
console.log({
allow,
$from,
state,
})
return allow
},
findSuggestionMatch: findSuggestionMatch
findSuggestionMatch: findSuggestionMatch,
},
}
},
@@ -196,10 +202,16 @@ export const Tags = Node.create<MentionOptions>({
renderHTML({node, HTMLAttributes}) {
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 [
'span',
mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes),
mergeAttributes(
{'data-type': this.name},
this.options.HTMLAttributes,
HTMLAttributes,
),
this.options.renderLabel({
options: this.options,
node,
@@ -208,7 +220,11 @@ export const Tags = Node.create<MentionOptions>({
}
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({
options: mergedOptions,
node,
@@ -217,7 +233,11 @@ export const Tags = Node.create<MentionOptions>({
if (typeof html === 'string') {
return [
'span',
mergeAttributes({ 'data-type': this.name }, this.options.HTMLAttributes, HTMLAttributes),
mergeAttributes(
{'data-type': this.name},
this.options.HTMLAttributes,
HTMLAttributes,
),
html,
]
}
@@ -226,7 +246,9 @@ export const Tags = Node.create<MentionOptions>({
renderText({node}) {
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({
options: this.options,
node,
@@ -240,7 +262,8 @@ export const Tags = Node.create<MentionOptions>({
addKeyboardShortcuts() {
return {
Backspace: () => this.editor.commands.command(({ tr, state }) => {
Backspace: () =>
this.editor.commands.command(({tr, state}) => {
let isMention = false
const {selection} = state
const {empty, anchor} = selection
@@ -253,7 +276,9 @@ export const Tags = Node.create<MentionOptions>({
if (node.type.name === this.name) {
isMention = true
tr.insertText(
this.options.deleteTriggerWithBackspace ? '' : this.options.suggestion.char || '',
this.options.deleteTriggerWithBackspace
? ''
: this.options.suggestion.char || '',
pos,
pos + node.nodeSize,
)
@@ -42,9 +42,12 @@ export function findSuggestionMatch({
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
const from = textFrom + fullMatch.indexOf(tag)
const to = from + tag.length + 1
const from = textFrom + match.index + leadingSpaceOffset
const to = from + tag.length + hashtagOffset
// If the $position is located within the matched substring, return that range
if (from < $position.pos && to >= $position.pos) {
@@ -54,7 +57,7 @@ export function findSuggestionMatch({
to,
},
query: tag.replace(TRAILING_PUNCTUATION_REGEX, ''),
text: fullMatch,
text: fullMatch.replace(/^\s{1}/, ''),
}
}