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 EditorThis 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;