Skip to content

Commit

Permalink
feat: link block
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholasio committed Jul 29, 2024
1 parent 14c398d commit 6c1c491
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 17 deletions.
17 changes: 15 additions & 2 deletions packages/core/src/react/components/BaseBlocksRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import { IDataWPBlock, parseBlockAttributes, ParsedBlock } from '../../dom/parse

const { default: parse, domToReact } = HtmlReactParser;

export type BlockContext = { settings?: HeadlessConfig; [key: string]: unknown };

/**
* The interface any children of {@link BlocksRenderer} must implement.
*/
export interface BlockProps<
BlockAttributes extends IDataWPBlock = IDataWPBlock,
Context extends Record<string, unknown> = Record<string, unknown>,
Context extends BlockContext = BlockContext,
> {
/**
* A test function receives a domNode and returns a boolean value indicating
Expand Down Expand Up @@ -74,6 +76,10 @@ export interface BlockProps<
blockContext?: Context;
}

export type BlockFC<Props extends BlockProps = BlockProps> = React.FC<Props> & {
test?: BlockProps['test'];
};

/**
* The common interface for a block transform component
*/
Expand Down Expand Up @@ -125,6 +131,11 @@ export interface BlockRendererProps {
*/
children?: ReactNode;

/**
* The headless config
*/
settings?: HeadlessConfig;

/**
* Whether to forward the block attributes to the children components.
*/
Expand Down Expand Up @@ -241,7 +252,9 @@ export function BaseBlocksRenderer({
}

if (typeof blockContext !== 'undefined') {
blockProps.blockContext = { ...blockContext };
blockProps.blockContext = { ...blockContext, settings };
} else {
blockProps.blockContext = { settings };
}

component = React.createElement(
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/react/components/BlocksRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ import { BlockRendererProps, BaseBlocksRenderer } from './BaseBlocksRenderer';
*
* @category React Components
*/
export function BlocksRenderer({ children, ...props }: BlockRendererProps) {
export function BlocksRenderer({ children, settings: propSettings, ...props }: BlockRendererProps) {
const settings = useSettings();

return (
<BaseBlocksRenderer {...props} settings={settings}>
<BaseBlocksRenderer {...props} settings={propSettings ?? settings}>
{children}
</BaseBlocksRenderer>
);
Expand Down
55 changes: 55 additions & 0 deletions packages/next/src/rsc/blocks/LinkBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Link from 'next/link.js';
import { removeSourceUrl } from '@headstartwp/core/utils';
import { BlockFC, BlockProps, IDataWPBlock } from '@headstartwp/core/react';
import { getAttributes, HeadlessConfig, isAnchorTag } from '@headstartwp/core';
import React from 'react';

interface LinkBlockProps extends BlockProps<IDataWPBlock> {
children?: React.ReactNode;
}

/**
* The Link Block converts a anchor tag node into a next/link component if it's an internal link
*
* #### Usage
*
* ```tsx
* import { BlocksRenderer } from "@headstartwp/core/react";
* import { LinkBlock } from "@headstartwp/next/app";
*
* <BlocksRenderer html={html}>
* <LinkBlock />
* </BlocksRenderer>
* ```
*
* @param props Link Block Props
* @param props.domNode The domNode element
* @param props.children Children prop
*
* @returns The next/link component
*
* @category React Components
*/
export const LinkBlock: BlockFC<LinkBlockProps> = ({ domNode, children, blockContext }) => {
const settings =
typeof blockContext?.settings !== 'undefined'
? blockContext.settings
: ({} as HeadlessConfig);

// Links might not always be an actual block since it can be just regular links
const { href, rel, className } = getAttributes(domNode?.attribs ?? {});

const link = removeSourceUrl({
link: href,
backendUrl: settings.sourceUrl || '',
publicUrl: settings.hostUrl ?? '/',
});

return (
<Link href={link} rel={rel} className={className}>
{children}
</Link>
);
};

LinkBlock.test = (node, site) => isAnchorTag(node, { isInternalLink: true }, site);
2 changes: 1 addition & 1 deletion packages/next/src/rsc/data/queries/queryAppSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function queryAppSettings<
try {
const result = await fetchAppSettings<T, P>(query, config);

return result;
return { ...result, config };
} catch (error) {
if (error instanceof Error && handleError) {
await handleFetchError(error, config, query.path);
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/rsc/data/queries/queryAuthorArchive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export async function queryAuthorArchive<

return {
...result,
config,
seo: prepareSEOMetadata(result.data.queriedObject, config),
};
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/rsc/data/queries/queryPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export async function queryPost<

return {
...result,
config,
seo: prepareSEOMetadata(result.data.post, config),
};
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/rsc/data/queries/queryPostOrPosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export async function queryPostOrPosts<

return {
...result,
config,
seo: prepareSEOMetadata(result.data.queriedObject, config),
};
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/rsc/data/queries/queryPosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export async function queryPosts<

return {
...result,
config,
seo: prepareSEOMetadata(result.data.queriedObject, config),
};
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/rsc/data/queries/querySearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export async function querySearch<

return {
...result,
config,
seo: prepareSEOMetadata(result.data.queriedObject, config),
};
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/rsc/data/queries/queryTerms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function queryTerms<
try {
const result = await fetchTerms<T, P>(query, config);

return result;
return { ...result, config };
} catch (error) {
if (error instanceof Error && handleError) {
await handleFetchError(error, config, query.path);
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/rsc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ export * from './handlers/revalidateRouterHandler';
// components
export * from './components/PreviewIndicator';
export * from './components/JSONLD';

// blocks
export * from './blocks/LinkBlock';
4 changes: 2 additions & 2 deletions projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ export async function generateMetadata({ params }: HeadstartWPRoute): Promise<Me
}

const Single = async ({ params }: HeadstartWPRoute) => {
const { data, seo } = await query({ params });
const { data, seo, config } = await query({ params });

return (
<article>
<h1>
<HtmlDecoder html={data.post.title.rendered ?? ''} />
</h1>

<Blocks html={data.post.content.rendered ?? ''} />
<Blocks html={data.post.content.rendered ?? ''} settings={config} />

{seo.schema && <JSONLD schema={seo.schema} />}
</article>
Expand Down
9 changes: 3 additions & 6 deletions projects/wp-nextjs-app/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BlocksRenderer } from '@headstartwp/core/react';
import { HeadstartWPRoute, JSONLD, queryPost } from '@headstartwp/next/app';
import { Metadata } from 'next';
import Blocks from '../components/Blocks';

async function query({ params }: HeadstartWPRoute) {
return queryPost({
Expand All @@ -9,9 +9,6 @@ async function query({ params }: HeadstartWPRoute) {
slug: 'sample-page',
postType: 'page',
},
options: {
cache: 'force-cache',
},
});
}

Expand All @@ -22,12 +19,12 @@ export async function generateMetadata({ params }: HeadstartWPRoute): Promise<Me
}

const Home = async ({ params }: HeadstartWPRoute) => {
const { data, seo } = await query({ params });
const { data, seo, config } = await query({ params });

return (
<main>
<div>
<BlocksRenderer html={data.post.content.rendered ?? ''} />
<Blocks html={data.post.content.rendered ?? ''} settings={config} />
</div>

{seo?.schema && <JSONLD schema={seo.schema} />}
Expand Down
12 changes: 9 additions & 3 deletions projects/wp-nextjs-app/src/components/Blocks.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import { BlocksRenderer } from '@headstartwp/core/react';
import React from 'react';
import { isBlockByName } from '@headstartwp/core';
import { HeadlessConfig, isBlockByName } from '@headstartwp/core';
import { LinkBlock } from '@headstartwp/next/app';
import { PostList } from './Blocks/PostList';

type BlockProps = {
html: string;
settings: HeadlessConfig;
};

const Blocks: React.FC<BlockProps> = ({ html }) => {
const Blocks: React.FC<BlockProps> = ({ html, settings }) => {
// we need to pass settings as a prop since there's no context in server components
// and BlocksRenderer needs the settings for the LinkBlock
// the settings is automatically passed to the children components via blockContext
return (
<BlocksRenderer forwardBlockAttributes html={html}>
<BlocksRenderer forwardBlockAttributes html={html} settings={settings}>
<PostList test={(node) => isBlockByName(node, 'core/query')} />
<LinkBlock />
</BlocksRenderer>
);
};
Expand Down

0 comments on commit 6c1c491

Please sign in to comment.