@studiocms/blog 0.1.0-beta.25 → 0.1.0-beta.27

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # StudioCMS - Blog Provider
2
2
 
3
+ [![codecov](https://codecov.io/github/withstudiocms/studiocms/graph/badge.svg?token=RN8LT1O5E2&component=studiocms_blog)](https://codecov.io/github/withstudiocms/studiocms)
4
+
3
5
  For information and docs related to this package see [The StudioCMS Docs](https://docs.studiocms.dev/package-catalog/studiocms-plugins/studiocms-blog/)
4
6
 
5
7
  ## License
@@ -9,6 +9,7 @@ import {
9
9
  headDefaults,
10
10
  } from 'studiocms:lib';
11
11
  import type { z } from 'astro/zod';
12
+ import { getHeroImage } from './heroHelper.js';
12
13
 
13
14
  let htmlDefaultHead: HeadUserConfig = [];
14
15
  let favicon = '';
@@ -31,14 +32,11 @@ interface Props {
31
32
 
32
33
  const canonicalURL = Astro.url;
33
34
 
34
- const {
35
- title,
36
- description,
37
- lang = 'en',
38
- image = 'https://images.unsplash.com/photo-1707343843982-f8275f3994c5?q=80&w=1032&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
39
- } = Astro.props;
35
+ const { title, description, lang = 'en', image } = Astro.props;
40
36
 
41
- const makeHeader = headDefaults(title, description, lang, Astro, favicon, image, canonicalURL);
37
+ const ogImage = getHeroImage(image, Astro);
38
+
39
+ const makeHeader = headDefaults(title, description, lang, Astro, favicon, ogImage, canonicalURL);
42
40
 
43
41
  const StudioCMSFrontEndHeads: z.input<ReturnType<typeof HeadConfigSchema>> = [
44
42
  // Fonts
@@ -3,7 +3,8 @@ import blogConfig from 'studiocms:blog/config';
3
3
  import { FormattedDate } from 'studiocms:components';
4
4
  import { CustomImage } from 'studiocms:imageHandler/components';
5
5
  import { pathWithBase } from 'studiocms:lib';
6
- import type { CombinedPageData } from 'studiocms:sdk/types';
6
+ import type { CombinedPageData } from 'studiocms/sdk/types';
7
+ import { getHeroImage } from './heroHelper.js';
7
8
 
8
9
  const blogRouteFullPath = `${blogConfig.route}/[...slug]`;
9
10
 
@@ -26,7 +27,7 @@ const { blogPageList } = Astro.props;
26
27
  blogPageList.length > 0 && blogPageList.map(({slug, heroImage, title, description, publishedAt}) => (
27
28
  <li>
28
29
  <a href={pathWithBase(getBlogRoute(slug))}>
29
- <CustomImage src={heroImage} alt={title} width={720} height={360}/>
30
+ <CustomImage src={getHeroImage(heroImage, Astro)} alt={title} width={720} height={360}/>
30
31
  <div>
31
32
  <span class="title">{title}</span>
32
33
  <span class="date"> <FormattedDate date={publishedAt} /> </span>
@@ -1,21 +1,24 @@
1
1
  ---
2
2
  import { FormattedDate } from 'studiocms:components';
3
3
  import { CustomImage } from 'studiocms:imageHandler/components';
4
+ import { getHeroImage } from './heroHelper.js';
4
5
 
5
6
  interface Props {
6
7
  title: string;
7
8
  publishedAt: Date;
8
9
  updatedAt?: Date | null;
9
- heroImage: string;
10
+ heroImage?: string;
10
11
  description: string;
11
12
  }
12
13
 
13
14
  const { title, description, heroImage, publishedAt, updatedAt } = Astro.props;
15
+
16
+ const heroImageToUse = getHeroImage(heroImage, Astro);
14
17
  ---
15
18
  <h1 class="title">{title}</h1>
16
19
 
17
20
  <div style="text-align: center;">
18
- <CustomImage src={heroImage} alt={title} width={720} height={360}/>
21
+ <CustomImage src={heroImageToUse} alt={title} width={720} height={360}/>
19
22
  <div>
20
23
  <p class="date">Published: <FormattedDate date={publishedAt}/></p>
21
24
  {updatedAt && <p class="date"> | Last Updated: <FormattedDate date={updatedAt}/></p>}
@@ -0,0 +1 @@
1
+ export declare const FALLBACK_OG_IMAGE = "https://images.unsplash.com/photo-1707343843982-f8275f3994c5?q=80&w=1032&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
@@ -0,0 +1,4 @@
1
+ const FALLBACK_OG_IMAGE = "https://images.unsplash.com/photo-1707343843982-f8275f3994c5?q=80&w=1032&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
2
+ export {
3
+ FALLBACK_OG_IMAGE
4
+ };
@@ -0,0 +1,22 @@
1
+ import type { AstroGlobal } from 'astro';
2
+ /**
3
+ * Trims leading and trailing whitespace from the given input string.
4
+ *
5
+ * @param input - The string to trim. Can be `string`, `null`, or `undefined`.
6
+ * @returns The trimmed string, or `undefined` if the input is `null` or `undefined`.
7
+ */
8
+ export declare function trimInput(input: string | null | undefined): string | undefined;
9
+ /**
10
+ * Retrieves the appropriate hero image URL based on the provided `hero` string,
11
+ * the site's default OG image, or a fallback image.
12
+ *
13
+ * The function checks the following, in order:
14
+ * 1. If a valid `hero` image URL is provided, it returns that.
15
+ * 2. If not, it attempts to use the site's default OG image from the Astro global context.
16
+ * 3. If neither is available, it returns a constant fallback image URL.
17
+ *
18
+ * @param hero - The primary hero image URL, or `undefined` if not provided.
19
+ * @param Astro - The Astro global context, used to access site configuration.
20
+ * @returns The resolved hero image URL as a string.
21
+ */
22
+ export declare function getHeroImage(hero: string | undefined, Astro: AstroGlobal): string;
@@ -0,0 +1,17 @@
1
+ import { FALLBACK_OG_IMAGE } from "./consts.js";
2
+ function trimInput(input) {
3
+ if (input == null) return void 0;
4
+ const trimmed = input.trim();
5
+ return trimmed.length > 0 ? trimmed : void 0;
6
+ }
7
+ function getHeroImage(hero, Astro) {
8
+ const primary = trimInput(hero);
9
+ const siteFallback = trimInput(Astro.locals?.StudioCMS?.siteConfig?.data?.defaultOgImage);
10
+ if (primary) return primary;
11
+ if (siteFallback) return siteFallback;
12
+ return FALLBACK_OG_IMAGE;
13
+ }
14
+ export {
15
+ getHeroImage,
16
+ trimInput
17
+ };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import type { AstroIntegration } from 'astro';
1
2
  import { type StudioCMSPlugin } from 'studiocms/plugins';
2
3
  import { type StudioCMSBlogOptions } from './types.js';
4
+ export declare function internalBlogIntegration(options?: StudioCMSBlogOptions): AstroIntegration;
3
5
  /**
4
6
  * Creates and configures the StudioCMS Blog plugin.
5
7
  *
package/dist/index.js CHANGED
@@ -3,15 +3,75 @@ import { pathWithBase } from "studiocms/lib/pathGenerators";
3
3
  import { definePlugin } from "studiocms/plugins";
4
4
  import { FrontEndConfigSchema } from "./types.js";
5
5
  const packageIdentifier = "@studiocms/blog";
6
- function studioCMSBlogPlugin(options) {
6
+ function internalBlogIntegration(options) {
7
7
  const resolvedOptions = FrontEndConfigSchema.parse(options);
8
8
  const {
9
9
  blog: { title, enableRSS, route: orgRoute },
10
- sitemap,
11
10
  injectRoutes,
12
11
  ...frontendConfig
13
12
  } = resolvedOptions;
14
13
  const route = pathWithBase(orgRoute);
14
+ const resEntrypoint = (path) => `@studiocms/blog/routes/${path}`;
15
+ return {
16
+ name: packageIdentifier,
17
+ hooks: {
18
+ /* v8 ignore start */
19
+ /* this is tested indirectly via the plugin tests */
20
+ "astro:config:setup": async (params) => {
21
+ const { injectRoute } = params;
22
+ if (injectRoutes) {
23
+ injectRoute({
24
+ entrypoint: resEntrypoint("[...slug].astro"),
25
+ pattern: pathWithBase("[...slug]"),
26
+ prerender: false
27
+ });
28
+ injectRoute({
29
+ entrypoint: resEntrypoint("blog/index.astro"),
30
+ pattern: `${route}`,
31
+ prerender: false
32
+ });
33
+ injectRoute({
34
+ entrypoint: resEntrypoint("blog/[...slug].astro"),
35
+ pattern: `${route}/[...slug]`,
36
+ prerender: false
37
+ });
38
+ if (enableRSS) {
39
+ injectRoute({
40
+ entrypoint: resEntrypoint("rss.xml.js"),
41
+ pattern: pathWithBase("rss.xml"),
42
+ prerender: false
43
+ });
44
+ }
45
+ }
46
+ addVirtualImports(params, {
47
+ name: packageIdentifier,
48
+ imports: {
49
+ "studiocms:blog/config": `
50
+ const config = {
51
+ title: ${JSON.stringify(title)},
52
+ enableRSS: ${enableRSS},
53
+ route: ${JSON.stringify(route)}
54
+ }
55
+ export default config;
56
+ `,
57
+ "studiocms:blog/frontend-config": `
58
+ const config = ${JSON.stringify(frontendConfig)};
59
+ export default config;
60
+ `
61
+ }
62
+ });
63
+ }
64
+ /* v8 ignore stop */
65
+ }
66
+ };
67
+ }
68
+ function studioCMSBlogPlugin(options) {
69
+ const resolvedOptions = FrontEndConfigSchema.parse(options);
70
+ const {
71
+ blog: { title, route: orgRoute },
72
+ sitemap
73
+ } = resolvedOptions;
74
+ const route = pathWithBase(orgRoute);
15
75
  const { resolve } = createResolver(import.meta.url);
16
76
  const editor = resolve("./components/editor.astro");
17
77
  const renderer = resolve("./components/renderer.astro");
@@ -22,55 +82,7 @@ function studioCMSBlogPlugin(options) {
22
82
  requires: ["@studiocms/md"],
23
83
  hooks: {
24
84
  "studiocms:astro:config": ({ addIntegrations }) => {
25
- addIntegrations({
26
- name: packageIdentifier,
27
- hooks: {
28
- "astro:config:setup": async (params) => {
29
- const { injectRoute } = params;
30
- if (injectRoutes) {
31
- injectRoute({
32
- entrypoint: resolve("./routes/[...slug].astro"),
33
- pattern: pathWithBase("[...slug]"),
34
- prerender: false
35
- });
36
- injectRoute({
37
- entrypoint: resolve("./routes/blog/index.astro"),
38
- pattern: `${route}`,
39
- prerender: false
40
- });
41
- injectRoute({
42
- entrypoint: resolve("./routes/blog/[...slug].astro"),
43
- pattern: `${route}/[...slug]`,
44
- prerender: false
45
- });
46
- if (enableRSS) {
47
- injectRoute({
48
- entrypoint: resolve("./routes/rss.xml.js"),
49
- pattern: pathWithBase("rss.xml"),
50
- prerender: false
51
- });
52
- }
53
- }
54
- addVirtualImports(params, {
55
- name: packageIdentifier,
56
- imports: {
57
- "studiocms:blog/config": `
58
- const config = {
59
- title: ${JSON.stringify(title)},
60
- enableRSS: ${enableRSS},
61
- route: ${JSON.stringify(route)}
62
- }
63
- export default config;
64
- `,
65
- "studiocms:blog/frontend-config": `
66
- const config = ${JSON.stringify(frontendConfig)};
67
- export default config;
68
- `
69
- }
70
- });
71
- }
72
- }
73
- });
85
+ addIntegrations(internalBlogIntegration(resolvedOptions));
74
86
  },
75
87
  "studiocms:config:setup": ({ setFrontend, setRendering, setSitemap }) => {
76
88
  setFrontend({
@@ -106,5 +118,6 @@ function studioCMSBlogPlugin(options) {
106
118
  var index_default = studioCMSBlogPlugin;
107
119
  export {
108
120
  index_default as default,
121
+ internalBlogIntegration,
109
122
  studioCMSBlogPlugin
110
123
  };
@@ -9,12 +9,14 @@ if (!slug) {
9
9
  slug = 'index';
10
10
  }
11
11
 
12
- const { data: page } = await runSDK(SDKCoreJs.GET.page.bySlug(slug));
12
+ const fullPageData = await runSDK(SDKCoreJs.GET.page.bySlug(slug));
13
13
 
14
- if (!page) {
14
+ if (!fullPageData) {
15
15
  return new Response(null, { status: 404 });
16
16
  }
17
17
 
18
+ const page = fullPageData.data;
19
+
18
20
  const { title, description, heroImage } = page;
19
21
  ---
20
22
 
@@ -14,13 +14,15 @@ if (!slug) {
14
14
 
15
15
  // Fetch the blog post content
16
16
 
17
- const { data: post } = await runSDK(SDKCoreJs.GET.page.bySlug(slug));
17
+ const fullPageData = await runSDK(SDKCoreJs.GET.page.bySlug(slug));
18
18
 
19
19
  // If no content is found, redirect to 404
20
- if (!post) {
20
+ if (!fullPageData) {
21
21
  return new Response(null, { status: 404 });
22
22
  }
23
23
 
24
+ const post = fullPageData.data;
25
+
24
26
  const { title, description, heroImage, publishedAt, updatedAt } = post;
25
27
  ---
26
28
 
@@ -3,13 +3,7 @@ import { pathWithBase } from "studiocms:lib";
3
3
  import { SDKCore } from "studiocms:sdk";
4
4
  import rss, {} from "@astrojs/rss";
5
5
  import { createJsonResponse, Effect, withEffectAPI } from "studiocms/effect";
6
- const blogRouteFullPath = `${blogConfig.route}/[...slug]`;
7
- function getBlogRoute(slug) {
8
- if (blogRouteFullPath) {
9
- return blogRouteFullPath.replace("[...slug]", slug);
10
- }
11
- return "#";
12
- }
6
+ import { getBlogRoute } from "../utils/remapFilter.js";
13
7
  const GET = withEffectAPI(
14
8
  Effect.fn(function* ({ site: _site, locals }) {
15
9
  const sdk = yield* SDKCore;
package/dist/types.d.ts CHANGED
@@ -2,6 +2,15 @@ import { z } from 'astro/zod';
2
2
  import { HeadConfigSchema } from 'studiocms/lib/head';
3
3
  export type HeadUserConfig = z.input<ReturnType<typeof HeadConfigSchema>>;
4
4
  export type HeadConfig = z.output<ReturnType<typeof HeadConfigSchema>>;
5
+ export declare const faviconTypeMap: {
6
+ '.ico': string;
7
+ '.gif': string;
8
+ '.jpeg': string;
9
+ '.jpg': string;
10
+ '.png': string;
11
+ '.svg': string;
12
+ };
13
+ export declare function isFaviconExt(ext: string): ext is keyof typeof faviconTypeMap;
5
14
  /**
6
15
  * Options for configuring the StudioCMS Blog.
7
16
  */
@@ -19,13 +28,13 @@ export declare const FrontEndConfigSchema: z.ZodDefault<z.ZodOptional<z.ZodObjec
19
28
  attrs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodBoolean, z.ZodUndefined]>>>;
20
29
  content: z.ZodDefault<z.ZodString>;
21
30
  }, "strip", z.ZodTypeAny, {
22
- tag: "title" | "link" | "base" | "meta" | "noscript" | "script" | "style" | "template";
23
- content: string;
31
+ tag: "title" | "base" | "link" | "style" | "meta" | "script" | "noscript" | "template";
24
32
  attrs: Record<string, string | boolean | undefined>;
33
+ content: string;
25
34
  }, {
26
- tag: "title" | "link" | "base" | "meta" | "noscript" | "script" | "style" | "template";
27
- content?: string | undefined;
35
+ tag: "title" | "base" | "link" | "style" | "meta" | "script" | "noscript" | "template";
28
36
  attrs?: Record<string, string | boolean | undefined> | undefined;
37
+ content?: string | undefined;
29
38
  }>, "many">>;
30
39
  /**
31
40
  * Favicon Configuration - The default favicon configuration for the Frontend
@@ -71,9 +80,9 @@ export declare const FrontEndConfigSchema: z.ZodDefault<z.ZodOptional<z.ZodObjec
71
80
  }, "strip", z.ZodTypeAny, {
72
81
  htmlDefaultLanguage: string;
73
82
  htmlDefaultHead: {
74
- tag: "title" | "link" | "base" | "meta" | "noscript" | "script" | "style" | "template";
75
- content: string;
83
+ tag: "title" | "base" | "link" | "style" | "meta" | "script" | "noscript" | "template";
76
84
  attrs: Record<string, string | boolean | undefined>;
85
+ content: string;
77
86
  }[];
78
87
  favicon: string;
79
88
  sitemap: boolean;
@@ -86,9 +95,9 @@ export declare const FrontEndConfigSchema: z.ZodDefault<z.ZodOptional<z.ZodObjec
86
95
  }, {
87
96
  htmlDefaultLanguage?: string | undefined;
88
97
  htmlDefaultHead?: {
89
- tag: "title" | "link" | "base" | "meta" | "noscript" | "script" | "style" | "template";
90
- content?: string | undefined;
98
+ tag: "title" | "base" | "link" | "style" | "meta" | "script" | "noscript" | "template";
91
99
  attrs?: Record<string, string | boolean | undefined> | undefined;
100
+ content?: string | undefined;
92
101
  }[] | undefined;
93
102
  favicon?: string | undefined;
94
103
  sitemap?: boolean | undefined;
package/dist/types.js CHANGED
@@ -65,5 +65,7 @@ const FrontEndConfigSchema = z.object({
65
65
  }).optional().default({})
66
66
  }).optional().default({});
67
67
  export {
68
- FrontEndConfigSchema
68
+ FrontEndConfigSchema,
69
+ faviconTypeMap,
70
+ isFaviconExt
69
71
  };
@@ -1,7 +1,8 @@
1
- import type { PageDataCacheObject } from 'studiocms:sdk/types';
2
1
  import type { APIContext } from 'astro';
2
+ import type { PageDataCacheObject } from 'studiocms/sdk/types';
3
3
  export declare function getBlogRoute(slug: string): string;
4
4
  export type SiteMapTemplate = {
5
5
  location: string;
6
6
  }[];
7
+ export type { APIContext, PageDataCacheObject };
7
8
  export declare const remapFilterSitemap: ((filter: string, context: APIContext, blog?: boolean) => (array: Array<PageDataCacheObject>) => SiteMapTemplate) & ((array: Array<PageDataCacheObject>, filter: string, context: APIContext, blog?: boolean) => SiteMapTemplate);
@@ -3,10 +3,7 @@ import { pathWithBase } from "studiocms:lib";
3
3
  import { dual } from "studiocms/effect";
4
4
  const blogRouteFullPath = `${blogConfig.route}/[...slug]`;
5
5
  function getBlogRoute(slug) {
6
- if (blogRouteFullPath) {
7
- return blogRouteFullPath.replace("[...slug]", slug);
8
- }
9
- return "#";
6
+ return blogRouteFullPath.replace("[...slug]", slug);
10
7
  }
11
8
  const remapFilterSitemap = dual(4, (array, filter, context, blog = false) => {
12
9
  function genLocation(slug) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studiocms/blog",
3
- "version": "0.1.0-beta.25",
3
+ "version": "0.1.0-beta.27",
4
4
  "description": "Add a blog to your StudioCMS project with ease!",
5
5
  "author": {
6
6
  "name": "withstudiocms",
@@ -48,7 +48,8 @@
48
48
  ".": {
49
49
  "types": "./dist/index.d.ts",
50
50
  "default": "./dist/index.js"
51
- }
51
+ },
52
+ "./routes/*": "./dist/routes/*"
52
53
  },
53
54
  "type": "module",
54
55
  "dependencies": {
@@ -60,14 +61,15 @@
60
61
  },
61
62
  "peerDependencies": {
62
63
  "astro": "^5.12.9",
63
- "effect": "^3.17.9",
64
+ "effect": "^3.17.14",
64
65
  "vite": "^6.3.4",
65
- "@studiocms/md": "0.1.0-beta.25",
66
- "studiocms": "0.1.0-beta.25"
66
+ "@studiocms/md": "0.1.0-beta.27",
67
+ "studiocms": "0.1.0-beta.27"
67
68
  },
68
69
  "scripts": {
69
70
  "build": "buildkit build 'src/**/*.{ts,astro,css,json,png,d.ts}'",
70
71
  "dev": "buildkit dev 'src/**/*.{ts,astro,css,json,png,d.ts}'",
71
- "typecheck": "tspc -p tsconfig.tspc.json"
72
+ "typecheck": "tspc -p tsconfig.tspc.json",
73
+ "test": "vitest"
72
74
  }
73
75
  }