fix mention problem
This commit is contained in:
parent
5a4b191536
commit
fa4d3046d0
|
|
@ -71,30 +71,36 @@ export function mention(node: HTMLInputElement | HTMLTextAreaElement) {
|
|||
});
|
||||
}
|
||||
|
||||
function insertText(text: string) {
|
||||
const before = node.value.slice(0, mentionStart);
|
||||
const after = node.value.slice(node.selectionStart ?? 0);
|
||||
function insertTextAt(from: number, to: number, text: string) {
|
||||
const before = node.value.slice(0, from);
|
||||
const after = node.value.slice(to);
|
||||
node.value = before + text + ' ' + after;
|
||||
const newPos = mentionStart + text.length + 1;
|
||||
const newPos = from + text.length + 1;
|
||||
node.selectionStart = newPos;
|
||||
node.selectionEnd = newPos;
|
||||
node.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
async function selectItem(index: number) {
|
||||
console.log('[mention-action] selectItem', index, 'items:', items.length, 'create:', createOptions.length, 'mentionStart:', mentionStart, 'value:', JSON.stringify(node.value));
|
||||
// Save positions before any async work (hide/blur could reset mentionStart)
|
||||
const savedFrom = mentionStart;
|
||||
const savedTo = node.selectionStart ?? node.value.length;
|
||||
console.log('[mention-action] selectItem', index, 'items:', items.length, 'create:', createOptions.length, 'from:', savedFrom, 'to:', savedTo);
|
||||
suppressInput = true;
|
||||
try {
|
||||
if (index < items.length) {
|
||||
console.log('[mention-action] inserting:', items[index].insertText);
|
||||
insertText(items[index].insertText);
|
||||
const text = items[index].insertText;
|
||||
console.log('[mention-action] inserting:', text);
|
||||
insertTextAt(savedFrom, savedTo, text);
|
||||
console.log('[mention-action] after insert, value:', JSON.stringify(node.value));
|
||||
} else {
|
||||
const opt = createOptions[index - items.length];
|
||||
if (!opt) return;
|
||||
await createMentionContext(opt);
|
||||
const tag = opt.type === 'company' ? quoteMention('@F:', opt.query) : opt.type === 'person' ? quoteMention('@', opt.query) : quoteMention('@P:', opt.query);
|
||||
insertText(tag);
|
||||
console.log('[mention-action] inserting after create:', tag);
|
||||
insertTextAt(savedFrom, savedTo, tag);
|
||||
console.log('[mention-action] after create insert, value:', JSON.stringify(node.value));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('mention selectItem error:', err);
|
||||
|
|
@ -112,6 +118,15 @@ export function mention(node: HTMLInputElement | HTMLTextAreaElement) {
|
|||
return;
|
||||
}
|
||||
const result = await fetchMentionItems(query);
|
||||
|
||||
// Re-check after async: state may have changed (e.g. selectItem ran)
|
||||
if (suppressInput) return;
|
||||
const queryNow = getQuery();
|
||||
if (queryNow === null) {
|
||||
if (active) hide();
|
||||
return;
|
||||
}
|
||||
|
||||
items = result.items;
|
||||
createOptions = result.createOptions;
|
||||
selectedIndex = 0;
|
||||
|
|
@ -144,6 +159,7 @@ export function mention(node: HTMLInputElement | HTMLTextAreaElement) {
|
|||
}
|
||||
|
||||
function handleBlur() {
|
||||
if (suppressInput) return;
|
||||
setTimeout(hide, 150);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,13 +76,16 @@ export const TiptapMention = Extension.create({
|
|||
}
|
||||
|
||||
async function selectItem(index: number) {
|
||||
console.log('[tiptap-mention] selectItem', index, 'items:', items.length, 'create:', createOptions.length, 'mentionFrom:', mentionFrom);
|
||||
// Save positions before any async work (hide could reset mentionFrom)
|
||||
const savedFrom = mentionFrom;
|
||||
const savedTo = editor.state.selection.from;
|
||||
console.log('[tiptap-mention] selectItem', index, 'items:', items.length, 'create:', createOptions.length, 'from:', savedFrom, 'to:', savedTo);
|
||||
try {
|
||||
if (index < items.length) {
|
||||
const text = items[index].insertText;
|
||||
console.log('[tiptap-mention] inserting:', text, 'deleteRange:', mentionFrom, '->', editor.state.selection.from);
|
||||
console.log('[tiptap-mention] inserting:', text);
|
||||
editor.chain().focus()
|
||||
.deleteRange({ from: mentionFrom, to: editor.state.selection.from })
|
||||
.deleteRange({ from: savedFrom, to: savedTo })
|
||||
.insertContent(text + ' ')
|
||||
.run();
|
||||
console.log('[tiptap-mention] after insert, content:', editor.storage.markdown.getMarkdown().slice(0, 100));
|
||||
|
|
@ -91,8 +94,9 @@ export const TiptapMention = Extension.create({
|
|||
if (!opt) return;
|
||||
await createMentionContext(opt);
|
||||
const tag = opt.type === 'company' ? quoteMention('@F:', opt.query) : opt.type === 'person' ? quoteMention('@', opt.query) : quoteMention('@P:', opt.query);
|
||||
console.log('[tiptap-mention] inserting after create:', tag);
|
||||
editor.chain().focus()
|
||||
.deleteRange({ from: mentionFrom, to: editor.state.selection.from })
|
||||
.deleteRange({ from: savedFrom, to: savedTo })
|
||||
.insertContent(tag + ' ')
|
||||
.run();
|
||||
}
|
||||
|
|
@ -119,7 +123,7 @@ export const TiptapMention = Extension.create({
|
|||
if (i === 0 || /\s/.test(textBefore[i - 1])) {
|
||||
const query = textBefore.slice(i + 1);
|
||||
// Skip @P: and @F: prefixes (handled differently)
|
||||
if (query.startsWith('P:') || query.startsWith('F:')) return null;
|
||||
if (/^[Pp]:|^[Ff]:/.test(query)) return null;
|
||||
const docFrom = from - (textBefore.length - i);
|
||||
return { active: true, query, from: docFrom };
|
||||
}
|
||||
|
|
@ -139,6 +143,13 @@ export const TiptapMention = Extension.create({
|
|||
|
||||
mentionFrom = mentionState.from;
|
||||
const result = await fetchMentionItems(mentionState.query);
|
||||
|
||||
// Re-check after async: state may have changed (e.g. selectItem ran)
|
||||
if (!getQueryFromState(view)) {
|
||||
if (active) hide();
|
||||
return;
|
||||
}
|
||||
|
||||
items = result.items;
|
||||
createOptions = result.createOptions;
|
||||
selectedIndex = 0;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function extractAssignments(text: string): string[] {
|
|||
}
|
||||
|
||||
export function extractProjects(text: string): string[] {
|
||||
const regex = /@P:(?:"([^"]+)"|([\w-]+))/g;
|
||||
const regex = /@[Pp]:(?:"([^"]+)"|([\w-]+))/g;
|
||||
const result: string[] = [];
|
||||
let match;
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
|
|
@ -19,7 +19,7 @@ export function extractProjects(text: string): string[] {
|
|||
}
|
||||
|
||||
export function extractMentions(text: string): string[] {
|
||||
const regex = /@(?!P:|F:)(?:"([^"]+)"|([\w-]+))/g;
|
||||
const regex = /@(?![Pp]:|[Ff]:)(?:"([^"]+)"|([\w-]+))/g;
|
||||
const result: string[] = [];
|
||||
let match;
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
|
|
@ -29,7 +29,7 @@ export function extractMentions(text: string): string[] {
|
|||
}
|
||||
|
||||
export function extractCompanies(text: string): string[] {
|
||||
const regex = /@F:(?:"([^"]+)"|([\w-]+))/g;
|
||||
const regex = /@[Ff]:(?:"([^"]+)"|([\w-]+))/g;
|
||||
const result: string[] = [];
|
||||
let match;
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ const assignmentExt: TokenizerExtension & RendererExtension = {
|
|||
const projectRefExt: TokenizerExtension & RendererExtension = {
|
||||
name: 'projectRef',
|
||||
level: 'inline',
|
||||
start(src: string) { return src.indexOf('@P:'); },
|
||||
start(src: string) { return src.search(/@[Pp]:/); },
|
||||
tokenizer(src: string) {
|
||||
const match = /^@P:"(.+?)"/.exec(src) ?? /^@P:([\w-]+)/.exec(src);
|
||||
const match = /^@[Pp]:"(.+?)"/.exec(src) ?? /^@[Pp]:([\w-]+)/.exec(src);
|
||||
if (match) {
|
||||
return { type: 'projectRef', raw: match[0], project: match[1] };
|
||||
}
|
||||
|
|
@ -37,9 +37,9 @@ const projectRefExt: TokenizerExtension & RendererExtension = {
|
|||
const companyRefExt: TokenizerExtension & RendererExtension = {
|
||||
name: 'companyRef',
|
||||
level: 'inline',
|
||||
start(src: string) { return src.indexOf('@F:'); },
|
||||
start(src: string) { return src.search(/@[Ff]:/); },
|
||||
tokenizer(src: string) {
|
||||
const match = /^@F:"(.+?)"/.exec(src) ?? /^@F:([\w-]+)/.exec(src);
|
||||
const match = /^@[Ff]:"(.+?)"/.exec(src) ?? /^@[Ff]:([\w-]+)/.exec(src);
|
||||
if (match) {
|
||||
return { type: 'companyRef', raw: match[0], company: match[1] };
|
||||
}
|
||||
|
|
@ -55,13 +55,13 @@ const personMentionExt: TokenizerExtension & RendererExtension = {
|
|||
start(src: string) {
|
||||
const idx = src.indexOf('@');
|
||||
if (idx === -1) return -1;
|
||||
// Skip if followed by P: or F: (handled by other extensions)
|
||||
// Skip if followed by P:/p: or F:/f: (handled by other extensions)
|
||||
const after = src.slice(idx + 1);
|
||||
if (after.startsWith('P:') || after.startsWith('F:')) return src.indexOf('@', idx + 1);
|
||||
if (/^[Pp]:|^[Ff]:/.test(after)) return src.indexOf('@', idx + 1);
|
||||
return idx;
|
||||
},
|
||||
tokenizer(src: string) {
|
||||
const match = /^@(?!P:|F:)"(.+?)"/.exec(src) ?? /^@(?!P:|F:)([\w-]+)/.exec(src);
|
||||
const match = /^@(?![Pp]:|[Ff]:)"(.+?)"/.exec(src) ?? /^@(?![Pp]:|[Ff]:)([\w-]+)/.exec(src);
|
||||
if (match) {
|
||||
return { type: 'personMention', raw: match[0], person: match[1] };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue