@btst/stack 1.0.0 → 1.1.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.
- package/README.md +237 -2
- package/dist/api/index.cjs +2 -2
- package/dist/api/index.d.cts +2 -2
- package/dist/api/index.d.mts +2 -2
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.mjs +2 -2
- package/dist/client/components/compose.cjs +68 -0
- package/dist/client/components/compose.mjs +65 -0
- package/dist/client/components/error-boundary.cjs +24 -0
- package/dist/client/components/error-boundary.mjs +22 -0
- package/dist/client/components/index.cjs +10 -0
- package/dist/client/components/index.d.cts +52 -0
- package/dist/client/components/index.d.mts +52 -0
- package/dist/client/components/index.d.ts +52 -0
- package/dist/client/components/index.mjs +2 -0
- package/dist/client/index.cjs +21 -16
- package/dist/client/index.d.cts +102 -14
- package/dist/client/index.d.mts +102 -14
- package/dist/client/index.d.ts +102 -14
- package/dist/client/index.mjs +19 -10
- package/dist/client/meta-utils.cjs +162 -0
- package/dist/client/meta-utils.mjs +160 -0
- package/dist/client/sitemap-utils.cjs +14 -0
- package/dist/client/sitemap-utils.mjs +12 -0
- package/dist/context/index.cjs +6 -51
- package/dist/context/index.d.cts +26 -26
- package/dist/context/index.d.mts +26 -26
- package/dist/context/index.d.ts +26 -26
- package/dist/context/index.mjs +1 -50
- package/dist/context/provider.cjs +51 -0
- package/dist/context/provider.mjs +46 -0
- package/dist/index.cjs +0 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +0 -2
- package/dist/plugins/api/index.cjs +15 -0
- package/dist/plugins/api/index.d.cts +41 -0
- package/dist/plugins/api/index.d.mts +41 -0
- package/dist/plugins/api/index.d.ts +41 -0
- package/dist/plugins/api/index.mjs +9 -0
- package/dist/plugins/blog/api/index.cjs +11 -0
- package/dist/plugins/blog/api/index.d.cts +7 -0
- package/dist/plugins/blog/api/index.d.mts +7 -0
- package/dist/plugins/blog/api/index.d.ts +7 -0
- package/dist/plugins/blog/api/index.mjs +2 -0
- package/dist/plugins/blog/api/plugin.cjs +569 -0
- package/dist/plugins/blog/api/plugin.mjs +565 -0
- package/dist/plugins/blog/client/components/forms/image-field.cjs +133 -0
- package/dist/plugins/blog/client/components/forms/image-field.mjs +131 -0
- package/dist/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
- package/dist/plugins/blog/client/components/forms/markdown-editor.cjs +106 -0
- package/dist/plugins/blog/client/components/forms/markdown-editor.mjs +104 -0
- package/dist/plugins/blog/client/components/forms/post-forms.cjs +401 -0
- package/dist/plugins/blog/client/components/forms/post-forms.mjs +398 -0
- package/dist/plugins/blog/client/components/forms/tags-multiselect.cjs +71 -0
- package/dist/plugins/blog/client/components/forms/tags-multiselect.mjs +65 -0
- package/dist/plugins/blog/client/components/index.cjs +17 -0
- package/dist/plugins/blog/client/components/index.d.cts +22 -0
- package/dist/plugins/blog/client/components/index.d.mts +22 -0
- package/dist/plugins/blog/client/components/index.d.ts +22 -0
- package/dist/plugins/blog/client/components/index.mjs +12 -0
- package/dist/plugins/blog/client/components/loading/form-page-skeleton.cjs +62 -0
- package/dist/plugins/blog/client/components/loading/form-page-skeleton.mjs +60 -0
- package/dist/plugins/blog/client/components/loading/index.cjs +20 -0
- package/dist/plugins/blog/client/components/loading/index.mjs +16 -0
- package/dist/plugins/blog/client/components/loading/list-page-skeleton.cjs +26 -0
- package/dist/plugins/blog/client/components/loading/list-page-skeleton.mjs +24 -0
- package/dist/plugins/blog/client/components/loading/page-header-skeleton.cjs +13 -0
- package/dist/plugins/blog/client/components/loading/page-header-skeleton.mjs +11 -0
- package/dist/plugins/blog/client/components/loading/post-card-skeleton.cjs +22 -0
- package/dist/plugins/blog/client/components/loading/post-card-skeleton.mjs +20 -0
- package/dist/plugins/blog/client/components/loading/post-page-skeleton.cjs +56 -0
- package/dist/plugins/blog/client/components/loading/post-page-skeleton.mjs +54 -0
- package/dist/plugins/blog/client/components/pages/404-page.cjs +19 -0
- package/dist/plugins/blog/client/components/pages/404-page.mjs +17 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.cjs +41 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.internal.cjs +57 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.internal.mjs +55 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.mjs +39 -0
- package/dist/plugins/blog/client/components/pages/home-page.cjs +41 -0
- package/dist/plugins/blog/client/components/pages/home-page.internal.cjs +61 -0
- package/dist/plugins/blog/client/components/pages/home-page.internal.mjs +59 -0
- package/dist/plugins/blog/client/components/pages/home-page.mjs +39 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.cjs +37 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.internal.cjs +53 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.internal.mjs +51 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.mjs +35 -0
- package/dist/plugins/blog/client/components/pages/post-page.cjs +39 -0
- package/dist/plugins/blog/client/components/pages/post-page.internal.cjs +101 -0
- package/dist/plugins/blog/client/components/pages/post-page.internal.mjs +99 -0
- package/dist/plugins/blog/client/components/pages/post-page.mjs +37 -0
- package/dist/plugins/blog/client/components/pages/tag-page.cjs +39 -0
- package/dist/plugins/blog/client/components/pages/tag-page.internal.cjs +61 -0
- package/dist/plugins/blog/client/components/pages/tag-page.internal.mjs +59 -0
- package/dist/plugins/blog/client/components/pages/tag-page.mjs +37 -0
- package/dist/plugins/blog/client/components/shared/better-blog-attribution.cjs +24 -0
- package/dist/plugins/blog/client/components/shared/better-blog-attribution.mjs +22 -0
- package/dist/plugins/blog/client/components/shared/default-error.cjs +18 -0
- package/dist/plugins/blog/client/components/shared/default-error.mjs +16 -0
- package/dist/plugins/blog/client/components/shared/defaults.cjs +13 -0
- package/dist/plugins/blog/client/components/shared/defaults.mjs +10 -0
- package/dist/plugins/blog/client/components/shared/empty-list.cjs +21 -0
- package/dist/plugins/blog/client/components/shared/empty-list.mjs +19 -0
- package/dist/plugins/blog/client/components/shared/error-placeholder.cjs +24 -0
- package/dist/plugins/blog/client/components/shared/error-placeholder.mjs +22 -0
- package/dist/plugins/blog/client/components/shared/highlight-text.cjs +53 -0
- package/dist/plugins/blog/client/components/shared/highlight-text.mjs +51 -0
- package/dist/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
- package/dist/plugins/blog/client/components/shared/markdown-content.cjs +324 -0
- package/dist/plugins/blog/client/components/shared/markdown-content.mjs +315 -0
- package/dist/plugins/blog/client/components/shared/on-this-page.cjs +161 -0
- package/dist/plugins/blog/client/components/shared/on-this-page.mjs +158 -0
- package/dist/plugins/blog/client/components/shared/page-header.cjs +40 -0
- package/dist/plugins/blog/client/components/shared/page-header.mjs +38 -0
- package/dist/plugins/blog/client/components/shared/page-layout.cjs +24 -0
- package/dist/plugins/blog/client/components/shared/page-layout.mjs +22 -0
- package/dist/plugins/blog/client/components/shared/page-wrapper.cjs +23 -0
- package/dist/plugins/blog/client/components/shared/page-wrapper.mjs +21 -0
- package/dist/plugins/blog/client/components/shared/post-card.cjs +279 -0
- package/dist/plugins/blog/client/components/shared/post-card.mjs +277 -0
- package/dist/plugins/blog/client/components/shared/post-navigation.cjs +74 -0
- package/dist/plugins/blog/client/components/shared/post-navigation.mjs +72 -0
- package/dist/plugins/blog/client/components/shared/posts-list.cjs +48 -0
- package/dist/plugins/blog/client/components/shared/posts-list.mjs +46 -0
- package/dist/plugins/blog/client/components/shared/recent-posts-carousel.cjs +59 -0
- package/dist/plugins/blog/client/components/shared/recent-posts-carousel.mjs +57 -0
- package/dist/plugins/blog/client/components/shared/search-input.cjs +136 -0
- package/dist/plugins/blog/client/components/shared/search-input.mjs +117 -0
- package/dist/plugins/blog/client/components/shared/search-modal.cjs +135 -0
- package/dist/plugins/blog/client/components/shared/search-modal.mjs +116 -0
- package/dist/plugins/blog/client/components/shared/tags-list.cjs +22 -0
- package/dist/plugins/blog/client/components/shared/tags-list.mjs +20 -0
- package/dist/plugins/blog/client/components/shared/use-route-lifecycle.cjs +50 -0
- package/dist/plugins/blog/client/components/shared/use-route-lifecycle.mjs +48 -0
- package/dist/plugins/blog/client/hooks/blog-hooks.cjs +380 -0
- package/dist/plugins/blog/client/hooks/blog-hooks.mjs +368 -0
- package/dist/plugins/blog/client/hooks/index.cjs +17 -0
- package/dist/plugins/blog/client/hooks/index.d.cts +150 -0
- package/dist/plugins/blog/client/hooks/index.d.mts +150 -0
- package/dist/plugins/blog/client/hooks/index.d.ts +150 -0
- package/dist/plugins/blog/client/hooks/index.mjs +1 -0
- package/dist/plugins/blog/client/hooks/use-debounce.cjs +16 -0
- package/dist/plugins/blog/client/hooks/use-debounce.mjs +14 -0
- package/dist/plugins/blog/client/index.cjs +7 -0
- package/dist/plugins/blog/client/index.d.cts +414 -0
- package/dist/plugins/blog/client/index.d.mts +414 -0
- package/dist/plugins/blog/client/index.d.ts +414 -0
- package/dist/plugins/blog/client/index.mjs +1 -0
- package/dist/plugins/blog/client/localization/blog-card.cjs +7 -0
- package/dist/plugins/blog/client/localization/blog-card.mjs +5 -0
- package/dist/plugins/blog/client/localization/blog-common.cjs +10 -0
- package/dist/plugins/blog/client/localization/blog-common.mjs +8 -0
- package/dist/plugins/blog/client/localization/blog-forms.cjs +40 -0
- package/dist/plugins/blog/client/localization/blog-forms.mjs +38 -0
- package/dist/plugins/blog/client/localization/blog-list.cjs +18 -0
- package/dist/plugins/blog/client/localization/blog-list.mjs +16 -0
- package/dist/plugins/blog/client/localization/blog-post.cjs +13 -0
- package/dist/plugins/blog/client/localization/blog-post.mjs +11 -0
- package/dist/plugins/blog/client/localization/index.cjs +17 -0
- package/dist/plugins/blog/client/localization/index.mjs +15 -0
- package/dist/plugins/blog/client/plugin.cjs +462 -0
- package/dist/plugins/blog/client/plugin.mjs +460 -0
- package/dist/plugins/blog/client.css +3 -0
- package/dist/plugins/blog/db.cjs +90 -0
- package/dist/plugins/blog/db.mjs +88 -0
- package/dist/plugins/blog/query-keys.cjs +181 -0
- package/dist/plugins/blog/query-keys.d.cts +530 -0
- package/dist/plugins/blog/query-keys.d.mts +530 -0
- package/dist/plugins/blog/query-keys.d.ts +530 -0
- package/dist/plugins/blog/query-keys.mjs +179 -0
- package/dist/plugins/blog/schemas.cjs +39 -0
- package/dist/plugins/blog/schemas.mjs +35 -0
- package/dist/plugins/blog/style.css +22 -0
- package/dist/plugins/blog/utils.cjs +97 -0
- package/dist/plugins/blog/utils.mjs +87 -0
- package/dist/plugins/client/index.cjs +15 -0
- package/dist/plugins/client/index.d.cts +57 -0
- package/dist/plugins/client/index.d.mts +57 -0
- package/dist/plugins/client/index.d.ts +57 -0
- package/dist/plugins/client/index.mjs +9 -0
- package/dist/{shared/stack.Br2KMECJ.cjs → plugins/utils.cjs} +1 -8
- package/dist/{shared/stack.CwGEQ10b.mjs → plugins/utils.mjs} +2 -8
- package/dist/shared/{stack.Dva9muUy.d.cts → stack.ByOugz9d.d.cts} +17 -23
- package/dist/shared/{stack.Dva9muUy.d.mts → stack.ByOugz9d.d.mts} +17 -23
- package/dist/shared/{stack.Dva9muUy.d.ts → stack.ByOugz9d.d.ts} +17 -23
- package/dist/shared/stack.Cr2JoQdo.d.cts +76 -0
- package/dist/shared/stack.Cr2JoQdo.d.mts +76 -0
- package/dist/shared/stack.Cr2JoQdo.d.ts +76 -0
- package/package.json +104 -16
- package/src/__tests__/plugins.test.tsx +539 -0
- package/src/__tests__/sitemap.test.ts +60 -0
- package/src/api/index.ts +73 -0
- package/src/client/components/compose.tsx +116 -0
- package/src/client/components/error-boundary.tsx +30 -0
- package/src/client/components/index.tsx +2 -0
- package/src/client/index.ts +107 -0
- package/src/client/meta-utils.ts +228 -0
- package/src/client/sitemap-utils.ts +46 -0
- package/src/context/index.ts +1 -0
- package/src/context/provider.tsx +157 -0
- package/src/index.ts +1 -0
- package/src/plugins/api/index.ts +51 -0
- package/src/plugins/blog/api/index.ts +2 -0
- package/src/plugins/blog/api/plugin.ts +759 -0
- package/src/plugins/blog/client/components/forms/image-field.tsx +165 -0
- package/src/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
- package/src/plugins/blog/client/components/forms/markdown-editor.tsx +136 -0
- package/src/plugins/blog/client/components/forms/post-forms.tsx +531 -0
- package/src/plugins/blog/client/components/forms/tags-multiselect.tsx +79 -0
- package/src/plugins/blog/client/components/index.tsx +11 -0
- package/src/plugins/blog/client/components/loading/form-page-skeleton.tsx +75 -0
- package/src/plugins/blog/client/components/loading/index.tsx +27 -0
- package/src/plugins/blog/client/components/loading/list-page-skeleton.tsx +38 -0
- package/src/plugins/blog/client/components/loading/page-header-skeleton.tsx +10 -0
- package/src/plugins/blog/client/components/loading/post-card-skeleton.tsx +30 -0
- package/src/plugins/blog/client/components/loading/post-page-skeleton.tsx +75 -0
- package/src/plugins/blog/client/components/pages/404-page.tsx +23 -0
- package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +60 -0
- package/src/plugins/blog/client/components/pages/edit-post-page.tsx +40 -0
- package/src/plugins/blog/client/components/pages/home-page.internal.tsx +71 -0
- package/src/plugins/blog/client/components/pages/home-page.tsx +42 -0
- package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +59 -0
- package/src/plugins/blog/client/components/pages/new-post-page.tsx +36 -0
- package/src/plugins/blog/client/components/pages/post-page.internal.tsx +142 -0
- package/src/plugins/blog/client/components/pages/post-page.tsx +38 -0
- package/src/plugins/blog/client/components/pages/tag-page.internal.tsx +74 -0
- package/src/plugins/blog/client/components/pages/tag-page.tsx +38 -0
- package/src/plugins/blog/client/components/shared/better-blog-attribution.tsx +19 -0
- package/src/plugins/blog/client/components/shared/default-error.tsx +20 -0
- package/src/plugins/blog/client/components/shared/defaults.tsx +9 -0
- package/src/plugins/blog/client/components/shared/empty-list.tsx +25 -0
- package/src/plugins/blog/client/components/shared/error-placeholder.tsx +20 -0
- package/src/plugins/blog/client/components/shared/highlight-text.tsx +80 -0
- package/src/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
- package/src/plugins/blog/client/components/shared/markdown-content.tsx +448 -0
- package/src/plugins/blog/client/components/shared/on-this-page.tsx +234 -0
- package/src/plugins/blog/client/components/shared/page-header.tsx +35 -0
- package/src/plugins/blog/client/components/shared/page-layout.tsx +23 -0
- package/src/plugins/blog/client/components/shared/page-wrapper.tsx +32 -0
- package/src/plugins/blog/client/components/shared/post-card.tsx +308 -0
- package/src/plugins/blog/client/components/shared/post-navigation.tsx +98 -0
- package/src/plugins/blog/client/components/shared/posts-list.tsx +67 -0
- package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +79 -0
- package/src/plugins/blog/client/components/shared/search-input.tsx +146 -0
- package/src/plugins/blog/client/components/shared/search-modal.tsx +162 -0
- package/src/plugins/blog/client/components/shared/tags-list.tsx +34 -0
- package/src/plugins/blog/client/components/shared/use-route-lifecycle.tsx +68 -0
- package/src/plugins/blog/client/hooks/blog-hooks.tsx +623 -0
- package/src/plugins/blog/client/hooks/index.tsx +1 -0
- package/src/plugins/blog/client/hooks/use-debounce.ts +43 -0
- package/src/plugins/blog/client/index.ts +9 -0
- package/src/plugins/blog/client/localization/blog-card.ts +3 -0
- package/src/plugins/blog/client/localization/blog-common.ts +7 -0
- package/src/plugins/blog/client/localization/blog-forms.ts +45 -0
- package/src/plugins/blog/client/localization/blog-list.ts +14 -0
- package/src/plugins/blog/client/localization/blog-post.ts +9 -0
- package/src/plugins/blog/client/localization/index.ts +15 -0
- package/src/plugins/blog/client/overrides.ts +123 -0
- package/src/plugins/blog/client/plugin.tsx +672 -0
- package/src/plugins/blog/client.css +3 -0
- package/src/plugins/blog/db.ts +90 -0
- package/src/plugins/blog/query-keys.ts +267 -0
- package/src/plugins/blog/schemas.ts +39 -0
- package/src/plugins/blog/style.css +22 -0
- package/src/plugins/blog/types.ts +37 -0
- package/src/plugins/blog/utils.ts +144 -0
- package/src/plugins/client/index.ts +53 -0
- package/src/plugins/index.ts +0 -0
- package/src/plugins/utils.ts +35 -0
- package/src/types.ts +209 -0
- package/dist/plugins/index.cjs +0 -16
- package/dist/plugins/index.d.cts +0 -64
- package/dist/plugins/index.d.mts +0 -64
- package/dist/plugins/index.d.ts +0 -64
- package/dist/plugins/index.mjs +0 -11
- package/dist/shared/stack.DvFqFlOV.d.cts +0 -22
- package/dist/shared/stack.DvFqFlOV.d.mts +0 -22
- package/dist/shared/stack.DvFqFlOV.d.ts +0 -22
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FormPageSkeleton } from "./form-page-skeleton";
|
|
2
|
+
import { ListPageSkeleton } from "./list-page-skeleton";
|
|
3
|
+
import { PostPageSkeleton } from "./post-page-skeleton";
|
|
4
|
+
|
|
5
|
+
export function FormLoading() {
|
|
6
|
+
return (
|
|
7
|
+
<div data-testid="form-skeleton">
|
|
8
|
+
<FormPageSkeleton />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function PostsLoading() {
|
|
14
|
+
return (
|
|
15
|
+
<div data-testid="posts-skeleton">
|
|
16
|
+
<ListPageSkeleton />
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function PostLoading() {
|
|
22
|
+
return (
|
|
23
|
+
<div data-testid="post-skeleton">
|
|
24
|
+
<PostPageSkeleton />
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PageHeaderSkeleton } from "./page-header-skeleton";
|
|
2
|
+
import { PageLayout } from "../shared/page-layout";
|
|
3
|
+
import { PostCardSkeleton } from "./post-card-skeleton";
|
|
4
|
+
import { Skeleton } from "@workspace/ui/components/skeleton";
|
|
5
|
+
|
|
6
|
+
export function ListPageSkeleton() {
|
|
7
|
+
return (
|
|
8
|
+
<PageLayout>
|
|
9
|
+
<div className="flex flex-col items-center gap-3">
|
|
10
|
+
<PageHeaderSkeleton />
|
|
11
|
+
</div>
|
|
12
|
+
<PostsListSkeleton count={6} />
|
|
13
|
+
</PageLayout>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function PostsListSkeleton({ count = 6 }: { count?: number }) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="w-full space-y-6">
|
|
20
|
+
<div className="flex justify-center pb-6">
|
|
21
|
+
<div className="flex w-full max-w-md items-center gap-2">
|
|
22
|
+
<Skeleton className="h-10 grow rounded-md" />
|
|
23
|
+
<Skeleton className="h-10 w-24 rounded-md" />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
28
|
+
{Array.from({ length: count }).map((_, index) => (
|
|
29
|
+
<PostCardSkeleton key={index} />
|
|
30
|
+
))}
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div className="flex justify-center">
|
|
34
|
+
<Skeleton className="h-10 w-40 rounded-md" />
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Skeleton } from "@workspace/ui/components/skeleton";
|
|
2
|
+
|
|
3
|
+
export function PageHeaderSkeleton() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="flex max-w-[600px] flex-col items-center gap-2">
|
|
6
|
+
<Skeleton className="h-12 w-56" />
|
|
7
|
+
<Skeleton className="h-4 w-80" />
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Card,
|
|
3
|
+
CardContent,
|
|
4
|
+
CardFooter,
|
|
5
|
+
CardHeader,
|
|
6
|
+
} from "@workspace/ui/components/card";
|
|
7
|
+
import { Skeleton } from "@workspace/ui/components/skeleton";
|
|
8
|
+
|
|
9
|
+
export function PostCardSkeleton() {
|
|
10
|
+
return (
|
|
11
|
+
<Card className="h-full">
|
|
12
|
+
<div className="relative h-48 w-full">
|
|
13
|
+
<Skeleton className="h-full w-full rounded-t-xl" />
|
|
14
|
+
</div>
|
|
15
|
+
<CardHeader>
|
|
16
|
+
<Skeleton className="mb-2 h-4 w-24" />
|
|
17
|
+
<Skeleton className="mb-2 h-6 w-full" />
|
|
18
|
+
</CardHeader>
|
|
19
|
+
<CardContent>
|
|
20
|
+
<Skeleton className="h-4 w-32" />
|
|
21
|
+
</CardContent>
|
|
22
|
+
<CardFooter>
|
|
23
|
+
<div className="flex w-full items-center justify-between">
|
|
24
|
+
<Skeleton className="h-5 w-16" />
|
|
25
|
+
<Skeleton className="h-8 w-20" />
|
|
26
|
+
</div>
|
|
27
|
+
</CardFooter>
|
|
28
|
+
</Card>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PageHeaderSkeleton } from "./page-header-skeleton";
|
|
2
|
+
import { PageLayout } from "../shared/page-layout";
|
|
3
|
+
import { Skeleton } from "@workspace/ui/components/skeleton";
|
|
4
|
+
|
|
5
|
+
export function PostPageSkeleton() {
|
|
6
|
+
return (
|
|
7
|
+
<PageLayout>
|
|
8
|
+
<div className="flex flex-col items-center gap-3">
|
|
9
|
+
<PageHeaderSkeleton />
|
|
10
|
+
</div>
|
|
11
|
+
<PostSkeleton />
|
|
12
|
+
</PageLayout>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function PostSkeleton() {
|
|
17
|
+
return (
|
|
18
|
+
<div className="w-full space-y-8">
|
|
19
|
+
{/* Title + Meta + Tags */}
|
|
20
|
+
<div className="hidden space-y-4">
|
|
21
|
+
{/* Title */}
|
|
22
|
+
<Skeleton className="h-12 w-3/4" />
|
|
23
|
+
|
|
24
|
+
{/* Meta: avatar, author, date */}
|
|
25
|
+
<div className="flex items-center gap-3">
|
|
26
|
+
<Skeleton className="h-8 w-8 rounded-full" />
|
|
27
|
+
<Skeleton className="h-4 w-32" />
|
|
28
|
+
<Skeleton className="h-4 w-24" />
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{/* Tags */}
|
|
32
|
+
<div className="flex flex-wrap gap-2">
|
|
33
|
+
<Skeleton className="h-6 w-20 rounded-full" />
|
|
34
|
+
<Skeleton className="h-6 w-16 rounded-full" />
|
|
35
|
+
<Skeleton className="h-6 w-24 rounded-full" />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
{/* Hero / Cover image */}
|
|
40
|
+
<Skeleton className="h-64 w-full rounded-md" />
|
|
41
|
+
|
|
42
|
+
{/* Content blocks */}
|
|
43
|
+
<div className="space-y-10">
|
|
44
|
+
<ContentBlockSkeleton />
|
|
45
|
+
<ImageBlockSkeleton />
|
|
46
|
+
<CodeBlockSkeleton />
|
|
47
|
+
<ContentBlockSkeleton />
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ContentBlockSkeleton() {
|
|
54
|
+
return (
|
|
55
|
+
<div className="space-y-4">
|
|
56
|
+
{/* Section heading */}
|
|
57
|
+
<Skeleton className="h-8 w-1/3" />
|
|
58
|
+
{/* Paragraph lines */}
|
|
59
|
+
<div className="space-y-2">
|
|
60
|
+
<Skeleton className="h-4 w-full" />
|
|
61
|
+
<Skeleton className="h-4 w-11/12" />
|
|
62
|
+
<Skeleton className="h-4 w-10/12" />
|
|
63
|
+
<Skeleton className="h-4 w-9/12" />
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ImageBlockSkeleton() {
|
|
70
|
+
return <Skeleton className="h-72 w-full rounded-md" />;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function CodeBlockSkeleton() {
|
|
74
|
+
return <Skeleton className="h-40 w-full rounded-md" />;
|
|
75
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePluginOverrides } from "@btst/stack/context";
|
|
4
|
+
import { ErrorPlaceholder } from "../shared/error-placeholder";
|
|
5
|
+
import { BLOG_LOCALIZATION } from "../../localization";
|
|
6
|
+
import type { BlogPluginOverrides } from "../../overrides";
|
|
7
|
+
import { PageWrapper } from "../shared/page-wrapper";
|
|
8
|
+
|
|
9
|
+
export function NotFoundPage({ message }: { message: string }) {
|
|
10
|
+
const { localization } = usePluginOverrides<
|
|
11
|
+
BlogPluginOverrides,
|
|
12
|
+
Partial<BlogPluginOverrides>
|
|
13
|
+
>("blog", {
|
|
14
|
+
localization: BLOG_LOCALIZATION,
|
|
15
|
+
});
|
|
16
|
+
const title = localization.BLOG_PAGE_NOT_FOUND_TITLE;
|
|
17
|
+
const desc = message || localization.BLOG_PAGE_NOT_FOUND_DESCRIPTION;
|
|
18
|
+
return (
|
|
19
|
+
<PageWrapper testId="404-page">
|
|
20
|
+
<ErrorPlaceholder title={title} message={desc} />
|
|
21
|
+
</PageWrapper>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useBasePath, usePluginOverrides } from "@btst/stack/context";
|
|
4
|
+
import { EditPostForm } from "../forms/post-forms";
|
|
5
|
+
import { PageHeader } from "../shared/page-header";
|
|
6
|
+
import { PageWrapper } from "../shared/page-wrapper";
|
|
7
|
+
import { BLOG_LOCALIZATION } from "../../localization";
|
|
8
|
+
import type { BlogPluginOverrides } from "../../overrides";
|
|
9
|
+
import { useRouteLifecycle } from "../shared/use-route-lifecycle";
|
|
10
|
+
|
|
11
|
+
// Internal component with actual page content
|
|
12
|
+
export function EditPostPage({ slug }: { slug: string }) {
|
|
13
|
+
const { localization } = usePluginOverrides<
|
|
14
|
+
BlogPluginOverrides,
|
|
15
|
+
Partial<BlogPluginOverrides>
|
|
16
|
+
>("blog", {
|
|
17
|
+
localization: BLOG_LOCALIZATION,
|
|
18
|
+
});
|
|
19
|
+
const basePath = useBasePath();
|
|
20
|
+
const { navigate } = usePluginOverrides<BlogPluginOverrides>("blog");
|
|
21
|
+
|
|
22
|
+
// Call lifecycle hooks
|
|
23
|
+
useRouteLifecycle({
|
|
24
|
+
routeName: "editPost",
|
|
25
|
+
context: {
|
|
26
|
+
path: `/blog/${slug}/edit`,
|
|
27
|
+
params: { slug },
|
|
28
|
+
isSSR: typeof window === "undefined",
|
|
29
|
+
},
|
|
30
|
+
beforeRenderHook: (overrides, context) => {
|
|
31
|
+
if (overrides.onBeforeEditPostPageRendered) {
|
|
32
|
+
return overrides.onBeforeEditPostPageRendered(slug, context);
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const handleClose = () => {
|
|
39
|
+
navigate(`${basePath}/blog`);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleSuccess = (post: { slug: string; published: boolean }) => {
|
|
43
|
+
// Navigate based on published status
|
|
44
|
+
navigate(`${basePath}/blog/${post.slug}`);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<PageWrapper className="gap-6" testId="edit-post-page">
|
|
49
|
+
<PageHeader
|
|
50
|
+
title={localization.BLOG_POST_EDIT_TITLE}
|
|
51
|
+
description={localization.BLOG_POST_EDIT_DESCRIPTION}
|
|
52
|
+
/>
|
|
53
|
+
<EditPostForm
|
|
54
|
+
postSlug={slug}
|
|
55
|
+
onClose={handleClose}
|
|
56
|
+
onSuccess={handleSuccess}
|
|
57
|
+
/>
|
|
58
|
+
</PageWrapper>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { lazy } from "react";
|
|
4
|
+
import { usePluginOverrides } from "@btst/stack/context";
|
|
5
|
+
import type { BlogPluginOverrides } from "../../overrides";
|
|
6
|
+
import { ComposedRoute } from "@btst/stack/client/components";
|
|
7
|
+
import { DefaultError } from "../shared/default-error";
|
|
8
|
+
import { FormLoading } from "../loading";
|
|
9
|
+
import { NotFoundPage } from "./404-page";
|
|
10
|
+
|
|
11
|
+
// Lazy load the internal component with actual page content
|
|
12
|
+
const EditPostPage = lazy(() =>
|
|
13
|
+
import("./edit-post-page.internal").then((m) => ({
|
|
14
|
+
default: m.EditPostPage,
|
|
15
|
+
})),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// Exported wrapped component with error and loading boundaries
|
|
19
|
+
export function EditPostPageComponent({ slug }: { slug: string }) {
|
|
20
|
+
const { onRouteError } = usePluginOverrides<BlogPluginOverrides>("blog");
|
|
21
|
+
return (
|
|
22
|
+
<ComposedRoute
|
|
23
|
+
path={`/blog/${slug}/edit`}
|
|
24
|
+
PageComponent={EditPostPage}
|
|
25
|
+
ErrorComponent={DefaultError}
|
|
26
|
+
LoadingComponent={FormLoading}
|
|
27
|
+
NotFoundComponent={NotFoundPage}
|
|
28
|
+
props={{ slug }}
|
|
29
|
+
onError={(error) => {
|
|
30
|
+
if (onRouteError) {
|
|
31
|
+
onRouteError("editPost", error, {
|
|
32
|
+
path: `/blog/${slug}/edit`,
|
|
33
|
+
isSSR: typeof window === "undefined",
|
|
34
|
+
slug,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { PageHeader } from "../shared/page-header";
|
|
4
|
+
import { PageWrapper } from "../shared/page-wrapper";
|
|
5
|
+
import { PostsList } from "../shared/posts-list";
|
|
6
|
+
import { TagsList } from "../shared/tags-list";
|
|
7
|
+
|
|
8
|
+
import { useSuspensePosts } from "../../hooks/blog-hooks";
|
|
9
|
+
import { BLOG_LOCALIZATION } from "../../localization";
|
|
10
|
+
import { usePluginOverrides } from "@btst/stack/context";
|
|
11
|
+
import type { BlogPluginOverrides } from "../../overrides";
|
|
12
|
+
import { useRouteLifecycle } from "../shared/use-route-lifecycle";
|
|
13
|
+
|
|
14
|
+
// Internal component with actual page content
|
|
15
|
+
export function HomePage({ published }: { published: boolean }) {
|
|
16
|
+
const { localization } = usePluginOverrides<
|
|
17
|
+
BlogPluginOverrides,
|
|
18
|
+
Partial<BlogPluginOverrides>
|
|
19
|
+
>("blog", {
|
|
20
|
+
localization: BLOG_LOCALIZATION,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Call lifecycle hooks
|
|
24
|
+
useRouteLifecycle({
|
|
25
|
+
routeName: published ? "posts" : "drafts",
|
|
26
|
+
context: {
|
|
27
|
+
path: published ? "/blog" : "/blog/drafts",
|
|
28
|
+
isSSR: typeof window === "undefined",
|
|
29
|
+
published,
|
|
30
|
+
},
|
|
31
|
+
beforeRenderHook: (overrides, context) => {
|
|
32
|
+
if (published && overrides.onBeforePostsPageRendered) {
|
|
33
|
+
return overrides.onBeforePostsPageRendered(context);
|
|
34
|
+
}
|
|
35
|
+
if (!published && overrides.onBeforeDraftsPageRendered) {
|
|
36
|
+
return overrides.onBeforeDraftsPageRendered(context);
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<PageWrapper testId={published ? "home-page" : "drafts-home-page"}>
|
|
44
|
+
<div className="flex flex-col items-center gap-3">
|
|
45
|
+
<PageHeader
|
|
46
|
+
title={
|
|
47
|
+
published
|
|
48
|
+
? localization.BLOG_LIST_TITLE
|
|
49
|
+
: localization.BLOG_LIST_DRAFTS_TITLE
|
|
50
|
+
}
|
|
51
|
+
childrenBottom={<TagsList />}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<Content published={published} />
|
|
55
|
+
</PageWrapper>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function Content({ published }: { published: boolean }) {
|
|
60
|
+
const { posts, loadMore, hasMore, isLoadingMore } = useSuspensePosts({
|
|
61
|
+
published: published,
|
|
62
|
+
});
|
|
63
|
+
return (
|
|
64
|
+
<PostsList
|
|
65
|
+
posts={posts}
|
|
66
|
+
onLoadMore={loadMore}
|
|
67
|
+
hasMore={hasMore}
|
|
68
|
+
isLoadingMore={isLoadingMore}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { lazy } from "react";
|
|
4
|
+
import { usePluginOverrides } from "@btst/stack/context";
|
|
5
|
+
import type { BlogPluginOverrides } from "../../overrides";
|
|
6
|
+
import { ComposedRoute } from "@btst/stack/client/components";
|
|
7
|
+
import { DefaultError } from "../shared/default-error";
|
|
8
|
+
import { PostsLoading } from "../loading";
|
|
9
|
+
import { NotFoundPage } from "./404-page";
|
|
10
|
+
|
|
11
|
+
// Lazy load the internal component with actual page content
|
|
12
|
+
const HomePage = lazy(() =>
|
|
13
|
+
import("./home-page.internal").then((m) => ({ default: m.HomePage })),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Exported wrapped component with error and loading boundaries
|
|
17
|
+
export function HomePageComponent({
|
|
18
|
+
published = true,
|
|
19
|
+
}: {
|
|
20
|
+
published?: boolean;
|
|
21
|
+
}) {
|
|
22
|
+
const { onRouteError } = usePluginOverrides<BlogPluginOverrides>("blog");
|
|
23
|
+
return (
|
|
24
|
+
<ComposedRoute
|
|
25
|
+
path={published ? "/blog" : "/blog/drafts"}
|
|
26
|
+
PageComponent={HomePage}
|
|
27
|
+
ErrorComponent={DefaultError}
|
|
28
|
+
LoadingComponent={PostsLoading}
|
|
29
|
+
NotFoundComponent={NotFoundPage}
|
|
30
|
+
props={{ published }}
|
|
31
|
+
onError={(error) => {
|
|
32
|
+
if (onRouteError) {
|
|
33
|
+
onRouteError("posts", error, {
|
|
34
|
+
path: published ? "/blog" : "/blog/drafts",
|
|
35
|
+
isSSR: typeof window === "undefined",
|
|
36
|
+
published,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useBasePath, usePluginOverrides } from "@btst/stack/context";
|
|
4
|
+
import { AddPostForm } from "../forms/post-forms";
|
|
5
|
+
import { PageHeader } from "../shared/page-header";
|
|
6
|
+
import { PageWrapper } from "../shared/page-wrapper";
|
|
7
|
+
import type { BlogPluginOverrides } from "../../overrides";
|
|
8
|
+
import { BLOG_LOCALIZATION } from "../../localization";
|
|
9
|
+
import { useRouteLifecycle } from "../shared/use-route-lifecycle";
|
|
10
|
+
|
|
11
|
+
// Internal component with actual page content
|
|
12
|
+
export function NewPostPage() {
|
|
13
|
+
const { localization } = usePluginOverrides<
|
|
14
|
+
BlogPluginOverrides,
|
|
15
|
+
Partial<BlogPluginOverrides>
|
|
16
|
+
>("blog", {
|
|
17
|
+
localization: BLOG_LOCALIZATION,
|
|
18
|
+
});
|
|
19
|
+
const { navigate } = usePluginOverrides<BlogPluginOverrides>("blog");
|
|
20
|
+
const basePath = useBasePath();
|
|
21
|
+
|
|
22
|
+
// Call lifecycle hooks
|
|
23
|
+
useRouteLifecycle({
|
|
24
|
+
routeName: "newPost",
|
|
25
|
+
context: {
|
|
26
|
+
path: "/blog/new",
|
|
27
|
+
isSSR: typeof window === "undefined",
|
|
28
|
+
},
|
|
29
|
+
beforeRenderHook: (overrides, context) => {
|
|
30
|
+
if (overrides.onBeforeNewPostPageRendered) {
|
|
31
|
+
return overrides.onBeforeNewPostPageRendered(context);
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const handleClose = () => {
|
|
38
|
+
navigate(`${basePath}/blog`);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleSuccess = (post: { published: boolean }) => {
|
|
42
|
+
// Navigate based on published status
|
|
43
|
+
if (post.published) {
|
|
44
|
+
navigate(`${basePath}/blog`);
|
|
45
|
+
} else {
|
|
46
|
+
navigate(`${basePath}/blog/drafts`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<PageWrapper className="gap-6" testId="new-post-page">
|
|
52
|
+
<PageHeader
|
|
53
|
+
title={localization.BLOG_POST_ADD_TITLE}
|
|
54
|
+
description={localization.BLOG_POST_ADD_DESCRIPTION}
|
|
55
|
+
/>
|
|
56
|
+
<AddPostForm onClose={handleClose} onSuccess={handleSuccess} />
|
|
57
|
+
</PageWrapper>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { lazy } from "react";
|
|
4
|
+
import { usePluginOverrides } from "@btst/stack/context";
|
|
5
|
+
import type { BlogPluginOverrides } from "../../overrides";
|
|
6
|
+
import { ComposedRoute } from "@btst/stack/client/components";
|
|
7
|
+
import { DefaultError } from "../shared/default-error";
|
|
8
|
+
import { FormLoading } from "../loading";
|
|
9
|
+
import { NotFoundPage } from "./404-page";
|
|
10
|
+
|
|
11
|
+
// Lazy load the internal component with actual page content
|
|
12
|
+
const NewPostPage = lazy(() =>
|
|
13
|
+
import("./new-post-page.internal").then((m) => ({ default: m.NewPostPage })),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Exported wrapped component with error and loading boundaries
|
|
17
|
+
export function NewPostPageComponent() {
|
|
18
|
+
const { onRouteError } = usePluginOverrides<BlogPluginOverrides>("blog");
|
|
19
|
+
return (
|
|
20
|
+
<ComposedRoute
|
|
21
|
+
path="/blog/new"
|
|
22
|
+
PageComponent={NewPostPage}
|
|
23
|
+
ErrorComponent={DefaultError}
|
|
24
|
+
LoadingComponent={FormLoading}
|
|
25
|
+
NotFoundComponent={NotFoundPage}
|
|
26
|
+
onError={(error) => {
|
|
27
|
+
if (onRouteError) {
|
|
28
|
+
onRouteError("newPost", error, {
|
|
29
|
+
path: `/blog/new`,
|
|
30
|
+
isSSR: typeof window === "undefined",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePluginOverrides, useBasePath } from "@btst/stack/context";
|
|
4
|
+
import { formatDate } from "date-fns";
|
|
5
|
+
import {
|
|
6
|
+
useSuspensePost,
|
|
7
|
+
useNextPreviousPosts,
|
|
8
|
+
useRecentPosts,
|
|
9
|
+
} from "../../hooks/blog-hooks";
|
|
10
|
+
import { EmptyList } from "../shared/empty-list";
|
|
11
|
+
import { MarkdownContent } from "../shared/markdown-content";
|
|
12
|
+
import { PageHeader } from "../shared/page-header";
|
|
13
|
+
import { PageWrapper } from "../shared/page-wrapper";
|
|
14
|
+
import type { BlogPluginOverrides } from "../../overrides";
|
|
15
|
+
import { DefaultImage, DefaultLink } from "../shared/defaults";
|
|
16
|
+
import { BLOG_LOCALIZATION } from "../../localization";
|
|
17
|
+
import { PostNavigation } from "../shared/post-navigation";
|
|
18
|
+
import { RecentPostsCarousel } from "../shared/recent-posts-carousel";
|
|
19
|
+
import { Badge } from "@workspace/ui/components/badge";
|
|
20
|
+
import { useRouteLifecycle } from "../shared/use-route-lifecycle";
|
|
21
|
+
import { OnThisPage, OnThisPageSelect } from "../shared/on-this-page";
|
|
22
|
+
import type { SerializedPost } from "../../../types";
|
|
23
|
+
|
|
24
|
+
// Internal component with actual page content
|
|
25
|
+
export function PostPage({ slug }: { slug: string }) {
|
|
26
|
+
const { Image, localization } = usePluginOverrides<
|
|
27
|
+
BlogPluginOverrides,
|
|
28
|
+
Partial<BlogPluginOverrides>
|
|
29
|
+
>("blog", {
|
|
30
|
+
Image: DefaultImage,
|
|
31
|
+
localization: BLOG_LOCALIZATION,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Call lifecycle hooks
|
|
35
|
+
useRouteLifecycle({
|
|
36
|
+
routeName: "post",
|
|
37
|
+
context: {
|
|
38
|
+
path: `/blog/${slug}`,
|
|
39
|
+
params: { slug },
|
|
40
|
+
isSSR: typeof window === "undefined",
|
|
41
|
+
},
|
|
42
|
+
beforeRenderHook: (overrides, context) => {
|
|
43
|
+
if (overrides.onBeforePostPageRendered) {
|
|
44
|
+
return overrides.onBeforePostPageRendered(slug, context);
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const { post } = useSuspensePost(slug ?? "");
|
|
51
|
+
|
|
52
|
+
const { previousPost, nextPost, ref } = useNextPreviousPosts(
|
|
53
|
+
post?.createdAt ?? new Date(),
|
|
54
|
+
{
|
|
55
|
+
enabled: !!post,
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const { recentPosts, ref: recentPostsRef } = useRecentPosts({
|
|
60
|
+
limit: 5,
|
|
61
|
+
excludeSlug: slug,
|
|
62
|
+
enabled: !!post,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!slug || !post) {
|
|
66
|
+
return (
|
|
67
|
+
<PageWrapper>
|
|
68
|
+
<EmptyList message={localization.BLOG_PAGE_NOT_FOUND_DESCRIPTION} />
|
|
69
|
+
</PageWrapper>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<PageWrapper className="gap-0 px-4 lg:px-4 py-0 pb-18" testId="post-page">
|
|
75
|
+
<div className="flex items-start w-full">
|
|
76
|
+
<div className="w-44 shrink-0 hidden xl:flex mr-auto" />
|
|
77
|
+
<div className="flex flex-col items-center flex-1 mx-auto w-full max-w-4xl min-w-0">
|
|
78
|
+
<OnThisPageSelect markdown={post.content} />
|
|
79
|
+
|
|
80
|
+
<PageHeader
|
|
81
|
+
title={post.title}
|
|
82
|
+
description={post.excerpt}
|
|
83
|
+
childrenTop={<PostHeaderTop post={post} />}
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
{post.image && (
|
|
87
|
+
<div className="flex flex-col gap-2 mt-6 aspect-video w-full relative">
|
|
88
|
+
<Image
|
|
89
|
+
src={post.image}
|
|
90
|
+
alt={post.title}
|
|
91
|
+
className="object-cover transition-transform duration-200"
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
<div className="w-full px-3">
|
|
97
|
+
<MarkdownContent markdown={post.content} />
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div className="flex flex-col gap-4 w-full">
|
|
101
|
+
<PostNavigation
|
|
102
|
+
previousPost={previousPost}
|
|
103
|
+
nextPost={nextPost}
|
|
104
|
+
ref={ref}
|
|
105
|
+
/>
|
|
106
|
+
|
|
107
|
+
<RecentPostsCarousel posts={recentPosts} ref={recentPostsRef} />
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<OnThisPage markdown={post.content} />
|
|
111
|
+
</div>
|
|
112
|
+
</PageWrapper>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function PostHeaderTop({ post }: { post: SerializedPost }) {
|
|
117
|
+
const { Link } = usePluginOverrides<
|
|
118
|
+
BlogPluginOverrides,
|
|
119
|
+
Partial<BlogPluginOverrides>
|
|
120
|
+
>("blog", {
|
|
121
|
+
Link: DefaultLink,
|
|
122
|
+
});
|
|
123
|
+
const basePath = useBasePath();
|
|
124
|
+
return (
|
|
125
|
+
<div className="flex flex-row items-center gap-2 flex-wrap mt-8">
|
|
126
|
+
<span className="font-light text-muted-foreground text-sm">
|
|
127
|
+
{formatDate(post.createdAt, "MMMM d, yyyy")}
|
|
128
|
+
</span>
|
|
129
|
+
{post.tags && post.tags.length > 0 && (
|
|
130
|
+
<div className="flex flex-wrap gap-2">
|
|
131
|
+
{post.tags.map((tag) => (
|
|
132
|
+
<Link key={tag.id} href={`${basePath}/blog/tag/${tag.slug}`}>
|
|
133
|
+
<Badge variant="secondary" className="text-xs">
|
|
134
|
+
{tag.name}
|
|
135
|
+
</Badge>
|
|
136
|
+
</Link>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|