@smartsuite-cms/emagazine-sdk 1.0.0

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.
@@ -0,0 +1,129 @@
1
+ /** Emagazine article item returned by the list endpoint */
2
+ interface EmagazineItem {
3
+ id: string;
4
+ title: string;
5
+ slug: string;
6
+ summary: string | null;
7
+ thumbnail_url: string | null;
8
+ status: 'draft' | 'pending' | 'published' | 'archived' | 'trash';
9
+ editor_type: 'grapes' | 'custom';
10
+ seo_title: string | null;
11
+ seo_description: string | null;
12
+ views_count: number;
13
+ is_featured: boolean;
14
+ created_at: string;
15
+ updated_at: string;
16
+ published_at: string | null;
17
+ author: {
18
+ full_name: string;
19
+ avatar_url: string | null;
20
+ } | null;
21
+ category: {
22
+ name: string;
23
+ } | null;
24
+ }
25
+ /** Detailed article data including HTML/CSS content */
26
+ interface EmagazineDetail {
27
+ id: string;
28
+ title: string;
29
+ slug: string;
30
+ summary: string | null;
31
+ html_content: string | null;
32
+ css_content: string | null;
33
+ thumbnail_url: string | null;
34
+ status: 'draft' | 'pending' | 'published' | 'archived' | 'trash';
35
+ editor_type: 'grapes' | 'custom';
36
+ seo_title: string | null;
37
+ seo_description: string | null;
38
+ views_count: number;
39
+ is_featured: boolean;
40
+ created_at: string;
41
+ updated_at: string;
42
+ published_at: string | null;
43
+ preview_url: string;
44
+ author: {
45
+ full_name: string;
46
+ avatar_url: string | null;
47
+ } | null;
48
+ category: {
49
+ name: string;
50
+ } | null;
51
+ }
52
+ /** Options for listing emagazines */
53
+ interface ListEmagazinesOptions {
54
+ /** Filter by status. Default: 'published' */
55
+ status?: 'draft' | 'pending' | 'published' | 'archived' | 'all';
56
+ /** Filter by category ID */
57
+ categoryId?: string;
58
+ /** Only return featured articles */
59
+ featured?: boolean;
60
+ /** Search by title (case-insensitive) */
61
+ search?: string;
62
+ /** Page number (1-based). Default: 1 */
63
+ page?: number;
64
+ /** Items per page. Default: 20, max: 100 */
65
+ limit?: number;
66
+ /** Sort field. Default: 'published_at' */
67
+ orderBy?: 'published_at' | 'created_at' | 'title' | 'views_count';
68
+ /** Sort direction. Default: 'desc' */
69
+ orderDirection?: 'asc' | 'desc';
70
+ }
71
+ /** Paginated list response */
72
+ interface PaginatedResponse<T> {
73
+ data: T[];
74
+ total: number;
75
+ page: number;
76
+ limit: number;
77
+ totalPages: number;
78
+ }
79
+ /** SDK configuration */
80
+ interface EmagazineSDKConfig {
81
+ /** Supabase project URL */
82
+ supabaseUrl: string;
83
+ /** Supabase anon key */
84
+ supabaseAnonKey: string;
85
+ /** CMS base URL for generating preview links. Default: same as supabaseUrl origin */
86
+ cmsBaseUrl?: string;
87
+ }
88
+
89
+ declare class EmagazineSDK {
90
+ private supabase;
91
+ private cmsBaseUrl;
92
+ constructor(config: EmagazineSDKConfig);
93
+ /**
94
+ * Load a paginated list of e-magazine articles.
95
+ * By default returns only published articles sorted by published_at desc.
96
+ */
97
+ listEmagazines(options?: ListEmagazinesOptions): Promise<PaginatedResponse<EmagazineItem>>;
98
+ /**
99
+ * Get a list of all available categories.
100
+ */
101
+ listCategories(): Promise<{
102
+ id: string;
103
+ name: string;
104
+ slug: string;
105
+ description: string | null;
106
+ }[]>;
107
+ /**
108
+ * Get detailed article data by ID, including preview_url, html_content, css_content.
109
+ */
110
+ getEmagazineById(id: string): Promise<EmagazineDetail | null>;
111
+ /**
112
+ * Get detailed article data by slug, including preview_url, html_content, css_content.
113
+ */
114
+ getEmagazineBySlug(slug: string): Promise<EmagazineDetail | null>;
115
+ /**
116
+ * Render an emagazine article into an HTML container element.
117
+ * Injects css_content as <style> and html_content into the container.
118
+ * Returns a cleanup function to remove injected styles.
119
+ */
120
+ renderToElement(detail: EmagazineDetail, container: HTMLElement): () => void;
121
+ /**
122
+ * Generate a full standalone HTML page string for an emagazine article.
123
+ * Useful for iframes or server-side rendering.
124
+ */
125
+ generateFullPageHTML(detail: EmagazineDetail): string;
126
+ private escapeHtml;
127
+ }
128
+
129
+ export { type EmagazineDetail, type EmagazineItem, EmagazineSDK, type EmagazineSDKConfig, type ListEmagazinesOptions, type PaginatedResponse };
@@ -0,0 +1,129 @@
1
+ /** Emagazine article item returned by the list endpoint */
2
+ interface EmagazineItem {
3
+ id: string;
4
+ title: string;
5
+ slug: string;
6
+ summary: string | null;
7
+ thumbnail_url: string | null;
8
+ status: 'draft' | 'pending' | 'published' | 'archived' | 'trash';
9
+ editor_type: 'grapes' | 'custom';
10
+ seo_title: string | null;
11
+ seo_description: string | null;
12
+ views_count: number;
13
+ is_featured: boolean;
14
+ created_at: string;
15
+ updated_at: string;
16
+ published_at: string | null;
17
+ author: {
18
+ full_name: string;
19
+ avatar_url: string | null;
20
+ } | null;
21
+ category: {
22
+ name: string;
23
+ } | null;
24
+ }
25
+ /** Detailed article data including HTML/CSS content */
26
+ interface EmagazineDetail {
27
+ id: string;
28
+ title: string;
29
+ slug: string;
30
+ summary: string | null;
31
+ html_content: string | null;
32
+ css_content: string | null;
33
+ thumbnail_url: string | null;
34
+ status: 'draft' | 'pending' | 'published' | 'archived' | 'trash';
35
+ editor_type: 'grapes' | 'custom';
36
+ seo_title: string | null;
37
+ seo_description: string | null;
38
+ views_count: number;
39
+ is_featured: boolean;
40
+ created_at: string;
41
+ updated_at: string;
42
+ published_at: string | null;
43
+ preview_url: string;
44
+ author: {
45
+ full_name: string;
46
+ avatar_url: string | null;
47
+ } | null;
48
+ category: {
49
+ name: string;
50
+ } | null;
51
+ }
52
+ /** Options for listing emagazines */
53
+ interface ListEmagazinesOptions {
54
+ /** Filter by status. Default: 'published' */
55
+ status?: 'draft' | 'pending' | 'published' | 'archived' | 'all';
56
+ /** Filter by category ID */
57
+ categoryId?: string;
58
+ /** Only return featured articles */
59
+ featured?: boolean;
60
+ /** Search by title (case-insensitive) */
61
+ search?: string;
62
+ /** Page number (1-based). Default: 1 */
63
+ page?: number;
64
+ /** Items per page. Default: 20, max: 100 */
65
+ limit?: number;
66
+ /** Sort field. Default: 'published_at' */
67
+ orderBy?: 'published_at' | 'created_at' | 'title' | 'views_count';
68
+ /** Sort direction. Default: 'desc' */
69
+ orderDirection?: 'asc' | 'desc';
70
+ }
71
+ /** Paginated list response */
72
+ interface PaginatedResponse<T> {
73
+ data: T[];
74
+ total: number;
75
+ page: number;
76
+ limit: number;
77
+ totalPages: number;
78
+ }
79
+ /** SDK configuration */
80
+ interface EmagazineSDKConfig {
81
+ /** Supabase project URL */
82
+ supabaseUrl: string;
83
+ /** Supabase anon key */
84
+ supabaseAnonKey: string;
85
+ /** CMS base URL for generating preview links. Default: same as supabaseUrl origin */
86
+ cmsBaseUrl?: string;
87
+ }
88
+
89
+ declare class EmagazineSDK {
90
+ private supabase;
91
+ private cmsBaseUrl;
92
+ constructor(config: EmagazineSDKConfig);
93
+ /**
94
+ * Load a paginated list of e-magazine articles.
95
+ * By default returns only published articles sorted by published_at desc.
96
+ */
97
+ listEmagazines(options?: ListEmagazinesOptions): Promise<PaginatedResponse<EmagazineItem>>;
98
+ /**
99
+ * Get a list of all available categories.
100
+ */
101
+ listCategories(): Promise<{
102
+ id: string;
103
+ name: string;
104
+ slug: string;
105
+ description: string | null;
106
+ }[]>;
107
+ /**
108
+ * Get detailed article data by ID, including preview_url, html_content, css_content.
109
+ */
110
+ getEmagazineById(id: string): Promise<EmagazineDetail | null>;
111
+ /**
112
+ * Get detailed article data by slug, including preview_url, html_content, css_content.
113
+ */
114
+ getEmagazineBySlug(slug: string): Promise<EmagazineDetail | null>;
115
+ /**
116
+ * Render an emagazine article into an HTML container element.
117
+ * Injects css_content as <style> and html_content into the container.
118
+ * Returns a cleanup function to remove injected styles.
119
+ */
120
+ renderToElement(detail: EmagazineDetail, container: HTMLElement): () => void;
121
+ /**
122
+ * Generate a full standalone HTML page string for an emagazine article.
123
+ * Useful for iframes or server-side rendering.
124
+ */
125
+ generateFullPageHTML(detail: EmagazineDetail): string;
126
+ private escapeHtml;
127
+ }
128
+
129
+ export { type EmagazineDetail, type EmagazineItem, EmagazineSDK, type EmagazineSDKConfig, type ListEmagazinesOptions, type PaginatedResponse };
package/dist/index.js ADDED
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ EmagazineSDK: () => EmagazineSDK
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/client.ts
28
+ var import_supabase_js = require("@supabase/supabase-js");
29
+ var EmagazineSDK = class {
30
+ constructor(config) {
31
+ if (!config.supabaseUrl || !config.supabaseAnonKey) {
32
+ throw new Error("[EmagazineSDK] supabaseUrl and supabaseAnonKey are required.");
33
+ }
34
+ this.supabase = (0, import_supabase_js.createClient)(config.supabaseUrl, config.supabaseAnonKey);
35
+ this.cmsBaseUrl = config.cmsBaseUrl || config.supabaseUrl.replace(".supabase.co", "");
36
+ }
37
+ /**
38
+ * Load a paginated list of e-magazine articles.
39
+ * By default returns only published articles sorted by published_at desc.
40
+ */
41
+ async listEmagazines(options = {}) {
42
+ const {
43
+ status = "published",
44
+ categoryId,
45
+ featured,
46
+ search,
47
+ page = 1,
48
+ limit: rawLimit = 20,
49
+ orderBy = "published_at",
50
+ orderDirection = "desc"
51
+ } = options;
52
+ const limit = Math.min(Math.max(rawLimit, 1), 100);
53
+ const from = (page - 1) * limit;
54
+ const to = from + limit - 1;
55
+ let query = this.supabase.from("emagazines").select(`
56
+ id, title, slug, summary, thumbnail_url, status, editor_type,
57
+ seo_title, seo_description, views_count, is_featured,
58
+ created_at, updated_at, published_at,
59
+ author:users(full_name, avatar_url),
60
+ category:emagazine_categories(name)
61
+ `, { count: "exact" });
62
+ if (status !== "all") {
63
+ query = query.eq("status", status);
64
+ } else {
65
+ query = query.neq("status", "trash");
66
+ }
67
+ if (categoryId) {
68
+ query = query.eq("category_id", categoryId);
69
+ }
70
+ if (featured !== void 0) {
71
+ query = query.eq("is_featured", featured);
72
+ }
73
+ if (search) {
74
+ query = query.ilike("title", `%${search}%`);
75
+ }
76
+ query = query.order(orderBy, { ascending: orderDirection === "asc" }).range(from, to);
77
+ const { data, error, count } = await query;
78
+ if (error) {
79
+ throw new Error(`[EmagazineSDK] listEmagazines failed: ${error.message}`);
80
+ }
81
+ const total = count ?? 0;
82
+ const rows = data || [];
83
+ return {
84
+ data: rows,
85
+ total,
86
+ page,
87
+ limit,
88
+ totalPages: Math.ceil(total / limit)
89
+ };
90
+ }
91
+ /**
92
+ * Get a list of all available categories.
93
+ */
94
+ async listCategories() {
95
+ const { data, error } = await this.supabase.from("emagazine_categories").select("id, name, slug, description").order("name");
96
+ if (error) {
97
+ throw new Error(`[EmagazineSDK] listCategories failed: ${error.message}`);
98
+ }
99
+ return data || [];
100
+ }
101
+ /**
102
+ * Get detailed article data by ID, including preview_url, html_content, css_content.
103
+ */
104
+ async getEmagazineById(id) {
105
+ if (!id) throw new Error("[EmagazineSDK] id is required.");
106
+ const { data, error } = await this.supabase.from("emagazines").select(`
107
+ id, title, slug, summary, html_content, css_content,
108
+ thumbnail_url, status, editor_type,
109
+ seo_title, seo_description, views_count, is_featured,
110
+ created_at, updated_at, published_at,
111
+ author:users(full_name, avatar_url),
112
+ category:emagazine_categories(name)
113
+ `).eq("id", id).single();
114
+ if (error) {
115
+ if (error.code === "PGRST116") return null;
116
+ throw new Error(`[EmagazineSDK] getEmagazineById failed: ${error.message}`);
117
+ }
118
+ if (!data) return null;
119
+ const row = data;
120
+ return {
121
+ ...row,
122
+ preview_url: `${this.cmsBaseUrl}/corporate/e-magazine/preview/${row.id}`
123
+ };
124
+ }
125
+ /**
126
+ * Get detailed article data by slug, including preview_url, html_content, css_content.
127
+ */
128
+ async getEmagazineBySlug(slug) {
129
+ if (!slug) throw new Error("[EmagazineSDK] slug is required.");
130
+ const { data, error } = await this.supabase.from("emagazines").select(`
131
+ id, title, slug, summary, html_content, css_content,
132
+ thumbnail_url, status, editor_type,
133
+ seo_title, seo_description, views_count, is_featured,
134
+ created_at, updated_at, published_at,
135
+ author:users(full_name, avatar_url),
136
+ category:emagazine_categories(name)
137
+ `).eq("slug", slug).single();
138
+ if (error) {
139
+ if (error.code === "PGRST116") return null;
140
+ throw new Error(`[EmagazineSDK] getEmagazineBySlug failed: ${error.message}`);
141
+ }
142
+ if (!data) return null;
143
+ const row = data;
144
+ return {
145
+ ...row,
146
+ preview_url: `${this.cmsBaseUrl}/corporate/e-magazine/preview/${row.id}`
147
+ };
148
+ }
149
+ /**
150
+ * Render an emagazine article into an HTML container element.
151
+ * Injects css_content as <style> and html_content into the container.
152
+ * Returns a cleanup function to remove injected styles.
153
+ */
154
+ renderToElement(detail, container) {
155
+ if (!detail.html_content) {
156
+ container.innerHTML = '<p style="text-align:center;padding:2rem;color:#999;">No content available.</p>';
157
+ return () => {
158
+ container.innerHTML = "";
159
+ };
160
+ }
161
+ const styleEl = document.createElement("style");
162
+ styleEl.setAttribute("data-emagazine-id", detail.id);
163
+ styleEl.textContent = detail.css_content || "";
164
+ document.head.appendChild(styleEl);
165
+ container.innerHTML = detail.html_content;
166
+ return () => {
167
+ styleEl.remove();
168
+ container.innerHTML = "";
169
+ };
170
+ }
171
+ /**
172
+ * Generate a full standalone HTML page string for an emagazine article.
173
+ * Useful for iframes or server-side rendering.
174
+ */
175
+ generateFullPageHTML(detail) {
176
+ const title = detail.seo_title || detail.title;
177
+ const description = detail.seo_description || detail.summary || "";
178
+ return `<!DOCTYPE html>
179
+ <html lang="vi">
180
+ <head>
181
+ <meta charset="UTF-8">
182
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
183
+ <title>${this.escapeHtml(title)}</title>
184
+ <meta name="description" content="${this.escapeHtml(description)}">
185
+ ${detail.thumbnail_url ? `<meta property="og:image" content="${this.escapeHtml(detail.thumbnail_url)}">` : ""}
186
+ <style>
187
+ * { margin: 0; padding: 0; box-sizing: border-box; }
188
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
189
+ img { max-width: 100%; height: auto; }
190
+ ${detail.css_content || ""}
191
+ </style>
192
+ </head>
193
+ <body>
194
+ ${detail.html_content || ""}
195
+ </body>
196
+ </html>`;
197
+ }
198
+ escapeHtml(str) {
199
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
200
+ }
201
+ };
202
+ // Annotate the CommonJS export names for ESM import in node:
203
+ 0 && (module.exports = {
204
+ EmagazineSDK
205
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,178 @@
1
+ // src/client.ts
2
+ import { createClient } from "@supabase/supabase-js";
3
+ var EmagazineSDK = class {
4
+ constructor(config) {
5
+ if (!config.supabaseUrl || !config.supabaseAnonKey) {
6
+ throw new Error("[EmagazineSDK] supabaseUrl and supabaseAnonKey are required.");
7
+ }
8
+ this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
9
+ this.cmsBaseUrl = config.cmsBaseUrl || config.supabaseUrl.replace(".supabase.co", "");
10
+ }
11
+ /**
12
+ * Load a paginated list of e-magazine articles.
13
+ * By default returns only published articles sorted by published_at desc.
14
+ */
15
+ async listEmagazines(options = {}) {
16
+ const {
17
+ status = "published",
18
+ categoryId,
19
+ featured,
20
+ search,
21
+ page = 1,
22
+ limit: rawLimit = 20,
23
+ orderBy = "published_at",
24
+ orderDirection = "desc"
25
+ } = options;
26
+ const limit = Math.min(Math.max(rawLimit, 1), 100);
27
+ const from = (page - 1) * limit;
28
+ const to = from + limit - 1;
29
+ let query = this.supabase.from("emagazines").select(`
30
+ id, title, slug, summary, thumbnail_url, status, editor_type,
31
+ seo_title, seo_description, views_count, is_featured,
32
+ created_at, updated_at, published_at,
33
+ author:users(full_name, avatar_url),
34
+ category:emagazine_categories(name)
35
+ `, { count: "exact" });
36
+ if (status !== "all") {
37
+ query = query.eq("status", status);
38
+ } else {
39
+ query = query.neq("status", "trash");
40
+ }
41
+ if (categoryId) {
42
+ query = query.eq("category_id", categoryId);
43
+ }
44
+ if (featured !== void 0) {
45
+ query = query.eq("is_featured", featured);
46
+ }
47
+ if (search) {
48
+ query = query.ilike("title", `%${search}%`);
49
+ }
50
+ query = query.order(orderBy, { ascending: orderDirection === "asc" }).range(from, to);
51
+ const { data, error, count } = await query;
52
+ if (error) {
53
+ throw new Error(`[EmagazineSDK] listEmagazines failed: ${error.message}`);
54
+ }
55
+ const total = count ?? 0;
56
+ const rows = data || [];
57
+ return {
58
+ data: rows,
59
+ total,
60
+ page,
61
+ limit,
62
+ totalPages: Math.ceil(total / limit)
63
+ };
64
+ }
65
+ /**
66
+ * Get a list of all available categories.
67
+ */
68
+ async listCategories() {
69
+ const { data, error } = await this.supabase.from("emagazine_categories").select("id, name, slug, description").order("name");
70
+ if (error) {
71
+ throw new Error(`[EmagazineSDK] listCategories failed: ${error.message}`);
72
+ }
73
+ return data || [];
74
+ }
75
+ /**
76
+ * Get detailed article data by ID, including preview_url, html_content, css_content.
77
+ */
78
+ async getEmagazineById(id) {
79
+ if (!id) throw new Error("[EmagazineSDK] id is required.");
80
+ const { data, error } = await this.supabase.from("emagazines").select(`
81
+ id, title, slug, summary, html_content, css_content,
82
+ thumbnail_url, status, editor_type,
83
+ seo_title, seo_description, views_count, is_featured,
84
+ created_at, updated_at, published_at,
85
+ author:users(full_name, avatar_url),
86
+ category:emagazine_categories(name)
87
+ `).eq("id", id).single();
88
+ if (error) {
89
+ if (error.code === "PGRST116") return null;
90
+ throw new Error(`[EmagazineSDK] getEmagazineById failed: ${error.message}`);
91
+ }
92
+ if (!data) return null;
93
+ const row = data;
94
+ return {
95
+ ...row,
96
+ preview_url: `${this.cmsBaseUrl}/corporate/e-magazine/preview/${row.id}`
97
+ };
98
+ }
99
+ /**
100
+ * Get detailed article data by slug, including preview_url, html_content, css_content.
101
+ */
102
+ async getEmagazineBySlug(slug) {
103
+ if (!slug) throw new Error("[EmagazineSDK] slug is required.");
104
+ const { data, error } = await this.supabase.from("emagazines").select(`
105
+ id, title, slug, summary, html_content, css_content,
106
+ thumbnail_url, status, editor_type,
107
+ seo_title, seo_description, views_count, is_featured,
108
+ created_at, updated_at, published_at,
109
+ author:users(full_name, avatar_url),
110
+ category:emagazine_categories(name)
111
+ `).eq("slug", slug).single();
112
+ if (error) {
113
+ if (error.code === "PGRST116") return null;
114
+ throw new Error(`[EmagazineSDK] getEmagazineBySlug failed: ${error.message}`);
115
+ }
116
+ if (!data) return null;
117
+ const row = data;
118
+ return {
119
+ ...row,
120
+ preview_url: `${this.cmsBaseUrl}/corporate/e-magazine/preview/${row.id}`
121
+ };
122
+ }
123
+ /**
124
+ * Render an emagazine article into an HTML container element.
125
+ * Injects css_content as <style> and html_content into the container.
126
+ * Returns a cleanup function to remove injected styles.
127
+ */
128
+ renderToElement(detail, container) {
129
+ if (!detail.html_content) {
130
+ container.innerHTML = '<p style="text-align:center;padding:2rem;color:#999;">No content available.</p>';
131
+ return () => {
132
+ container.innerHTML = "";
133
+ };
134
+ }
135
+ const styleEl = document.createElement("style");
136
+ styleEl.setAttribute("data-emagazine-id", detail.id);
137
+ styleEl.textContent = detail.css_content || "";
138
+ document.head.appendChild(styleEl);
139
+ container.innerHTML = detail.html_content;
140
+ return () => {
141
+ styleEl.remove();
142
+ container.innerHTML = "";
143
+ };
144
+ }
145
+ /**
146
+ * Generate a full standalone HTML page string for an emagazine article.
147
+ * Useful for iframes or server-side rendering.
148
+ */
149
+ generateFullPageHTML(detail) {
150
+ const title = detail.seo_title || detail.title;
151
+ const description = detail.seo_description || detail.summary || "";
152
+ return `<!DOCTYPE html>
153
+ <html lang="vi">
154
+ <head>
155
+ <meta charset="UTF-8">
156
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
157
+ <title>${this.escapeHtml(title)}</title>
158
+ <meta name="description" content="${this.escapeHtml(description)}">
159
+ ${detail.thumbnail_url ? `<meta property="og:image" content="${this.escapeHtml(detail.thumbnail_url)}">` : ""}
160
+ <style>
161
+ * { margin: 0; padding: 0; box-sizing: border-box; }
162
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
163
+ img { max-width: 100%; height: auto; }
164
+ ${detail.css_content || ""}
165
+ </style>
166
+ </head>
167
+ <body>
168
+ ${detail.html_content || ""}
169
+ </body>
170
+ </html>`;
171
+ }
172
+ escapeHtml(str) {
173
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
174
+ }
175
+ };
176
+ export {
177
+ EmagazineSDK
178
+ };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@smartsuite-cms/emagazine-sdk",
3
+ "version": "1.0.0",
4
+ "description": "SDK to load and display e-magazine articles from Smart Suite CMS",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
13
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
14
+ },
15
+ "keywords": [
16
+ "emagazine",
17
+ "cms",
18
+ "smartsuite",
19
+ "sdk"
20
+ ],
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "@supabase/supabase-js": "^2.49.0"
24
+ },
25
+ "devDependencies": {
26
+ "tsup": "^8.0.0",
27
+ "typescript": "^5.5.0"
28
+ }
29
+ }