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

@thangph2146/nextjs-editor

v1.0.10

Published

Rich text editor for Next.js built on Lexical

Readme

@thangph2146/nextjs-editor

Rich text editor cho Next.js, xây trên Lexical.

Cài đặt

pnpm add @thangph2146/nextjs-editor
# hoặc
npm i @thangph2146/nextjs-editor
yarn add @thangph2146/nextjs-editor

Yêu cầu

  • React 18+
  • Next.js 14+ (cho tính năng ảnh dùng next/image / next/dynamic)

Sử dụng

1. Import CSS (bắt buộc)

Trong app/layout.tsx hoặc _app.tsx:

import "@thangph2146/nextjs-editor/styles.css"

Lưu ý: Chỉ cần import file trên. Bạn không cần thêm @source hay cấu hình nào trong globals.css. Style editor được đóng gói sẵn, scope trong #editor-xkhông bọc trong @layer (unlayered). Để style editor không bị SCSS app ghi đè, nên import styles.css sau cùng (sau globals và SCSS của app).

2. Dùng component Editor (Client Component)

"use client"

import { Editor } from "@thangph2146/nextjs-editor"
import type { SerializedEditorState } from "@thangph2146/nextjs-editor"

export function MyEditor() {
  const [state, setState] = useState<SerializedEditorState | undefined>()

  return (
    <Editor
      editorSerializedState={state}
      onSerializedChange={setState}
      readOnly={false}
    />
  )
}

Props

| Prop | Type | Mô tả | |------|------|--------| | editorState | EditorState | Trạng thái Lexical (live instance) | | editorSerializedState | SerializedEditorState | Trạng thái đã serialize (JSON) | | onChange | (state: EditorState) => void | Gọi khi nội dung thay đổi (live state) | | onSerializedChange | (state: SerializedEditorState) => void | Gọi khi nội dung thay đổi (JSON, dễ lưu DB) | | readOnly | boolean | Chế độ chỉ đọc |

Ghi chú

  • Ảnh: Mặc định dialog chèn ảnh có tab URLFile. Để tab Thư viện hiển thị ảnh từ API của project, xem Cấu hình API thư viện ảnh bên dưới.
  • Next.js: Package dùng next/imagenext/dynamic khi chạy trong Next.js; cần cài next trong project.

Tránh SCSS/CSS của app ghi đè editor

