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

@smartsuite-cms/emagazine-sdk

v1.0.2

Published

SDK to load and display e-magazine articles from Smart Suite CMS

Readme

📖 Hướng Dẫn Sử Dụng E-Magazine SDK

SDK giúp các project khác truy xuất dữ liệu bài đăng e-magazine từ hệ thống Smart Suite CMS.


📦 Cài Đặt

Cách 1: Cài từ npm (khuyến nghị)

npm install @smartsuite-cms/emagazine-sdk

Cách 2: Cài trực tiếp từ thư mục local

# Trong project khác, trỏ đến thư mục sdk
npm install ../path/to/CMS/sdk

Cách 3: Sao chép thư mục dist/ vào project

Copy thư mục sdk/dist/ và file sdk/package.json sang project đích, rồi import trực tiếp.

Build từ source (dành cho contributor)

cd sdk
npm install
npm run build

Publish lên npm registry

cd sdk
npm publish --access public
# hoặc private:
# npm publish --access restricted

⚡ Khởi Tạo

import { EmagazineSDK } from '@smartsuite-cms/emagazine-sdk'

const sdk = new EmagazineSDK({
  supabaseUrl: 'YOUR_SUPABASE_URL',
  supabaseAnonKey: 'YOUR_SUPABASE_ANON_KEY',
  cmsBaseUrl: 'YOUR_CMS_BASE_URL', // URL CMS để tạo link preview
})

| Thuộc tính | Bắt buộc | Mô tả | |---|---|---| | supabaseUrl | ✅ | URL Supabase project của CMS | | supabaseAnonKey | ✅ | Anon key của Supabase project | | cmsBaseUrl | ❌ | URL gốc CMS (dùng tạo link preview). Mặc định tự tính từ supabaseUrl |


📋 API Reference

1. Lấy danh sách bài đăng

const result = await sdk.listEmagazines({
  status: 'published',  // 'draft' | 'pending' | 'published' | 'archived' | 'all'
  page: 1,
  limit: 10,
  orderBy: 'published_at', // 'published_at' | 'created_at' | 'title' | 'views_count'
  orderDirection: 'desc',  // 'asc' | 'desc'
})

console.log(result.data)       // Mảng bài viết
console.log(result.total)      // Tổng số bài viết
console.log(result.totalPages) // Tổng số trang

Các tùy chọn lọc

// Lọc theo danh mục
const result = await sdk.listEmagazines({
  categoryId: 'uuid-of-category',
})

// Chỉ lấy bài nổi bật
const featured = await sdk.listEmagazines({
  featured: true,
})

// Tìm kiếm theo tiêu đề
const searched = await sdk.listEmagazines({
  search: 'từ khóa tìm kiếm',
})

// Kết hợp nhiều bộ lọc
const filtered = await sdk.listEmagazines({
  status: 'published',
  featured: true,
  search: 'công nghệ',
  page: 1,
  limit: 5,
  orderBy: 'views_count',
  orderDirection: 'desc',
})

Cấu trúc dữ liệu trả về (EmagazineItem)

interface EmagazineItem {
  id: string
  title: string
  slug: string
  summary: string | null
  thumbnail_url: string | null
  status: 'draft' | 'pending' | 'published' | 'archived' | 'trash'
  editor_type: 'grapes' | 'custom'
  seo_title: string | null
  seo_description: string | null
  views_count: number
  is_featured: boolean
  created_at: string
  updated_at: string
  published_at: string | null
  author: { full_name: string; avatar_url: string | null } | null
  category: { name: string } | null
}

2. Lấy chi tiết bài viết theo ID

const article = await sdk.getEmagazineById('article-uuid-here')

if (article) {
  console.log(article.title)        // Tiêu đề
  console.log(article.preview_url)  // Link preview trên CMS
  console.log(article.html_content) // Nội dung HTML
  console.log(article.css_content)  // CSS đi kèm
}

3. Lấy chi tiết bài viết theo Slug

const article = await sdk.getEmagazineBySlug('bai-viet-dau-tien')

Cấu trúc dữ liệu trả về (EmagazineDetail)

