@btst/stack 1.0.1 → 1.1.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.
Files changed (282) hide show
  1. package/README.md +156 -709
  2. package/dist/api/index.cjs +2 -1
  3. package/dist/api/index.d.cts +4 -3
  4. package/dist/api/index.d.mts +4 -3
  5. package/dist/api/index.d.ts +4 -3
  6. package/dist/api/index.mjs +1 -1
  7. package/dist/client/components/compose.cjs +68 -0
  8. package/dist/client/components/compose.mjs +65 -0
  9. package/dist/client/components/error-boundary.cjs +24 -0
  10. package/dist/client/components/error-boundary.mjs +22 -0
  11. package/dist/client/components/index.cjs +10 -0
  12. package/dist/client/components/index.d.cts +52 -0
  13. package/dist/client/components/index.d.mts +52 -0
  14. package/dist/client/components/index.d.ts +52 -0
  15. package/dist/client/components/index.mjs +2 -0
  16. package/dist/client/index.cjs +24 -5
  17. package/dist/client/index.d.cts +125 -8
  18. package/dist/client/index.d.mts +125 -8
  19. package/dist/client/index.d.ts +125 -8
  20. package/dist/client/index.mjs +21 -4
  21. package/dist/client/meta-utils.cjs +162 -0
  22. package/dist/client/meta-utils.mjs +160 -0
  23. package/dist/client/path-utils.cjs +15 -0
  24. package/dist/client/path-utils.mjs +13 -0
  25. package/dist/client/sitemap-utils.cjs +14 -0
  26. package/dist/client/sitemap-utils.mjs +12 -0
  27. package/dist/context/index.cjs +6 -63
  28. package/dist/context/index.d.cts +21 -24
  29. package/dist/context/index.d.mts +21 -24
  30. package/dist/context/index.d.ts +21 -24
  31. package/dist/context/index.mjs +1 -61
  32. package/dist/context/provider.cjs +51 -0
  33. package/dist/context/provider.mjs +46 -0
  34. package/dist/index.cjs +2 -3
  35. package/dist/index.d.cts +3 -2
  36. package/dist/index.d.mts +3 -2
  37. package/dist/index.d.ts +3 -2
  38. package/dist/index.mjs +1 -2
  39. package/dist/plugins/api/index.cjs +13 -0
  40. package/dist/plugins/api/index.d.cts +40 -0
  41. package/dist/plugins/api/index.d.mts +40 -0
  42. package/dist/plugins/api/index.d.ts +40 -0
  43. package/dist/plugins/api/index.mjs +8 -0
  44. package/dist/plugins/blog/api/index.cjs +11 -0
  45. package/dist/plugins/blog/api/index.d.cts +7 -0
  46. package/dist/plugins/blog/api/index.d.mts +7 -0
  47. package/dist/plugins/blog/api/index.d.ts +7 -0
  48. package/dist/plugins/blog/api/index.mjs +2 -0
  49. package/dist/plugins/blog/api/plugin.cjs +569 -0
  50. package/dist/plugins/blog/api/plugin.mjs +565 -0
  51. package/dist/plugins/blog/client/components/forms/image-field.cjs +133 -0
  52. package/dist/plugins/blog/client/components/forms/image-field.mjs +131 -0
  53. package/dist/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
  54. package/dist/plugins/blog/client/components/forms/markdown-editor.cjs +106 -0
  55. package/dist/plugins/blog/client/components/forms/markdown-editor.mjs +104 -0
  56. package/dist/plugins/blog/client/components/forms/post-forms.cjs +401 -0
  57. package/dist/plugins/blog/client/components/forms/post-forms.mjs +398 -0
  58. package/dist/plugins/blog/client/components/forms/tags-multiselect.cjs +71 -0
  59. package/dist/plugins/blog/client/components/forms/tags-multiselect.mjs +65 -0
  60. package/dist/plugins/blog/client/components/index.cjs +17 -0
  61. package/dist/plugins/blog/client/components/index.d.cts +22 -0
  62. package/dist/plugins/blog/client/components/index.d.mts +22 -0
  63. package/dist/plugins/blog/client/components/index.d.ts +22 -0
  64. package/dist/plugins/blog/client/components/index.mjs +12 -0
  65. package/dist/plugins/blog/client/components/loading/form-page-skeleton.cjs +62 -0
  66. package/dist/plugins/blog/client/components/loading/form-page-skeleton.mjs +60 -0
  67. package/dist/plugins/blog/client/components/loading/index.cjs +20 -0
  68. package/dist/plugins/blog/client/components/loading/index.mjs +16 -0
  69. package/dist/plugins/blog/client/components/loading/list-page-skeleton.cjs +26 -0
  70. package/dist/plugins/blog/client/components/loading/list-page-skeleton.mjs +24 -0
  71. package/dist/plugins/blog/client/components/loading/page-header-skeleton.cjs +13 -0
  72. package/dist/plugins/blog/client/components/loading/page-header-skeleton.mjs +11 -0
  73. package/dist/plugins/blog/client/components/loading/post-card-skeleton.cjs +22 -0
  74. package/dist/plugins/blog/client/components/loading/post-card-skeleton.mjs +20 -0
  75. package/dist/plugins/blog/client/components/loading/post-page-skeleton.cjs +56 -0
  76. package/dist/plugins/blog/client/components/loading/post-page-skeleton.mjs +54 -0
  77. package/dist/plugins/blog/client/components/pages/404-page.cjs +19 -0
  78. package/dist/plugins/blog/client/components/pages/404-page.mjs +17 -0
  79. package/dist/plugins/blog/client/components/pages/edit-post-page.cjs +41 -0
  80. package/dist/plugins/blog/client/components/pages/edit-post-page.internal.cjs +57 -0
  81. package/dist/plugins/blog/client/components/pages/edit-post-page.internal.mjs +55 -0
  82. package/dist/plugins/blog/client/components/pages/edit-post-page.mjs +39 -0
  83. package/dist/plugins/blog/client/components/pages/home-page.cjs +41 -0
  84. package/dist/plugins/blog/client/components/pages/home-page.internal.cjs +61 -0
  85. package/dist/plugins/blog/client/components/pages/home-page.internal.mjs +59 -0
  86. package/dist/plugins/blog/client/components/pages/home-page.mjs +39 -0
  87. package/dist/plugins/blog/client/components/pages/new-post-page.cjs +37 -0
  88. package/dist/plugins/blog/client/components/pages/new-post-page.internal.cjs +53 -0
  89. package/dist/plugins/blog/client/components/pages/new-post-page.internal.mjs +51 -0
  90. package/dist/plugins/blog/client/components/pages/new-post-page.mjs +35 -0
  91. package/dist/plugins/blog/client/components/pages/post-page.cjs +39 -0
  92. package/dist/plugins/blog/client/components/pages/post-page.internal.cjs +101 -0
  93. package/dist/plugins/blog/client/components/pages/post-page.internal.mjs +99 -0
  94. package/dist/plugins/blog/client/components/pages/post-page.mjs +37 -0
  95. package/dist/plugins/blog/client/components/pages/tag-page.cjs +39 -0
  96. package/dist/plugins/blog/client/components/pages/tag-page.internal.cjs +61 -0
  97. package/dist/plugins/blog/client/components/pages/tag-page.internal.mjs +59 -0
  98. package/dist/plugins/blog/client/components/pages/tag-page.mjs +37 -0
  99. package/dist/plugins/blog/client/components/shared/better-blog-attribution.cjs +24 -0
  100. package/dist/plugins/blog/client/components/shared/better-blog-attribution.mjs +22 -0
  101. package/dist/plugins/blog/client/components/shared/default-error.cjs +18 -0
  102. package/dist/plugins/blog/client/components/shared/default-error.mjs +16 -0
  103. package/dist/plugins/blog/client/components/shared/defaults.cjs +13 -0
  104. package/dist/plugins/blog/client/components/shared/defaults.mjs +10 -0
  105. package/dist/plugins/blog/client/components/shared/empty-list.cjs +21 -0
  106. package/dist/plugins/blog/client/components/shared/empty-list.mjs +19 -0
  107. package/dist/plugins/blog/client/components/shared/error-placeholder.cjs +24 -0
  108. package/dist/plugins/blog/client/components/shared/error-placeholder.mjs +22 -0
  109. package/dist/plugins/blog/client/components/shared/highlight-text.cjs +53 -0
  110. package/dist/plugins/blog/client/components/shared/highlight-text.mjs +51 -0
  111. package/dist/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
  112. package/dist/plugins/blog/client/components/shared/markdown-content.cjs +324 -0
  113. package/dist/plugins/blog/client/components/shared/markdown-content.mjs +315 -0
  114. package/dist/plugins/blog/client/components/shared/on-this-page.cjs +161 -0
  115. package/dist/plugins/blog/client/components/shared/on-this-page.mjs +158 -0
  116. package/dist/plugins/blog/client/components/shared/page-header.cjs +40 -0
  117. package/dist/plugins/blog/client/components/shared/page-header.mjs +38 -0
  118. package/dist/plugins/blog/client/components/shared/page-layout.cjs +24 -0
  119. package/dist/plugins/blog/client/components/shared/page-layout.mjs +22 -0
  120. package/dist/plugins/blog/client/components/shared/page-wrapper.cjs +23 -0
  121. package/dist/plugins/blog/client/components/shared/page-wrapper.mjs +21 -0
  122. package/dist/plugins/blog/client/components/shared/post-card.cjs +279 -0
  123. package/dist/plugins/blog/client/components/shared/post-card.mjs +277 -0
  124. package/dist/plugins/blog/client/components/shared/post-navigation.cjs +74 -0
  125. package/dist/plugins/blog/client/components/shared/post-navigation.mjs +72 -0
  126. package/dist/plugins/blog/client/components/shared/posts-list.cjs +48 -0
  127. package/dist/plugins/blog/client/components/shared/posts-list.mjs +46 -0
  128. package/dist/plugins/blog/client/components/shared/recent-posts-carousel.cjs +59 -0
  129. package/dist/plugins/blog/client/components/shared/recent-posts-carousel.mjs +57 -0
  130. package/dist/plugins/blog/client/components/shared/search-input.cjs +136 -0
  131. package/dist/plugins/blog/client/components/shared/search-input.mjs +117 -0
  132. package/dist/plugins/blog/client/components/shared/search-modal.cjs +135 -0
  133. package/dist/plugins/blog/client/components/shared/search-modal.mjs +116 -0
  134. package/dist/plugins/blog/client/components/shared/tags-list.cjs +22 -0
  135. package/dist/plugins/blog/client/components/shared/tags-list.mjs +20 -0
  136. package/dist/plugins/blog/client/components/shared/use-route-lifecycle.cjs +50 -0
  137. package/dist/plugins/blog/client/components/shared/use-route-lifecycle.mjs +48 -0
  138. package/dist/plugins/blog/client/hooks/blog-hooks.cjs +380 -0
  139. package/dist/plugins/blog/client/hooks/blog-hooks.mjs +368 -0
  140. package/dist/plugins/blog/client/hooks/index.cjs +17 -0
  141. package/dist/plugins/blog/client/hooks/index.d.cts +150 -0
  142. package/dist/plugins/blog/client/hooks/index.d.mts +150 -0
  143. package/dist/plugins/blog/client/hooks/index.d.ts +150 -0
  144. package/dist/plugins/blog/client/hooks/index.mjs +1 -0
  145. package/dist/plugins/blog/client/hooks/use-debounce.cjs +16 -0
  146. package/dist/plugins/blog/client/hooks/use-debounce.mjs +14 -0
  147. package/dist/plugins/blog/client/index.cjs +7 -0
  148. package/dist/plugins/blog/client/index.d.cts +414 -0
  149. package/dist/plugins/blog/client/index.d.mts +414 -0
  150. package/dist/plugins/blog/client/index.d.ts +414 -0
  151. package/dist/plugins/blog/client/index.mjs +1 -0
  152. package/dist/plugins/blog/client/localization/blog-card.cjs +7 -0
  153. package/dist/plugins/blog/client/localization/blog-card.mjs +5 -0
  154. package/dist/plugins/blog/client/localization/blog-common.cjs +10 -0
  155. package/dist/plugins/blog/client/localization/blog-common.mjs +8 -0
  156. package/dist/plugins/blog/client/localization/blog-forms.cjs +40 -0
  157. package/dist/plugins/blog/client/localization/blog-forms.mjs +38 -0
  158. package/dist/plugins/blog/client/localization/blog-list.cjs +18 -0
  159. package/dist/plugins/blog/client/localization/blog-list.mjs +16 -0
  160. package/dist/plugins/blog/client/localization/blog-post.cjs +13 -0
  161. package/dist/plugins/blog/client/localization/blog-post.mjs +11 -0
  162. package/dist/plugins/blog/client/localization/index.cjs +17 -0
  163. package/dist/plugins/blog/client/localization/index.mjs +15 -0
  164. package/dist/plugins/blog/client/plugin.cjs +462 -0
  165. package/dist/plugins/blog/client/plugin.mjs +460 -0
  166. package/dist/plugins/blog/client.css +3 -0
  167. package/dist/plugins/blog/db.cjs +90 -0
  168. package/dist/plugins/blog/db.mjs +88 -0
  169. package/dist/plugins/blog/query-keys.cjs +181 -0
  170. package/dist/plugins/blog/query-keys.d.cts +530 -0
  171. package/dist/plugins/blog/query-keys.d.mts +530 -0
  172. package/dist/plugins/blog/query-keys.d.ts +530 -0
  173. package/dist/plugins/blog/query-keys.mjs +179 -0
  174. package/dist/plugins/blog/schemas.cjs +39 -0
  175. package/dist/plugins/blog/schemas.mjs +35 -0
  176. package/dist/plugins/blog/style.css +22 -0
  177. package/dist/plugins/blog/utils.cjs +97 -0
  178. package/dist/plugins/blog/utils.mjs +87 -0
  179. package/dist/plugins/client/index.cjs +15 -0
  180. package/dist/plugins/client/index.d.cts +57 -0
  181. package/dist/plugins/client/index.d.mts +57 -0
  182. package/dist/plugins/client/index.d.ts +57 -0
  183. package/dist/plugins/client/index.mjs +9 -0
  184. package/dist/{shared/stack.3OUyGp_E.mjs → plugins/utils.mjs} +1 -1
  185. package/dist/shared/{stack.DORw_1ps.d.cts → stack.ByOugz9d.d.cts} +17 -1
  186. package/dist/shared/{stack.DORw_1ps.d.mts → stack.ByOugz9d.d.mts} +17 -1
  187. package/dist/shared/{stack.DORw_1ps.d.ts → stack.ByOugz9d.d.ts} +17 -1
  188. package/dist/shared/stack.CoPoHVfV.d.cts +76 -0
  189. package/dist/shared/stack.CoPoHVfV.d.mts +76 -0
  190. package/dist/shared/stack.CoPoHVfV.d.ts +76 -0
  191. package/package.json +102 -14
  192. package/src/__tests__/plugins.test.tsx +539 -0
  193. package/src/__tests__/sitemap.test.ts +60 -0
  194. package/src/api/index.ts +75 -0
  195. package/src/client/components/compose.tsx +116 -0
  196. package/src/client/components/error-boundary.tsx +30 -0
  197. package/src/client/components/index.tsx +2 -0
  198. package/src/client/index.ts +109 -0
  199. package/src/client/meta-utils.ts +228 -0
  200. package/src/client/path-utils.ts +38 -0
  201. package/src/client/sitemap-utils.ts +46 -0
  202. package/src/context/index.ts +1 -0
  203. package/src/context/provider.tsx +157 -0
  204. package/src/index.ts +1 -0
  205. package/src/plugins/api/index.ts +50 -0
  206. package/src/plugins/blog/api/index.ts +2 -0
  207. package/src/plugins/blog/api/plugin.ts +759 -0
  208. package/src/plugins/blog/client/components/forms/image-field.tsx +165 -0
  209. package/src/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
  210. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +136 -0
  211. package/src/plugins/blog/client/components/forms/post-forms.tsx +531 -0
  212. package/src/plugins/blog/client/components/forms/tags-multiselect.tsx +79 -0
  213. package/src/plugins/blog/client/components/index.tsx +11 -0
  214. package/src/plugins/blog/client/components/loading/form-page-skeleton.tsx +75 -0
  215. package/src/plugins/blog/client/components/loading/index.tsx +27 -0
  216. package/src/plugins/blog/client/components/loading/list-page-skeleton.tsx +38 -0
  217. package/src/plugins/blog/client/components/loading/page-header-skeleton.tsx +10 -0
  218. package/src/plugins/blog/client/components/loading/post-card-skeleton.tsx +30 -0
  219. package/src/plugins/blog/client/components/loading/post-page-skeleton.tsx +75 -0
  220. package/src/plugins/blog/client/components/pages/404-page.tsx +23 -0
  221. package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +60 -0
  222. package/src/plugins/blog/client/components/pages/edit-post-page.tsx +40 -0
  223. package/src/plugins/blog/client/components/pages/home-page.internal.tsx +71 -0
  224. package/src/plugins/blog/client/components/pages/home-page.tsx +42 -0
  225. package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +59 -0
  226. package/src/plugins/blog/client/components/pages/new-post-page.tsx +36 -0
  227. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +142 -0
  228. package/src/plugins/blog/client/components/pages/post-page.tsx +38 -0
  229. package/src/plugins/blog/client/components/pages/tag-page.internal.tsx +74 -0
  230. package/src/plugins/blog/client/components/pages/tag-page.tsx +38 -0
  231. package/src/plugins/blog/client/components/shared/better-blog-attribution.tsx +19 -0
  232. package/src/plugins/blog/client/components/shared/default-error.tsx +20 -0
  233. package/src/plugins/blog/client/components/shared/defaults.tsx +9 -0
  234. package/src/plugins/blog/client/components/shared/empty-list.tsx +25 -0
  235. package/src/plugins/blog/client/components/shared/error-placeholder.tsx +20 -0
  236. package/src/plugins/blog/client/components/shared/highlight-text.tsx +80 -0
  237. package/src/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
  238. package/src/plugins/blog/client/components/shared/markdown-content.tsx +448 -0
  239. package/src/plugins/blog/client/components/shared/on-this-page.tsx +234 -0
  240. package/src/plugins/blog/client/components/shared/page-header.tsx +35 -0
  241. package/src/plugins/blog/client/components/shared/page-layout.tsx +23 -0
  242. package/src/plugins/blog/client/components/shared/page-wrapper.tsx +32 -0
  243. package/src/plugins/blog/client/components/shared/post-card.tsx +308 -0
  244. package/src/plugins/blog/client/components/shared/post-navigation.tsx +98 -0
  245. package/src/plugins/blog/client/components/shared/posts-list.tsx +67 -0
  246. package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +79 -0
  247. package/src/plugins/blog/client/components/shared/search-input.tsx +146 -0
  248. package/src/plugins/blog/client/components/shared/search-modal.tsx +162 -0
  249. package/src/plugins/blog/client/components/shared/tags-list.tsx +34 -0
  250. package/src/plugins/blog/client/components/shared/use-route-lifecycle.tsx +68 -0
  251. package/src/plugins/blog/client/hooks/blog-hooks.tsx +623 -0
  252. package/src/plugins/blog/client/hooks/index.tsx +1 -0
  253. package/src/plugins/blog/client/hooks/use-debounce.ts +43 -0
  254. package/src/plugins/blog/client/index.ts +9 -0
  255. package/src/plugins/blog/client/localization/blog-card.ts +3 -0
  256. package/src/plugins/blog/client/localization/blog-common.ts +7 -0
  257. package/src/plugins/blog/client/localization/blog-forms.ts +45 -0
  258. package/src/plugins/blog/client/localization/blog-list.ts +14 -0
  259. package/src/plugins/blog/client/localization/blog-post.ts +9 -0
  260. package/src/plugins/blog/client/localization/index.ts +15 -0
  261. package/src/plugins/blog/client/overrides.ts +123 -0
  262. package/src/plugins/blog/client/plugin.tsx +672 -0
  263. package/src/plugins/blog/client.css +3 -0
  264. package/src/plugins/blog/db.ts +90 -0
  265. package/src/plugins/blog/query-keys.ts +267 -0
  266. package/src/plugins/blog/schemas.ts +39 -0
  267. package/src/plugins/blog/style.css +22 -0
  268. package/src/plugins/blog/types.ts +37 -0
  269. package/src/plugins/blog/utils.ts +144 -0
  270. package/src/plugins/client/index.ts +53 -0
  271. package/src/plugins/index.ts +0 -0
  272. package/src/plugins/utils.ts +35 -0
  273. package/src/types.ts +209 -0
  274. package/dist/plugins/index.cjs +0 -15
  275. package/dist/plugins/index.d.cts +0 -64
  276. package/dist/plugins/index.d.mts +0 -64
  277. package/dist/plugins/index.d.ts +0 -64
  278. package/dist/plugins/index.mjs +0 -11
  279. package/dist/shared/stack.DrUAVfIH.d.cts +0 -17
  280. package/dist/shared/stack.DrUAVfIH.d.mts +0 -17
  281. package/dist/shared/stack.DrUAVfIH.d.ts +0 -17
  282. /package/dist/{shared/stack.CktCg4PJ.cjs → plugins/utils.cjs} +0 -0
