@smartsuite-cms/emagazine-sdk 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +444 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# 📖 Hướng Dẫn Sử Dụng E-Magazine SDK
|
|
2
|
+
|
|
3
|
+
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**.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📦 Cài Đặt
|
|
8
|
+
|
|
9
|
+
### Cách 1: Cài từ npm (khuyến nghị)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @smartsuite-cms/emagazine-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Cách 2: Cài trực tiếp từ thư mục local
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Trong project khác, trỏ đến thư mục sdk
|
|
19
|
+
npm install ../path/to/CMS/sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Cách 3: Sao chép thư mục `dist/` vào project
|
|
23
|
+
|
|
24
|
+
Copy thư mục `sdk/dist/` và file `sdk/package.json` sang project đích, rồi import trực tiếp.
|
|
25
|
+
|
|
26
|
+
### Build từ source (dành cho contributor)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd sdk
|
|
30
|
+
npm install
|
|
31
|
+
npm run build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Publish lên npm registry
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd sdk
|
|
38
|
+
npm publish --access public
|
|
39
|
+
# hoặc private:
|
|
40
|
+
# npm publish --access restricted
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## ⚡ Khởi Tạo
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { EmagazineSDK } from '@smartsuite-cms/emagazine-sdk'
|
|
49
|
+
|
|
50
|
+
const sdk = new EmagazineSDK({
|
|
51
|
+
supabaseUrl: 'YOUR_SUPABASE_URL',
|
|
52
|
+
supabaseAnonKey: 'YOUR_SUPABASE_ANON_KEY',
|
|
53
|
+
cmsBaseUrl: 'YOUR_CMS_BASE_URL', // URL CMS để tạo link preview
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Thuộc tính | Bắt buộc | Mô tả |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `supabaseUrl` | ✅ | URL Supabase project của CMS |
|
|
60
|
+
| `supabaseAnonKey` | ✅ | Anon key của Supabase project |
|
|
61
|
+
| `cmsBaseUrl` | ❌ | URL gốc CMS (dùng tạo link preview). Mặc định tự tính từ `supabaseUrl` |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 📋 API Reference
|
|
66
|
+
|
|
67
|
+
### 1. Lấy danh sách bài đăng
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const result = await sdk.listEmagazines({
|
|
71
|
+
status: 'published', // 'draft' | 'pending' | 'published' | 'archived' | 'all'
|
|
72
|
+
page: 1,
|
|
73
|
+
limit: 10,
|
|
74
|
+
orderBy: 'published_at', // 'published_at' | 'created_at' | 'title' | 'views_count'
|
|
75
|
+
orderDirection: 'desc', // 'asc' | 'desc'
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
console.log(result.data) // Mảng bài viết
|
|
79
|
+
console.log(result.total) // Tổng số bài viết
|
|
80
|
+
console.log(result.totalPages) // Tổng số trang
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Các tùy chọn lọc
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Lọc theo danh mục
|
|
87
|
+
const result = await sdk.listEmagazines({
|
|
88
|
+
categoryId: 'uuid-of-category',
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// Chỉ lấy bài nổi bật
|
|
92
|
+
const featured = await sdk.listEmagazines({
|
|
93
|
+
featured: true,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Tìm kiếm theo tiêu đề
|
|
97
|
+
const searched = await sdk.listEmagazines({
|
|
98
|
+
search: 'từ khóa tìm kiếm',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Kết hợp nhiều bộ lọc
|
|
102
|
+
const filtered = await sdk.listEmagazines({
|
|
103
|
+
status: 'published',
|
|
104
|
+
featured: true,
|
|
105
|
+
search: 'công nghệ',
|
|
106
|
+
page: 1,
|
|
107
|
+
limit: 5,
|
|
108
|
+
orderBy: 'views_count',
|
|
109
|
+
orderDirection: 'desc',
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Cấu trúc dữ liệu trả về (`EmagazineItem`)
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
interface EmagazineItem {
|
|
117
|
+
id: string
|
|
118
|
+
title: string
|
|
119
|
+
slug: string
|
|
120
|
+
summary: string | null
|
|
121
|
+
thumbnail_url: string | null
|
|
122
|
+
status: 'draft' | 'pending' | 'published' | 'archived' | 'trash'
|
|
123
|
+
editor_type: 'grapes' | 'custom'
|
|
124
|
+
seo_title: string | null
|
|
125
|
+
seo_description: string | null
|
|
126
|
+
views_count: number
|
|
127
|
+
is_featured: boolean
|
|
128
|
+
created_at: string
|
|
129
|
+
updated_at: string
|
|
130
|
+
published_at: string | null
|
|
131
|
+
author: { full_name: string; avatar_url: string | null } | null
|
|
132
|
+
category: { name: string } | null
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### 2. Lấy chi tiết bài viết theo ID
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const article = await sdk.getEmagazineById('article-uuid-here')
|
|
142
|
+
|
|
143
|
+
if (article) {
|
|
144
|
+
console.log(article.title) // Tiêu đề
|
|
145
|
+
console.log(article.preview_url) // Link preview trên CMS
|
|
146
|
+
console.log(article.html_content) // Nội dung HTML
|
|
147
|
+
console.log(article.css_content) // CSS đi kèm
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 3. Lấy chi tiết bài viết theo Slug
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const article = await sdk.getEmagazineBySlug('bai-viet-dau-tien')
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Cấu trúc dữ liệu trả về (`EmagazineDetail`)
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
interface EmagazineDetail {
|
|
161
|
+
id: string
|
|
162
|
+
title: string
|
|
163
|
+
slug: string
|
|
164
|
+
summary: string | null
|
|
165
|
+
html_content: string | null // ⭐ Nội dung HTML
|
|
166
|
+
css_content: string | null // ⭐ CSS đi kèm
|
|
167
|
+
preview_url: string // ⭐ Link preview CMS
|
|
168
|
+
thumbnail_url: string | null
|
|
169
|
+
status: string
|
|
170
|
+
editor_type: 'grapes' | 'custom'
|
|
171
|
+
seo_title: string | null
|
|
172
|
+
seo_description: string | null
|
|
173
|
+
views_count: number
|
|
174
|
+
is_featured: boolean
|
|
175
|
+
created_at: string
|
|
176
|
+
updated_at: string
|
|
177
|
+
published_at: string | null
|
|
178
|
+
author: { full_name: string; avatar_url: string | null } | null
|
|
179
|
+
category: { name: string } | null
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### 4. Lấy danh sách danh mục
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const categories = await sdk.listCategories()
|
|
189
|
+
// [{ id: '...', name: 'Technology', slug: 'technology', description: '...' }]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### 5. Hiển thị bài viết lên DOM (Browser)
|
|
195
|
+
|
|
196
|
+
Sử dụng method `renderToElement()` để inject HTML + CSS vào một element:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const article = await sdk.getEmagazineById('article-uuid')
|
|
200
|
+
if (!article) return
|
|
201
|
+
|
|
202
|
+
const container = document.getElementById('article-container')!
|
|
203
|
+
const cleanup = sdk.renderToElement(article, container)
|
|
204
|
+
|
|
205
|
+
// Khi không cần nữa (ví dụ chuyển trang), gọi cleanup:
|
|
206
|
+
cleanup()
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
> **Lưu ý:** Method này sẽ tự động inject `<style>` vào `<head>` và trả về hàm cleanup để xóa khi unmount.
|
|
210
|
+
|
|
211
|
+
#### Ví dụ trong React:
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { useEffect, useRef, useState } from 'react'
|
|
215
|
+
import { EmagazineSDK } from '@smartsuite-cms/emagazine-sdk'
|
|
216
|
+
|
|
217
|
+
const sdk = new EmagazineSDK({
|
|
218
|
+
supabaseUrl: 'YOUR_SUPABASE_URL',
|
|
219
|
+
supabaseAnonKey: 'YOUR_SUPABASE_ANON_KEY',
|
|
220
|
+
cmsBaseUrl: 'YOUR_CMS_BASE_URL',
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
function ArticleViewer({ articleId }: { articleId: string }) {
|
|
224
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
225
|
+
const [loading, setLoading] = useState(true)
|
|
226
|
+
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
let cleanup: (() => void) | undefined
|
|
229
|
+
|
|
230
|
+
const load = async () => {
|
|
231
|
+
setLoading(true)
|
|
232
|
+
const article = await sdk.getEmagazineById(articleId)
|
|
233
|
+
if (article && containerRef.current) {
|
|
234
|
+
cleanup = sdk.renderToElement(article, containerRef.current)
|
|
235
|
+
}
|
|
236
|
+
setLoading(false)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
load()
|
|
240
|
+
return () => cleanup?.()
|
|
241
|
+
}, [articleId])
|
|
242
|
+
|
|
243
|
+
if (loading) return <div>Đang tải...</div>
|
|
244
|
+
return <div ref={containerRef} />
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### 6. Tạo HTML Page hoàn chỉnh
|
|
251
|
+
|
|
252
|
+
Tạo chuỗi HTML đầy đủ (bao gồm `<html>`, `<head>`, `<body>`) để dùng cho iframe hoặc server-side rendering:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const article = await sdk.getEmagazineById('article-uuid')
|
|
256
|
+
if (article) {
|
|
257
|
+
const fullHTML = sdk.generateFullPageHTML(article)
|
|
258
|
+
|
|
259
|
+
// Hiển thị trong iframe
|
|
260
|
+
const iframe = document.getElementById('preview-frame') as HTMLIFrameElement
|
|
261
|
+
iframe.srcdoc = fullHTML
|
|
262
|
+
|
|
263
|
+
// Hoặc ghi ra file (server-side)
|
|
264
|
+
// fs.writeFileSync('output.html', fullHTML)
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 🔧 Ví Dụ Hoàn Chỉnh
|
|
271
|
+
|
|
272
|
+
### Vanilla JavaScript
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<!DOCTYPE html>
|
|
276
|
+
<html lang="vi">
|
|
277
|
+
<head>
|
|
278
|
+
<meta charset="UTF-8">
|
|
279
|
+
<title>E-Magazine Reader</title>
|
|
280
|
+
<style>
|
|
281
|
+
body { font-family: 'Segoe UI', sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
282
|
+
.article-card { border: 1px solid #eee; border-radius: 12px; padding: 16px; margin: 12px 0; cursor: pointer; transition: box-shadow 0.3s; }
|
|
283
|
+
.article-card:hover { box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
|
|
284
|
+
.article-card img { width: 100%; height: 200px; object-fit: cover; border-radius: 8px; }
|
|
285
|
+
.article-card h2 { margin: 12px 0 8px; }
|
|
286
|
+
.article-card p { color: #666; }
|
|
287
|
+
</style>
|
|
288
|
+
</head>
|
|
289
|
+
<body>
|
|
290
|
+
<h1>📰 E-Magazine</h1>
|
|
291
|
+
<div id="articles-list"></div>
|
|
292
|
+
<div id="article-detail" style="display:none"></div>
|
|
293
|
+
|
|
294
|
+
<script type="module">
|
|
295
|
+
import { EmagazineSDK } from './path-to-sdk/dist/index.mjs'
|
|
296
|
+
|
|
297
|
+
const sdk = new EmagazineSDK({
|
|
298
|
+
supabaseUrl: 'YOUR_SUPABASE_URL',
|
|
299
|
+
supabaseAnonKey: 'YOUR_SUPABASE_ANON_KEY',
|
|
300
|
+
cmsBaseUrl: 'YOUR_CMS_BASE_URL',
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
// Hiển thị danh sách bài viết
|
|
304
|
+
const { data: articles, total } = await sdk.listEmagazines({
|
|
305
|
+
status: 'published',
|
|
306
|
+
limit: 10,
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
const list = document.getElementById('articles-list')
|
|
310
|
+
articles.forEach(article => {
|
|
311
|
+
const card = document.createElement('div')
|
|
312
|
+
card.className = 'article-card'
|
|
313
|
+
card.innerHTML = `
|
|
314
|
+
${article.thumbnail_url ? `<img src="${article.thumbnail_url}" alt="${article.title}">` : ''}
|
|
315
|
+
<h2>${article.title}</h2>
|
|
316
|
+
<p>${article.summary || ''}</p>
|
|
317
|
+
<small>👤 ${article.author?.full_name || 'Ẩn danh'} · 📁 ${article.category?.name || ''}</small>
|
|
318
|
+
`
|
|
319
|
+
card.onclick = () => loadArticle(article.id)
|
|
320
|
+
list.appendChild(card)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
// Xem chi tiết bài viết
|
|
324
|
+
async function loadArticle(id) {
|
|
325
|
+
const detail = await sdk.getEmagazineById(id)
|
|
326
|
+
if (!detail) return
|
|
327
|
+
|
|
328
|
+
const container = document.getElementById('article-detail')
|
|
329
|
+
container.style.display = 'block'
|
|
330
|
+
document.getElementById('articles-list').style.display = 'none'
|
|
331
|
+
|
|
332
|
+
sdk.renderToElement(detail, container)
|
|
333
|
+
}
|
|
334
|
+
</script>
|
|
335
|
+
</body>
|
|
336
|
+
</html>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Next.js (Server Component)
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
// app/magazine/page.tsx
|
|
343
|
+
import { EmagazineSDK } from '@smartsuite-cms/emagazine-sdk'
|
|
344
|
+
|
|
345
|
+
const sdk = new EmagazineSDK({
|
|
346
|
+
supabaseUrl: process.env.SUPABASE_URL!,
|
|
347
|
+
supabaseAnonKey: process.env.SUPABASE_ANON_KEY!,
|
|
348
|
+
cmsBaseUrl: process.env.CMS_BASE_URL,
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
export default async function MagazinePage() {
|
|
352
|
+
const { data: articles } = await sdk.listEmagazines({
|
|
353
|
+
status: 'published',
|
|
354
|
+
limit: 12,
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<div>
|
|
359
|
+
<h1>E-Magazine</h1>
|
|
360
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 20 }}>
|
|
361
|
+
{articles.map(article => (
|
|
362
|
+
<a key={article.id} href={`/magazine/${article.slug}`}>
|
|
363
|
+
{article.thumbnail_url && <img src={article.thumbnail_url} alt={article.title} />}
|
|
364
|
+
<h2>{article.title}</h2>
|
|
365
|
+
<p>{article.summary}</p>
|
|
366
|
+
</a>
|
|
367
|
+
))}
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
// app/magazine/[slug]/page.tsx
|
|
376
|
+
import { EmagazineSDK } from '@smartsuite-cms/emagazine-sdk'
|
|
377
|
+
|
|
378
|
+
const sdk = new EmagazineSDK({
|
|
379
|
+
supabaseUrl: process.env.SUPABASE_URL!,
|
|
380
|
+
supabaseAnonKey: process.env.SUPABASE_ANON_KEY!,
|
|
381
|
+
cmsBaseUrl: process.env.CMS_BASE_URL,
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
export default async function ArticlePage({ params }: { params: { slug: string } }) {
|
|
385
|
+
const article = await sdk.getEmagazineBySlug(params.slug)
|
|
386
|
+
if (!article) return <div>Không tìm thấy bài viết</div>
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<div>
|
|
390
|
+
<style dangerouslySetInnerHTML={{ __html: article.css_content || '' }} />
|
|
391
|
+
<div dangerouslySetInnerHTML={{ __html: article.html_content || '' }} />
|
|
392
|
+
</div>
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## ⚠️ Lưu Ý Quan Trọng
|
|
400
|
+
|
|
401
|
+
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`.
|
|
402
|
+
|
|
403
|
+
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.
|
|
404
|
+
|
|
405
|
+
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).
|
|
406
|
+
|
|
407
|
+
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.
|
|
408
|
+
|
|
409
|
+
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.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## 📂 Cấu Trúc SDK
|
|
414
|
+
|
|
415
|
+
```
|
|
416
|
+
sdk/
|
|
417
|
+
├── package.json
|
|
418
|
+
├── tsconfig.json
|
|
419
|
+
└── src/
|
|
420
|
+
├── index.ts # Entry point - export SDK class và types
|
|
421
|
+
├── client.ts # EmagazineSDK class chính
|
|
422
|
+
└── types.ts # TypeScript interfaces
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## 🔄 Build & Phát Triển
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
# Cài đặt dependencies
|
|
431
|
+
cd sdk
|
|
432
|
+
npm install
|
|
433
|
+
|
|
434
|
+
# Build production
|
|
435
|
+
npm run build
|
|
436
|
+
|
|
437
|
+
# Watch mode (dev)
|
|
438
|
+
npm run dev
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Sau khi build, output nằm trong `dist/`:
|
|
442
|
+
- `dist/index.js` — CommonJS
|
|
443
|
+
- `dist/index.mjs` — ES Module
|
|
444
|
+
- `dist/index.d.ts` — TypeScript declarations
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartsuite-cms/emagazine-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "SDK to load and display e-magazine articles from Smart Suite CMS",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -26,4 +26,4 @@
|
|
|
26
26
|
"tsup": "^8.0.0",
|
|
27
27
|
"typescript": "^5.5.0"
|
|
28
28
|
}
|
|
29
|
-
}
|
|
29
|
+
}
|