interface EmagazineDetail {
  id: string
  title: string
  slug: string
  summary: string | null
  html_content: string | null   // ⭐ Nội dung HTML
  css_content: string | null    // ⭐ CSS đi kèm
  preview_url: string           // ⭐ Link preview CMS
  thumbnail_url: string | null
  status: string
  editor_type: 'grapes' | 'custom'
  seo_title: string | null
  seo_description: string | null
  views_count: number
  is_featured: boolean
  created_at: string
  updated_at: string
  published_at: string | null
  author: { full_name: string; avatar_url: string | null } | null
  category: { name: string } | null
}

4. Lấy danh sách danh mục

const categories = await sdk.listCategories()
// [{ id: '...', name: 'Technology', slug: 'technology', description: '...' }]

5. Hiển thị bài viết lên DOM (Browser)

Sử dụng method renderToElement() để inject HTML + CSS vào một element:

const article = await sdk.getEmagazineById('article-uuid')
if (!article) return

const container = document.getElementById('article-container')!
const cleanup = sdk.renderToElement(article, container)

// Khi không cần nữa (ví dụ chuyển trang), gọi cleanup:
cleanup()

Lưu ý: Method này sẽ tự động inject <style> vào <head> và trả về hàm cleanup để xóa khi unmount.

Ví dụ trong React:

import { useEffect, useRef, useState } from 'react'
import { EmagazineSDK } from '@smartsuite-cms/emagazine-sdk'

const sdk = new EmagazineSDK({
  supabaseUrl: 'YOUR_SUPABASE_URL',
  supabaseAnonKey: 'YOUR_SUPABASE_ANON_KEY',
  cmsBaseUrl: 'YOUR_CMS_BASE_URL',
})

function ArticleViewer({ articleId }: { articleId: string }) {
  const containerRef = useRef<HTMLDivElement>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    let cleanup: (() => void) | undefined

    const load = async () => {
      setLoading(true)
      const article = await sdk.getEmagazineById(articleId)
      if (article && containerRef.current) {
        cleanup = sdk.renderToElement(article, containerRef.current)
      }
      setLoading(false)
    }

    load()
    return () => cleanup?.()
  }, [articleId])

  if (loading) return <div>Đang tải...</div>
  return <div ref={containerRef} />
}

6. Tạo HTML Page hoàn chỉnh

Tạo chuỗi HTML đầy đủ (bao gồm <html>, <head>, <body>) để dùng cho iframe hoặc server-side rendering:

const article = await sdk.getEmagazineById('article-uuid')
if (article) {
  const fullHTML = sdk.generateFullPageHTML(article)

  // Hiển thị trong iframe
  const iframe = document.getElementById('preview-frame') as HTMLIFrameElement
  iframe.srcdoc = fullHTML

  // Hoặc ghi ra file (server-side)
  // fs.writeFileSync('output.html', fullHTML)
}

🔧 Ví Dụ Hoàn Chỉnh

