@sparrowengg/payloadcms-plugin-interlink-content
v0.1.15
Published
A Payload CMS plugin for adding AI interlinking capabilities to rich text fields
Maintainers
Keywords
Readme
@sparrowengg/payloadcms-plugin-interlink-content
Internal Payload CMS plugin of SurveySparrow for adding AI interlinking capabilities to rich text fields.
Requirements
This package has the following peer dependencies. Make sure you have these:
"payload": "^3.33.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@payloadcms/richtext-lexical": "^3.33.0",
"@payloadcms/ui": "^3.33.0"Installation
1. Install the package
pnpm add @sparrowengg/payloadcms-plugin-interlink-content2. Environment Variables
In your .env file, add the following environment variables:
NEXT_PUBLIC_SERVER_URL=https://www.yoursite.com
NEXT_PUBLIC_SITEMAP_URL=https://www.yoursite.com/sitemap.xml
NEXT_PUBLIC_PINECONE_TEAM=<Get value from AI team>
NEXT_PUBLIC_AI_HYPERLINK_API=<Get value from AI team>Note: Sitemap index URL is recommended for NEXT_PUBLIC_SITEMAP_URL. This is used for validating the AI provided hyperlinks.
3. Add Route Handlers
The plugin makes use of Payload's server-side lexical-markdown conversion helpers.
Add the following 2 route handlers under app/(payload)/api/ folder:
/api/convert-lexical-to-markdown/route.ts
import config from '@payload-config';
import { convertLexicalToMarkdown } from '@payloadcms/richtext-lexical';
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical';
export async function POST(request: Request) {
const data: SerializedEditorState = await request.json();
const editorConfig = ((await config).editor as any)?.editorConfig || {};
const markdown = convertLexicalToMarkdown({
data,
editorConfig,
});
return new Response(JSON.stringify(markdown));
}/api/convert-markdown-to-lexical/route.ts
import config from '@payload-config';
import { convertMarkdownToLexical } from '@payloadcms/richtext-lexical';
export async function POST(request: Request) {
const markdown = (await request.json()) as string;
const editorConfig = ((await config).editor as any)?.editorConfig || {};
const lexicalJSON = convertMarkdownToLexical({
editorConfig,
markdown,
});
return new Response(JSON.stringify(lexicalJSON));
}4. Add Custom Styles
Add this in your custom.scss of Payload CMS:
/* Hide Interlink Widget Playground */
#nav-global-interlink-widget-playground {
display: none;
}
li:has(div.card-interlink-widget-playground) {
display: none;
}Exports
The package provides two main exports:
1. Default Export (@sparrowengg/payloadcms-plugin-interlink-content)
- Contains the Payload CMS plugin
- Used for registering the plugin with Payload
- Used for interlinking individual pages
2. Component Export (@sparrowengg/payloadcms-plugin-interlink-content/interlink-bulk)
- Contains the Payload CMS Custom View
- Used for interlinking all the pages in a collection
Usage
Single Interlinking Instructions
1. Register the Plugin
Import and register the plugin in your Payload config:
import { buildConfig } from 'payload/config';
import { interlinkWidget } from '@sparrowengg/payloadcms-plugin-interlink-content';
export default buildConfig({
plugins: [
interlinkWidget({
enabled: true,
options: {
serverUrl: process.env.NEXT_PUBLIC_SERVER_URL,
sitemapUrl: process.env.NEXT_PUBLIC_SITEMAP_URL,
pineconeTeam: process.env.NEXT_PUBLIC_PINECONE_TEAM,
hyperlinkApi: process.env.NEXT_PUBLIC_AI_HYPERLINK_API,
},
}),
],
// ... rest of your config
});2. Enable the Widget
Enable the widget on necessary rich text fields:
{
name: 'content',
type: 'richText',
custom: {
interlinkWidget: true, // Add this
},
}3. Regenerate Payload's Importmap
pnpm payload generate:importmap4. Using the Widget
- Widget will appear below those rich text fields on frontend
- Start interlinking using the button
- If your page title field is not named
'title'and page slug field is not named'slug', you will have to provide the current page's title and slug values through the 'Customise' option in the widget
5. Verify and Apply Changes
- Hyperlinked content will open in a playground editor
- Verify the changes and copy paste the whole rich text editor content to the corresponding field
⚠️ Warning: Content with images are only partially supported due to a Payload CMS bug. Hyperlinked content without images & blocks will open in a new tab in broken state. DO NOT copy paste the whole content in that case. Copy paste the hyperlinks individually from it.
Bulk Interlinking Instructions (Optional)
1. Setup Tailwind CSS for Payload CMS
Bulk Interlinking dashboard uses Tailwind CSS for styling.
Create the following payload-tailwind-styles.css and import it into app/(payload)/layout.tsx:
/* Define the Tailwind layers in the correct order */
@layer theme,
base,
components,
utilities;
/* Import the Tailwind theme styles */
@import 'tailwindcss/theme.css' layer(theme);
/* Skip the preflight reset to avoid conflicts with Payload's admin styles */
/* @import 'tailwindcss/preflight.css' layer(base); */
/* Import Tailwind utilities */
@import 'tailwindcss/utilities.css' layer(utilities);
/* Support Payload's dark mode toggle by adding a custom variant */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
/* Reset styles that break Payload's admin styles */
.table {
display: block;
}
/* Include the plugins styles in Tailwind scanning */
/* Give appropriate relative path to node modules */
@source '../../node_modules/@sparrowengg';2. Create Custom View
Create a custom view in Payload CMS (e.g., payload/views/bulk-interlink/index.tsx):
import type { AdminViewServerProps } from 'payload';
import { DefaultTemplate } from '@payloadcms/next/templates';
import { Gutter } from '@payloadcms/ui';
import { redirect } from 'next/navigation';
import { ClientWrapper } from '@sparrowengg/payloadcms-plugin-interlink-content/interlink-bulk';
export default function BulkInterlinkDashboard({
initPageResult,
params,
searchParams,
payload,
}: AdminViewServerProps) {
// Check if user is authenticated
const user = initPageResult.req.user;
if (!user) {
redirect('/admin/login');
}
return (
<DefaultTemplate
i18n={initPageResult.req.i18n}
locale={initPageResult.locale}
params={params}
payload={initPageResult.req.payload}
permissions={initPageResult.permissions}
searchParams={searchParams}
user={user}
visibleEntities={initPageResult.visibleEntities}
>
{/* Configure this as per your access control setup */}
{user.roles.includes('admin') ? (
<Gutter>
<ClientWrapper
collections={Object.keys(payload.collections)}
options={{
serverUrl: process.env.NEXT_PUBLIC_SERVER_URL,
sitemapUrl: process.env.NEXT_PUBLIC_SITEMAP_URL,
pineconeTeam: process.env.NEXT_PUBLIC_PINECONE_TEAM,
hyperlinkApi: process.env.NEXT_PUBLIC_AI_HYPERLINK_API,
}}
/>
</Gutter>
) : (
<Gutter>
<div className="text-center">
<h1>Insufficient Permissions</h1>
<p>You don't have permission to access this dashboard.</p>
</div>
</Gutter>
)}
</DefaultTemplate>
);
}3. Link Custom View
Link the custom view to payload.config.ts:
export default buildConfig({
admin: {
components: {
views: {
bulkInterlinkView: {
Component: '@/payload/views/BulkInterlinkDashboard',
path: '/views/bulk-interlink',
},
},
},
},
});4. Regenerate Payload's Importmap
pnpm payload generate:importmap5. Access the Dashboard
Access the Bulk Interlink Dashboard at this path in browser: /admin/views/bulk-interlink
License
MIT
