npm package: @sparrowengg/payloadcms-plugin-interlink-content
Description: A Payload CMS plugin for adding AI interlinking capabilities to rich text fields
Author: Pranav K S
Latest version: 0.1.15

# @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:

```json
"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

```bash
pnpm add @sparrowengg/payloadcms-plugin-interlink-content
```

### 2. Environment Variables

In your `.env` file, add the following environment variables:

```env
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`

```typescript
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`

```typescript
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:

```css
/* 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:

```typescript
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:

```typescript
{
  name: 'content',
  type: 'richText',
  custom: {
    interlinkWidget: true, // Add this
  },
}
```

### 3. Regenerate Payload's Importmap

```bash
pnpm payload generate:importmap
```

### 4. 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`:

```css
/* 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`):

```typescript
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&apos;t have permission to access this dashboard.</p>
          </div>
        </Gutter>
      )}
    </DefaultTemplate>
  );
}
```

### 3. Link Custom View

Link the custom view to `payload.config.ts`:

```typescript
export default buildConfig({
  admin: {
    components: {
      views: {
        bulkInterlinkView: {
          Component: '@/payload/views/BulkInterlinkDashboard',
          path: '/views/bulk-interlink',
        },
      },
    },
  },
});
```

### 4. Regenerate Payload's Importmap

```bash
pnpm payload generate:importmap
```

### 5. Access the Dashboard

Access the Bulk Interlink Dashboard at this path in browser: `/admin/views/bulk-interlink`

## License

MIT