This documentation is for a legacy version of Remix and Supabase. For the latest version, please visit the Remix and Supabase V2 documentation

AI Text Editor Plugin for the Remix Supabase Saas Starter Kit

This plugin adds an AI WYSIWYG Editor to your Remix Supabase SaaS application.

This plugin adds an AI Editor component Remix application. You can import this component anywhere in your application.

It is built with Lexical, a text editor framework for React, by Meta.

This plugin is currently experimental.

Using the Plugin

Installation

To install the plugin, you can use git subtrees from your original repository:

git subtree add --prefix plugins/text-editor git@github.com:makerkit/remix-supabase-saas-kit-plugins.git text-editor --squash

After running this command, you will have the plugin in your repository at plugins/text-editor. Once pulled, you can apply any customization you need.

Using the CLI

If you're using the CLI, you can run the following command to install the plugin:

npx @makerkit/cli@latest plugins install

Follow the instructions to install the plugin.

Add the plugin as a workspace in your package.json

You can do so by adding the following to your package.json file:

{
"workspaces": [
"plugins/text-editor"
]
}

Add it next to the other workspaces in your package.json file.

Add the paths alias to the TypeScript configuration

To make sure that the TypeScript compiler can find the plugin, you will need to add the following paths alias to your tsconfig.json file, in addition to the other paths aliases that you may have.

If not yet present, add the following to your tsconfig.json file:

{
"compilerOptions": {
"paths": {
"~/plugins/*": [
"./plugins/*"
]
}
}
}

You only need to add this once, even if you have multiple plugins.

Installing dependencies

To install the dependencies, you can run the following command:

npm i

NPM will install the dependencies in the plugins/text-editor folder as an NPM workspace.

Add the required API handlers

You will need to add the following API handlers to your application:

  • app/routes/resources.ai.autocomplete.ts
  • app/routes/resources.ai.edit.ts

Edit Route

To add the edit route, create a file at app/routes/resources.ai.edit.ts with the following content:

import type { ActionFunctionArgs } from '@remix-run/node';
import { createAIEditActionHandler } from '~/plugins/text-editor/lib/route-handler';
export const action = (params: ActionFunctionArgs) =>
createAIEditActionHandler(params.request);

Autocomplete Route

To add the autocomplete route, create a file at app/routes/resources.ai.autocomplete.ts with the following content:

import type { ActionFunctionArgs } from '@remix-run/node';
import { createAIAutocompleteActionHandler } from '~/plugins/text-editor/lib/route-handler';
export const action = (params: ActionFunctionArgs) =>
createAIAutocompleteActionHandler(params.request);

Style

Import the CSS file at plugins/text-editor/editor.css into your global CSS file at app/styles/index.css:

@import "../../plugins/text-editor/editor.css";

Using the plugin

To use the plugin, you can import the component from the plugin folder.

NB: the example below uses rxjs, which is a dependency not included in the kit. I do recommend using it, but you can use any other library to handle debouncing and other logic that you may need if you want to use autosaving for your application.

import { useCallback, useEffect, useMemo, useRef } from 'react';
import { toast } from 'sonner';
import {
debounceTime,
distinctUntilChanged,
Subject,
switchMap,
tap,
} from 'rxjs';
import Editor from '~/plugins/text-editor/components/Editor';
export default function EditorContainer() {
const subject$ = useMemo(() => new Subject(), []);
const currentToastId = useRef<string | number>();
const onChange = useCallback(
(content: string) => {
subject$.next(content);
},
[subject$],
);
useEffect(() => {
const subscription = subject$
.pipe(
debounceTime(2000),
distinctUntilChanged(),
switchMap(() => {
if (currentToastId.current) {
toast.dismiss(currentToastId.current);
}
currentToastId.current = toast.loading('Saving...', {
id: currentToastId.current,
});
return new Promise((resolve) => {
setTimeout(resolve, 2000);
});
}),
tap(() => {
toast.success('Saved!', {
id: currentToastId.current,
});
}),
)
.subscribe(() => {
currentToastId.current = undefined;
});
return () => {
subscription.unsubscribe();
};
}, [subject$]);
return (
<Editor
className={'h-[80vh] w-[100vh]'}
content={getInitialContent()}
onChange={onChange}
/>
);
}
function getInitialContent() {
return `
## Introducing Makerkit's AI Editor
This plugin is powered by OpenAI's GPT-3 and Lexical's Editor, and helps you add an AI-powered editor to your SaaS, in just a few lines of code.
Install it using the CLI:
\`\`\`
npx @makerkit/cli plugins install
\`\`\`
Available for *Pro* and *Teams* customers **for free**.
`.trim();
}

Now, import the component EditorContainer anywhere in your application:

import { Toaster } from 'sonner';
import { lazy } from 'react';
import DarkModeToggle from '~/components/DarkModeToggle';
import Logo from '~/core/ui/Logo';
import ClientOnly from '~/core/ui/ClientOnly';
const EditorContainer = lazy(() => {
return import('~/components/EditorContainer');
});
function EditorPage() {
return (
<div
className={
'w-screen h-screen flex justify-center items-center bg-gray-50' +
' dark:bg-dark-900 flex flex-col space-y-4'
}
>
<Toaster />
<div className={'fixed top-4 right-4'}>
<DarkModeToggle />
</div>
<Logo href={'/'} className={'w-32 h-32'} />
<ClientOnly>
<EditorContainer />
</ClientOnly>
</div>
);
}
export default EditorPage;