Skip to content

Commit 1475ddf

Browse files
committed
feat: new middleware context prop
1 parent fcfe91e commit 1475ddf

27 files changed

Lines changed: 257 additions & 222 deletions

apps/site/cms/remote-markdown.tsx

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import type {MiddlewarePageResponse} from "@combostrap/interact/types";
1+
import type {Page} from "@combostrap/interact/types";
22
import {markdownToPageSync} from "@combostrap/interact/markdown";
3+
import type {ContextProps, MiddlewareHandler} from "@combostrap/interact/types";
34

45
// noinspection JSUnusedGlobalSymbols - loaded dynamically
56
export async function handler({
67
basePath = "https://raw.githubusercontent.com",
78
prefix = "/github"
8-
}: { basePath: string, prefix: string }) {
9-
return async (request: Request): Promise<MiddlewarePageResponse | undefined> => {
9+
}: { basePath: string, prefix: string }): Promise<MiddlewareHandler> {
10+
return async (context: ContextProps): Promise<Page | undefined> => {
1011

11-
const pathname = new URL(request.url).pathname
12+
const pathname = context.url.pathname
1213

1314
// check if you handle the request
1415
if (!(pathname.startsWith(prefix) && pathname.endsWith(".md"))) {
@@ -22,25 +23,21 @@ export async function handler({
2223

2324
// parse and return
2425
try {
25-
return {
26-
page: markdownToPageSync(markdown, {format: 'md'})
27-
};
26+
return markdownToPageSync(markdown, {format: 'md'})
2827
} catch (e) {
28+
context.response.status = 500
2929
return {
30-
status: 500,
31-
page: {
32-
default:
33-
() => {
34-
return (
35-
<>
36-
<p>An error has been seen while parsing:</p>
37-
<p>{String(e)}</p>
38-
<p>Content:</p>
39-
<pre dangerouslySetInnerHTML={{__html: markdown}}></pre>
40-
</>
41-
)
42-
}
43-
}
30+
default:
31+
() => {
32+
return (
33+
<>
34+
<p>An error has been seen while parsing:</p>
35+
<p>{String(e)}</p>
36+
<p>Content:</p>
37+
<pre dangerouslySetInnerHTML={{__html: markdown}}></pre>
38+
</>
39+
)
40+
}
4441
}
4542
}
4643
}

apps/site/pages/reference/markdown-component.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ All [default syntax with gfm](markdown-syntax.md) is supported
3838
You can also use:
3939

4040
* [svg](../components/svg.md) to embed and optimize SVG files
41+
* [icon](../components/icon.md) to embed and optimize SVG files as icon
4142
* [image](../components/image.md) to process and transform Raster Image
4243

4344

apps/site/pages/reference/page-provider-cms.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Page Provider and CMS Binding
33
---
44

5-
A page provider is a handler function that returns a [page](page.md).
5+
A page provider is a middleware function that returns a [page](page.md).
66

77
All CMS would be implemented as a page provider.
88

@@ -15,12 +15,15 @@ You can create a provider plugin with a module:
1515
* with as default, a mandatory React Element
1616
* and optionally, a [frontmatter](frontmatter.md) and a `toc`
1717

18-
```javascript
18+
```tsx
1919
// ./src/cms/my-provider.js
20-
export async function handler(props) {
21-
return async (request) => {
20+
import {ContextProps} from "@combostrap/interact/types";
21+
import {MiddlewareHandler} from "./interactMiddleware";
2222

23-
const pathname = new URL(request.url).pathname
23+
export async function handler(props): Promise<MiddlewareHandler> {
24+
return async (context: ContextProps) => {
25+
26+
const pathname = context.url.pathname
2427

2528
// check if you handle the request
2629
if (!pathname.startsWith("/my-provider")) {
@@ -49,7 +52,7 @@ export async function handler(props) {
4952

5053
* You can take a look to the `localPagesMiddleware.tsx` file, it's a CMS plugin that returns
5154
local [Markdown file as page](md-page.md).
52-
* The [remote markdown example](https://github.com/combostrap/interact/blob/main/apps/site/cms/remote-markdown.tsx) page
55+
* The [remote Markdown example](https://github.com/combostrap/interact/blob/main/apps/site/cms/remote-markdown.tsx) page
5356
provider that returns Markdown page from GitHub.
5457

5558
## Registration

apps/site/pages/reference/rsc.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,27 @@ Why? Here are the possible reasons:
7878
* The imported component from the `interact:components` module was not registered in
7979
the [component section of the configuration file](conf.md)
8080
* your [layout component](layout.md) is a [client component](#use-client) and it should not.
81+
* you are passing to the context to a page component as props
82+
83+
Example: This [partial](layout.md#partials) is passing explicitly the context to the `html` tag that has a null value
84+
85+
```tsx
86+
export default async function Html({page, ...props}: LayoutProps) {
87+
88+
return (
89+
<html {...props}>
90+
{props.children}
91+
</html>
92+
)
93+
}
94+
```
95+
96+
The correct fix is:
97+
98+
```tsx
99+
export default async function Html({page, context, ...props}: LayoutProps) {
100+
}
101+
```
81102

82103
### Invalid Hook Call Warning in Rsc environment
83104

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
// don't use the relative path (not resolved)
22
import type {Page} from "@combostrap/interact/types"
33

4+
// Parsed request information used to route between RSC/SSR rendering.
5+
// Created by parseRenderRequest() from incoming HTTP requests.
6+
export type RscProps = {
7+
isRsc: boolean // true if request should return RSC payload (via _.rsc suffix)
8+
isAction: boolean // true if this is a server action call (POST request)
9+
actionId?: string // server action ID from x-rsc-action header
10+
}
11+
412
/**
5-
* These props are passed to layout and partials component
13+
* The context request props
14+
* These props are passed to:
15+
* * Middleware
16+
* * and finally to layout and partials components
17+
* Page is not in the context because it receives it as props
618
*/
719
export type ContextProps = {
8-
page: Page,
920
request: Request,
21+
url: URL // normalized URL with _.rsc suffix removed
22+
rsc: RscProps
23+
response: {
24+
status?: number
25+
headers?: HeadersInit;
26+
}
27+
};
28+
29+
export type LayoutProps = {
30+
page: Page,
31+
context: ContextProps
1032
};
Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
11
import type {Page} from "../pages/interactPage.js";
22
import type {ReactNode} from "react";
3+
import type {ContextProps} from "../componentsProvider/contextProps.js";
4+
35

4-
export type MiddlewarePageResponse = {
5-
status?: number;
6-
headers?: HeadersInit;
7-
page: Page
8-
}
96

10-
export type ReactNodeResponse = {
11-
status?: number;
12-
headers?: HeadersInit;
13-
root: ReactNode
14-
}
157

168

179
type MiddlewareResponseValue =
1810
| Response
1911
| (() => Response)
2012
| (() => Promise<Response>)
21-
| MiddlewarePageResponse
22-
| (() => MiddlewarePageResponse)
23-
| (() => Promise<MiddlewarePageResponse>)
13+
| Page
14+
| (() => Page)
15+
| (() => Promise<Page>)
2416

2517
type MiddlewareResult =
2618
| MiddlewareResponseValue // short-circuit with a response
@@ -33,5 +25,5 @@ export type Middleware = {
3325
handler: MiddlewareHandler
3426
}
3527

36-
export type MiddlewareHandler = (request: Request) => MiddlewareResult | Promise<MiddlewareResult>;
28+
export type MiddlewareHandler = (context: ContextProps) => MiddlewareResult | Promise<MiddlewareResult>;
3729

src/interact/pages/interactPage.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
*/
44

55
import type {ComponentType} from "react";
6+
import type {ContextProps} from "../componentsProvider/contextProps.js";
67

7-
export type PageComponent = ComponentType<{ request: Request }>
8+
export type PageComponent = ComponentType<ContextProps>
89

910
export interface TocNode {
1011
value: string;

src/interact/vite/layoutProviderModule.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
declare module 'interact:layouts' {
44

55
import type {ComponentType} from "react";
6-
import type {ContextProps} from "@combostrap/interact/types";
6+
import type {LayoutProps} from "@combostrap/interact/types";
77

8-
export function getLayoutComponent(name: string): ComponentType<ContextProps> | undefined
8+
export function getLayoutComponent(name: string): ComponentType<LayoutProps> | undefined
99

1010
}

src/interact/vite/ssg.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async function renderStatic(config: ResolvedConfig) {
4343
* Import the created rsc build
4444
*/
4545
const rscIndexFilePath = path.join(rscEnv.build.outDir, 'index.js')
46-
// @ts-ignore - error TS6142: Module 'entry.rsc.js' was resolved to entry.rsc.tsx', but '--jsx' is not set.
46+
// @ts-ignore - argh we need jsx and if it follows, it will need explicit file extensions - we need a bundler or just start with tsx
4747
const entryRscModule: typeof import('../../resources/rsc/server/entry.rsc.tsx') = await import(/* @vite-ignore */ pathToFileURL(rscIndexFilePath).href)
4848

4949
// entry provides a list of static paths

src/resources/components/layouts/Hamburger.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
1-
import type {ContextProps} from "@combostrap/interact/types";
1+
import type {LayoutProps} from "@combostrap/interact/types";
22
import {getInteractConfig} from "@combostrap/interact/config";
33
import Header from "../partials/Header";
44
import Body from "../partials/Body";
55
import Html from "../partials/Html";
66
import Head from "../partials/Head";
7-
import {cn} from "../../lib/utils";
7+
import {cn} from "@/lib/utils";
88

99
/**
1010
* Hamburger Layout
1111
*/
1212
// noinspection JSUnusedGlobalSymbols - imported dynamically
13-
export default function Hamburger(contextProps: ContextProps) {
14-
let Component = contextProps.page.default
13+
export default function Hamburger(layoutProps: LayoutProps) {
14+
let Component = layoutProps.page.default
1515
let interactConfig = getInteractConfig();
1616
return (
17-
<Html {...contextProps}>
18-
<Head {...contextProps}/>
19-
<Body {...contextProps}>
20-
<Header {...contextProps} />
17+
<Html {...layoutProps}>
18+
<Head {...layoutProps}/>
19+
<Body {...layoutProps}>
20+
<Header {...layoutProps} />
2121
<div className={
2222
cn(
2323
interactConfig.style.container.containerClass,
2424
"position-relative",
2525
)}>
2626
<main>
27-
<Component request={contextProps.request}/>
27+
<Component {...layoutProps.context}/>
2828
</main>
2929
</div>
3030
</Body>

0 commit comments

Comments
 (0)