File size: 1,933 Bytes
cfb0fa4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | // mention-extension.ts
type MentionOptions = {
triggerChar?: string; // default "@"
className?: string; // default "mention"
extraAttrs?: Record<string, string>; // additional HTML attrs
};
function escapeHtml(s: string) {
return s.replace(
/[&<>"']/g,
(c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]!
);
}
function mentionStart(src: string) {
// Find the first "<" followed by trigger char
// We'll refine inside tokenizer
return src.indexOf('<');
}
function mentionTokenizer(this: any, src: string, options: MentionOptions = {}) {
const trigger = options.triggerChar ?? '@';
// Build dynamic regex for `<@id>`, `<@id|label>`, `<@id|>`
// Added forward slash (/) to the character class for IDs
const re = new RegExp(`^<\\${trigger}([\\w.\\-:/]+)(?:\\|([^>]*))?>`);
const m = re.exec(src);
if (!m) return;
const [, id, label] = m;
return {
type: 'mention',
raw: m[0],
triggerChar: trigger,
id,
label: label && label.length > 0 ? label : id
};
}
function mentionRenderer(token: any, options: MentionOptions = {}) {
const trigger = options.triggerChar ?? '@';
const cls = options.className ?? 'mention';
const extra = options.extraAttrs ?? {};
const attrs = Object.entries({
class: cls,
'data-type': 'mention',
'data-id': token.id,
'data-mention-suggestion-char': trigger,
...extra
})
.map(([k, v]) => `${k}="${escapeHtml(String(v))}"`)
.join(' ');
return `<span ${attrs}>${escapeHtml(trigger + token.label)}</span>`;
}
export function mentionExtension(opts: MentionOptions = {}) {
return {
name: 'mention',
level: 'inline' as const,
start: mentionStart,
tokenizer(src: string) {
return mentionTokenizer.call(this, src, opts);
},
renderer(token: any) {
return mentionRenderer(token, opts);
}
};
}
// Usage:
// import { marked } from 'marked';
// marked.use({ extensions: [mentionExtension({ triggerChar: '@' })] });
|