@@ -0,0 +1,460 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { defineClientPlugin, createApiClient } from '@btst/stack/plugins/client';
3
+ import { createRoute } from '@btst/yar';
4
+ import { createBlogQueryKeys } from '../query-keys.mjs';
5
+ import { HomePageComponent } from './components/pages/home-page.mjs';
6
+ import { NewPostPageComponent } from './components/pages/new-post-page.mjs';
7
+ import { EditPostPageComponent } from './components/pages/edit-post-page.mjs';
8
+ import { TagPageComponent } from './components/pages/tag-page.mjs';
9
+ import { PostPageComponent } from './components/pages/post-page.mjs';
10
+
11
+ function createPostsLoader(published, config) {
12
+ return async () => {
13
+ if (typeof window === "undefined") {
14
+ const { queryClient, apiBasePath, apiBaseURL, hooks } = config;
15
+ const context = {
16
+ path: published ? "/blog" : "/blog/drafts",
17
+ isSSR: true,
18
+ apiBaseURL,
19
+ apiBasePath
20
+ };
21
+ try {
22
+ if (hooks?.beforeLoadPosts) {
23
+ const canLoad = await hooks.beforeLoadPosts({ published }, context);
24
+ if (!canLoad) {
25
+ throw new Error("Load prevented by beforeLoadPosts hook");
26
+ }
27
+ }
28
+ const limit = 10;
29
+ const client = createApiClient({
30
+ baseURL: apiBaseURL,
31
+ basePath: apiBasePath
32
+ });
33
+ const queries = createBlogQueryKeys(client);
34
+ const listQuery = queries.posts.list({
35
+ query: void 0,
36
+ limit,
37
+ published
38
+ });
39
+ await queryClient.prefetchInfiniteQuery({
40
+ ...listQuery,
41
+ initialPageParam: 0
42
+ });
43
+ const tagsQuery = queries.tags.list();
44
+ await queryClient.prefetchQuery(tagsQuery);
45
+ if (hooks?.afterLoadPosts) {
46
+ const posts = queryClient.getQueryData(listQuery.queryKey) || null;
47
+ await hooks.afterLoadPosts(posts, { published }, context);
48
+ }
49
+ const queryState = queryClient.getQueryState(listQuery.queryKey);
50
+ if (queryState?.error) {
51
+ if (hooks?.onLoadError) {
52
+ const error = queryState.error instanceof Error ? queryState.error : new Error(String(queryState.error));
53
+ await hooks.onLoadError(error, context);
54
+ }
55
+ }
56
+ } catch (error) {
57
+ if (hooks?.onLoadError) {
58
+ await hooks.onLoadError(error, context);
59
+ }
60
+ }
61
+ }
62
+ };
63
+ }
64
+ function createPostLoader(slug, config) {
65
+ return async () => {
66
+ if (typeof window === "undefined") {
67
+ const { queryClient, apiBasePath, apiBaseURL, hooks } = config;
68
+ const context = {
69
+ path: `/blog/${slug}`,
70
+ params: { slug },
71
+ isSSR: true,
72
+ apiBaseURL,
73
+ apiBasePath
74
+ };
75
+ try {
76
+ if (hooks?.beforeLoadPost) {
77
+ const canLoad = await hooks.beforeLoadPost(slug, context);
78
+ if (!canLoad) {
79
+ throw new Error("Load prevented by beforeLoadPost hook");
80
+ }
81
+ }
82
+ const client = createApiClient({
83
+ baseURL: apiBaseURL,
84
+ basePath: apiBasePath
85
+ });
86
+ const queries = createBlogQueryKeys(client);
87
+ const postQuery = queries.posts.detail(slug);
88
+ await queryClient.prefetchQuery(postQuery);
89
+ if (hooks?.afterLoadPost) {
90
+ const post = queryClient.getQueryData(postQuery.queryKey) || null;
91
+ await hooks.afterLoadPost(post, slug, context);
92
+ }
93
+ const queryState = queryClient.getQueryState(postQuery.queryKey);
94
+ if (queryState?.error) {
95
+ if (hooks?.onLoadError) {
96
+ const error = queryState.error instanceof Error ? queryState.error : new Error(String(queryState.error));
97
+ await hooks.onLoadError(error, context);
98
+ }
99
+ }
100
+ } catch (error) {
101
+ if (hooks?.onLoadError) {
102
+ await hooks.onLoadError(error, context);
103
+ }
104
+ }
105
+ }
106
+ };
107
+ }
108
+ function createTagLoader(tagSlug, config) {
109
+ return async () => {
110
+ if (typeof window === "undefined") {
111
+ const { queryClient, apiBasePath, apiBaseURL, hooks } = config;
112
+ const context = {
113
+ path: `/blog/tag/${tagSlug}`,
114
+ params: { tagSlug },
115
+ isSSR: true,
116
+ apiBaseURL,
117
+ apiBasePath
118
+ };
119
+ try {
120
+ const limit = 10;
121
+ const client = createApiClient({
122
+ baseURL: apiBaseURL,
123
+ basePath: apiBasePath
124
+ });
125
+ const queries = createBlogQueryKeys(client);
126
+ const listQuery = queries.posts.list({
127
+ query: void 0,
128
+ limit,
129
+ published: true,
130
+ tagSlug
131
+ });
132
+ await queryClient.prefetchInfiniteQuery({
133
+ ...listQuery,
134
+ initialPageParam: 0
135
+ });
136
+ const tagsQuery = queries.tags.list();
137
+ await queryClient.prefetchQuery(tagsQuery);
138
+ if (hooks?.onLoadError) {
139
+ const queryState = queryClient.getQueryState(listQuery.queryKey);
140
+ if (queryState?.error) {
141
+ const error = queryState.error instanceof Error ? queryState.error : new Error(String(queryState.error));
142
+ await hooks.onLoadError(error, context);
143
+ }
144
+ }
145
+ } catch (error) {
146
+ if (hooks?.onLoadError) {
147
+ await hooks.onLoadError(error, context);
148
+ }
149
+ }
150
+ }
151
+ };
152
+ }
153
+ function createPostsListMeta(published, config) {
154
+ return () => {
155
+ const { siteBaseURL, siteBasePath, seo } = config;
156
+ const path = published ? "/blog" : "/blog/drafts";
157
+ const fullUrl = `${siteBaseURL}${siteBasePath}${path}`;
158
+ const title = published ? "Blog" : "Draft Posts";
159
+ const description = published ? "Read our latest articles, insights, and updates on web development, technology, and more." : "View and manage your draft blog posts.";
160
+ return [
161
+ // Primary meta tags
162
+ { title },
163
+ { name: "title", content: title },
164
+ { name: "description", content: description },
165
+ {
166
+ name: "keywords",
167
+ content: "blog, articles, technology, web development, insights"
168
+ },
169
+ ...seo?.author ? [{ name: "author", content: seo.author }] : [],
170
+ {
171
+ name: "robots",
172
+ content: published ? "index, follow" : "noindex, nofollow"
173
+ },
174
+ // Open Graph / Facebook
175
+ { property: "og:type", content: "website" },
176
+ { property: "og:title", content: title },
177
+ { property: "og:description", content: description },
178
+ { property: "og:url", content: fullUrl },
179
+ ...seo?.siteName ? [{ property: "og:site_name", content: seo.siteName }] : [],
180
+ ...seo?.locale ? [{ property: "og:locale", content: seo.locale }] : [],
181
+ ...seo?.defaultImage ? [{ property: "og:image", content: seo.defaultImage }] : [],
182
+ // Twitter Card
183
+ { name: "twitter:card", content: "summary_large_image" },
184
+ { name: "twitter:title", content: title },
185
+ { name: "twitter:description", content: description },
186
+ ...seo?.twitterHandle ? [{ name: "twitter:site", content: seo.twitterHandle }] : []
187
+ ];
188
+ };
189
+ }
190
+ function createPostMeta(slug, config) {
191
+ return () => {
192
+ const { queryClient } = config;
193
+ const { apiBaseURL, apiBasePath, siteBaseURL, siteBasePath, seo } = config;
194
+ const queries = createBlogQueryKeys(
195
+ createApiClient({
196
+ baseURL: apiBaseURL,
197
+ basePath: apiBasePath
198
+ })
199
+ );
200
+ const post = queryClient.getQueryData(
201
+ queries.posts.detail(slug).queryKey
202
+ );
203
+ if (!post) {
204
+ return [
205
+ { title: "Unknown route" },
206
+ { name: "title", content: "Unknown route" },
207
+ { name: "robots", content: "noindex" }
208
+ ];
209
+ }
210
+ const fullUrl = `${siteBaseURL}${siteBasePath}/blog/${post.slug}`;
211
+ const title = post.title;
212
+ const description = post.excerpt || post.content.substring(0, 160);
213
+ const publishedTime = post.publishedAt ? new Date(post.publishedAt).toISOString() : new Date(post.createdAt).toISOString();
214
+ const modifiedTime = new Date(post.updatedAt).toISOString();
215
+ const image = post.image || seo?.defaultImage;
216
+ return [
217
+ // Primary meta tags
218
+ { title },
219
+ { name: "title", content: title },
220
+ { name: "description", content: description },
221
+ ...post.authorId || seo?.author ? [{ name: "author", content: post.authorId || seo?.author }] : [],
222
+ {
223
+ name: "robots",
224
+ content: post.published ? "index, follow" : "noindex, nofollow"
225
+ },
226
+ {
227
+ name: "keywords",
228
+ content: `blog, article, ${post.slug.replace(/-/g, ", ")}`
229
+ },
230
+ // Open Graph / Facebook
231
+ { property: "og:type", content: "article" },
232
+ { property: "og:title", content: title },
233
+ { property: "og:description", content: description },
234
+ { property: "og:url", content: fullUrl },
235
+ ...seo?.siteName ? [{ property: "og:site_name", content: seo.siteName }] : [],
236
+ ...seo?.locale ? [{ property: "og:locale", content: seo.locale }] : [],
237
+ ...image ? [{ property: "og:image", content: image }] : [],
238
+ ...image ? [
239
+ { property: "og:image:width", content: "1200" },
240
+ { property: "og:image:height", content: "630" },
241
+ { property: "og:image:alt", content: title }
242
+ ] : [],
243
+ // Article-specific Open Graph tags
244
+ { property: "article:published_time", content: publishedTime },
245
+ { property: "article:modified_time", content: modifiedTime },
246
+ ...post.authorId ? [{ property: "article:author", content: post.authorId }] : [],
247
+ // Twitter Card
248
+ {
249
+ name: "twitter:card",
250
+ content: image ? "summary_large_image" : "summary"
251
+ },
252
+ { name: "twitter:title", content: title },
253
+ { name: "twitter:description", content: description },
254
+ ...seo?.twitterHandle ? [{ name: "twitter:site", content: seo.twitterHandle }] : [],
255
+ ...post.authorId || seo?.twitterHandle ? [
256
+ {
257
+ name: "twitter:creator",
258
+ content: post.authorId || seo?.twitterHandle
259
+ }
260
+ ] : [],
261
+ ...image ? [{ name: "twitter:image", content: image }] : [],
262
+ ...image ? [{ name: "twitter:image:alt", content: title }] : [],
263
+ // Additional SEO tags
264
+ { name: "publish_date", content: publishedTime }
265
+ ];
266
+ };
267
+ }
268
+ function createTagMeta(tagSlug, config) {
269
+ return () => {
270
+ const { queryClient } = config;
271
+ const { apiBaseURL, apiBasePath, siteBaseURL, siteBasePath, seo } = config;
272
+ const queries = createBlogQueryKeys(
273
+ createApiClient({
274
+ baseURL: apiBaseURL,
275
+ basePath: apiBasePath
276
+ })
277
+ );
278
+ const tags = queryClient.getQueryData(
279
+ queries.tags.list().queryKey
280
+ );
281
+ const tag = tags?.find((t) => t.slug === tagSlug);
282
+ if (!tag) {
283
+ return [
284
+ { title: "Unknown route" },
285
+ { name: "title", content: "Unknown route" },
286
+ { name: "robots", content: "noindex" }
287
+ ];
288
+ }
289
+ const fullUrl = `${siteBaseURL}${siteBasePath}/blog/tag/${tag.slug}`;
290
+ const title = `${tag.name} Posts`;
291
+ const description = `Browse all ${tag.name} posts`;
292
+ return [
293
+ { title },
294
+ { name: "title", content: title },
295
+ { name: "description", content: description },
296
+ { name: "robots", content: "index, follow" },
297
+ { name: "keywords", content: `blog, ${tag.name}, articles` },
298
+ { property: "og:type", content: "website" },
299
+ { property: "og:title", content: title },
300
+ { property: "og:description", content: description },
301
+ { property: "og:url", content: fullUrl },
302
+ ...seo?.siteName ? [{ property: "og:site_name", content: seo.siteName }] : [],
303
+ ...seo?.defaultImage ? [{ property: "og:image", content: seo.defaultImage }] : [],
304
+ { name: "twitter:card", content: "summary" },
305
+ { name: "twitter:title", content: title }
306
+ ];
307
+ };
308
+ }
309
+ function createNewPostMeta(config) {
310
+ return () => {
311
+ const { siteBaseURL, siteBasePath } = config;
312
+ const fullUrl = `${siteBaseURL}${siteBasePath}/blog/new`;
313
+ const title = "Create New Post";
314
+ return [
315
+ { title },
316
+ { name: "title", content: title },
317
+ { name: "description", content: "Write and publish a new blog post." },
318
+ { name: "robots", content: "noindex, nofollow" },
319
+ // Open Graph
320
+ { property: "og:type", content: "website" },
321
+ { property: "og:title", content: title },
322
+ {
323
+ property: "og:description",
324
+ content: "Write and publish a new blog post."
325
+ },
326
+ { property: "og:url", content: fullUrl },
327
+ // Twitter
328
+ { name: "twitter:card", content: "summary" },
329
+ { name: "twitter:title", content: title }
330
+ ];
331
+ };
332
+ }
333
+ function createEditPostMeta(slug, config) {
334
+ return () => {
335
+ const { queryClient } = config;
336
+ const { apiBaseURL, apiBasePath, siteBaseURL, siteBasePath } = config;
337
+ const queries = createBlogQueryKeys(
338
+ createApiClient({
339
+ baseURL: apiBaseURL,
340
+ basePath: apiBasePath
341
+ })
342
+ );
343
+ const post = queryClient.getQueryData(
344
+ queries.posts.detail(slug).queryKey
345
+ );
346
+ const fullUrl = `${siteBaseURL}${siteBasePath}/blog/${slug}/edit`;
347
+ const title = post ? `Edit: ${post.title}` : "Unknown route";
348
+ return [
349
+ { title },
350
+ { name: "title", content: title },
351
+ { name: "description", content: "Edit your blog post." },
352
+ { name: "robots", content: "noindex, nofollow" },
353
+ // Open Graph
354
+ { property: "og:type", content: "website" },
355
+ { property: "og:title", content: title },
356
+ { property: "og:url", content: fullUrl },
357
+ // Twitter
358
+ { name: "twitter:card", content: "summary" },
359
+ { name: "twitter:title", content: title }
360
+ ];
361
+ };
362
+ }
363
+ const blogClientPlugin = (config) => defineClientPlugin({
364
+ name: "blog",
365
+ routes: () => ({
366
+ posts: createRoute("/blog", () => {
367
+ return {
368
+ PageComponent: () => /* @__PURE__ */ jsx(HomePageComponent, { published: true }),
369
+ loader: createPostsLoader(true, config),
370
+ meta: createPostsListMeta(true, config)
371
+ };
372
+ }),
373
+ drafts: createRoute("/blog/drafts", () => {
374
+ return {
375
+ PageComponent: () => /* @__PURE__ */ jsx(HomePageComponent, { published: false }),
376
+ loader: createPostsLoader(false, config),
377
+ meta: createPostsListMeta(false, config)
378
+ };
379
+ }),
380
+ newPost: createRoute("/blog/new", () => {
381
+ return {
382
+ PageComponent: NewPostPageComponent,
383
+ meta: createNewPostMeta(config)
384
+ };
385
+ }),
386
+ editPost: createRoute("/blog/:slug/edit", ({ params: { slug } }) => {
387
+ return {
388
+ PageComponent: () => /* @__PURE__ */ jsx(EditPostPageComponent, { slug }),
389
+ loader: createPostLoader(slug, config),
390
+ meta: createEditPostMeta(slug, config)
391
+ };
392
+ }),
393
+ tag: createRoute("/blog/tag/:tagSlug", ({ params: { tagSlug } }) => {
394
+ return {
395
+ PageComponent: () => /* @__PURE__ */ jsx(TagPageComponent, { tagSlug }),
396
+ loader: createTagLoader(tagSlug, config),
397
+ meta: createTagMeta(tagSlug, config)
398
+ };
399
+ }),
400
+ post: createRoute("/blog/:slug", ({ params: { slug } }) => {
401
+ return {
402
+ PageComponent: () => /* @__PURE__ */ jsx(PostPageComponent, { slug }),
403
+ loader: createPostLoader(slug, config),
404
+ meta: createPostMeta(slug, config)
405
+ };
406
+ })
407
+ }),
408
+ sitemap: async () => {
409
+ const origin = `${config.siteBaseURL}${config.siteBasePath}`;
410
+ const indexUrl = `${origin}/blog`;
411
+ const client = createApiClient({
412
+ baseURL: config.apiBaseURL,
413
+ basePath: config.apiBasePath
414
+ });
415
+ const limit = 100;
416
+ let offset = 0;
417
+ const posts = [];
418
+ while (true) {
419
+ const res = await client("/posts", {
420
+ method: "GET",
421
+ query: {
422
+ offset,
423
+ limit,
424
+ published: "true"
425
+ }
426
+ });
427
+ const page = res.data ?? [];
428
+ posts.push(...page);
429
+ if (page.length < limit) break;
430
+ offset += limit;
431
+ }
432
+ const getLastModified = (p) => {
433
+ const dates = [p.updatedAt, p.publishedAt, p.createdAt].filter(
434
+ Boolean
435
+ );
436
+ if (dates.length === 0) return void 0;
437
+ const times = dates.map((d) => new Date(d).getTime()).filter((t) => !Number.isNaN(t));
438
+ if (times.length === 0) return void 0;
439
+ return new Date(Math.max(...times));
440
+ };
441
+ const latestTime = posts.map((p) => getLastModified(p)?.getTime() ?? 0).reduce((a, b) => Math.max(a, b), 0);
442
+ const entries = [
443
+ {
444
+ url: indexUrl,
445
+ lastModified: latestTime ? new Date(latestTime) : void 0,
446
+ changeFrequency: "daily",
447
+ priority: 0.7
448
+ },
449
+ ...posts.map((p) => ({
450
+ url: `${origin}/blog/${p.slug}`,
451
+ lastModified: getLastModified(p),
452
+ changeFrequency: "monthly",
453
+ priority: 0.6
454
+ }))
455
+ ];
456
+ return entries;
457
+ }
458
+ });
459
+
460
+ export { blogClientPlugin };
@@ -0,0 +1,3 @@
1
+ @import "./client/components/forms/markdown-editor-styles.css";
2
+ @import "./client/components/shared/markdown-content-styles.css";
3
+ @import "@milkdown/crepe/theme/common/style.css";
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ const db = require('@btst/db');
4
+
5
+ const blogSchema = db.createDbPlugin("blog", {
6
+ post: {
7
+ modelName: "post",
8
+ fields: {
9
+ title: {
10
+ type: "string",
11
+ required: true
12
+ },
13
+ content: {
14
+ type: "string",
15
+ required: true
16
+ },
17
+ excerpt: {
18
+ type: "string",
19
+ defaultValue: ""
20
+ },
21
+ slug: {
22
+ type: "string",
23
+ required: true,
24
+ unique: true
25
+ },
26
+ image: {
27
+ type: "string",
28
+ required: false
29
+ },
30
+ published: {
31
+ type: "boolean",
32
+ defaultValue: false
33
+ },
34
+ publishedAt: {
35
+ type: "date",
36
+ required: false
37
+ },
38
+ authorId: {
39
+ type: "string",
40
+ required: false
41
+ },
42
+ createdAt: {
43
+ type: "date",
44
+ defaultValue: () => /* @__PURE__ */ new Date()
45
+ },
46
+ updatedAt: {
47
+ type: "date",
48
+ defaultValue: () => /* @__PURE__ */ new Date()
49
+ }
50
+ }
51
+ },
52
+ tag: {
53
+ modelName: "tag",
54
+ fields: {
55
+ name: {
56
+ type: "string",
57
+ required: true,
58
+ unique: true
59
+ },
60
+ slug: {
61
+ type: "string",
62
+ required: true,
63
+ unique: true
64
+ },
65
+ createdAt: {
66
+ type: "date",
67
+ defaultValue: () => /* @__PURE__ */ new Date()
68
+ },
69
+ updatedAt: {
70
+ type: "date",
71
+ defaultValue: () => /* @__PURE__ */ new Date()
72
+ }
73
+ }
74
+ },
75
+ postTag: {
76
+ modelName: "postTag",
77
+ fields: {
78
+ postId: {
79
+ type: "string",
80
+ required: true
81
+ },
82
+ tagId: {
83
+ type: "string",
84
+ required: true
85
+ }
86
+ }
87
+ }
88
+ });
89
+
90
+ exports.blogSchema = blogSchema;
@@ -0,0 +1,88 @@
1
+ import { createDbPlugin } from '@btst/db';
2
+
3
+ const blogSchema = createDbPlugin("blog", {
4
+ post: {
5
+ modelName: "post",
6
+ fields: {
7
+ title: {
8
+ type: "string",
9
+ required: true
10
+ },
11
+ content: {
12
+ type: "string",
13
+ required: true
14
+ },
15
+ excerpt: {
16
+ type: "string",
17
+ defaultValue: ""
18
+ },
19
+ slug: {
20
+ type: "string",
21
+ required: true,
22
+ unique: true
23
+ },
24
+ image: {
25
+ type: "string",
26
+ required: false
27
+ },
28
+ published: {
29
+ type: "boolean",
30
+ defaultValue: false
31
+ },
32
+ publishedAt: {
33
+ type: "date",
34
+ required: false
35
+ },
36
+ authorId: {
37
+ type: "string",
38
+ required: false
39
+ },
40
+ createdAt: {
41
+ type: "date",
42
+ defaultValue: () => /* @__PURE__ */ new Date()
43
+ },
44
+ updatedAt: {
45
+ type: "date",
46
+ defaultValue: () => /* @__PURE__ */ new Date()
47
+ }
48
+ }
49
+ },
50
+ tag: {
51
+ modelName: "tag",
52
+ fields: {
53
+ name: {
54
+ type: "string",
55
+ required: true,
56
+ unique: true
57
+ },
58
+ slug: {
59
+ type: "string",
60
+ required: true,
61
+ unique: true
62
+ },
63
+ createdAt: {
64
+ type: "date",
65
+ defaultValue: () => /* @__PURE__ */ new Date()
66
+ },
67
+ updatedAt: {
68
+ type: "date",
69
+ defaultValue: () => /* @__PURE__ */ new Date()
70
+ }
71
+ }
72
+ },
73
+ postTag: {
74
+ modelName: "postTag",
75
+ fields: {
76
+ postId: {
77
+ type: "string",
78
+ required: true
79
+ },
80
+ tagId: {
81
+ type: "string",
82
+ required: true
83
+ }
84
+ }
85
+ }
86
+ });
87
+
88
+ export { blogSchema };