npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@editora/mentions

v1.0.1

Published

Mentions plugin for Editora Rich Text Editor

Downloads

19

Readme

@editora/mentions

Version License TypeScript Size

[!IMPORTANT] Live Website: https://editora-ecosystem.netlify.app/
Storybook: https://editora-ecosystem-storybook.netlify.app/

@editora/mentions adds @mention autocomplete and atomic mention tokens to Editora.

Features

  • Trigger mentions with @ while typing
  • Keyboard navigation (ArrowUp/ArrowDown, Enter, Tab, Esc)
  • Multi-instance safe popup handling
  • Atomic mention token deletion (Backspace/Delete)
  • Dark/light theme styles

Install

npm install @editora/mentions

Usage

import { MentionPlugin } from '@editora/mentions';

const mentions = MentionPlugin({
  triggerChars: ['@'],
  minChars: 1,
  maxSuggestions: 8,
  items: [
    { id: 'john.doe', label: 'John Doe', meta: '[email protected]' },
    { id: 'sarah.lee', label: 'Sarah Lee', meta: '[email protected]' },
  ],
});

Use it with @editora/react or web component setup by including it in your plugin list.

Web Component Configuration

mentions supports the same option shape in web components via pluginConfig.

<editora-editor id="wc-editor"></editora-editor>
<script>
  const el = document.getElementById('wc-editor');
  el.setConfig({
    plugins: 'bold italic mentions history',
    toolbar: { items: 'bold italic insertMention undo redo' },
    pluginConfig: {
      mentions: {
        minChars: 2,
        maxSuggestions: 10,
        items: [
          { id: 'john.doe', label: 'John Doe', meta: '[email protected]' },
          { id: 'sarah.lee', label: 'Sarah Lee', meta: '[email protected]' },
        ],
      },
    },
  });
</script>

Notes:

  • Use plugin name mentions (alias mention also works).
  • If search is provided, it overrides api and static items.

Data Source Modes

Mentions can load suggestions from three sources:

  1. items (static list, default)
  2. search(query, trigger) (custom logic, sync or async)
  3. api (declarative HTTP config)

Resolution precedence is:

search -> api -> items

If search is provided, it is used as the source of truth.

Search Callback (Optional)

Use search when you want full control in code (custom filtering, caching, auth, multi-source lookup).

import { MentionPlugin } from '@editora/mentions';

const mentions = MentionPlugin({
  minChars: 1,
  maxSuggestions: 8,
  search: async (query, trigger) => {
    if (trigger !== '@') return [];
    if (!query.trim()) return [];

    const res = await fetch(`/api/users/typeahead?q=${encodeURIComponent(query)}&limit=8`);
    if (!res.ok) return [];

    const json = await res.json();
    return (json.items || []).map((u: any) => ({
      id: String(u.id),
      label: String(u.name),
      meta: String(u.email || ''),
      value: `@${u.username}`,
    }));
  },
});

API Typeahead (Optional)

Static items remain the default. For database-backed typeahead, pass api:

import { MentionPlugin } from '@editora/mentions';

const mentions = MentionPlugin({
  minChars: 2,
  maxSuggestions: 10,
  api: {
    url: '/api/users/search',
    method: 'GET',
    headers: () => ({
      Authorization: `Bearer ${localStorage.getItem('token') || ''}`,
    }),
    queryParam: 'q',
    limitParam: 'limit',
    staticParams: { status: 'active' },
    debounceMs: 220,
    timeoutMs: 8000,
    responsePath: 'data.items',
    mapItem: (raw) => ({
      id: String((raw as any).id),
      label: String((raw as any).name),
      meta: String((raw as any).email || ''),
    }),
    onError: (error) => {
      console.error('Mention API failed', error);
    },
  },
});

For full control, use api.buildRequest(ctx) and/or api.transformResponse(response, ctx).

Usage Scenarios

Scenario 1: Static Team Directory

Best for small fixed lists (for example internal users loaded at boot).

MentionPlugin({
  items: [
    { id: 'john.doe', label: 'John Doe', meta: 'Engineering' },
    { id: 'sarah.lee', label: 'Sarah Lee', meta: 'Product' },
  ],
});

Scenario 2: Client-side Search Over Preloaded Data

