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

@cychiuae/strapi-plugin-lexical

v0.3.9-alpha.1

Published

Adds a custom field with the lexical WYSIWYG editor

Readme

This is fork from https://github.com/hashbite/strapi-plugin-lexical

strapi-plugin-lexical

Integrates the Lexical WYSIWYG editor as a custom field in Strapi. Basically a port of Lexical playground into strapi environment with some nice extras.

screenshot-lexical

https://nodei.co/npm/strapi-plugin-lexical.png?downloads=true&downloadRank=true&stars=true

Alpha Software
This plugin is in active development. Contributions in the form of bug reports, feature suggestions, and pull requests are highly encouraged!

Installation

  1. Install the plugin:

    npm install strapi-plugin-lexical
  2. Enable the plugin:

    // ./config/plugins.js
    {
     lexical: {
       enabled: true,
     },
    

}; ```

  1. Include the required CSS and Prism.js in your Strapi admin:

    // ./src/admin/app.js
    import "strapi-plugin-lexical/dist/style.css";
    import "prismjs";
  2. Add Vite support for Prism.js:

    • Install the plugin:

      npm install --save-dev vite-plugin-prismjs
    • Update your Vite configuration:

      // ./src/admin/vite.config.js
      import { mergeConfig } from "vite";
      import prismjs from "vite-plugin-prismjs";
      
      export default (config) =>
        mergeConfig(config, {
          plugins: [
            prismjs({
              languages: "all", // Load all languages or customize as needed
            }),
          ],
        });

    Note: Prism.js is required even if you don't plan to support code blocks. If you find a workaround to avoid this, please share it with us via a pull request or issue. We happily skip this installation step if we can!

Usage

  • A new Lexical custom field type will be available in the Strapi content-type builder.
  • Currently, it supports features migrated from the Lexical playground.
  • For rendering content on your frontend, consider using libraries like payload-lexical-react-renderer or similar tools.

Handling Media and Internal Links

This plugin ensures reliable rendering of images and internal links by maintaining relationships between rich text content and referenced entities. By using a regular media field and automatically generated or your own link components we can ensure that all referenced media and internal links are readily available for your frontend, always reflecting the latest data.

Important: This is readme section is WIP. We will update this soon and give better examples on how to query and render images and links

Opt-in Mechanism

To enable this feature, you have to create secondary fields:

  • With the suffix Media (e.G. YourFieldNameMedia): This field must be a multiple media field with editing disabled.
  • With the suffix Links (e.G. YourFieldNameLinks): This must be a component, either use our pregenerated Links component or build your own. Important: It should only contain relation fields and the field name must match the linked collection name.

Integration in Lexical documents

Media is stored as a custom Lexical nodes, while store relations to strapi content with a custom URL format for links. These are automatically parsed and extracted into the fields you created above.

Media References
  • Images are stored as strapi-image node in Lexical.
  • Other file types are planned but not yet supported.
  • The structure is rather simple, as you can see:

strapi-image Lexical Node Data Structure:

{ "documentId": "id_of_media_asset" }
Internal Links
  • Internal links are stored using the regular link node in Lexical.

  • The URL follows the format:
    strapi://collectionName/documentId

    This ensures that even if a page’s slug changes, links remain valid.

Rendering Strapi media and links

There are two options:

Fetch while rendering

@todo

Benefit: Less code, multiple API calls while rendering

  • adjust the rendering functions of each lexical node
  • actually.. can it even be asnyc? double check... this is the not recommended way anyways...

Example: Fetching the Latest API Data for a link

const [collectionName, documentId] = linkNode.url.replace("strapi://", "").split("/");
const articles = client.collection(collectionName);
const singleArticle = await articles.findOne(documentId);
// your link generation logic here ...
return `/${singleArticle.locale}/blog/${singleArticle.slug}`
Prefetch and inject for rendering

@todo

To render media and links, we have to query the data from our media field and fields within the link component, then inject the data into our rendering process.

Benefit: only one API call, more control

  • fetch fields
  • iterate through the lexical document
  • inject the document from the strapi api response into the lexical node for later rendering
  • the data is now available when rendering the lexical node in your renderer

Example Renderer with NextJS:

// LexicalRenderer.tsx
import Image from "next/image";
import Link from "next/link";

import type { Media_Plain } from "@strapi/common/schemas-to-ts/Media";
import { Links_Plain } from "@strapi/components/links/interfaces/Links";

import React from "react";

import clsx from "clsx/lite";

import { createPath } from "@/utils/paths";

import type {
  AbstractNode,
  ElementRenderer,
  ElementRenderers,
  Node,
  PayloadLexicalReactRendererContent,
} from "lexical-renderer-atelier-disko";
import {
  defaultElementRenderers,
  PayloadLexicalReactRenderer,
} from "lexical-renderer-atelier-disko";

type StrapiImageNode = {
  documentId: string;
  entity: Media_Plain;
} & AbstractNode<"strapi-image">;

type NodeAll = Node | StrapiImageNode;

const elementRenderers: ElementRenderers & {
  "strapi-image": ElementRenderer<StrapiImageNode>;
} = {
  ...defaultElementRenderers,
  // Define your custom lexical nodes here
  link: (element, children, parent, className) => (
    <Link
      href={element.url}
      className={className}
      target={element.newTab ? "_blank" : "_self"}
    >
      {children}
    </Link>
  ),
  "strapi-image": (element, children, parent, className) => (
    <Image
      className={clsx("mx-auto", className)}
      src={`${process.env.NEXT_PUBLIC_IMAGE_BASE_URL}${element.entity.url}`}
      alt={element.entity.alternativeText}
      width={Math.floor(element.entity.width / 2)}
      height={Math.floor(element.entity.height / 2)}
      sizes={`(max-width: 768px) 100vw, ${Math.floor(
        (element.entity.width / 2) * 1.25
      )}px`}
      loading="lazy"
    />
  ),
};

export function LexicalRenderer({
  children,
  classNames,
  media,
  links,
}: {
  children: PayloadLexicalReactRendererContent;
  classNames?: { [key: string]: string };
  media?: Media_Plain[];
  links?: Links_Plain;
}) {
  // Inject media and links into our lexical document
  const injectedDocument = React.useMemo(() => {
    if (!children) {
      return null;
    }

    if (media || links) {
      const injectStrapiEntities = (nodes: NodeAll[]) => {
        for (const node of nodes) {
          // Media (Images only for now)
          if (node.type === "strapi-image" && media?.length) {
            const foundMedia = media.find(
              // @ts-expect-error documentId is there. the ts schema plugin is just outdated :(
              ({ documentId }) => documentId === node.documentId
            );
            if (foundMedia) {
              node.entity = foundMedia;
            }
          }

          // Links
          if (
            node.type === "link" &&
            links &&
            node.url.indexOf("strapi://") === 0
          ) {
            // Extract info from strapi link
            const [collectionName, linkDocumentId] = (node.url as string)
              .replace("strapi://", "")
              .split("/") as [keyof Links_Plain, string];
            if (links[collectionName]) {
              // Find linked document
              const foundCollectionDocument = links[collectionName].find(
                ({ documentId }) => documentId === linkDocumentId
              );
              if (foundCollectionDocument) {
                // Generate page link with helper function
                node.url = createPath(
                  collectionName,
                  foundCollectionDocument.locale,
                  foundCollectionDocument.slug
                );
              }
            }
          }
          if (node.type !== "strapi-image" && node.children) {
            injectStrapiEntities(node.children);
          }
        }
      };

      injectStrapiEntities(children.root.children);
    }

    return children;
  }, [children, media, links]);

  if (!children || !injectedDocument) {
    return null;
  }

  return (
    <PayloadLexicalReactRenderer
      content={injectedDocument}
      classNames={classNames}
      elementRenderers={elementRenderers}
    />
  );
}

export const lexicalToPlaintext = (json: { root: Node }) => {
  const traverse = (node: Node): string => {
    if (node.type === "text" && node.text) return node.text;
    if (node.children) return node.children.map(traverse).join(" ");
    return "";
  };
  return traverse(json.root);
};

Roadmap

v0 - Alpha

  • [x] Implement basic functionality.
  • [x] Port features from the Lexical playground as the initial foundation.
  • [x] Integrate Strapi Media Assets and enable linking to Strapi Collection Entries
  • [ ] Create field presets:
    • Simple, Complex, and Full (selectable during field setup).
  • [ ] Gather community feedback.
  • [ ] Look for a potential co-maintainer.

v1 - Stable

  • Introduce plugin-based architecture:
    • Allow users to extend functionality with their own plugins.
  • Enable configuration of presets via plugin settings.
  • Open to community ideas! Submit an issue.

Contributing

We welcome contributions! Here’s how you can help:

  • Report bugs or suggest features via the issue tracker.
  • Submit pull requests to improve functionality or documentation.
  • Share your feedback and ideas to shape the plugin’s future.

Resources


🛠️ Sponsored by hashbite.net | support & custom development available

We welcome everyone to post issues, fork the project, and contribute via pull requests. Together we can make this a better tool for all of us!

If the contribution process feels too slow or complex for your needs, hashbite.net can quickly implement features, fix bugs, or develop custom variations of this plugin on a paid basis. Just reach out through their website for direct support.