Style editor được build không bọc trong @layer (unlayered). Nên import @thangph2146/nextjs-editor/styles.css sau cùng trong layout.tsx (sau globals và SCSS của app) để style editor có thứ tự load sau và dễ thắng khi specificity ngang nhau. App có thể ghi đè style editor nếu dùng selector cùng hoặc cao hơn specificity (vd. trùng #editor-x hoặc class trong scope).

Mọi style global/SCSS không nằm trong Tailwind cần nằm trong @layer app. Lưu ý: Trong Sass, @use không được phép bên trong @layer, nên không bọc trực tiếp nội dung có @use trong một block @layer app { }.

Cách khuyến nghị — File wrapper dùng @import:

Tạo file SCSS mới (ví dụ src/styles/app-layer.scss) chỉ dùng @import (Sass cho phép @import trong @layer):

@layer app {
  @import "main";
}

Trong layout.tsx (hoặc nơi đang import file SCSS chính), đổi sang import file wrapper:

import "../styles/app-layer.scss";

Cách khác — File CSS import SCSS (nếu bundler hỗ trợ):

Tạo file src/app/globals-app.css:

@layer app {
  @import "../styles/main.scss";
}

Rồi trong layout.tsx import sau globals.css: import "./globals-app.css". (Một số bundler có thể không compile .scss khi import từ .css.)

Sau khi cấu hình (nếu dùng), style editor (đã load khi import @thangph2146/nextjs-editor/styles.css) có thứ tự sau SCSS trong @layer app; cấu trúc Tailwind và theme của editor trong #editor-x có thể bị app ghi đè nếu selector app có specificity cao hơn.

Cấu hình API thư viện ảnh

Tab Thư viện trong dialog chèn ảnh cần dữ liệu từ API/uploads của project. Bạn cấu hình bằng cách alias module uploads-hooks của package sang file hooks trong project.

Bước 1: Alias trong Next.js

Trong next.config.ts (hoặc next.config.js), thêm webpack.resolve.alias để trỏ @thangph2146/nextjs-editor/uploads-hooks sang file hooks uploads của project:

// next.config.ts
import type { NextConfig } from "next";
import path from "path";

const nextConfig: NextConfig = {
  // ... config khác
  webpack: (config) => {
    config.resolve ??= {};
    config.resolve.alias = {
      ...config.resolve.alias,
      "@thangph2146/nextjs-editor/uploads-hooks": path.resolve(
        __dirname,
        "src/features/uploads/hooks/use-uploads-queries.ts"  // đường dẫn tới file hooks của bạn
      ),
    };
    return config;
  },
};

export default nextConfig;

Đường dẫn file có thể khác tùy cấu trúc project (ví dụ src/lib/uploads-queries.ts).

Bước 2: File hooks phải export useImagesList

File được alias tới phải export hook useImagesList(page, limit) với đúng chuẩn:

  • Tham số: page: number, limit: number (ví dụ useImagesList(1, 100)).
  • Giá trị trả về: object tương thích với TanStack Query (useQuery), ít nhất:
    • data: response từ API, có dạng:
      • data.data.folderTree: cây thư mục ảnh (xem kiểu FolderNode bên dưới).
      • data.data.pagination: { page, limit, total, totalPages } (tùy chọn).
    • isLoading: boolean.

Kiểu dữ liệu (TypeScript):

interface ImageItem {
  fileName: string;
  originalName: string;
  size: number;
  mimeType: string;
  url: string;           // URL đầy đủ để hiển thị ảnh (img src)
  relativePath: string;
  createdAt: number;
}

interface FolderNode {
  name: string;
  path: string;
  images: ImageItem[];
  subfolders: FolderNode[];  // đệ quy
}

Ví dụ hook (gọi API):

// useImagesList gọi GET /api/uploads?page=1&limit=100 (hoặc endpoint của bạn)
export function useImagesList(page = 1, limit = 50) {
  return useQuery({
    queryKey: ["uploads", "images", page, limit],
    queryFn: async () => {
      const res = await fetch(`/api/uploads?page=${page}&limit=${limit}`);
      const json = await res.json();
      if (!json.success) throw new Error("Failed to fetch images");
      return json; // { success, data: { data: [], folderTree, pagination } }
    },
  });
}

Bước 3: API backend trả về folderTree

Endpoint list ảnh (ví dụ GET /api/uploads hoặc GET /api/admin/uploads) nên trả về JSON có folderTree — cây thư mục chứa ảnh, để editor hiển thị theo cấu trúc thư mục.

Ví dụ response:

{
  "success": true,
  "data": {
    "data": [],
    "folderTree": {
      "name": "images",
      "path": "images",
      "images": [
        {
          "fileName": "2024/01/15/abc.jpg",
          "originalName": "abc.jpg",
          "size": 12345,
          "mimeType": "image/jpeg",
          "url": "https://example.com/api/uploads/serve/images/2024/01/15/abc.jpg",
          "relativePath": "images/2024/01/15/abc.jpg",
          "createdAt": 1705312800000
        }
      ],
      "subfolders": [
        {
          "name": "2024",
          "path": "images/2024",
          "images": [],
          "subfolders": []
        }
      ]
    },
    "pagination": { "page": 1, "limit": 100, "total": 50, "totalPages": 1 }
  }
}
  • folderTree.url (hoặc mỗi ImageItem.url) phải là URL mà trình duyệt tải được (absolute hoặc same-origin).
  • Nếu không có alias, tab Thư viện sẽ hiển thị "Chưa có hình ảnh nào được upload" (stub mặc định).

Tóm tắt

| Việc | Mô tả | |------|--------| | Alias | @thangph2146/nextjs-editor/uploads-hooks → file hooks uploads của project (next.config webpack). | | Hook | File đó export useImagesList(page, limit) trả về { data, isLoading }, data.data.folderTree kiểu FolderNode. | | API | Backend trả về list ảnh kèm folderTree (cây thư mục + ảnh trong từng thư mục). |

Build từ source

cd nextjs-editor
pnpm install
pnpm build

Cập nhật thư viện lên npm

Khi đã sửa code và muốn publish phiên bản mới:

1. Tăng version trong package.json

Dùng Semantic Versioning:

  • Patch (sửa lỗi): 1.0.01.0.1
    npm version patch
  • Minor (tính năng mới, không phá tương thích): 1.0.01.1.0
    npm version minor
  • Major (thay đổi breaking): 1.0.02.0.0
    npm version major

Hoặc sửa tay trường "version" trong package.json.

2. Build lại

cd nextjs-editor
pnpm build

3. Publish lên npm

# Package scope → cần --access public (chỉ lần đầu hoặc nếu chưa set)
npm publish --access public

Nếu đã publish public trước đó, chỉ cần:

npm publish

4. (Tùy chọn) Commit & push tag

git add package.json
git commit -m "chore: bump version to x.y.z"
git push origin main
# Tag do npm version tạo (vd. v1.0.1):
git push origin v1.0.1

Người dùng cài phiên bản mới:

# Luôn lấy bản mới nhất trong dải version đã khai báo (^1.0.0)
pnpm update @thangph2146/nextjs-editor

# Hoặc cài đúng version cụ thể
pnpm add @thangph2146/[email protected]

License

MIT