Best when you preload a large list once, then filter in memory.

const users = await loadUsersOnce();

MentionPlugin({
  search: (query) => {
    const q = query.toLowerCase();
    return users
      .filter((u) => u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q))
      .slice(0, 10)
      .map((u) => ({ id: u.id, label: u.name, meta: u.email }));
  },
});

Scenario 3: Server Typeahead via Search Callback

Best when you need custom auth, request cancellation strategy, or merged data from multiple APIs.

MentionPlugin({
  search: async (query, trigger) => {
    const [users, teams] = await Promise.all([
      fetch(`/api/users?q=${encodeURIComponent(query)}&trigger=${trigger}`).then((r) => r.json()),
      fetch(`/api/teams?q=${encodeURIComponent(query)}&trigger=${trigger}`).then((r) => r.json()),
    ]);

    return [
      ...(users.items || []).map((u: any) => ({ id: `u:${u.id}`, label: u.name, meta: 'User' })),
      ...(teams.items || []).map((t: any) => ({ id: `t:${t.id}`, label: t.name, meta: 'Team' })),
    ].slice(0, 10);
  },
});

Scenario 4: Declarative API Configuration

Best when you want less custom code and standardized request behavior.

MentionPlugin({
  api: {
    url: '/api/users/search',
    method: 'GET',
    queryParam: 'q',
    limitParam: 'limit',
    responsePath: 'data.items',
    mapItem: (raw) => ({
      id: String((raw as any).id),
      label: String((raw as any).name),
    }),
  },
});

Scenario 5: Multiple Trigger Characters

Use different mention patterns in one editor (@ users, # tags, etc).

MentionPlugin({
  triggerChars: ['@', '#'],
  search: async (query, trigger) => {
    if (trigger === '@') {
      const r = await fetch(`/api/users?q=${encodeURIComponent(query)}`);
      const j = await r.json();
      return (j.items || []).map((u: any) => ({ id: u.id, label: u.name, meta: 'User' }));
    }

    const r = await fetch(`/api/tags?q=${encodeURIComponent(query)}`);
    const j = await r.json();
    return (j.items || []).map((t: any) => ({ id: t.id, label: t.name, meta: 'Tag' }));
  },
});

API Request Customization

MentionApiConfig supports full request control:

  • url: API endpoint.
  • method: HTTP method (GET, POST, PUT, PATCH, HEAD, etc).
  • headers: static headers object or (ctx) => headers.
  • credentials, mode, cache: forwarded to fetch.
  • queryParam, triggerParam, limitParam: query/body field names.
  • staticParams: additional static parameters.
  • body: static body or (ctx) => body for non-GET/HEAD calls.
  • buildRequest: full low-level override. If provided, it takes priority.
  • responseType: json (default) or text.
  • responsePath: dotted path in response payload (for example data.items).
  • mapItem: convert one raw API record into a MentionItem.
  • transformResponse: convert whole payload into MentionItem[].
  • debounceMs: debounce query calls.
  • timeoutMs: request timeout before abort.
  • onError: centralized request error hook.

Example with full buildRequest control:

const mentions = MentionPlugin({
  api: {
    buildRequest: (ctx) => ({
      url: "/api/v2/users/typeahead",
      init: {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify({
          search: ctx.query,
          trigger: ctx.trigger,
          pageSize: ctx.limit,
          includeInactive: false,
        }),
      },
    }),
    transformResponse: (response) => {
      const rows = (response as any)?.results ?? [];
      return rows.map((row: any) => ({
        id: String(row.userId),
        label: String(row.displayName),
        meta: String(row.email || ""),
        value: `@${row.username}`,
      }));
    },
  },
});

Options

  • triggerChars?: string[]
  • minChars?: number
  • maxQueryLength?: number
  • maxSuggestions?: number
  • items?: MentionItem[]
  • api?: MentionApiConfig
  • search?: (query, trigger) => MentionItem[] | Promise<MentionItem[]>
  • itemRenderer?: (item, query) => string
  • emptyStateText?: string
  • noResultsText?: string
  • loadingText?: string
  • insertSpaceAfterMention?: boolean

Security Note

If you provide itemRenderer, return trusted HTML only.