Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rich-crabs-lead.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"starsquid": patch
---

Support RenderMarkdown API
19 changes: 8 additions & 11 deletions packages/starsquid/src/data/models/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { z, type ZodTypeAny } from "astro/zod";
import type {
AppDto,
FeatureDto,
FeaturesDto,
FieldDto,
FieldPropertiesDto,
NestedFieldDto,
Expand Down Expand Up @@ -34,16 +33,11 @@ const appDtoSchema = z.object({
roleProperties: z.record(z.any()),
}) satisfies z.ZodType<AppDto>;

const featureDtoSchema = z.object({
const newsDtoSchema = z.object({
name: z.string(),
text: z.string(),
}) satisfies z.ZodType<FeatureDto>;

const featuresDtoSchema = z.object({
features: z.array(featureDtoSchema),
version: z.number(),
}) satisfies z.ZodType<FeaturesDto>;

const scheduleJobDtoSchema = z.object({
id: z.string(),
status: z.string(),
Expand Down Expand Up @@ -117,15 +111,17 @@ export const contentDtoSchema = <T extends z.ZodTypeAny>(schema: T) =>
//satisfies z.ZodType<ContentDto>;

// Helper type to infer the content DTO schema for a specific data type
export type ContentDtoType<T extends z.ZodTypeAny> = z.infer<ReturnType<typeof contentDtoSchema<T>>>;
export type ContentDtoType<T extends z.ZodTypeAny> = z.infer<
ReturnType<typeof contentDtoSchema<T>>
>;

/**
* A schema for a collection of content items.
* The generic type `T` represents the shape of the data schema for individual content items.
*/
// Temporarily disable the export or remove if unused
export const contentsDtoSchema = <T>(
schema: z.ZodType<T, z.ZodTypeDef, unknown>,
schema: z.ZodType<T, z.ZodTypeDef, unknown>
) =>
z.object({
links: z.record(z.string(), resourceLinkSchema),
Expand All @@ -136,7 +132,8 @@ export const contentsDtoSchema = <T>(
//satisfies z.ZodType<ContentsDto>;

export type ContentsDtoType<T extends z.ZodTypeAny> = z.infer<
ReturnType<typeof contentsDtoSchema<T>>>;
ReturnType<typeof contentsDtoSchema<T>>
>;

export enum SYSTEM_SCHEMAS {
APP = "app",
Expand All @@ -145,5 +142,5 @@ export enum SYSTEM_SCHEMAS {

export const SYSTEM_SCHEMAS_Map = new Map<string, ZodTypeAny>([
[SYSTEM_SCHEMAS.APP, appDtoSchema],
[SYSTEM_SCHEMAS.NEWS, featuresDtoSchema],
[SYSTEM_SCHEMAS.NEWS, newsDtoSchema],
]);
83 changes: 60 additions & 23 deletions packages/starsquid/src/loaders.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import type { Loader } from "astro/loaders";
import {
defineCollection,
} from "astro:content";
import {
SYSTEM_SCHEMAS,
SYSTEM_SCHEMAS_Map,
} from "./data/models/schemas.js";
import { defineCollection } from "astro:content";
import { SYSTEM_SCHEMAS, SYSTEM_SCHEMAS_Map } from "./data/models/schemas.js";
import { SquidexClientFactory } from "./data/core/api.js";
import type { LoaderCollectionOpts } from "./type.js";
import { AstroError } from "astro/errors";
Expand All @@ -24,21 +19,44 @@ export function squidexCollections({
if (!squidexUrl || !squidexClientId || !squidexClientSecret) {
throw new AstroError(
`Missing Squidex configuration. Please set the following environment variables:
${!squidexUrl ? "SQUIDEX_URL, " : ""}${!squidexClientId ? "SQUIDEX_CLIENT_ID, " : ""}${!squidexClientSecret ? "SQUIDEX_CLIENT_SECRET" : ""}`.replace(/, $/, ".")
${!squidexUrl ? "SQUIDEX_URL, " : ""}${
!squidexClientId ? "SQUIDEX_CLIENT_ID, " : ""
}${!squidexClientSecret ? "SQUIDEX_CLIENT_SECRET" : ""}`.replace(
/, $/,
"."
)
);
}
} else {
console.log("Using provided Squidex client");
}
const client = squidexClient ?? SquidexClientFactory(squidexAppName, squidexClientId, squidexClientSecret, squidexUrl);
const client =
squidexClient ??
SquidexClientFactory(
squidexAppName,
squidexClientId,
squidexClientSecret,
squidexUrl
);

const squidexSchemaLoader = (schemaName: string) => squidexLoader({ schemaName, client });
const squidexMakeSchemaLoader = (schemaName: string) => squidexMakeLoader({ schemaName, client });
const squidexSchemaLoader = (schemaName: string) =>
squidexLoader({ schemaName, client });
const squidexMakeSchemaLoader = (schemaName: string) =>
squidexMakeLoader({ schemaName, client });

const collections = Object.fromEntries(
squidexSchemas.map((schema) => {
const isSystemSchema = Object.values(SYSTEM_SCHEMAS).includes(schema as SYSTEM_SCHEMAS);
return [schema, defineCollection({ loader: isSystemSchema ? squidexMakeSchemaLoader(schema) : squidexSchemaLoader(schema) })];
const isSystemSchema = Object.values(SYSTEM_SCHEMAS).includes(
schema as SYSTEM_SCHEMAS
);
return [
schema,
defineCollection({
loader: isSystemSchema
? squidexMakeSchemaLoader(schema)
: squidexSchemaLoader(schema),
}),
];
})
);

Expand All @@ -51,17 +69,27 @@ function squidexLoader({
schemaName,
client,
}: {
schemaName: string,
schemaName: string;
client: ReturnType<typeof SquidexClientFactory>;
}): Loader {
return {
name: loaderName,
load: async ({ logger, parseData, store }) => {
load: async ({ renderMarkdown, logger, parseData, store }) => {
const contents = await client.contents.getContents(schemaName);
for (const item of contents.items) {
const id = item.id;
const parsedData = await parseData({ id, data: JSON.parse(JSON.stringify(item)) });
store.set({ id, data: parsedData });
const parsedData = await parseData({
id,
data: JSON.parse(JSON.stringify(item)),
});

const content = item.data?.content?.iv;

store.set({
id,
data: parsedData,
rendered: content ? await renderMarkdown(content) : undefined,
});
}
logger.info(`Loaded ${contents.total} records from "${schemaName}"`);
},
Expand All @@ -70,7 +98,7 @@ function squidexLoader({
schemaName: schemaName,
client: client,
}),
}
};
}

function squidexMakeLoader({
Expand All @@ -82,20 +110,29 @@ function squidexMakeLoader({
}): Loader {
return {
name: loaderName,
load: async ({ store, parseData, logger }) => {
load: async ({ renderMarkdown, store, parseData, logger }) => {
switch (schemaName) {
case SYSTEM_SCHEMAS.APP: {
const app = await client.apps.getApp();
const id = app.id;
const data = await parseData({ id, data: JSON.parse(JSON.stringify(app)) });
const data = await parseData({
id,
data: JSON.parse(JSON.stringify(app)),
});
store.set({ id, data });
break;
}
case SYSTEM_SCHEMAS.NEWS: {
const news = await client.news.getNews({ version: 1 });
const id = String(news.version);
const data = await parseData({ id, data: JSON.parse(JSON.stringify(news)) });
store.set({ id, data });

for (const feature of news.features) {
const id = crypto.randomUUID();
store.set({
id,
data: feature as unknown as Record<string, unknown>,
rendered: await renderMarkdown(feature.text),
});
}
break;
}
default:
Expand Down
4 changes: 4 additions & 0 deletions playground/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export default defineConfig({
],
},
{ label: "Features", autogenerate: { directory: "/docs/features" } },
{
label: "Convention",
autogenerate: { directory: "/docs/convention" },
},
{ label: "Changelog", link: "/docs/changelog/" },
{ label: "Demo", link: "/" },
],
Expand Down
8 changes: 4 additions & 4 deletions playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@
"dependencies": {
"@11ty/eleventy-fetch": "^5.1.0",
"@astrojs/check": "^0.9.4",
"@astrojs/react": "^4.2.4",
"@astrojs/starlight": "^0.34.0",
"@astrojs/react": "^4.3.0",
"@astrojs/starlight": "^0.35.1",
"@astrojs/starlight-tailwind": "^4.0.1",
"@astrojs/vercel": "^8.1.4",
"@astrojs/vercel": "^8.2.2",
"@clerk/astro": "^2.10.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-icons": "^1.3.0",
"@tailwindcss/vite": "^4.1.4",
"@types/react": "^18.3.8",
"@types/react-dom": "^18.3.0",
"@vercel/speed-insights": "^1.2.0",
"astro": "5.7.4",
"astro": "5.12.0",
"astro-remote": "^0.3.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand Down
4 changes: 2 additions & 2 deletions playground/src/components/AuthButtons.astro
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import {
<SignedOut>
<SignInButton
mode="modal"
class="max-xs:h-[32px] max-xs:text-[10px] max-xs:py-0 max-xs:px-1.5 max-md:h-[38px] max-md:text-xs max-md:py-0 max-md:px-2 px-4 py-2 text-sm font-medium dark:bg-black dark:text-white dark:hover:text-white/80 bg-white text-black hover:text-black/80"
class="max-xs:h-[32px] max-xs:text-[10px] max-xs:py-0 max-xs:px-1.5 max-md:h-[38px] max-md:text-xs max-md:py-0 max-md:px-2 px-4 py-2 text-sm font-medium dark:text-white dark:hover:text-white/80 text-black hover:text-black/80"
>
Sign In
</SignInButton>
</SignedOut>

<SignedIn>
<SignOutButton
class="max-xs:h-[32px] max-xs:text-[10px] max-xs:py-0 max-xs:px-1.5 max-md:h-[38px] max-md:text-xs max-md:py-0 max-md:px-2 px-4 py-2 text-sm font-medium bg-white/50 text-black hover:text-black/80 dark:bg-black/50 dark:text-white dark:hover:text-white/80"
class="max-xs:h-[32px] max-xs:text-[10px] max-xs:py-0 max-xs:px-1.5 max-md:h-[38px] max-md:text-xs max-md:py-0 max-md:px-2 px-4 py-2 text-sm font-medium text-black hover:text-black/80 dark:text-white dark:hover:text-white/80"
>
Sign Out
</SignOutButton>
Expand Down
22 changes: 22 additions & 0 deletions playground/src/content/docs/docs/convention/rendermarkdown.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: RenderMarkdown
---

We use the content field to render markdown, so we convention that your schema uses the content field as the markdown content storage field.

There is key code.
```ts
const id = item.id;
const parsedData = await parseData({
id,
data: JSON.parse(JSON.stringify(item)),
});

const content = item.data?.content?.iv;

store.set({
id,
data: parsedData,
rendered: content ? await renderMarkdown(content) : undefined,
});
```
18 changes: 8 additions & 10 deletions playground/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
---
import Layout from "@/layouts/Layout.astro";
import { getCollection } from "astro:content";
import { Markdown } from "astro-remote";
import { getCollection, render } from "astro:content";
import { routes } from "../routing";
import { SCHEMAS } from "@/data/models/schemas";
import { getAllIntroductions } from "@/data/models/Introduction";

const app = (await getCollection(SCHEMAS.APP))?.[0];
const newsFeature = (await getCollection(SCHEMAS.NEWS))?.[0];
const features = newsFeature.data.features.slice(0, 2);
const news = (await getCollection(SCHEMAS.NEWS)).slice(0, 2);

const intros = await getAllIntroductions();
---
Expand All @@ -19,9 +17,7 @@ const intros = await getAllIntroductions();
<div class="container mx-auto my-24">
<div class="flex flex-col items-center justify-between">
<div class="flex flex-col items-center gap-y-4 mb-8">
<h1 class="ss-h1">
Welcome to the StarSquid Playground
</h1>
<h1 class="ss-h1">Welcome to the StarSquid Playground</h1>
</div>

{
Expand Down Expand Up @@ -54,7 +50,9 @@ const intros = await getAllIntroductions();

<strong>Change log</strong>
{
features.map((feature) => {
news.map(async (feature) => {
const { Content } = await render(feature);

return (
<div class="flex gap-x-3">
<div class="relative last:after:hidden after:absolute after:top-7 after:bottom-0 after:start-3.5 after:w-px after:-translate-x-[0.5px] after:bg-gray-200 dark:after:bg-neutral-700">
Expand Down Expand Up @@ -83,10 +81,10 @@ const intros = await getAllIntroductions();
<line x1="16" x2="8" y1="17" y2="17" />
<line x1="10" x2="8" y1="9" y2="9" />
</svg>
{feature.name}
{feature.data.name}
</h3>
<p class="mt-1 text-sm text-gray-600 dark:text-neutral-400">
<Markdown content={feature.text} />
<Content />
</p>
<button
type="button"
Expand Down
5 changes: 3 additions & 2 deletions playground/src/pages/intro/[slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
import { CatalogComponent } from "@/components/react/ShadcnComponents";
import Layout from "@/layouts/Layout.astro";
import { COLUMN } from "@/helpers/constants";
import { Markdown } from "astro-remote";
import { getIntroductionBySlug } from "@/data/models/Introduction";
import AccordionGroup from "../../components/AccordionGroup.astro";
import { render } from "astro:content";

const { slug } = Astro.params;
if (!slug || slug === undefined) return Astro.redirect("/404");

const intro = await getIntroductionBySlug(slug);
const { data } = intro.data;
if (!intro || !data) return Astro.redirect("/404");
const { Content } = await render(intro);

const catalogs = Astro.locals.catalogs;

Expand All @@ -33,7 +34,7 @@ Astro.cookies.set(COLUMN, slug, { path: "/" });

<section class="w-full">
<div class="flex flex-row mb-6">
<Markdown content={data.description.iv ?? ""} />
<Content />
</div>

<div class="flex flex-col lg:flex-row gap-6">
Expand Down
Loading
Loading