next-blog-ai
Version:
AI-powered blog generation and content delivery for Next.js and other frameworks
1,009 lines (939 loc) • 24.9 kB
JavaScript
'use strict';
// src/core/utils.ts
function snakeToCamel(str) {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
function normalizeKeys(obj) {
if (obj === null || obj === void 0) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => normalizeKeys(item));
}
if (typeof obj === "object") {
const newObj = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const camelKey = snakeToCamel(key);
newObj[camelKey] = normalizeKeys(obj[key]);
}
}
return newObj;
}
return obj;
}
function createRequestId(endpoint, params) {
const sortedParams = Object.keys(params).sort().map((key) => `${key}=${params[key]}`).join("&");
return `${endpoint}?${sortedParams}`;
}
function addHtmlStyles(html, styles) {
if (!html)
return html;
if (html.includes("<style>")) {
return html.replace(
/<style>([\s\S]*?)<\/style>/,
`<style>$1
${styles}</style>`
);
}
return `<style>${styles}</style>${html}`;
}
// src/core/styles.ts
var DEFAULT_BLOG_STYLES = `
/* Base styles */
.next-blog-ai-content {
line-height: 1.6;
color: inherit;
max-width: 100%;
margin: 0 auto;
}
/* Headings */
.next-blog-ai-content h1,
.next-blog-ai-content h2,
.next-blog-ai-content h3,
.next-blog-ai-content h4,
.next-blog-ai-content h5,
.next-blog-ai-content h6 {
margin-top: 2rem;
margin-bottom: 1rem;
font-weight: 600;
line-height: 1.25;
color: inherit;
}
.next-blog-ai-content h1 {
font-size: 2.25rem;
}
.next-blog-ai-content h2 {
font-size: 1.75rem;
border-bottom: 1px solid #eee;
padding-bottom: 0.3rem;
}
.next-blog-ai-content h3 {
font-size: 1.5rem;
}
.next-blog-ai-content h4 {
font-size: 1.25rem;
}
.next-blog-ai-content h5, .next-blog-ai-content h6 {
font-size: 1rem;
}
/* Paragraphs and text */
.next-blog-ai-content p {
margin-top: 0;
margin-bottom: 1.5rem;
}
.next-blog-ai-content strong {
font-weight: 600;
}
.next-blog-ai-content em {
font-style: italic;
}
/* Lists */
.next-blog-ai-content ul,
.next-blog-ai-content ol {
margin-top: 0;
margin-bottom: 1.5rem;
padding-left: 2rem;
}
.next-blog-ai-content li {
margin-bottom: 0.5rem;
}
.next-blog-ai-content li > ul,
.next-blog-ai-content li > ol {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
/* Links */
.next-blog-ai-content a {
color: #0070f3;
text-decoration: none;
}
.next-blog-ai-content a:hover {
text-decoration: underline;
}
/* Blockquotes */
.next-blog-ai-content blockquote {
margin: 1.5rem 0;
padding: 0.5rem 1rem;
border-left: 4px solid #ddd;
color: #666;
background-color: #f9f9f9;
}
.next-blog-ai-content blockquote > :first-child {
margin-top: 0;
}
.next-blog-ai-content blockquote > :last-child {
margin-bottom: 0;
}
/* Code blocks */
.next-blog-ai-content pre {
margin: 1.5rem 0;
padding: 1rem;
overflow: auto;
background-color: #f6f8fa;
border-radius: 3px;
font-size: 0.875rem;
}
.next-blog-ai-content code {
font-size: 0.875rem;
padding: 0.2em 0.4em;
background-color: rgba(27, 31, 35, 0.05);
border-radius: 3px;
}
.next-blog-ai-content pre code {
padding: 0;
background-color: transparent;
}
/* Tables */
.next-blog-ai-content table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
}
.next-blog-ai-content table th,
.next-blog-ai-content table td {
padding: 0.5rem 1rem;
border: 1px solid #e1e4e8;
}
.next-blog-ai-content table th {
background-color: #f6f8fa;
font-weight: 600;
text-align: left;
}
.next-blog-ai-content table tr:nth-child(even) {
background-color: #f7f7f7;
}
/* Images */
.next-blog-ai-content img {
max-width: 100%;
height: auto;
display: block;
margin: 1.5rem auto;
}
.next-blog-ai-content figure {
margin: 1.5rem auto;
text-align: center;
}
.next-blog-ai-content figcaption {
font-size: 0.875rem;
color: #666;
margin-top: 0.5rem;
}
/* Comments */
.next-blog-ai-comments {
margin-top: 3rem;
border-top: 1px solid #eee;
padding-top: 2rem;
}
.next-blog-ai-comment {
margin-bottom: 2rem;
padding: 1rem;
background-color: #f9f9f9;
border-radius: 4px;
}
.next-blog-ai-comment-header {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.next-blog-ai-comment-author {
font-weight: 600;
}
.next-blog-ai-comment-date {
color: #666;
font-size: 0.875rem;
}
.next-blog-ai-comment-content {
line-height: 1.5;
}
/* Comment form */
.next-blog-ai-comment-form {
margin-top: 2rem;
}
.next-blog-ai-comment-form label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
}
.next-blog-ai-comment-form input,
.next-blog-ai-comment-form textarea {
width: 100%;
padding: 0.5rem;
margin-bottom: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.next-blog-ai-comment-form textarea {
min-height: 6rem;
}
.next-blog-ai-comment-form button {
background-color: #0070f3;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
}
.next-blog-ai-comment-form button:hover {
background-color: #0060df;
}
`;
var DEFAULT_BLOG_LIST_STYLES = `
/* Blog post list - Grid View (Default) */
.next-blog-ai-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
margin: 2rem 0;
}
(max-width: 768px) {
.next-blog-ai-list {
grid-template-columns: 1fr;
}
}
/* Post content padding for grid view */
.next-blog-ai-post-content {
padding: 1.5rem;
display: flex;
flex-direction: column;
}
/* List View Styles */
.next-blog-ai-list.list-view {
display: block;
}
.next-blog-ai-list.list-view .next-blog-ai-post-card {
display: flex;
flex-direction: row;
margin-bottom: 2rem;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.next-blog-ai-list.list-view .next-blog-ai-post-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.next-blog-ai-list.list-view .next-blog-ai-post-image {
width: 250px;
min-width: 250px;
height: 180px;
padding-bottom: 0;
position: relative;
background-color: #f0f0f0;
}
.next-blog-ai-list.list-view .next-blog-ai-post-image img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.next-blog-ai-list.list-view .next-blog-ai-post-content {
flex: 1;
padding: 1.5rem;
display: flex;
flex-direction: column;
}
/* Make sure grid view items display correctly */
.next-blog-ai-list:not(.list-view) .next-blog-ai-post-card {
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
/* Responsive adjustments for list view */
(max-width: 768px) {
.next-blog-ai-list.list-view .next-blog-ai-post-card {
flex-direction: column;
}
.next-blog-ai-list.list-view .next-blog-ai-post-image {
width: 100%;
height: 0;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
}
}
.next-blog-ai-post-card {
display: flex;
flex-direction: column;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.next-blog-ai-post-title {
font-size: 1.25rem;
font-weight: 600;
margin-top: 0;
margin-bottom: 0.5rem;
color: #111;
}
.next-blog-ai-post-title a {
color: inherit;
text-decoration: none;
}
.next-blog-ai-post-title a:hover {
color: #0070f3;
}
.next-blog-ai-post-meta {
font-size: 0.875rem;
color: #666;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 1rem;
}
.next-blog-ai-post-excerpt {
margin-bottom: 1.5rem;
flex-grow: 1;
}
.next-blog-ai-post-footer {
margin-top: auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.next-blog-ai-read-more {
color: #0070f3;
text-decoration: none;
font-weight: 500;
font-size: 0.875rem;
}
.next-blog-ai-read-more:hover {
text-decoration: underline;
}
.next-blog-ai-comments-count {
font-size: 0.875rem;
color: #666;
}
/* Pagination */
.next-blog-ai-pagination {
display: flex;
justify-content: center;
margin: 3rem 0;
gap: 0.5rem;
}
.next-blog-ai-pagination-item {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2.5rem;
height: 2.5rem;
padding: 0 0.75rem;
border-radius: 4px;
background-color: #f0f0f0;
color: #333;
text-decoration: none;
font-weight: 500;
}
.next-blog-ai-pagination-item:hover {
background-color: #e0e0e0;
}
.next-blog-ai-pagination-item.active {
background-color: #0070f3;
color: white;
}
.next-blog-ai-pagination-item.disabled {
color: #999;
pointer-events: none;
}
`;
// src/core/client.ts
var DEFAULT_API_URL = "https://next-blog-ai.com/api/blog";
function getApiUrl() {
if (typeof process !== "undefined" && process.env && process.env.NEXT_BLOG_AI_LOCAL_DEVELOPMENT === "true") {
return process.env.NEXT_BLOG_AI_DEV_URL || "http://localhost:3000/api/blog";
}
return DEFAULT_API_URL;
}
var DEFAULT_CACHE_TTL = 0;
var _NextBlogAI = class _NextBlogAI {
/**
* Create a new instance of NextBlogAI client
*/
constructor(config) {
var _a, _b;
if (!config.apiKey) {
throw new Error("API key is required");
}
this.apiKey = config.apiKey;
this.apiUrl = getApiUrl();
this.defaultFormat = config.defaultFormat || "html";
this.defaultBlogListDisplay = config.defaultBlogListDisplay || "grid";
this.defaultCacheTTL = config.defaultCacheTTL || DEFAULT_CACHE_TTL;
this.cache = /* @__PURE__ */ new Map();
this.pendingRequests = /* @__PURE__ */ new Map();
this.customStyles = {
blogContent: (_a = config.styles) == null ? void 0 : _a.blogContent,
blogList: (_b = config.styles) == null ? void 0 : _b.blogList
};
this.defaultFetchOptions = config.defaultFetchOptions;
this.retryOptions = {
..._NextBlogAI.DEFAULT_RETRY_OPTIONS,
...config.retry
};
}
/**
* Generate a cache key from the request parameters
*/
generateCacheKey(endpoint, params) {
const sortedParams = Object.keys(params).sort().map((key) => `${key}=${params[key]}`).join("&");
return `${endpoint}?${sortedParams}`;
}
/**
* Get data from cache if available and not expired
*/
getFromCache(cacheKey, options) {
var _a;
if (options == null ? void 0 : options.bypassCache) {
return null;
}
const cachedData = this.cache.get(cacheKey);
if (!cachedData) {
return null;
}
const ttl = (_a = options == null ? void 0 : options.ttl) != null ? _a : this.defaultCacheTTL;
const now = Date.now();
const expirationTime = cachedData.timestamp + ttl * 1e3;
if (now > expirationTime) {
this.cache.delete(cacheKey);
return null;
}
return cachedData.data;
}
/**
* Store data in cache
*/
storeInCache(cacheKey, data) {
this.cache.set(cacheKey, {
data,
timestamp: Date.now()
});
}
/**
* Calculate retry delay with exponential backoff and optional jitter
*/
getRetryDelay(attempt) {
const { initialDelay, backoffFactor, maxDelay, jitter } = this.retryOptions;
let delay = initialDelay * Math.pow(backoffFactor, attempt);
delay = Math.min(delay, maxDelay);
if (jitter) {
const jitterFactor = 0.25;
const randomFactor = 1 - jitterFactor + Math.random() * jitterFactor * 2;
delay = delay * randomFactor;
}
return delay;
}
/**
* Sleep for specified milliseconds
*/
async sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Make API request with proper error handling and retry mechanism
*/
async makeRequest(endpoint, params = {}, options = {}) {
const method = options.method || "GET";
const requestParams = { ...params, api_key: this.apiKey };
let cacheKey = "";
if (method === "GET") {
cacheKey = this.generateCacheKey(endpoint, requestParams);
const cachedData = this.getFromCache(cacheKey, options.cache);
if (cachedData) {
return { data: cachedData };
}
}
if (method === "GET") {
const requestId = createRequestId(endpoint, requestParams);
const pendingRequest = this.pendingRequests.get(requestId);
if (pendingRequest) {
return pendingRequest;
}
const requestPromise = this.executeRequest(
endpoint,
method,
requestParams,
options,
cacheKey
);
this.pendingRequests.set(requestId, requestPromise);
requestPromise.finally(() => {
this.pendingRequests.delete(requestId);
});
return requestPromise;
}
return this.executeRequest(
endpoint,
method,
requestParams,
options,
cacheKey
);
}
/**
* Execute the actual API request with retries
*/
async executeRequest(endpoint, method, requestParams, options, cacheKey) {
var _a;
let attempt = 0;
const maxRetries = this.retryOptions.maxRetries;
while (true) {
try {
const url = new URL(`${this.apiUrl}/${endpoint}`);
if (method === "GET") {
Object.entries(requestParams).forEach(([key, value]) => {
if (value !== void 0 && value !== null) {
url.searchParams.append(key, String(value));
}
});
}
const fetchOptions = {
...this.defaultFetchOptions,
...(_a = options.cache) == null ? void 0 : _a.fetchOptions
};
const response = await fetch(url.toString(), {
method,
headers: {
"Content-Type": "application/json"
},
body: method !== "GET" ? JSON.stringify({ ...options.body, api_key: this.apiKey }) : void 0,
// Apply framework-specific fetch options if present
...fetchOptions
});
const isRetriableStatus = response.status >= 500 || response.status === 429 || // Too Many Requests
response.status === 408;
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: "Unknown error" }));
const error = {
message: errorData.error || "An unknown error occurred",
status: response.status,
code: errorData.code || `HTTP_${response.status}`
};
if (isRetriableStatus && attempt < maxRetries) {
attempt++;
const delay = this.getRetryDelay(attempt);
await this.sleep(delay);
continue;
}
return { error };
}
const rawData = await response.json();
const normalizedData = normalizeKeys(rawData);
if (endpoint === "list" && normalizedData && typeof normalizedData === "object") {
const listData = normalizedData;
if (listData.format === "html" && "content" in listData) {
const styles = this.customStyles.blogList || DEFAULT_BLOG_LIST_STYLES;
const displayFormat = requestParams.display || this.defaultBlogListDisplay;
const displayClass = displayFormat === "list" ? "list-view" : "grid-view";
listData.content = listData.content.replace(
'class="next-blog-ai-list"',
`class="next-blog-ai-list ${displayClass}"`
);
listData.content = addHtmlStyles(listData.content, styles);
}
} else if (endpoint === "post" && normalizedData && typeof normalizedData === "object") {
const postData = normalizedData;
if (postData.format === "html" && "content" in postData) {
const styles = this.customStyles.blogContent || DEFAULT_BLOG_STYLES;
postData.content = addHtmlStyles(postData.content, styles);
if ("comments" in postData && typeof postData.comments === "string") {
postData.comments = addHtmlStyles(postData.comments, styles);
}
if ("commentForm" in postData && typeof postData.commentForm === "string") {
postData.commentForm = addHtmlStyles(
postData.commentForm,
styles
);
}
}
}
if (method === "GET" && cacheKey) {
this.storeInCache(cacheKey, normalizedData);
}
return { data: normalizedData };
} catch (err) {
if (attempt < maxRetries) {
attempt++;
const delay = this.getRetryDelay(attempt);
await this.sleep(delay);
continue;
}
const error = {
message: err instanceof Error ? err.message : "Network error occurred",
status: 0,
code: "NETWORK_ERROR"
};
return { error };
}
}
}
/**
* Get a list of blog posts
*/
async getBlogPosts(options = {}) {
const params = {
page: options.page || 1,
per_page: options.perPage || 10,
format: options.format || this.defaultFormat,
display: options.display || this.defaultBlogListDisplay
};
return this.makeRequest("list", params, {
cache: options.cache
});
}
/**
* Get a single blog post by slug
*/
async getBlogPost(slug, options = {}) {
if (!slug) {
return {
error: {
message: "Slug is required",
status: 400
}
};
}
const params = {
slug,
format: options.format || this.defaultFormat
};
return this.makeRequest("post", params, {
cache: options.cache
});
}
/**
* Submit a comment on a blog post
*/
async submitComment(data) {
if (!data.postId) {
return {
error: {
message: "Post ID is required",
status: 400
}
};
}
if (!data.authorName) {
return {
error: {
message: "Author name is required",
status: 400
}
};
}
if (!data.content) {
return {
error: {
message: "Comment content is required",
status: 400
}
};
}
return this.makeRequest(
"comment",
{},
{
method: "POST",
body: {
post_id: data.postId,
author_name: data.authorName,
author_email: data.authorEmail,
content: data.content
}
}
);
}
/**
* Get only SEO metadata for a blog post by slug
* Optimized version that doesn't fetch the full content
*/
async getBlogPostSEO(slug, options = {}) {
if (!slug) {
return {
error: {
message: "Slug is required",
status: 400
}
};
}
const params = {
slug,
seo_only: true
};
return this.makeRequest("post/seo", params, {
cache: options.cache
});
}
/**
* Get only SEO metadata for blog list
* Optimized version that doesn't fetch the full content
*/
async getBlogListSEO(options = {}) {
const params = {
page: options.page || 1,
per_page: options.perPage || 10,
seo_only: true
};
return this.makeRequest("list/seo", params, {
cache: options.cache
});
}
/**
* Get all blog post slugs
* Useful for static site generation in frameworks like Next.js
*/
async getAllBlogSlugs(options = {}) {
const params = {};
return this.makeRequest("slugs", params, {
cache: options.cache
});
}
/**
* Clear the entire cache
*/
clearCache() {
this.cache.clear();
}
/**
* Clear a specific item from the cache
*/
clearCacheItem(endpoint, params) {
const cacheKey = this.generateCacheKey(endpoint, params);
this.cache.delete(cacheKey);
}
/**
* Set custom styles for HTML content
*/
setStyles(styles) {
if (styles.blogContent) {
this.customStyles.blogContent = styles.blogContent;
}
if (styles.blogList) {
this.customStyles.blogList = styles.blogList;
}
}
};
/**
* Default retry options
*/
_NextBlogAI.DEFAULT_RETRY_OPTIONS = {
maxRetries: 3,
initialDelay: 300,
backoffFactor: 2,
maxDelay: 1e4,
jitter: true
};
var NextBlogAI = _NextBlogAI;
function createNextBlogAI(config) {
const configWithDefaults = {
...config,
defaultFetchOptions: config.defaultFetchOptions || {
cache: "no-store",
next: {
revalidate: 0
}
}
};
return new NextBlogAI(configWithDefaults);
}
// src/frameworks/next.ts
function mapNextCacheOptions(options) {
if (!options) {
return void 0;
}
return {
ttl: options.revalidate,
bypassCache: options.dynamic || options.revalidate === 0,
// Include the framework-specific fetch options
fetchOptions: {
cache: options.dynamic || options.revalidate === 0 ? "no-store" : "force-cache",
next: {
revalidate: options.revalidate
}
},
next: options
};
}
function withNextCache(fn) {
return function(client, ...args) {
const lastArg = args[args.length - 1];
if (lastArg && typeof lastArg === "object" && "next" in lastArg) {
const { next, ...rest } = lastArg;
const options = {
...rest,
cache: mapNextCacheOptions(next)
};
args[args.length - 1] = options;
}
return fn(client, ...args);
};
}
async function getBlogPostsWithNextCache(client, options = {}) {
const cacheOptions = mapNextCacheOptions(options.next);
const { next, ...restOptions } = options;
return client.getBlogPosts({
...restOptions,
cache: cacheOptions
});
}
async function getBlogPostWithNextCache(client, slug, options = {}) {
const cacheOptions = mapNextCacheOptions(options.next);
const { next, ...restOptions } = options;
return client.getBlogPost(slug, {
...restOptions,
cache: cacheOptions
});
}
async function getBlogPostSEOWithNextCache(client, slug, options = {}) {
const cacheOptions = mapNextCacheOptions(options.next);
const { next, ...restOptions } = options;
return client.getBlogPostSEO(slug, {
...restOptions,
cache: cacheOptions
});
}
async function getBlogListSEOWithNextCache(client, options = {}) {
const cacheOptions = mapNextCacheOptions(options.next);
const { next, ...restOptions } = options;
return client.getBlogListSEO({
...restOptions,
cache: cacheOptions
});
}
async function getAllBlogSlugsWithNextCache(client, options = {}) {
const cacheOptions = mapNextCacheOptions(options.next);
const { next, ...restOptions } = options;
return client.getAllBlogSlugs({
...restOptions,
cache: cacheOptions
});
}
function createNextBlogAIForNextJs(apiKey, defaultCacheOptions = { revalidate: 0 }) {
const client = new NextBlogAI({ apiKey });
const mergeWithDefaults = (options) => {
if (!defaultCacheOptions)
return options;
return {
...defaultCacheOptions,
...options
};
};
return {
client,
getBlogPosts: (options = {}) => {
const finalOptions = {
...options,
next: options.next || mergeWithDefaults(options.next)
};
return getBlogPostsWithNextCache(client, finalOptions);
},
getBlogPost: (slug, options = {}) => {
const finalOptions = {
...options,
next: options.next || mergeWithDefaults(options.next)
};
return getBlogPostWithNextCache(client, slug, finalOptions);
},
getBlogPostSEO: (slug, options = {}) => {
const finalOptions = {
...options,
next: options.next || mergeWithDefaults(options.next)
};
return getBlogPostSEOWithNextCache(client, slug, finalOptions);
},
getBlogListSEO: (options = {}) => {
const finalOptions = {
...options,
next: options.next || mergeWithDefaults(options.next)
};
return getBlogListSEOWithNextCache(client, finalOptions);
},
getAllBlogSlugs: (options = {}) => {
const finalOptions = {
...options,
next: options.next || mergeWithDefaults(options.next)
};
return getAllBlogSlugsWithNextCache(client, finalOptions);
}
};
}
exports.DEFAULT_BLOG_LIST_STYLES = DEFAULT_BLOG_LIST_STYLES;
exports.DEFAULT_BLOG_STYLES = DEFAULT_BLOG_STYLES;
exports.NextBlogAI = NextBlogAI;
exports.addHtmlStyles = addHtmlStyles;
exports.createNextBlogAI = createNextBlogAI;
exports.createNextBlogAIForNextJs = createNextBlogAIForNextJs;
exports.getAllBlogSlugsWithNextCache = getAllBlogSlugsWithNextCache;
exports.getBlogListSEOWithNextCache = getBlogListSEOWithNextCache;
exports.getBlogPostSEOWithNextCache = getBlogPostSEOWithNextCache;
exports.getBlogPostWithNextCache = getBlogPostWithNextCache;
exports.getBlogPostsWithNextCache = getBlogPostsWithNextCache;
exports.normalizeKeys = normalizeKeys;
exports.withNextCache = withNextCache;
//# sourceMappingURL=out.js.map
//# sourceMappingURL=index.js.map