Vanilla JavaScript

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <title>E-Magazine Reader</title>
  <style>
    body { font-family: 'Segoe UI', sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
    .article-card { border: 1px solid #eee; border-radius: 12px; padding: 16px; margin: 12px 0; cursor: pointer; transition: box-shadow 0.3s; }
    .article-card:hover { box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
    .article-card img { width: 100%; height: 200px; object-fit: cover; border-radius: 8px; }
    .article-card h2 { margin: 12px 0 8px; }
    .article-card p { color: #666; }
  </style>
</head>
<body>
  <h1>📰 E-Magazine</h1>
  <div id="articles-list"></div>
  <div id="article-detail" style="display:none"></div>

  <script type="module">
    import { EmagazineSDK } from './path-to-sdk/dist/index.mjs'

    const sdk = new EmagazineSDK({
      supabaseUrl: 'YOUR_SUPABASE_URL',
      supabaseAnonKey: 'YOUR_SUPABASE_ANON_KEY',
      cmsBaseUrl: 'YOUR_CMS_BASE_URL',
    })

    // Hiển thị danh sách bài viết
    const { data: articles, total } = await sdk.listEmagazines({
      status: 'published',
      limit: 10,
    })

    const list = document.getElementById('articles-list')
    articles.forEach(article => {
      const card = document.createElement('div')
      card.className = 'article-card'
      card.innerHTML = `
        ${article.thumbnail_url ? `<img src="${article.thumbnail_url}" alt="${article.title}">` : ''}
        <h2>${article.title}</h2>
        <p>${article.summary || ''}</p>
        <small>👤 ${article.author?.full_name || 'Ẩn danh'} · 📁 ${article.category?.name || ''}</small>
      `
      card.onclick = () => loadArticle(article.id)
      list.appendChild(card)
    })

    // Xem chi tiết bài viết
    async function loadArticle(id) {
      const detail = await sdk.getEmagazineById(id)
      if (!detail) return

      const container = document.getElementById('article-detail')
      container.style.display = 'block'
      document.getElementById('articles-list').style.display = 'none'

      sdk.renderToElement(detail, container)
    }
  </script>
</body>
</html>

Next.js (Server Component)

// app/magazine/page.tsx
import { EmagazineSDK } from '@smartsuite-cms/emagazine-sdk'

const sdk = new EmagazineSDK({
  supabaseUrl: process.env.SUPABASE_URL!,
  supabaseAnonKey: process.env.SUPABASE_ANON_KEY!,
  cmsBaseUrl: process.env.CMS_BASE_URL,
})

export default async function MagazinePage() {
  const { data: articles } = await sdk.listEmagazines({
    status: 'published',
    limit: 12,
  })

  return (
    <div>
      <h1>E-Magazine</h1>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 20 }}>
        {articles.map(article => (
          <a key={article.id} href={`/magazine/${article.slug}`}>
            {article.thumbnail_url && <img src={article.thumbnail_url} alt={article.title} />}
            <h2>{article.title}</h2>
            <p>{article.summary}</p>
          </a>
        ))}
      </div>
    </div>
  )
}
// app/magazine/[slug]/page.tsx
import { EmagazineSDK } from '@smartsuite-cms/emagazine-sdk'

const sdk = new EmagazineSDK({
  supabaseUrl: process.env.SUPABASE_URL!,
  supabaseAnonKey: process.env.SUPABASE_ANON_KEY!,
  cmsBaseUrl: process.env.CMS_BASE_URL,
})

export default async function ArticlePage({ params }: { params: { slug: string } }) {
  const article = await sdk.getEmagazineBySlug(params.slug)
  if (!article) return <div>Không tìm thấy bài viết</div>

  return (
    <div>
      <style dangerouslySetInnerHTML={{ __html: article.css_content || '' }} />
      <div dangerouslySetInnerHTML={{ __html: article.html_content || '' }} />
    </div>
  )
}

⚠️ Lưu Ý Quan Trọng

  1. RLS (Row Level Security): SDK sử dụng anon key nên chỉ truy xuất được dữ liệu mà RLS policies cho phép. Nếu cần access tất cả, bạn cần tạo thêm RLS policy cho phép đọc public các bài published.

  2. CORS: Nếu gọi từ browser khác domain, đảm bảo Supabase project cho phép domain của bạn trong CORS settings.

  3. Performance: SDK trả về dữ liệu phân trang (pagination). Hãy sử dụng limit hợp lý (khuyến nghị 10-20 item/trang).

  4. CSS Isolation: Khi render html_content + css_content, CSS có thể ảnh hưởng đến layout hiện tại. Nên wrap trong một container có class riêng hoặc sử dụng iframe với generateFullPageHTML() để cách ly hoàn toàn.

  5. preview_url: Link preview trỏ đến trang CMS theo format /corporate/e-magazine/preview/{id}. Cần đảm bảo CMS đang chạy và route này accessible.


📂 Cấu Trúc SDK

sdk/
├── package.json
├── tsconfig.json
└── src/
    ├── index.ts       # Entry point - export SDK class và types
    ├── client.ts      # EmagazineSDK class chính
    └── types.ts       # TypeScript interfaces

🔄 Build & Phát Triển

# Cài đặt dependencies
cd sdk
npm install

# Build production
npm run build

# Watch mode (dev)
npm run dev

Sau khi build, output nằm trong dist/:

  • dist/index.js — CommonJS
  • dist/index.mjs — ES Module
  • dist/index.d.ts — TypeScript declarations