@btst/stack 1.1.9 → 1.1.11
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 +3 -168
- package/dist/client/components/index.d.cts +22 -0
- package/dist/client/components/index.d.mts +22 -0
- package/dist/client/components/index.d.ts +22 -0
- package/dist/context/index.d.cts +21 -1
- package/dist/context/index.d.mts +21 -1
- package/dist/context/index.d.ts +21 -1
- package/dist/packages/better-stack/src/plugins/blog/client/components/shared/on-this-page.cjs +7 -2
- package/dist/packages/better-stack/src/plugins/blog/client/components/shared/on-this-page.mjs +7 -2
- package/dist/packages/better-stack/src/plugins/blog/client/plugin.cjs +3 -3
- package/dist/packages/better-stack/src/plugins/blog/client/plugin.mjs +3 -3
- package/dist/plugins/blog/client/hooks/index.d.cts +72 -0
- package/dist/plugins/blog/client/hooks/index.d.mts +72 -0
- package/dist/plugins/blog/client/hooks/index.d.ts +72 -0
- package/dist/plugins/blog/client/index.d.cts +69 -0
- package/dist/plugins/blog/client/index.d.mts +69 -0
- package/dist/plugins/blog/client/index.d.ts +69 -0
- package/dist/plugins/blog/query-keys.d.cts +62 -0
- package/dist/plugins/blog/query-keys.d.mts +62 -0
- package/dist/plugins/blog/query-keys.d.ts +62 -0
- package/package.json +1 -1
- package/src/client/components/compose.tsx +13 -0
- package/src/client/components/error-boundary.tsx +9 -0
- package/src/context/provider.tsx +21 -1
- package/src/plugins/blog/api/plugin.ts +62 -3
- package/src/plugins/blog/client/components/shared/on-this-page.tsx +4 -2
- package/src/plugins/blog/client/hooks/blog-hooks.tsx +72 -0
- package/src/plugins/blog/client/overrides.ts +10 -0
- package/src/plugins/blog/client/plugin.tsx +66 -8
|
@@ -34,52 +34,99 @@ const SHARED_QUERY_CONFIG = {
|
|
|
34
34
|
gcTime: 1000 * 60 * 10, // 10 minutes
|
|
35
35
|
} as const;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Options for the usePosts hook
|
|
39
|
+
*/
|
|
37
40
|
export interface UsePostsOptions {
|
|
41
|
+
/** Filter posts by tag name */
|
|
38
42
|
tag?: string;
|
|
43
|
+
/** Filter posts by tag slug */
|
|
39
44
|
tagSlug?: string;
|
|
45
|
+
/** Number of posts to fetch per page (default: 10) */
|
|
40
46
|
limit?: number;
|
|
47
|
+
/** Whether to enable the query (default: true) */
|
|
41
48
|
enabled?: boolean;
|
|
49
|
+
/** Search query to filter posts by title, content, or excerpt */
|
|
42
50
|
query?: string;
|
|
51
|
+
/** Filter by published status */
|
|
43
52
|
published?: boolean;
|
|
53
|
+
/** Filter by specific post slug */
|
|
44
54
|
slug?: string;
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Result from the usePosts hook
|
|
59
|
+
*/
|
|
47
60
|
export interface UsePostsResult {
|
|
61
|
+
/** Array of fetched posts */
|
|
48
62
|
posts: SerializedPost[];
|
|
63
|
+
/** Whether the initial load is in progress */
|
|
49
64
|
isLoading: boolean;
|
|
65
|
+
/** Error if the query failed */
|
|
50
66
|
error: Error | null;
|
|
67
|
+
/** Function to load the next page of posts */
|
|
51
68
|
loadMore: () => void;
|
|
69
|
+
/** Whether there are more posts to load */
|
|
52
70
|
hasMore: boolean;
|
|
71
|
+
/** Whether the next page is being loaded */
|
|
53
72
|
isLoadingMore: boolean;
|
|
73
|
+
/** Function to refetch the posts */
|
|
54
74
|
refetch: () => void;
|
|
55
75
|
}
|
|
56
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Options for the usePostSearch hook
|
|
79
|
+
*/
|
|
57
80
|
export interface UsePostSearchOptions {
|
|
81
|
+
/** Search query string to filter posts */
|
|
58
82
|
query: string;
|
|
83
|
+
/** Whether to enable the search query (default: true) */
|
|
59
84
|
enabled?: boolean;
|
|
85
|
+
/** Debounce delay in milliseconds (default: 300) */
|
|
60
86
|
debounceMs?: number;
|
|
87
|
+
/** Number of results to return (default: 10) */
|
|
61
88
|
limit?: number;
|
|
89
|
+
/** Filter by published status (default: true) */
|
|
62
90
|
published?: boolean;
|
|
63
91
|
}
|
|
64
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Result from the usePostSearch hook
|
|
95
|
+
*/
|
|
65
96
|
export interface UsePostSearchResult {
|
|
97
|
+
/** Array of posts matching the search query */
|
|
66
98
|
posts: SerializedPost[];
|
|
99
|
+
/** Alias for posts (React Query compatibility) */
|
|
67
100
|
data: SerializedPost[];
|
|
101
|
+
/** Whether the search is in progress */
|
|
68
102
|
isLoading: boolean;
|
|
103
|
+
/** Error if the search failed */
|
|
69
104
|
error: Error | null;
|
|
105
|
+
/** Function to refetch the search results */
|
|
70
106
|
refetch: () => void;
|
|
107
|
+
/** Whether a search is currently in progress (includes debounce time) */
|
|
71
108
|
isSearching: boolean;
|
|
109
|
+
/** The debounced search query being used */
|
|
72
110
|
searchQuery: string;
|
|
73
111
|
}
|
|
74
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Result from the usePost hook
|
|
115
|
+
*/
|
|
75
116
|
export interface UsePostResult {
|
|
117
|
+
/** The fetched post, or null if not found */
|
|
76
118
|
post: SerializedPost | null;
|
|
119
|
+
/** Whether the post is being loaded */
|
|
77
120
|
isLoading: boolean;
|
|
121
|
+
/** Error if the query failed */
|
|
78
122
|
error: Error | null;
|
|
123
|
+
/** Function to refetch the post */
|
|
79
124
|
refetch: () => void;
|
|
80
125
|
}
|
|
81
126
|
|
|
127
|
+
/** Input type for creating a new post */
|
|
82
128
|
export type PostCreateInput = z.infer<typeof createPostSchema>;
|
|
129
|
+
/** Input type for updating an existing post */
|
|
83
130
|
export type PostUpdateInput = z.infer<typeof updatePostSchema>;
|
|
84
131
|
|
|
85
132
|
/**
|
|
@@ -532,15 +579,27 @@ export function usePostSearch({
|
|
|
532
579
|
};
|
|
533
580
|
}
|
|
534
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Options for the useNextPreviousPosts hook
|
|
584
|
+
*/
|
|
535
585
|
export interface UseNextPreviousPostsOptions {
|
|
586
|
+
/** Whether to enable the query (default: true) */
|
|
536
587
|
enabled?: boolean;
|
|
537
588
|
}
|
|
538
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Result from the useNextPreviousPosts hook
|
|
592
|
+
*/
|
|
539
593
|
export interface UseNextPreviousPostsResult {
|
|
594
|
+
/** The previous post (older), or null if none exists */
|
|
540
595
|
previousPost: SerializedPost | null;
|
|
596
|
+
/** The next post (newer), or null if none exists */
|
|
541
597
|
nextPost: SerializedPost | null;
|
|
598
|
+
/** Whether the query is loading */
|
|
542
599
|
isLoading: boolean;
|
|
600
|
+
/** Error if the query failed */
|
|
543
601
|
error: Error | null;
|
|
602
|
+
/** Function to refetch the posts */
|
|
544
603
|
refetch: () => void;
|
|
545
604
|
}
|
|
546
605
|
|
|
@@ -596,16 +655,29 @@ export function useNextPreviousPosts(
|
|
|
596
655
|
};
|
|
597
656
|
}
|
|
598
657
|
|
|
658
|
+
/**
|
|
659
|
+
* Options for the useRecentPosts hook
|
|
660
|
+
*/
|
|
599
661
|
export interface UseRecentPostsOptions {
|
|
662
|
+
/** Maximum number of recent posts to fetch (default: 5) */
|
|
600
663
|
limit?: number;
|
|
664
|
+
/** Slug of a post to exclude from results */
|
|
601
665
|
excludeSlug?: string;
|
|
666
|
+
/** Whether to enable the query (default: true) */
|
|
602
667
|
enabled?: boolean;
|
|
603
668
|
}
|
|
604
669
|
|
|
670
|
+
/**
|
|
671
|
+
* Result from the useRecentPosts hook
|
|
672
|
+
*/
|
|
605
673
|
export interface UseRecentPostsResult {
|
|
674
|
+
/** Array of recent posts */
|
|
606
675
|
recentPosts: SerializedPost[];
|
|
676
|
+
/** Whether the query is loading */
|
|
607
677
|
isLoading: boolean;
|
|
678
|
+
/** Error if the query failed */
|
|
608
679
|
error: Error | null;
|
|
680
|
+
/** Function to refetch the posts */
|
|
609
681
|
refetch: () => void;
|
|
610
682
|
}
|
|
611
683
|
|
|
@@ -6,9 +6,13 @@ import type { BlogLocalization } from "./localization";
|
|
|
6
6
|
* Context passed to lifecycle hooks
|
|
7
7
|
*/
|
|
8
8
|
export interface RouteContext {
|
|
9
|
+
/** Current route path */
|
|
9
10
|
path: string;
|
|
11
|
+
/** Route parameters (e.g., { slug: "my-post" }) */
|
|
10
12
|
params?: Record<string, string>;
|
|
13
|
+
/** Whether rendering on server (true) or client (false) */
|
|
11
14
|
isSSR: boolean;
|
|
15
|
+
/** Additional context properties */
|
|
12
16
|
[key: string]: any;
|
|
13
17
|
}
|
|
14
18
|
|
|
@@ -19,7 +23,13 @@ export interface RouteContext {
|
|
|
19
23
|
* to customize the behavior for their framework (Next.js, React Router, etc.)
|
|
20
24
|
*/
|
|
21
25
|
export interface BlogPluginOverrides {
|
|
26
|
+
/**
|
|
27
|
+
* Link component for navigation
|
|
28
|
+
*/
|
|
22
29
|
Link?: ComponentType<React.ComponentProps<"a"> & Record<string, any>>;
|
|
30
|
+
/**
|
|
31
|
+
* Post card component for displaying a post
|
|
32
|
+
*/
|
|
23
33
|
PostCard?: ComponentType<{
|
|
24
34
|
post: SerializedPost;
|
|
25
35
|
}>;
|
|
@@ -17,9 +17,13 @@ import { PostPageComponent } from "./components/pages/post-page";
|
|
|
17
17
|
* Context passed to route hooks
|
|
18
18
|
*/
|
|
19
19
|
export interface RouteContext {
|
|
20
|
+
/** Current route path */
|
|
20
21
|
path: string;
|
|
22
|
+
/** Route parameters (e.g., { slug: "my-post" }) */
|
|
21
23
|
params?: Record<string, string>;
|
|
24
|
+
/** Whether rendering on server (true) or client (false) */
|
|
22
25
|
isSSR: boolean;
|
|
26
|
+
/** Additional context properties */
|
|
23
27
|
[key: string]: any;
|
|
24
28
|
}
|
|
25
29
|
|
|
@@ -27,12 +31,19 @@ export interface RouteContext {
|
|
|
27
31
|
* Context passed to loader hooks
|
|
28
32
|
*/
|
|
29
33
|
export interface LoaderContext {
|
|
34
|
+
/** Current route path */
|
|
30
35
|
path: string;
|
|
36
|
+
/** Route parameters (e.g., { slug: "my-post" }) */
|
|
31
37
|
params?: Record<string, string>;
|
|
38
|
+
/** Whether rendering on server (true) or client (false) */
|
|
32
39
|
isSSR: boolean;
|
|
40
|
+
/** Base URL for API calls */
|
|
33
41
|
apiBaseURL: string;
|
|
42
|
+
/** Path where the API is mounted */
|
|
34
43
|
apiBasePath: string;
|
|
44
|
+
/** Optional headers for the request */
|
|
35
45
|
headers?: Headers;
|
|
46
|
+
/** Additional context properties */
|
|
36
47
|
[key: string]: any;
|
|
37
48
|
}
|
|
38
49
|
|
|
@@ -41,26 +52,35 @@ export interface LoaderContext {
|
|
|
41
52
|
* Note: queryClient is passed at runtime to both loader and meta (for SSR isolation)
|
|
42
53
|
*/
|
|
43
54
|
export interface BlogClientConfig {
|
|
44
|
-
|
|
55
|
+
/** Base URL for API calls (e.g., "http://localhost:3000") */
|
|
45
56
|
apiBaseURL: string;
|
|
57
|
+
/** Path where the API is mounted (e.g., "/api/data") */
|
|
46
58
|
apiBasePath: string;
|
|
59
|
+
/** Base URL of your site for SEO meta tags */
|
|
47
60
|
siteBaseURL: string;
|
|
61
|
+
/** Path where pages are mounted (e.g., "/pages") */
|
|
48
62
|
siteBasePath: string;
|
|
63
|
+
/** React Query client instance for caching */
|
|
49
64
|
queryClient: QueryClient;
|
|
50
65
|
|
|
51
|
-
|
|
66
|
+
/** Optional SEO configuration for meta tags */
|
|
52
67
|
seo?: {
|
|
68
|
+
/** Site name for Open Graph tags */
|
|
53
69
|
siteName?: string;
|
|
70
|
+
/** Default author name */
|
|
54
71
|
author?: string;
|
|
72
|
+
/** Twitter handle (e.g., "@yourhandle") */
|
|
55
73
|
twitterHandle?: string;
|
|
74
|
+
/** Locale for Open Graph (e.g., "en_US") */
|
|
56
75
|
locale?: string;
|
|
76
|
+
/** Default image URL for social sharing */
|
|
57
77
|
defaultImage?: string;
|
|
58
78
|
};
|
|
59
79
|
|
|
60
|
-
|
|
80
|
+
/** Optional hooks for customizing behavior */
|
|
61
81
|
hooks?: BlogClientHooks;
|
|
62
82
|
|
|
63
|
-
|
|
83
|
+
/** Optional headers for SSR (e.g., forwarding cookies) */
|
|
64
84
|
headers?: Headers;
|
|
65
85
|
}
|
|
66
86
|
|
|
@@ -69,27 +89,61 @@ export interface BlogClientConfig {
|
|
|
69
89
|
* All hooks are optional and allow consumers to customize behavior
|
|
70
90
|
*/
|
|
71
91
|
export interface BlogClientHooks {
|
|
72
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Called before loading posts list. Return false to cancel loading.
|
|
94
|
+
* @param filter - Filter parameters including published status
|
|
95
|
+
* @param context - Loader context with path, params, etc.
|
|
96
|
+
*/
|
|
73
97
|
beforeLoadPosts?: (
|
|
74
98
|
filter: { published: boolean },
|
|
75
99
|
context: LoaderContext,
|
|
76
100
|
) => Promise<boolean> | boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Called after posts are loaded. Return false to cancel further processing.
|
|
103
|
+
* @param posts - Array of loaded posts or null
|
|
104
|
+
* @param filter - Filter parameters used
|
|
105
|
+
* @param context - Loader context
|
|
106
|
+
*/
|
|
77
107
|
afterLoadPosts?: (
|
|
78
108
|
posts: Post[] | null,
|
|
79
109
|
filter: { published: boolean },
|
|
80
110
|
context: LoaderContext,
|
|
81
111
|
) => Promise<boolean> | boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Called before loading a single post. Return false to cancel loading.
|
|
114
|
+
* @param slug - Post slug being loaded
|
|
115
|
+
* @param context - Loader context
|
|
116
|
+
*/
|
|
82
117
|
beforeLoadPost?: (
|
|
83
118
|
slug: string,
|
|
84
119
|
context: LoaderContext,
|
|
85
120
|
) => Promise<boolean> | boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Called after a post is loaded. Return false to cancel further processing.
|
|
123
|
+
* @param post - Loaded post or null if not found
|
|
124
|
+
* @param slug - Post slug that was requested
|
|
125
|
+
* @param context - Loader context
|
|
126
|
+
*/
|
|
86
127
|
afterLoadPost?: (
|
|
87
128
|
post: Post | null,
|
|
88
129
|
slug: string,
|
|
89
130
|
context: LoaderContext,
|
|
90
131
|
) => Promise<boolean> | boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Called before loading the new post page. Return false to cancel.
|
|
134
|
+
* @param context - Loader context
|
|
135
|
+
*/
|
|
91
136
|
beforeLoadNewPost?: (context: LoaderContext) => Promise<boolean> | boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Called after the new post page is loaded. Return false to cancel.
|
|
139
|
+
* @param context - Loader context
|
|
140
|
+
*/
|
|
92
141
|
afterLoadNewPost?: (context: LoaderContext) => Promise<boolean> | boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Called when a loading error occurs
|
|
144
|
+
* @param error - The error that occurred
|
|
145
|
+
* @param context - Loader context
|
|
146
|
+
*/
|
|
93
147
|
onLoadError?: (error: Error, context: LoaderContext) => Promise<void> | void;
|
|
94
148
|
}
|
|
95
149
|
|
|
@@ -181,13 +235,17 @@ function createPostsLoader(published: boolean, config: BlogClientConfig) {
|
|
|
181
235
|
};
|
|
182
236
|
}
|
|
183
237
|
|
|
184
|
-
function createPostLoader(
|
|
238
|
+
function createPostLoader(
|
|
239
|
+
slug: string,
|
|
240
|
+
config: BlogClientConfig,
|
|
241
|
+
path?: string,
|
|
242
|
+
) {
|
|
185
243
|
return async () => {
|
|
186
244
|
if (typeof window === "undefined") {
|
|
187
245
|
const { queryClient, apiBasePath, apiBaseURL, hooks, headers } = config;
|
|
188
246
|
|
|
189
247
|
const context: LoaderContext = {
|
|
190
|
-
path: `/blog/${slug}`,
|
|
248
|
+
path: path ?? `/blog/${slug}`,
|
|
191
249
|
params: { slug },
|
|
192
250
|
isSSR: true,
|
|
193
251
|
apiBaseURL,
|
|
@@ -648,7 +706,7 @@ export const blogClientPlugin = (config: BlogClientConfig) =>
|
|
|
648
706
|
editPost: createRoute("/blog/:slug/edit", ({ params: { slug } }) => {
|
|
649
707
|
return {
|
|
650
708
|
PageComponent: () => <EditPostPageComponent slug={slug} />,
|
|
651
|
-
loader: createPostLoader(slug, config),
|
|
709
|
+
loader: createPostLoader(slug, config, `/blog/${slug}/edit`),
|
|
652
710
|
meta: createEditPostMeta(slug, config),
|
|
653
711
|
};
|
|
654
712
|
}),
|